Как предотвратить принудительное размещение LTR в RTL на некоторых устройствах Android?

После реализации поддержки RTL в моем приложении пользователь сообщил, что текст LTR принудительно переводится как RTL в арабских локалях. Это выглядит следующим образом:

Обратите внимание, как текст выровнен по правому краю, даже если он должен быть выровнен по левому краю.

Пользователь сказал мне, что эта ошибка присутствует только на

  • Huawei Y5 Prime 2018 версия 8.1.0
  • и LG G3 версия 6.0.0

он отсутствует в Samsung Galaxy J7 версии 6.0.1 или в эмуляторе Android с SDK 23, 26, 27, 28. Там он правильно отображает LTR в локалях RTL, как показано на этом снимке экрана:

Я не могу воспроизвести его в эмуляторе - там текст LTR отображается с выравниванием по левому краю, а текст RTL (в данном случае арабский) правильно отображается с выравниванием по правому краю. Я подозреваю, что основной причиной является то, что некоторые производители добавили код для принудительного размещения макетов RTL в локалях RTL.

Обратите внимание, что поскольку мое приложение предназначено для чтения RSS-каналов, у меня нет контроля над отображаемым текстом, поэтому я вынужден полагаться на Bidi-алгоритмы (которые работают нормально! За исключением этих устройств).

Я уточняю android:supportsRtl="true" в манифесте:

<application
      android:allowBackup="true"
      android:name=".FeederApplication"
      android:icon="@mipmap/ic_launcher_round"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:label="@string/app_name"
      android:supportsRtl="true"
      android:usesCleartextTraffic="true"
      android:theme="@style/AppTheme">
...
</application>

ссылка на полный манифест

И это макет со скриншота:

<com.nononsenseapps.feeder.views.ObservableScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/scroll_view"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.nononsenseapps.feeder.ui.ReaderFragment">

  <!-- Action bar is overlayed, so add some padding -->
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingStart="@dimen/keyline_1"
    android:paddingTop="?actionBarSize"
    android:paddingEnd="@dimen/keyline_1"
    android:paddingBottom="@dimen/activity_vertical_margin">


    <TextView
      android:id="@+id/story_title"
      style="@style/TextAppearance.Reader.Title"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginBottom="6dp"
      android:textDirection="anyRtl"
      android:textIsSelectable="true"
      android:transitionName="title"
      tools:text="@tools:sample/cities" />

    <TextView
      android:id="@+id/story_feedtitle"
      style="@style/TextAppearance.Reader.Author"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="2dp"
      android:layout_marginBottom="2dp"
      android:textDirection="locale"
      android:textIsSelectable="true"
      tools:text="CowboyProgrammer" />

    <TextView
      android:id="@+id/story_author"
      style="@style/TextAppearance.Reader.Author"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="2dp"
      android:layout_marginBottom="8dp"
      android:textDirection="locale"
      android:textIsSelectable="true"
      tools:text="Jonas, Sep 14 2015" />

    <com.nononsenseapps.feeder.views.LinkedTextView
      android:id="@+id/story_body"
      style="@style/TextAppearance.Reader.Body"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="8dp"
      android:minHeight="300dp"
      android:textDirection="anyRtl"
      android:textIsSelectable="true"
      tools:text="@tools:sample/lorem/random" />
  </LinearLayout>


</com.nononsenseapps.feeder.views.ObservableScrollView>

В случае, если вам интересно, где находится ImageView - их нет. Изображение, которое вы видите на скриншоте, включено как ImageSpan внутри LinkedTextView,

я использую anyRtl чтобы получить правильное форматирование смешанного текста. Поведение по умолчанию (firstStrong) будет отображать текст как выровненный по левому краю, если он начинается с английского слова в противном случае. Это код, устанавливающий текст в TextViews:

val viewModel = getFeedItemViewModel(_id)
viewModel.liveItem.observe(this, androidx.lifecycle.Observer {
    rssItem = it

    rssItem?.let { rssItem ->
        setViewTitle()

        // feedDisplayTitle is a SpannableString
        mFeedTitleTextView.text = rssItem.feedDisplayTitle

        rssItem.pubDate.let { pubDate ->
            rssItem.author.let { author ->
                when {
                    author == null && pubDate != null ->
                        mAuthorTextView.text = getString(R.string.on_date,
                                pubDate.withZone(DateTimeZone.getDefault())
                                        .toString(dateTimeFormat))
                    author != null && pubDate != null ->
                        mAuthorTextView.text = getString(R.string.by_author_on_date,
                                // Must wrap author in unicode marks to ensure it formats
                                // correctly in RTL
                                unicodeWrap(author),
                                pubDate.withZone(DateTimeZone.getDefault())
                                        .toString(dateTimeFormat))
                    else -> mAuthorTextView.visibility = View.GONE
                }
            }
        }
    }
})

viewModel.liveImageText.observe(this, androidx.lifecycle.Observer {
    // the liveImageText is a SpannableString
    bodyTextView.text = it
})

// [...]

fun Fragment.unicodeWrap(text: String): String =
        BidiFormatter.getInstance(getLocale()).unicodeWrap(text)

fun Fragment.getLocale(): Locale? =
        context?.getLocale()

fun Context.unicodeWrap(text: String): String =
        BidiFormatter.getInstance(getLocale()).unicodeWrap(text)

fun Context.getLocale(): Locale =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            resources.configuration.locales[0]
        } else {
            @Suppress("DEPRECATION")
            resources.configuration.locale
        }

ссылка на полный файл

Как я уже сказал, я не контролирую отображаемый текст, а просто конвертирую (возможно, в формате HTML) текст в SpannableString перед отображением ( используя этот код).

Итак, как говорится в заголовке, кто-нибудь знает способ добавить обходной путь для конкретных устройств, где возникает эта ошибка?

1 ответ

Возможно ли, что у вашего пользователя есть приложение на языке RTL, и устройство просто пропускает ответ алгоритма Bidi при построении текста в некоторых случаях?

Другие вопросы по тегам