可以使用以下方法获取当前的区域设置方向:
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与正确的一侧对齐:
如果它可以像我想象的那样工作(意思是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?
答案 0 :(得分:7)
默认情况下,方向是LTR,但实际上它是否与右边对齐,以防语言环境发生变化?
不同之处在于时间。创建视图时,会为其分配一个默认值,直到解析实际值。实际上有两个值保持不变:
getLayoutDirection()
返回默认LAYOUT_DIRECTION_LTR
,getRawLayoutDirection()
(隐藏API)返回LAYOUT_DIRECTION_INHERIT
。当原始布局方向为LAYOUT_DIRECTION_INHERIT
时,实际布局方向将作为measure
调用的一部分进行解析。该视图然后遍历其父母
ViewRootImpl
)。在第二种情况下,当视图层次结构尚未附加到窗口时,布局方向未解析且getLayoutDirection()
仍返回默认值。这是您的示例代码中发生的情况。
当视图层次结构附加到视图根时,将为Configuration
对象分配布局方向。换句话说,读取已解决的布局方向仅在将视图层次结构附加到窗口后才有意义。
无论开发人员为其设置(或未设置),我如何检查给定View的方向是LTR还是RTL?
首先检查布局方向是否已解决。如果是,您可以使用该值。
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
中)不保证该行为,因此请勿使用它们。
它从哪里获取getLayoutDirection和getRawLayoutDirection的值?
View.getRawLayoutDirection()
(隐藏API)会返回您通过View.setLayoutDirection()
设置的内容。默认情况下它是LAYOUT_DIRECTION_INHERIT
,这意味着“从我的父级继承布局方向”。
View.getLayoutDirection()
返回已解决的布局方向,即LOCATION_DIRECTION_LTR
(默认情况下,直到实际解决)或LOCATION_DIRECTION_RTL
。此方法不返回任何其他值。仅当视图是附加到视图根的视图层次结构的一部分时,返回值才有意义。
为什么LAYOUT_DIRECTION_LTR是默认值?
从历史上看,Android根本不支持从右到左的脚本(请参阅here),从左到右是最合理的默认值。
视图的根会返回某个区域设置吗?
默认情况下,所有视图都会继承其父级的布局方向。那么最顶层的视图在连接之前获取布局方向 ?无处,它不能。
当窗口附加视图层次结构时会发生以下情况:
final Configuration config = context.getResources().getConfiguration();
final int layoutDirection = config.getLayoutDirection();
rootView.setLayoutDirection(layoutDirection);
使用系统区域设置设置默认配置,并从该区域设置获取布局方向。然后将根视图设置为使用该布局方向。现在,LAYOUT_DIRECTION_INHERIT
的所有孩子都可以遍历并被解析为绝对值。
即使不需要等待视图准备好,我的小功能的一些修改是否能够工作?
如上所述,遗憾的是,没有。
编辑:您的小功能看起来会更像这样:
@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,但可能不会出现在供应商实现中。彻底测试!