gpt4 book ai didi

android - 如何确定 View 的当前方向(RTL/LTR)?

转载 作者:塔克拉玛干 更新时间:2023-11-02 20:49:06 24 4
gpt4 key购买 nike

背景

可以使用以下方法获取当前的语言环境方向:

val isRtl=TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL

也可以获得 layout direction一个 View ,如果开发人员设置了它:
val layoutDirection = ViewCompat.getLayoutDirection(someView)

问题

View 的默认 layoutDirection 不基于其语言环境。实际上是 LAYOUT_DIRECTION_LTR .

当您将设备的区域设置从 LTR(从左到右)区域设置(如英语)更改为 RTL(从右到左)区域设置(如阿拉伯语或希伯来语)时, View 将相应对齐,但您的值默认情况下, View 将保持 LTR ...

这意味着给定一个 View ,我看不出如何确定它经过的正确方向。

我试过的

我做了一个简单的 POC。它有一个带有 TextView 的 LinearLayout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/linearLayout" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:gravity="center_vertical" tools:context=".MainActivity">

<TextView
android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Hello World!"/>

</LinearLayout>

在代码中,我写了语言环境和 View 的方向:
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
Log.d("AppLog", "locale direction:isRTL? $isRtl")
Log.d("AppLog", "linearLayout direction:${layoutDirectionValueToStr(ViewCompat.getLayoutDirection(linearLayout))}")
Log.d("AppLog", "textView direction:${layoutDirectionValueToStr(ViewCompat.getLayoutDirection(textView))}")
}

fun layoutDirectionValueToStr(layoutDirection: Int): String =
when (layoutDirection) {
ViewCompat.LAYOUT_DIRECTION_INHERIT -> "LAYOUT_DIRECTION_INHERIT"
ViewCompat.LAYOUT_DIRECTION_LOCALE -> "LAYOUT_DIRECTION_LOCALE"
ViewCompat.LAYOUT_DIRECTION_LTR -> "LAYOUT_DIRECTION_LTR"
ViewCompat.LAYOUT_DIRECTION_RTL -> "LAYOUT_DIRECTION_RTL"
else -> "unknown"
}
}

结果是,即使我切换到 RTL 语言环境(希伯来语 - עברית),它也会在日志中打印:
locale direction:isRTL? true 
linearLayout direction:LAYOUT_DIRECTION_LTR
textView direction:LAYOUT_DIRECTION_LTR

当然,根据当前的语言环境,textView 与正确的一侧对齐:

enter image description here

如果它可以像我想象的那样工作(默认为 LAYOUT_DIRECTION_LOCALE),则此代码将检查 View 是否处于 RTL 中:
fun isRTL(v: View): Boolean = when (ViewCompat.getLayoutDirection(v)) {
View.LAYOUT_DIRECTION_RTL -> true
View.LAYOUT_DIRECTION_INHERIT -> isRTL(v.parent as View)
View.LAYOUT_DIRECTION_LTR -> false
View.LAYOUT_DIRECTION_LOCALE -> TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
else -> false
}

但它不能,因为 LTR 是默认的,但它甚至不重要......

所以这段代码是错误的。

