⬆️ ⬇️

Implementation of the skin system in the Android application, or changing colors in one click

Greetings to all habravchan!



I recently implemented an interesting, in my opinion, task in an android application and decided to share my experience with you.

The task is as follows: change the color gamut of the application with one click. The so-called implementation of skins for the application.



Task


To make dynamic all views of the application, as well as in runtime to implement the change of skins.

')

Solution idea


When creating an activity, the system first parses the xml markup and constantly pulls the overrayd method getResources () when it encounters a link to resources from the markup. It is precisely in this link that we can intervene and slip the system’s own resources, and not those that it requests. At the same time, without noticing the trick, the system will apply the necessary resources to the views. And this is what we, in fact, sought.



Implementation of the idea


Based on the above, we need:



1) in the markup it is necessary to indicate links to resources (ie, implicitly). Well, of course, everything is specified via drawable, as well as colors
android:background="@color/color_tabbar_background" 


2) to create a basic class BasicActivity, which is inherited from Activity and from which all our activities in the application will be inherited, override the getResources () method in it.



3) Also, we will need a class, let's call it ResourseManager, which is inherited from Resourses.



Let's take a closer look at what the getResources () method does in the activation. This is what the official source tells us. Those. it returns an object of the Resources type, so we need in our BasicActivity class in the overridden getResources () method to check which theme is current and whether it is applied at all, if so, then create our own instance of the ResourseManager class. Here is a sample code for clarity.



 public class BasicActivity extends Activity { private ResourceManager manager = null; @Override public Resources getResources() { if (App.checkCurrentSkin()) { if (manager == null) { manager = new ResourceManager(super.getResources()); } return manager; } else return super.getResources(); } } 


This is all that needs to be done in the basic activation.

Now go to our ResourseManager. In it, we definitely need a constructor, as well as overridden methods getColor (int id), loadDrawable (TypedValue value, int id), getColorStateList (int id). With the last two methods we have small problems - they are packaged. This means that we do not have access to them. But in such cases, JAVA Reflection comes to our rescue! How it works, you can familiarize yourself, I will not tell, because This is not the topic of my article. Just give you the code for a better understanding.

 public class ResourceManager extends Resources { Skin mSkin = App.getCurrentSkin(); Method loadDrawable; Method loadColorStateList; Resources mBaseResources; public ResourceManager(Resources baseResources) { super(baseResources.getAssets(), baseResources.getDisplayMetrics(), baseResources.getConfiguration()); mBaseResources = baseResources; Method[] methods = Resources.class.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals("loadDrawable")) { loadDrawable = method; loadDrawable.setAccessible(true); } else if (method.getName().equals("loadColorStateList")) { loadColorStateList = method; loadColorStateList.setAccessible(true); } } } 


So we have allowed the use of the methods we need. Now it remains for the small, in these methods to determine the desired resource by its ID and return another. In your application, you can somehow keep the Skin object, which describes the fields corresponding to your dynamic views and the meaning of their colors. Here is a piece of implementation methods

 @Override public int getColor(int id) throws NotFoundException { String color; switch (id) { case R.color.chat_in: color = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_RECEIVE_MESSAGE_BUBLE).getBackgroundColor(); //    #b7c0c7 return Color.parseColor(color); case R.color.chat_send: color = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_SEND_MESSAGE_BUBLE).getBackgroundColor(); return Color.parseColor(color); …. 


Next method

 public Drawable loadDrawable(TypedValue value, int id) { Drawable d = null; String color; String colorSel; GradientDrawable result; switch (value.resourceId) { case R.color.color_background_main: color = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_VIEW).getBackgroundColor(); return new ColorDrawable(Color.parseColor(color)); case R.drawable.button_blue_selector: color = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_BUTTON_BLUE).getBackgroundColor(); colorSel = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_BUTTON_BLUE).getHighlightedBackgroundColor(); return createSelector(Color.parseColor(color), getDarkerColor(color), colorSel); case R.drawable.chat_in: color = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_RECEIVE_MESSAGE_BUBLE).getBackgroundColor(); result = new GradientDrawable(Orientation.TOP_BOTTOM, new int[]{Color.parseColor(color), Color.parseColor(color)}); result.setCornerRadius(25); return result; case R.drawable.list_view_selector: colorSel = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_TABLE_VIEW_CELL) .getHighlightedBackgroundColor(); ColorDrawable drawable = new ColorDrawable(Color.parseColor(colorSel)); StateListDrawable listDrawable = new StateListDrawable(); listDrawable.addState(new int[]{android.R.attr.state_pressed}, drawable); return listDrawable; case R.drawable.button_last_menu_selector: color = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_TABLE_VIEW_CELL).getBackgroundColor(); colorSel = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_TABLE_VIEW_CELL) .getHighlightedBackgroundColor(); return createSelectorForDifferentCorers(Color.parseColor(color), getDarkerColor(color), colorSel, 0, 0, 15, 15); default: break; } try { d = (Drawable) loadDrawable.invoke(mBaseResources, value, id); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return d; } 


