Android dependent string theme

Let's say I have an application with two themes: male and female. Themes simply change the color palette and several drawings to suit the user's preferences.

Many thanks to http://www.androidengineer.com/2010/06/using-themes-in-android-applications.html for his hints of doing this work.

But now I can say that I want to get a little cuter with the application, not only change the colors and drawings, but I also want to change the lines. For example, I could add a pirate theme, and then "Submit" would be "Arrrrgh!"

So, my main question is: how can I change the lines throughout the application through custom themes?

Edit:

Creation: the application has 12 buttons and 32 text views. I would like to have a theme dependent, and I would like to do it without a giant collation or a lot of user attrs.

All 3 of the existing solutions will work. Looking for something cleaner, although I do not know what such a beast exists.

+6
source share
5 answers

Let's say I have an application with two themes: male and female. Themes simply change the color palette and several drawings to suit the user's preferences.

How about we pretend that you are doing something else? This is an anti-design pattern that associates certain colors based on gender (for example, “girls like pink”).

This does not mean that your technical goal is bad, it’s just a really stereotypical example.

For example, I can add a pirate theme, and then "Submit" will be "Arrrrgh!"

Only if “Cancel” is displayed on “Avast!”.

How can I change the lines throughout the application through custom themes?

You did not say where these lines come from. Are they string resources? Database Records? What do you get from the web service? Something else?

I assume at the moment that these are string resources. By definition, you will need to have N copies of rows, one per topic.

Since gender and pirated status are not tracked by Android as possible resource set qualifiers, you cannot have these string resources in different resource sets. Although they may be in different files (for example, res/values/strings_theme1.xml ), file names are not part of the resource identifiers for strings. So you have to use some kind of prefix / suffix to keep track of which lines belong to those (e.g. @string/btn_submit_theme1 ).

If these lines do not change at run time - that’s all that is in your layout resource, you can take a page from Chris Jenkins’s Calligraphy Library . It has its own subclass of LayoutInflater , used to overload some standard XML attributes. In his case, the focus is on android:fontFamily , where he supports this mapping to the font file in assets.

In your case, you can overload android:text . In your layout file, instead of pointing to any of your actual lines, you could get this base name of the string resource you need, without any topic identifier (for example, if the real lines are @string/btn_submit_theme1 and kin, you could have android:text="btn_submit" ). Your subclass LayoutInflater should capture this value, add a suffix for the theme name, use getIdentifier() on your Resources to find the actual resource identifier of the string and from there bind the string to your theme.

A variant of this would be to put the base name in android:tag instead of android:text . android:text can point to one of your real line resources to help with GUI design, etc. Your LayoutInflater will grab the tag and use it to get the correct line at runtime.

If you replace the text with other text torn from the subject line resources, you can isolate your get-the-string-given-the-base-name logic in a static utility, wherever you apply it.

If used correctly, this process will require a little work, it will scale to arbitrary complexity in terms of the number of visible widgets and user interface lines. You still need to remember adding values ​​for all topics for any new lines that you define (bonus points for creating a custom Lint check or Gradle task for checking this).

+2
source

Yes, it can be done, and here’s how: first you need to define a theme attribute, for example:

 <attr name="myStringAttr" format="string|reference" /> 

Then in your themes add this line

 <item name="myStringAttr">Yarrrrr!</item> 

or

 <item name="myStringAttr">@string/yarrrrr</item> 

Then you can use this attribute in the XML file (see ? Instead of @ ).

 <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="?attr/myStringAttr" /> 

or, from code, for example:

 public CharSequence resolveMyStringAttr(Context context) { Theme theme = context.getTheme(); TypedValue value = new TypedValue(); if (!theme.resolveAttribute(R.attr.myStringAttr, value, true)) { return null; } return value.string; } 
+7
source

Since the resource is just an int at heart, you can store several of them at runtime, and they replace them procedurally as you use them.


 <?xml version="1.0" encoding="utf-8"?> <resources> <string name="OK_NORMAL">Okay</string> <string name="OK_PIRATE">Yaaarrrr!</string> <string name="OK_NINJA">Hooooaaa!</string> </resources> 

 public enum ThemeMode { PIRATE, NINJA, NORMAL; } 

 public class MyThemeStrings { public static int OK_PIRATE = R.string.OK_PIRATE; public static int OK_NINJA = R.string.OK_NINJA; public static int OK_NORMAL = R.string.OK_NORMAL; } 

 public setOkButtonText(ThemeMode themeMode) { // buttonOk is instantiated elsewhere switch (themeMode) { case PIRATE: buttonOk.setText(MyThemeStrings.OK_PIRATE); break; case NINJA: buttonOk.setText(MyThemeStrings.OK_NINJA); break; default: Log.e(TAG, "Unhandled ThemeMode: " + themeMode.name()); // no break here so it flows into the NORMAL base case as a default case NORMAL: buttonOk.setText(MyThemeStrings.OK_NORMAL); break; } } 

Although, writing all this is probably the best way to do this through separate XML files. I will review it now and write a second solution if I find it.

+1
source

Well, I have a second option, which may actually be easier to maintain and keep your code cleaner, although it may be a more resource hunger due to loading an array for each line. I have not tested it, but I suggest it as another option, but I would not use it if you offer too many theme options.


 public enum ThemeMode { NORMAL(0), PIRATE(1), NINJA(2); private int index; private ThemeMode(int index) { this.index = index; } public int getIndex() { return this.index; } } 

 <resources> <!-- ALWAYS define strings in the correct order according to the index values defined in the enum --> <string-array name="OK_ARRAY"> <item>OK</item> <item>Yaarrrr!</item> <item>Hooaaaa!</item> </string-array> <string-array name="CANCEL_ARRAY"> <item>Cancel</item> <item>Naarrrrr!</item> <item>Wha!</item> </string-array> </resources> 

 public setButtonTexts(Context context, ThemeMode themeMode) { // buttons are instantiated elsewhere buttonOk.setText(context.getResources() .getStringArray(R.array.CANCEL_ARRAY)[themeMode.getIndex()]); buttonCancel.setText(context.getResources() .getStringArray(R.array.OK_ARRAY)[themeMode.getIndex()]); } 
+1
source

So, I did not have the opportunity to verify this, but by reading the file in Locale, it looks like you can create your own location.

http://developer.android.com/reference/java/util/Locale.html

and help from another stackoverflow

Set locale programmatically

A little combination leads me to:

 Locale pirate = new Locale("Pirate"); Configuration config = new Configuration(); config.locale = pirate; this.getActivity().getBaseContext().getResources() .updateConfiguration(config, this.getActivity().getBaseContext().getResources().getDisplayMetrics()); 

I believe this will allow you to have res / values-pirate / strings as the real valid resource that will be used when you are a pirate. Any lines or settings that you do not override then return to the res / values ​​/ ... default parameters, so you can do this for as many as you want. Again, if that works.

+1
source

Source: https://habr.com/ru/post/971143/


All Articles