问题
  • 怎么可能默认情况下,方向是 LTR,但实际上它会向右对齐,以防语言环境发生变化?
  • 无论开发人员为它设置(或未设置)什么,我如何检查给定 View 的方向是 LTR 还是 RTL ?
  • 最佳答案

    How could it be that by default, the direction is LTR, yet in practice it gets aligned to the right, in case the locale has changed?



    区别在于时间。创建 View 时,会为其分配一个默认值,直到解析出实际值。实际上有两个值保持:
  • getLayoutDirection()返回默认值 LAYOUT_DIRECTION_LTR ,
  • getRawLayoutDirection() (隐藏 API)返回 LAYOUT_DIRECTION_INHERIT .

  • 当原始布局方向为 LAYOUT_DIRECTION_INHERIT 时实际布局方向被解析为 measure 的一部分称呼。然后 View 遍历其父 View
  • 直到找到一个具有具体值集的 View
  • 或直到它到达缺少的 View 根(窗口,或 ViewRootImpl )。

  • 在第二种情况下,当 View 层次结构尚未附加到窗口时,布局方向未解析, getLayoutDirection()仍然返回默认值。这就是您的示例代码中发生的情况。

    当 View 层次结构附加到 View 根目录时,它会从 Configuration 分配布局方向。目的。换句话说 仅在将 View 层次结构附加到窗口 后读取已解析的布局方向才有意义.

    How can I check if a given View's direction would be LTR or RTL , no matter what the developer has set (or not set) for it ?



    首先检查布局方向是否解析。如果是,您可以使用该值。
    if (ViewCompat.isLayoutDirectionResolved(view)) {
    val rtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL
    // Use the resolved value.
    } else {
    // Use one of the other options.
    }

    请注意,该方法在 Kitkat 下始终返回 false。

    如果布局方向未解决,您将不得不延迟检查。

    选项 1:将其发布到主线程消息队列。我们假设在运行时, View 层次结构已附加到 window.xml 中。
    view.post {
    val rtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL
    // Use the resolved value.
    }

    选项 2:当 View 层次结构准备好执行绘图时得到通知。这适用于所有 API 级别。
    view.viewTreeObserver.addOnPreDrawListener(
    object : ViewTreeObserver.OnPreDrawListener {
    override fun onPreDraw(): Boolean {
    view.viewTreeObserver.removeOnPreDrawListener(this)
    val rtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL
    // Use the resolved value.
    return true
    }
    })

    注:你实际上可以继承任何 View并覆盖其 onAttachedToWindow方法,因为布局方向被解析为 super.onAttachedToWindow() 的一部分称呼。其他回调(在 ActivityOnWindowAttachedListener 中)执行 不是 保证这种行为,所以不要使用它们。

    更多问题的更多答案

    Where does it get the value of getLayoutDirection and getRawLayoutDirection ?


    View.getRawLayoutDirection() (隐藏 API)返回您通过 View.setLayoutDirection() 设置的内容.默认为 LAYOUT_DIRECTION_INHERIT ,这意味着“从我的 parent 那里继承布局方向”。
    View.getLayoutDirection()返回解析后的布局方向,即 LOCATION_DIRECTION_LTR (也是默认值,直到实际解决)或 LOCATION_DIRECTION_RTL .此方法不返回任何其他值。只有在 View 是附加到 View 根的 View 层次结构的一部分时发生测量后,返回值才有意义。

    Why is LAYOUT_DIRECTION_LTR the default value ?



    从历史上看,Android 根本不支持从右到左的脚本(参见 here ),从左到右是最合理的默认值。

    Would the root of the views return something of the locale?



    默认情况下,所有 View 都继承其父级的布局方向。那么最上面的 View 在附加之前从哪里获得布局方向呢?无处可去。

    当 View 层次结构附加到窗口时,会发生以下情况:
    final Configuration config = context.getResources().getConfiguration();
    final int layoutDirection = config.getLayoutDirection();
    rootView.setLayoutDirection(layoutDirection);

    默认配置使用系统语言环境设置,布局方向取自该语言环境。然后将 Root View 设置为使用该布局方向。现在它的所有 child 都带有 LAYOUT_DIRECTION_INHERIT可以遍历并解析到这个绝对值。

    Would some modifications of my small function be able to work even without the need to wait for the view to be ready?



    正如上面详细解释的那样,遗憾的是,没有。

    编辑:你的小函数看起来更像这样:
    @get:RequiresApi(17)
    private val getRawLayoutDirectionMethod: Method by lazy(LazyThreadSafetyMode.NONE) {
    // This method didn't exist until API 17. It's hidden API.
    View::class.java.getDeclaredMethod("getRawLayoutDirection")
    }

    val View.rawLayoutDirection: Int
    @TargetApi(17) get() = when {
    Build.VERSION.SDK_INT >= 17 -> {
    getRawLayoutDirectionMethod.invoke(this) as Int // Use hidden API.
    }
    Build.VERSION.SDK_INT >= 14 -> {
    layoutDirection // Until API 17 this method was hidden and returned raw value.
    }
    else -> ViewCompat.LAYOUT_DIRECTION_LTR // Until API 14 only LTR was a thing.
    }

    @Suppress("DEPRECATION")
    val Configuration.layoutDirectionCompat: Int
    get() = if (Build.VERSION.SDK_INT >= 17) {
    layoutDirection
    } else {
    TextUtilsCompat.getLayoutDirectionFromLocale(locale)
    }


    private fun View.resolveLayoutDirection(): Int {
    val rawLayoutDirection = rawLayoutDirection
    return when (rawLayoutDirection) {
    ViewCompat.LAYOUT_DIRECTION_LTR,
    ViewCompat.LAYOUT_DIRECTION_RTL -> {
    // If it's set to absolute value, return the absolute value.
    rawLayoutDirection
    }
    ViewCompat.LAYOUT_DIRECTION_LOCALE -> {
    // This mimics the behavior of View class.
    TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
    }
    ViewCompat.LAYOUT_DIRECTION_INHERIT -> {
    // This mimics the behavior of View and ViewRootImpl classes.
    // Traverse parent views until we find an absolute value or _LOCALE.
    (parent as? View)?.resolveLayoutDirection() ?: run {
    // If we're not attached return the value from Configuration object.
    resources.configuration.layoutDirectionCompat
    }
    }
    else -> throw IllegalStateException()
    }
    }

    fun View.getRealLayoutDirection(): Int =
    if (ViewCompat.isLayoutDirectionResolved(this)) {
    layoutDirection
    } else {
    resolveLayoutDirection()
    }

    现在调用 View.getRealLayoutDirection()并获得您正在寻找的值(value)。

    请注意,这种方法在很大程度上依赖于访问隐藏的 API,这些 API 存在于 AOSP 中,但可能不存在于供应商实现中。彻底测试这个!

    关于android - 如何确定 View 的当前方向(RTL/LTR)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48277282/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com