Как сделать традиционный монгольский скрипт ListView в Android

Как сделать горизонтально прокручиваемый ListView для вертикального монгольского скрипта в приложениях Android?

Фон

Android имеет довольно хорошую поддержку для многих языков мира, даже для языков RTL, таких как арабский и иврит. Тем не менее, нет встроенной поддержки языков "сверху вниз", таких как традиционный монгольский (который все еще очень жив во Внутренней Монголии и не следует путать с кириллическим монгольским). На следующем рисунке показано направление текста с добавлением английского для ясности.

Поскольку эта функциональность не встроена в Android, она делает практически все аспекты разработки приложений чрезвычайно сложными. Это особенно верно для горизонтальных ListViews, которые не поддерживаются "из коробки" в Android. В Интернете также очень и очень мало информации. Существует ряд разработчиков приложений для традиционного монгольского языка, но, по коммерческим или иным причинам, они не делают свой код открытым исходным кодом.

Из-за этих трудностей я хотел бы составить серию вопросов Stackru, которые могут служить центральным местом для сбора ответов на некоторые из более сложных проблем программирования, связанных с разработкой традиционных монгольских приложений. Даже если вы не владеете монгольским языком, ваша помощь в просмотре кода, комментировании и вопросах, даче ответов или даже голосовании по данному вопросу будет принята с благодарностью.

Монгольская горизонтальная прокрутка ListView с вертикальным скриптом

Монгольский ListView должен иметь следующие требования:

  • Прокрутка по горизонтали слева направо
  • Сенсорные события работают так же, как с обычным ListView
  • Пользовательские макеты поддерживаются так же, как в обычном ListView

Также необходимо поддерживать все, что поддерживает монгольский TextView:

  • Поддерживает традиционный монгольский шрифт
  • Отображает текст вертикально сверху вниз
  • Перенос строки идет слева направо.
  • Разрывы строк происходят в пробелах (как на английском)

На рисунке ниже показаны основные функции, которые должен иметь монгольский ListView:

Мой ответ ниже, но я приветствую другие способы решения этой проблемы.

Другие связанные вопросы в этой серии:

IOS:

1 ответ

Решение

Обновить

RecyclerViews имеют горизонтальное расположение. Таким образом, относительно легко поместить текстовый объект Vertical Mongolian внутри одного из них. Вот пример из mongol-library,

Смотрите этот ответ для общего решения использования RecyclerView сделать горизонтально прокручиваемый список.

Старый ответ

Весьма прискорбно, что горизонтальные ListViews не предоставляются Android API. Есть несколько вопросов и ответов о Stackru, которые говорят о том, как их делать. Вот пара примеров:

Но когда я действительно пытался реализовать эти предложения, а также включить монгольский вертикальный текст, у меня было ужасное время. Где-то в моем поиске я нашел немного другой ответ. Это был класс, который вращал весь макет. Это было сделано путем расширения 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);

    }
}

Тогда пользовательский XML-макет элемента ListView может выглядеть примерно так:

<?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.
Другие вопросы по тегам