Background
It's possible to get the current locale direction, using this:
val isRtl=TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
It's also possible to get the layout direction of a view, if the developer has set it:
val layoutDirection = ViewCompat.getLayoutDirection(someView)
The problem
The default layoutDirection of a view isn't based on its locale. It's actually LAYOUT_DIRECTION_LTR .
When you change the locale of the device from LTR (Left-To-Right) locale (like English) to RTL (Right-To-Left) locale (like Arabic or Hebrew) , the views will get aligned accordingly, yet the values you get by default of the views will stay LTR...
This means that given a view, I don't see how it's possible to determine the correct direction it will go by.
What I've tried
I've made a simple POC. It has a LinearLayout with a TextView:
<?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>
In code, I write the direction of the locale, and of the views:
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"
}
}
The result is that even when I switch to RTL locale (Hebrew - עברית), it prints this in logs:
locale direction:isRTL? true
linearLayout direction:LAYOUT_DIRECTION_LTR
textView direction:LAYOUT_DIRECTION_LTR
And of course, the textView is aligned to the correct side, according to the current locale:
If it would have worked as I would imagine (meaning LAYOUT_DIRECTION_LOCALE by deafult), this code would have checked if a view is in RTL or not:
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
}
But it can't, because LTR is the default one, and yet it doesn't even matter...
So this code is wrong.
The questions
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?
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 ?
The difference is in time. When the view is created it's assigned a default value until the real value is resolved. Actually there are two values maintained:
getLayoutDirection()
returns the defaultLAYOUT_DIRECTION_LTR
,getRawLayoutDirection()
(hidden API) returnsLAYOUT_DIRECTION_INHERIT
.When raw layout direction is
LAYOUT_DIRECTION_INHERIT
the actual layout direction is resolved as part of themeasure
call. The view then traverses its parentsViewRootImpl
).In the second case, when the view hierarchy is not attached to a window yet, layout direction is not resolved and
getLayoutDirection()
still returns the default value. This is what happens in your sample code.When view hierarchy is attached to view root, it is assigned layout direction from the
Configuration
object. In other words reading resolved layout direction only makes sense after the view hierarchy has been attached to window.First check, whether layout direction is resolved. If it is, you may work with the value.
Note that the method always returns false below Kitkat.
If layout direction is not resolved, you'll have to delay the check.
Option 1: Post it to the main thread message queue. We're assuming that by the time this runs, the view hierarchy has been attached to window.
Option 2: Get notified when the view hierarchy is ready to perform drawing. This is available on all API levels.
Note: You actually can subclass any
View
and override itsonAttachedToWindow
method, because layout direction is resolved as part ofsuper.onAttachedToWindow()
call. Other callbacks (inActivity
orOnWindowAttachedListener
) do not guarantee that behavior, so don't use them.More answers to more questions
View.getRawLayoutDirection()
(hidden API) returns what you set viaView.setLayoutDirection()
. By default it'sLAYOUT_DIRECTION_INHERIT
, which means "inherit layout direction from my parent".View.getLayoutDirection()
returns the resolved layout direction, that's eitherLOCATION_DIRECTION_LTR
(also default, until actually resolved) orLOCATION_DIRECTION_RTL
. This method does not return any other values. The return value only makes sense after a measurement happened while the view was part of a view hierarchy that's attached to a view root.Historically Android didn't support right-to-left scripts at all (see here), left-to-right is the most sensible default value.
All views inherit their parent's layout direction by default. So where does the topmost view get the layout direction before it's attached? Nowhere, it can't.
When a view hierarchy is attached to window something like this happens:
Default configuration is set up with system locale and layout direction is taken from that locale. Root view is then set to use that layout direction. Now all its children with
LAYOUT_DIRECTION_INHERIT
can traverse and be resolved to this absolute value.As explained in great detail above, sadly, no.
Edit: Your small function would look a little more like this:
Now call
View.getRealLayoutDirection()
and get the value you were looking for.Please note that this approach relies heavily on accessing hidden API which is present in AOSP but may not be present in vendor implementations. Test this thoroughly!