ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2019-05-27 11:14:36  阅读:651  来源: 互联网

标签:android android-layout locale right-to-left left-to-right


背景

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

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

如果开发人员设置了视图,也可以获得layout direction的视图:

val layoutDirection = ViewCompat.getLayoutDirection(someView)

问题

视图的默认layoutDirection不基于其语言环境.它实际上是LAYOUT_DIRECTION_LTR.

当您将设备的区域设置从LTR(从左到右)区域设置(如英语)更改为RTL(从右到左)区域设置(如阿拉伯语或希伯来语)时,视图将相应地对齐,但值为默认获取视图将保留LTR …

这意味着在给定视图的情况下,我看不出如何确定它将采用的正确方向.

我试过的

我做了一个简单的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>

在代码中,我编写了语言环境和视图的方向:

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

如果它可以像我想象的那样工作(意思是deafult的LAYOUT_DIRECTION_LOCALE),这段代码将检查视图是否在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?

不同之处在于时间.创建视图时,会为其分配一个默认值,直到解析实际值.实际上有两个值保持:

> getLayoutDirection()返回默认的LAYOUT_DIRECTION_LTR,
> getRawLayoutDirection()(隐藏API)返回LAYOUT_DIRECTION_INHERIT.

当原始布局方向为LAYOUT_DIRECTION_INHERIT时,实际布局方向将作为度量调用的一部分进行解析.然后该视图遍历其父母

>直到找到具有具体值集的视图
>或直到它到达缺少的视图根(窗口或ViewRootImpl).

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

将视图层次结构附加到视图根时,将从Configuration对象为其分配布局方向.换句话说,只有将视图层次结构附加到窗口后才能读取已解析的布局方向.

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.post {
    val rtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL
    // Use the resolved value.
}

选项2:在视图层次结构准备好执行绘图时收到通知.这适用于所有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()调用的一部分解析的.其他回调(在Activity或OnWindowAttachedListener中)不保证该行为,因此不要使用它们.

更多问题的答案

Where does it get the value of getLayoutDirection and getRawLayoutDirection ?

View.getRawLayoutDirection()(隐藏API)返回您通过View.setLayoutDirection()设置的内容.默认情况下,它是LAYOUT_DIRECTION_INHERIT,这意味着“从我的父级继承布局方向”.

View.getLayoutDirection()返回已解析的布局方向,即LOCATION_DIRECTION_LTR(也是默认值,直到实际解析)或LOCATION_DIRECTION_RTL.此方法不返回任何其他值.只有在视图是附加到视图根的视图层次结构的一部分时,返回值才有意义.

Why is LAYOUT_DIRECTION_LTR the default value ?

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

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

默认情况下,所有视图都继承其父级的布局方向.那么最顶层的视图在连接之前获取布局方向在哪里?无处,它不可能.

当视图层次结构附加到窗口时会发生以下情况:

final Configuration config = context.getResources().getConfiguration();
final int layoutDirection = config.getLayoutDirection();
rootView.setLayoutDirection(layoutDirection);

使用系统区域设置设置默认配置,并从该区域设置获取布局方向.然后将根视图设置为使用该布局方向.现在,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()并获取您要查找的值.

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

标签:android,android-layout,locale,right-to-left,left-to-right
来源: https://codeday.me/bug/20190527/1162684.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有