Android如何制作传统蒙文ListView

How to make a traditional Mongolian script ListView in Android

如何在 Android 应用程序中为垂直蒙文制作水平滚动的 ListView?

背景

Android 对世界上的许多语言都有很好的支持,甚至包括阿拉伯语和希伯来语等 RTL 语言。但是,没有内置对像 traditional Mongolian (which is still very much alive in Inner Mongolia and not to be confused with Cyrillic Mongolian 这样的自上而下语言的支持。下图显示了为清楚起见添加了英文的文本方向。

由于此功能未内置于 Android,因此几乎使应用程序开发的每个方面都变得极其困难。对于水平 ListView 尤其如此,在 Android 中不支持开箱即用。网上的信息也非常非常少。传统蒙文的app开发者不少,但无论是出于商业原因还是其他原因,他们似乎都没有将自己的代码开源。

由于这些困难,我想提出一系列 Whosebug 问题,这些问题可以作为收集与传统蒙古语应用程序开发相关的一些较难编程问题的答案的中心位置。即使您不懂蒙古语,您帮助审查代码、提出评论和问题、给出答案甚至对问题进行投票也将不胜感激。

蒙古语水平滚动ListView与垂直脚本

一个蒙文ListView需要具备以下条件:

还需要支持 支持的所有内容:

下图是蒙文ListView应该具备的基本功能:

我的答案如下,但我欢迎其他解决此问题的方法。

本系列其他相关问题:

iOS:

更新

RecyclerView 具有水平布局。因此,将垂直蒙古文 TextView 放入其中一个中相对容易。 Here is an example from mongol-library.

有关使用 RecyclerView 制作水平滚动列表的一般解决方案,请参阅

旧答案

很遗憾 Android API 没有提供水平的 ListView。不过,有许多 Whosebug 问答讨论了如何去做。这里有几个示例:

  • How can I make a horizontal ListView in Android?
  • Horizontal ListView in Android?

但是当我真正尝试实施这些建议以及合并蒙古语垂直文本时,我遇到了一个糟糕的时刻。在我搜索的某个地方,我发现了一个略有不同的答案。这是 a class that rotated an entire layout。它通过扩展 ViewGroup 来做到这一点。通过这种方式,任何东西(包括 ListView)都可以放入 ViewGroup 中并进行旋转。所有触摸事件也有效。

正如我在 中解释的那样,仅仅旋转蒙古文字是不够的。如果每个 ListView 项(或 ViewGroup 中的其他文本元素)都只有一行,这就足够了,但旋转多行会使换行方向错误。但是,水平镜像布局并使用垂直镜像字体可以解决这个问题,如下图所示。

我调整了旋转的 ViewGroup 代码也可以进行水平镜像。

public class MongolViewGroup extends ViewGroup {

    private int angle = 90;
    private final Matrix rotateMatrix = new Matrix();
    private final Rect viewRectRotated = new Rect();
    private final RectF tempRectF1 = new RectF();
    private final RectF tempRectF2 = new RectF();
    private final float[] viewTouchPoint = new float[2];
    private final float[] childTouchPoint = new float[2];
    private boolean angleChanged = true;

    public MongolViewGroup(Context context) {
        this(context, null);
    }

    public MongolViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);
    }

    public View getView() {
        return getChildAt(0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final View view = getView();
        if (view != null) {
            measureChild(view, heightMeasureSpec, widthMeasureSpec);
            setMeasuredDimension(resolveSize(view.getMeasuredHeight(), widthMeasureSpec),
                    resolveSize(view.getMeasuredWidth(), heightMeasureSpec));
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (angleChanged) {
            final RectF layoutRect = tempRectF1;
            final RectF layoutRectRotated = tempRectF2;
            layoutRect.set(0, 0, right - left, bottom - top);
            rotateMatrix.setRotate(angle, layoutRect.centerX(), layoutRect.centerY());
            rotateMatrix.postScale(-1, 1);
            rotateMatrix.mapRect(layoutRectRotated, layoutRect);
            layoutRectRotated.round(viewRectRotated);
            angleChanged = false;
        }
        final View view = getView();
        if (view != null) {
            view.layout(viewRectRotated.left, viewRectRotated.top, viewRectRotated.right,
                    viewRectRotated.bottom);
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {

        canvas.save();
        canvas.rotate(-angle, getWidth() / 2f, getHeight() / 2f);
        canvas.scale(-1, 1);
        super.dispatchDraw(canvas);
        canvas.restore();
    }

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        invalidate();
        return super.invalidateChildInParent(location, dirty);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        viewTouchPoint[0] = event.getX();
        viewTouchPoint[1] = event.getY();
        rotateMatrix.mapPoints(childTouchPoint, viewTouchPoint);
        event.setLocation(childTouchPoint[0], childTouchPoint[1]);
        boolean result = super.dispatchTouchEvent(event);
        event.setLocation(viewTouchPoint[0], viewTouchPoint[1]);
        return result;
    }


}

蒙古文垂直镜像字体仍然需要在其他地方设置。我发现制作自定义 TextView 最简单:

public class MongolNonRotatedTextView extends TextView {

    // This class does not rotate the textview. It only displays the Mongol font.
    // For use with MongolLayout, which does all the rotation and mirroring.

    // Constructors
    public MongolNonRotatedTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public MongolNonRotatedTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MongolNonRotatedTextView(Context context) {
        super(context);
        init();
    }

    // This class requires the mirrored Mongolian font to be in the assets/fonts folder
    private void init() {
         Typeface tf = Typeface.createFromAsset(getContext().getAssets(),
         "fonts/MongolMirroredFont.ttf");
         setTypeface(tf);

    }
}

然后自定义 ListView 项目 xml 布局看起来像这样:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rlListItem"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

        <com.example.MongolNonRotatedTextView
            android:id="@+id/tvListViewText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"/>

</RelativeLayout>

已知问题:

  • 如果仔细观察下图,您会发现文字周围有淡淡的水平和垂直线条。虽然这张图片来自另一个开发者的应用程序,但当我使用旋转的 ViewGroup 时,我在我的应用程序中得到了相同的工件(但当我使用 时却没有)。如果有人知道这些来自哪里,请给我留言!

  • 此解决方案不处理 Unicode 文本的呈现。您要么需要使用非 Unicode 文本(不鼓励),要么需要在您的应用程序中包含渲染引擎。 (Android 目前不支持 OpenType smartfont 渲染。希望这会在未来改变。iOS,相比之下确实支持复杂的文本渲染字体。)参见 this link Unicode 蒙古文渲染引擎示例。