And last

 @Override public ColorStateList getColorStateList(int id) throws NotFoundException { String color; String colorSel; ColorStateList res; switch (id) { case R.drawable.tab_text_selector: color = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_TABBAR_ITEM).getTextColor(); colorSel = mSkin.getControlWithType(SkinConstants.SKIN_CONTROL_TABBAR_ITEM).getHighlightedTextColor(); res = new ColorStateList(new int[][]{new int[]{android.R.attr.state_selected}, new int[]{-android.R.attr.state_selected}}, new int[]{Color.parseColor(colorSel), Color.parseColor(color)}); return res; default: break; } return super.getColorStateList(id); } 




From this code it is clear that we programmatically create all the drawabls. For example



 private Drawable createSelectorForDifferentCorers(int startColor, int endColor, String colorSel, float topLeft, float topRight, float bottomRight, float bottomLeft) { StateListDrawable drawable = new StateListDrawable(); GradientDrawable gradientDrawable1 = new GradientDrawable(Orientation.TOP_BOTTOM, new int[]{startColor, endColor}); GradientDrawable gradientDrawable2 = new GradientDrawable(Orientation.TOP_BOTTOM, new int[]{ Color.parseColor(colorSel), getDarkerColor(colorSel)}); gradientDrawable1.setShape(GradientDrawable.RECTANGLE); gradientDrawable1.setCornerRadii(new float[]{topLeft, topLeft, topRight, topRight, bottomRight, bottomRight, bottomLeft, bottomLeft}); gradientDrawable1.setStroke(1, endColor); gradientDrawable2.setShape(GradientDrawable.RECTANGLE); gradientDrawable2.setCornerRadii(new float[]{topLeft, topLeft, topRight, topRight, bottomRight, bottomRight, bottomLeft, bottomLeft}); gradientDrawable2.setStroke(1, endColor); drawable.addState(new int[]{android.R.attr.state_pressed}, gradientDrawable2); drawable.addState(new int[]{android.R.attr.state_checked}, gradientDrawable2); drawable.addState(new int[]{-android.R.attr.state_selected}, gradientDrawable1); return drawable; } 




In general, a huge flight for your imagination.

I also want to immediately discuss one important point. If you have a complicated picture that you cannot programmatically draw, such as an icon for tobacco, then you can also change its color. For this, it is necessary that the picture for the icon be pure white, since we will mix it with the color we need by multiplying the meanings of the colors. Here we get the color of the picture we need

 Drawable example = ImageManager.blendColorDrawable(this, R.drawable.arrow, R.color.color_2_end); 


and we can do with it, anything. At least put an icon on the tab.

 public static Drawable blendColorDrawable(Context context, int baseId, int colorId) { Resources res = context.getResources(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; Bitmap base = BitmapFactory.decodeResource(res, baseId, options); Bitmap blend = Bitmap.createBitmap(base.getWidth(), base.getHeight(), Config.ARGB_8888); blend.eraseColor(context.getResources().getColor(colorId)); Bitmap result = base.copy(Config.ARGB_8888, true); Paint p = new Paint(); p.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY)); p.setShader(new BitmapShader(blend, TileMode.CLAMP, TileMode.CLAMP)); Canvas c = new Canvas(); c.setBitmap(result); c.drawBitmap(base, 0, 0, null); c.drawRect(0, 0, base.getWidth(), base.getHeight(), p); return new BitmapDrawable(context.getResources(), result); 




Since I chose pieces from a large project, where it’s just one of the features, I don’t attach an example of the project. If you do not understand and it will be very necessary, I can give you an example of the source code. Write in the comments!



PS Remember, in order for the changes to take effect, you need to recreate the activation



Update: I want to note that this method is for those whose skins (themes) are dynamic objects that you cannot prescribe in xml.

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



All Articles