如何在Android中制作传统蒙古文ListView

5
我应该如何在Android应用中制作一个用于竖排蒙古文的横向滚动ListView?
背景:
Android对世界上许多语言都有相当好的支持,甚至包括从右到左的语言,例如阿拉伯语和希伯来语。然而,并没有内置支持从上到下书写的语言,例如传统蒙古文(在内蒙古仍然很常见,不要与西里尔蒙古文混淆)。下图展示了文字方向,为了清晰起见,还加入了英文。

enter image description here

由于这个功能没有内置在Android中,几乎使得每一个应用程序开发方面都变得极其困难。这在水平ListView方面尤为明显,在Android中不支持此功能。此外,网上提供的信息非常少。有许多传统蒙古文的应用程序开发人员,但无论是出于商业原因还是其他原因,他们似乎不愿意公开他们的代码。
因为这些困难,我想制作一系列StackOverflow问题,可以作为一个集中收集与传统蒙古文应用程序开发相关的一些更困难编程问题答案的地方。即使您不懂蒙古语,也希望您能帮助审查代码,发表评论和提问,给出答案,甚至点赞问题。
蒙古水平滚动ListView需要具备以下要求:
  • 从左到右水平滚动
  • 触摸事件与普通ListView相同
  • 支持自定义布局与普通ListView相同

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

  • 支持传统的蒙古文字体
  • 将文本从上到下垂直显示
  • 换行从左到右进行
  • 在空格处断行(与英语相同)

下面的图片展示了蒙古文ListView应该具备的基本功能:

enter image description here

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

本系列中的其他相关问题:

iOS:

1个回答

3

更新

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

enter image description here

请参阅此答案,了解使用RecyclerView制作水平滚动列表的通用解决方案。

旧答案

非常不幸地,Android API没有提供水平ListView。有许多StackOverflow Q&A谈论如何实现它们。以下是一些示例:

但是当我尝试实现这些建议并结合蒙古语垂直文本时,我遇到了很大的困难。在我的搜索中,我找到了一个略微不同的答案。它是一个旋转整个布局的类。它通过扩展ViewGroup来实现。以这种方式,任何东西(包括ListView)都可以放在ViewGroup中并旋转。所有触摸事件也都有效。

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

enter image description here

我将旋转的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时),我的应用程序中也出现了同样的伪影。如果有人知道它们来自哪里,请给我留言!

enter image description here

  • 此解决方案无法处理Unicode文本的呈现。您要么需要使用非Unicode文本(不建议),要么需要在应用程序中包含呈现引擎。(Android目前不支持OpenType智能字体呈现。希望将来会改变。相比之下,iOS支持复杂的文本呈现字体。)请参见此链接以获取Unicode蒙古文呈现引擎示例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接