3

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

背景

Android 对世界上的许多语言都有相当好的支持,甚至包括阿拉伯语和希伯来语等 RTL 语言。但是,没有内置支持像传统蒙古语这样的自上而下的语言(在内蒙古仍然非常活跃,不要与西里尔蒙古语混淆)。下图显示了为清楚起见添加了英文的文本方向。

在此处输入图像描述

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

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

带有垂直脚本的蒙古语水平滚动ListView

一个蒙古语 ListView 需要具备以下要求:

  • 从左到右水平滚动
  • 触摸事件的工作方式与普通 ListView 相同
  • 与普通 ListView 一样支持自定义布局

还需要支持蒙古文 TextView支持的所有内容:

  • 支持传统的蒙古字体
  • 从上到下垂直显示文本
  • 换行从左到右。
  • 换行出现在空格处(同英文)

下图显示了蒙古语 ListView 应具备的基本功能:

在此处输入图像描述

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

本系列其他相关问题:

IOS:

4

1 回答 1

2

更新

RecyclerViews 具有水平布局。因此,在其中一个中放置一个垂直蒙古文 TextView 相对容易。以下是来自 的示例mongol-library

在此处输入图像描述

有关使用 a创建水平滚动列表的一般解决方案,请参阅此答案。RecyclerView

旧答案

很遗憾,Android API 没有提供水平 ListView。不过,有许多 StackOverflow Q&A 讨论了如何做到这一点。以下是几个示例:

但是当我真正尝试实施这些建议以及合并蒙古文垂直文本时,我的日子过得很糟糕。在我搜索的某个地方,我发现了一个略有不同的答案。这是一个旋转整个布局的类。它通过扩展 ViewGroup 来实现。这样,任何东西(包括 ListView)都可以放在 ViewGroup 中并进行旋转。所有的触摸事件也都有效。

正如我在关于蒙古语 TextViews 的回答中解释的那样,仅仅旋转蒙古语文本是不够的。如果每个 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 时(但在使用旋转后的TextView时却没有),我的应用程序中得到了相同的工件。如果有人知道这些来自哪里,请给我留言!

在此处输入图像描述

  • 此解决方案不处理呈现 Unicode 文本。要么你需要使用非 Unicode 文本(不鼓励),要么你需要在你的应用程序中包含一个渲染引擎。(Android 目前不支持 OpenType 智能字体渲染。希望将来会有所改变。相比之下,iOS 确实支持复杂的文本渲染字体。)请参阅此链接以获取 Unicode 蒙古语渲染引擎示例。
于 2015-04-20T15:20:00.113 回答