From Right to Left and Back

From Right to Left and Back

Talk given at Droidcon SF 2016 about implementing RTL on Android.

Some of the topics discussed:
- overview of RTL
- how to implement RTL pre and post api-17
- implementing RTL in custom Views, ViewGroups, and Drawables
- common pitfalls and problems and how to solve them

credit for images:
- android flavor versions: https://www.android.com/history
- various meems: https://imgflip.com/memegenerator

F60e42d94f99f029b590206076dbd354?s=128

Ahmed El-Helw

March 17, 2016
Tweet

Transcript

  1. 3.

    Potential Reach • Arabic: 295 million native speakers • Urdu:

    66 million native speakers • Persian: 45 million native speakers Nationalencyklopedin, 2010
  2. 4.

    Statistics - Q1 2015 • 95% of phones shipped in

    MEA are iOS / Android • shipments of iOS/Android phones “increased by a combined 67% year on year” IDC Press Release, July 12th, 2015
  3. 5.

    –IDC Press Release, July 12th, 2015 “In the Middle East,

    Android currently represents 80% of market's volume, while iOS accounts for 17%; in Africa, these figures stand at 89% and 7%, respectively.”
  4. 8.

    RTL • Font • Reshaper support • Layout mirroring •

    Bidirectional text support (Bidi) د م ح ا دمحأ
  5. 9.
  6. 16.

    Jellybean • Bidi support - 4.1 • Layout mirroring support,

    improved fonts - 4.2 • Improved Bidi support - 4.3
  7. 17.
  8. 19.
  9. 23.

    Resolving Layout Direction • android:supportsRTL - if not, return LTR

    • based on android:layoutDirection • LAYOUT_DIRECTION_RTL or LAYOUT_DIRECTION_LTR • LAYOUT_DIRECTION_LOCALE • LAYOUT_DIRECTION_INHERIT
  10. 24.

    Resolving Layout Direction • for the inherit case, ViewRootImpl sets

    top level direction to use Locale. • resolving Locale happens in TextUtils (17+) • TextUtils gets information from ICU
  11. 31.

    Mirroring Layouts • ldrtl / ldltr resource qualifiers • lower

    priority than -lang • useful for mirroring drawables
  12. 34.

    if ((gravity & RELATIVE_LAYOUT_DIRECTION) > 0) { if ((gravity &

    Gravity.START) == Gravity.START) { if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { // start resolving to right } else { // start resolving to left } } else if ((gravity & Gravity.END) == Gravity.END) { if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { // end resolving to left } else { // end resolving to right } } } else if ((gravity & Gravity.LEFT) == Gravity.LEFT) { // left } else if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { // right }
  13. 41.
  14. 44.
  15. 46.

    Gravity.apply( gravity, w, h, container, outRect, layoutDirection); a Rect in

    which the item will be positioned. should be at least as large as the item.
  16. 52.
  17. 53.

    LinearLayout int start = 0; int dir = 1; //

    In case of RTL, start drawing from the last child.
 if (isLayoutRtl) { start = count - 1; dir = -1;
 } 
 for (int i = 0; i < count; i++) { int childIndex = start + dir * i; final View child = getVirtualChildAt(childIndex); // ... }
  18. 55.
  19. 57.
  20. 58.

    @Override protected void onDraw(Canvas canvas) { boolean isRtl = getLayoutDirection()

    == LAYOUT_DIRECTION_RTL; if (isRtl) { canvas.save(); canvas.translate(getWidth(), 0); canvas.scale(-1.0f, 1.0f); } canvas.drawRect(0, 0, 100, 100, paint); if (isRtl) { canvas.restore(); } }
  21. 59.
  22. 63.

    Older APIs • no layout-ldrtl / layout-ldltr • have to

    fallback to layout-ar, layout-he, …
  23. 64.

    Example <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="120dp"> <TextView

    android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="@string/two" android:background="#E3F2FD"/> </LinearLayout> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="@string/one" android:background="#E8F5E9"/>
  24. 65.
  25. 66.
  26. 67.

    layout-ar <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="120dp"> <TextView

    android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="@string/two" android:background="#E3F2FD"/> </LinearLayout> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="@string/one" android:background="#E8F5E9"/>
  27. 68.
  28. 70.

    layout-ar <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="120dp" android:layout_direction="ltr">

    <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="@string/one" android:background="#E8F5E9"/> </LinearLayout> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="@string/two" android:background="#E3F2FD"/>
  29. 71.
  30. 72.

    • for certain text pre-17, may need to explicitly set

    the gravity. • GravityCompat and ViewCompat are your friends
  31. 73.

    Older APIs - pre-14 • include a font capable of

    rendering the language • bundle your own reshaper • be wary of custom vendor solutions
  32. 74.
  33. 76.

    RelativeLayout <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
 android:layout_height="match_parent"> <Button android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:text="@string/app_name" android:layout_alignParentLeft="true" android:layout_alignParentStart="true"/>
 </RelativeLayout>
  34. 79.

    RelativeLayout • same problem with right/end • Fixed in 4.3

    (api 18) • layout-v17 with android:alignParentStart
  35. 80.
  36. 81.

    Character Types • Strong a b ت ب • Weak

    + - : . , • Neutral whitespace • Explicit Formatting Characters
  37. 82.

    Text Direction • TEXT_DIRECTION_INHERIT • TEXT_DIRECTION_FIRST_STRONG • TEXT_DIRECTION_ANY_RTL • TEXT_DIRECTION_LTR

    • TEXT_DIRECTION_RTL • TEXT_DIRECTION_LOCALE • TEXT_DIRECTION_FIRST_STRONG_LTR • TEXT_DIRECTION_FIRST_STRONG_RTL • TEXT_DIRECTION_INHERIT • TEXT_DIRECTION_FIRST_STRONG • TEXT_DIRECTION_ANY_RTL • TEXT_DIRECTION_LTR • TEXT_DIRECTION_RTL • TEXT_DIRECTION_LOCALE • TEXT_DIRECTION_FIRST_STRONG_LTR • TEXT_DIRECTION_FIRST_STRONG_RTL
  38. 84.
  39. 86.

    @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); setContentView(R.layout.rtl_text); // Ahmed

    in English, ﺪﻤﺣأ in Arabic String name = getString(R.string.name); String s = getString(R.string.name_format); TextView tv = (TextView) findViewById(R.id.first); tv.setText(String.format(s, name, 3)); }
  40. 87.
  41. 88.

    @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); setContentView(R.layout.rtl_text); // Ahmed

    in Arabic, ﺪﻤﺣأ in English String name = getString(R.string.name_opposite); String s = getString(R.string.name_format); TextView tv = (TextView) findViewById(R.id.first); tv.setText(String.format(s, name, 3)); }
  42. 89.
  43. 90.

    @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); setContentView(R.layout.rtl_text); // Ahmed

    in Arabic, ﺪﻤﺣأ in English String name = getString(R.string.name_opposite); String s = getString(R.string.name_format); BidiFormatter formatter = BidiFormatter.getInstance();
 name = formatter.unicodeWrap(name); TextView tv = (TextView) findViewById(R.id.first); tv.setText(String.format(s, name, 3)); }
  44. 91.
  45. 92.
  46. 93.

    Tips • android:supportsRTL=“true” • for layout parameters, add START and

    END in addition to RIGHT and LEFT • be careful when mixing RTL and LTR text
  47. 94.

    Tips • Be wary when using the default locale •

    ship RTL to 17+ to save a lot of work • test layouts and views in RTL and LTR on various api versions
  48. 95.