Force RTL layout direction not working for app

2019-01-23 23:38发布

问题:

I'm trying to add RTL language support in my app (specifically Arabic right now). I'll be supporting English as well. What I've done:

  • Set minSdkVersion to 17
  • Added android:supportsRtl="true" to my application tag in AndroidManifest.xml
  • Switched my left/right attributes to start/end

At first I made these changes manually, then I used Android Studio's "Refactor -> Add RTL Support Where Possible..." menu item.

When I preview my layout files, I can see the RTL preview is properly mirroring the UI; however, even when I use the "Force RTL Layout Direction", my app doesn't display the RTL layout. The system UI is flipped, so that option does work in general.

Is there anything else I need to do to show the RTL layout? I'm hoping I have missed something obvious. I am testing this on an API 21 emulator.

Update

I inherited some of this code. Something might be overriding a setting and forcing this into LTR mode. I made a test application to test out RTL mode and it worked fine. What sort of code could cause the "Force RTL Layout Direction" setting to be ignored (or be overridden)?

Update 2

I've checked that the locale is being set properly, and it is. I also checked the configuration, and ldrtl is set. I verified in the signed apk file that android:supportsRtl made it in and none of the layout files had android:layoutDirection="ltr". I've even tried manually putting android:layoutDirection="rtl" to try to force a layout to mirror, but that didn't work either.`

Update 3

I added another activity to the project, made it the launcher activity, and made sure it isn't connected to any existing code. It is a subclass of Activity. The issue still exists. So theoretically this is a configuration issue. Like I said I checked the AndroidManifest.xml file and all the layout files that are generated, and the RTL support and layout changes all made it in. What could be going wrong with configuration?

回答1:

Most obscure bug ever. As I mentioned in the question, most of this code was inherited. It ended up being a bitwise operator issue that was screwing up flags for the application. &= was being used instead of & to check if a flag was set.

As noted in the comments, the code was taken from an example in an Android Developers blog post, which explains why so many others have run into this same issue. I filed a bug report, and the blog post has since been silently updated. Here is the original code, which I removed &= from. Do not use the following code as is. You need to change &= to &:

isDebuggable = (appContext.getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE) != 0;


回答2:

Try this...

  1. Create a class to maintain global application state.

    public class YourGlobalClass extends Application {
    
      @Override
      public void onCreate() {
      updateLanguage(this, null);
      super.onCreate();
      }
    
    public static void updateLanguage(Context ctx, String lang) {
    
     Configuration cfg = new Configuration();
     LocalSharedManager manager = new LocalSharedManager(ctx);
     String language = manager.GetValueFromSharedPrefs("force_locale");
    
        if (TextUtils.isEmpty(language) && lang == null) {
           cfg.locale = Locale.getDefault();
           String tmp_locale = "";
           tmp_locale = Locale.getDefault().toString().substring(0, 2);
           manager.SaveValueToSharedPrefs("force_locale", tmp_locale);
    
       } else if (lang != null) {
           cfg.locale = new Locale(lang);
           manager.SaveValueToSharedPrefs("force_locale", lang);
    
       } else if (!TextUtils.isEmpty(language)) {
           cfg.locale = new Locale(language);
       }
       ctx.getResources().updateConfiguration(cfg, null);
      }
    
    }
    
  2. Specify global class to your AndroidManifest.xml's tag, which will cause that class to be instantiated with your saved locale, when the process for your application/package is created. Like, android:name="com.your.package.YourGlobalClass" android:supportsRtl="true"

  3. Create following two methods in your MainActivity.java (wherever you want).

    public class MainActivity extends ActionBarActivity{
    
       .......
    
       // Implement OnclickListener for english_locale button
       findViewById(R.id.english_locale).setOnClickListener(new OnClickListener()
         {
    
            @Override
            public void onClick(View v)
            {
                changeEnglish();
            }
         });
    
        // Implement OnclickListener for arabic_locale button
        findViewById(R.id.arabic_locale).setOnClickListener(new OnClickListener()
           {
    
              @Override
              public void onClick(View v)
               {
                  changeArabic();
                }
           });
    
        /**
        * Method that Update UI for Arabic locale.
        */
        public void changeArabic() {
             new AsyncTask<Void, Void, Void>() {
    
             @Override
             protected Void doInBackground(Void... params) {
                  String app_locale = "ar";
                  Locale locale = new Locale(app_locale);
                  Locale.setDefault(locale);
    
                  //Configuration to query the current layout direction.
                  Configuration config = new Configuration();
                  config.locale = locale;
                  getResources().updateConfiguration(config,
                    getResources().getDisplayMetrics());
                  Bidi bidi = new Bidi(app_locale,
                    Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT);
                  bidi.isRightToLeft();
                  YourGlobalClass.updateLanguage(getActivity(), "ar");
    
                  //Refreshing current fragment
    
                  Intent i = getActivity().getIntent();
                  startActivity(i);
                  getActivity().finish();
               return null;
              }
    
           }.execute();
         }
    
            /**
            * Method that Update UI for Default(English) locale.
            */
          public void changeEnglish() {
    
              new AsyncTask<Void, Void, Void>() {
    
              @Override
              protected Void doInBackground(Void... params) {
                  String app_locale = "en";
                  Locale locale = new Locale(app_locale);
                  Locale.setDefault(locale);
    
                  //Configuration to query the current layout direction.
                  Configuration config = new Configuration();
                  config.locale = locale;
                  getResources().updateConfiguration(config,
                    getResources().getDisplayMetrics());
                  Bidi bidi = new Bidi(app_locale,
                    Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
                  bidi.isLeftToRight();
                  YourGlobalClass.updateLanguage(getActivity(), "en");
    
                  //Refreshing current fragment
                  Intent i = getActivity().getIntent();
                  startActivity(i);
                  getActivity().finish();
    
              return null;
              }
    
           }.execute();
         }
    
       ......
      //MainActivity end
    }
    
  4. Happy coding...