Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Ahmed El-Helw

March 17, 2016
Tweet

More Decks by Ahmed El-Helw

Other Decks in Programming

Transcript

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

    66 million native speakers • Persian: 45 million native speakers Nationalencyklopedin, 2010
  2. 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. –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. RTL • Font • Reshaper support • Layout mirroring •

    Bidirectional text support (Bidi) د م ح ا دمحأ
  5. Jellybean • Bidi support - 4.1 • Layout mirroring support,

    improved fonts - 4.2 • Improved Bidi support - 4.3
  6. 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
  7. 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
  8. Mirroring Layouts • ldrtl / ldltr resource qualifiers • lower

    priority than -lang • useful for mirroring drawables
  9. 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 }
  10. 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.
  11. 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); // ... }
  12. @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(); } }
  13. Older APIs • no layout-ldrtl / layout-ldltr • have to

    fallback to layout-ar, layout-he, …
  14. 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"/>
  15. 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"/>
  16. 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"/>
  17. • for certain text pre-17, may need to explicitly set

    the gravity. • GravityCompat and ViewCompat are your friends
  18. Older APIs - pre-14 • include a font capable of

    rendering the language • bundle your own reshaper • be wary of custom vendor solutions
  19. 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>
  20. RelativeLayout • same problem with right/end • Fixed in 4.3

    (api 18) • layout-v17 with android:alignParentStart
  21. Character Types • Strong a b ت ب • Weak

    + - : . , • Neutral whitespace • Explicit Formatting Characters
  22. 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
  23. @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)); }
  24. @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)); }
  25. @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)); }
  26. 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
  27. 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