Drawable coloring for the back-compatibility scheme

Source: Internet
Author: User

See Android Weekly The latest issue has an article: tinting drawables, using Colorfilter to create a manual TintBitmapDrawable , have seen some articles used in this way to achieve drawable coloring or implement similar functions. However, this is not a perfect solution, this article will introduce a perfect back-compatibility scheme.

Solution Solutions

In fact, in the Android support V4 package provides DrawableCompat classes, we can easily write the following helper methods to achieve drawable coloring, as follows:

public static drawable tintdrawable (drawable drawable, colorstatelist colors) {    final drawable wrappeddrawable = Draw Ablecompat.wrap (drawable);    Drawablecompat.settintlist (wrappeddrawable, colors);    return wrappeddrawable;}

Examples of Use:

EditText editText1 = (EditText) Findviewbyid (r.id.edit_1); final drawable originaldrawable = Edittext1.getbackground (); Final drawable wrappeddrawable = tintdrawable (originaldrawable, colorstatelist.valueof (color.red)); Edittext1.setbackgrounddrawable (wrappeddrawable); EditText editText2 = (EditText) Findviewbyid (r.id.edit_2); Edittext2.setbackgrounddrawable (Tintdrawable ( Edittext2.getbackground (),        colorstatelist.valueof (Color.parsecolor ("#03A9F4")));

The effect is as follows:

This approach supports almost all drawable types and is perfectly compatible with almost all Android versions.

Optimized use ColorStateListColoring

Coloring is supported in this way ColorStateList , so that we can also color different colors depending on the state of the View.
For the above example of EditText, we can optimize it, depending on whether it gets the focus and sets it to a different color. We create a new one res/color/edittext_tint_colors.xml as follows:

<?xml version= "1.0" encoding= "Utf-8"? ><selector xmlns:android= "http://schemas.android.com/apk/res/ Android ">    <item android:color=" @color/red "android:state_focused=" true "/>    <item Android:color = "@color/gray"/></selector>

The code changes to this:

Edittext2.setbackgrounddrawable (Tintdrawable (Edittext2.getbackground (),    getresources (). GetColorStateList ( (r.color.edittext_tint_colors)));
Optimization of Bitmapdrawable

First look at the problem. The original Icon looks like this:

We use two ImageView, one without any processing, one using the following code coloring:

ImageView ImageView = (ImageView) Findviewbyid (r.id.image_1); final drawable originalbitmapdrawable = Getresources (). Getdrawable (R.drawable.icon); Imageview.setimagedrawable (Tintdrawable (originalbitmapdrawable, Colorstatelist.valueof (Color.magenta));

The effect is as follows:

What's going on? I clearly only to the back one set the coloring of the drawable, why two have been coloring? This is because Android in order to optimize the performance of the system, the resource drawable only one copy, you modify it, equal to all the changes. If you set the same resource for two View, it's the same state:

It is also that they are shared state. Fortunately, Drawable provides a way to mutate() break this sharing state, which is tantamount to telling the system that I want to modify (mutate) this drawable. After the method is called to drawable mutate() . Their relationship becomes as shown in the following diagram:

Let's Change the code:

ImageView ImageView = (ImageView) Findviewbyid (r.id.image_1); final drawable originalbitmapdrawable = Getresources (). Getdrawable (R.drawable.icon). mutate (); Imageview.setimagedrawable (Tintdrawable, Colorstatelist.valueof (Color.magenta));

It's perfect for the effect we wanted before.

You may have this worry that the call mutate() is not in memory to copy the Bitmap? In fact, this is not the case, or the public Bitmap, just copy a status value, this data is very small, so don't worry. For more information, refer to this article: drawable mutations.

EditText Cursor Coloring

With the previous method, we have been able to color the EditText background (Tint) into any desired colour. But a closer look, there is a problem, the input, the color of the cursor is the original color, as shown:

Supported at the beginning of Android 3.1 (API 12), which is the textCursorDrawable drawable that can customize the cursor. Unfortunately, this method can only be used in XML, which is not related to this article, the specific use can refer to this answer, and did not provide an interface to dynamically modify.

We have a more eclectic solution, that is, through the reflection mechanism to obtain CursorDrawable , and then through the method of this article, to the drawable coloring.

public static void Tintcursordrawable (EditText EditText, int color) {try {Field fcursordrawableres = TextView.        Class.getdeclaredfield ("Mcursordrawableres");        Fcursordrawableres.setaccessible (TRUE);        int mcursordrawableres = Fcursordrawableres.getint (EditText);        Field feditor = TextView.class.getDeclaredField ("Meditor");        Feditor.setaccessible (TRUE);        Object editor = Feditor.get (editText);        Class<?> clazz = Editor.getclass ();        Field fcursordrawable = Clazz.getdeclaredfield ("mcursordrawable");        Fcursordrawable.setaccessible (TRUE);        if (mcursordrawableres <= 0) {return;        } drawable cursordrawable = Edittext.getcontext (). Getresources (). getdrawable (Mcursordrawableres);        if (cursordrawable = = null) {return;        } drawable tintdrawable = Tintdrawable (cursordrawable, colorstatelist.valueof (color)); drawable[] Drawables = new drawable[] {tintdrawable, tintdrawabLe};    Fcursordrawable.set (editor, drawables); } catch (Throwable ignored) {}}

The principle is relatively simple, is directly obtained to EditText mCursorDrawableRes , and then through this ID get to the corresponding drawable, call our coloring function tintDrawable , and then set in. The effect is as follows:

Principle Analysis

Above is our entire solution, we next analyze the DrawableCompat coloring related source code, understand the principle. Let's review the function we wrote tintDrawable , which only calls DrawableCompat two methods. We analyze these two methods in detail below.

First, by DrawableCompat.wrap() obtaining an encapsulated drawable:

Android.support.v4.graphics.drawable.DrawableCompat.javapublic static drawable Wrap (drawable drawable) {    Return Impl.wrap (drawable);}

The IMPL wrap function is called, and the IMPL is implemented as follows:

/** * Select The correct implementation to use for the current platform. */static final Drawableimpl impl;static {    final int version = Android.os.Build.VERSION.SDK_INT;    if (version >=) {        IMPL = new Mdrawableimpl ();    } else if (version >=) {        IMPL = new Lollipopmr1drawa Bleimpl ();    } else if (version >=) {        IMPL = new Lollipopdrawableimpl ();    } else if (version >=) {        IMPL = new KITK Atdrawableimpl ();    } else if (version >=) {        IMPL = new Jellybeanmr1drawableimpl ();    } else if (version >= one) {        IMPL = n EW Honeycombdrawableimpl ();    } else {        IMPL = new Basedrawableimpl ();}    }

Obviously, this is based on different API level to choose a different implementation class, and then look down a bit, found that the API level is greater than or equal to 22 of the inheritance LollipopMr1DrawableImpl , we look at its wrap() implementation:

Static Class Lollipopmr1drawableimpl extends Lollipopdrawableimpl {    @Override public    drawable Wrap (drawable drawable) {        return drawablecompatapi22.wrapfortinting (drawable);    }} Class DrawableCompatApi22 {public    static drawable wrapfortinting (drawable drawable) {        //We don ' t need to wrap an Ything in LOLLIPOP-MR1        return drawable;    }}

Since API 22 starts with drwable, Tint is supported and does not require any encapsulation.
Let's take a look at it. wrap() returns a drawable that encapsulates a layer, which we BaseDrawableImpl analyze as an example:

Static Class Basedrawableimpl implements Drawableimpl {    ...    @Override Public    drawable Wrap (drawable drawable) {        return drawablecompatbase.wrapfortinting (drawable);    }    ...}

Called Here DrawableCompatBase.wrapForTinting() , the implementation is as follows:

Class Drawablecompatbase {    ...    public static drawable wrapfortinting (drawable drawable) {        if (! ( drawable instanceof Drawablewrapperdonut) {           return new drawablewrapperdonut (drawable);        }        return drawable;}    }

In fact, here is a DrawableWrapperDonut wrapper object that returns a. Similarly, the final implementation of the other API level less than 22 is analyzed, and the final return is an inherited DrawableWrapperDonut object.

Back to the beginning of the code, the implementation of our analysis DrawableCompat.setTintList() is actually called IMPL.setTintList() , through the previous analysis we know that only the API level of less than 22 to do special processing, we still take BaseDrawableImpl for example analysis:

Static Class Basedrawableimpl implements Drawableimpl {    ...    @Override public    void Settintlist (drawable drawable, colorstatelist tint) {        drawablecompatbase.settintlist ( drawable, tint);    }    ...}

Here is the call DrawableCompatBase.setTintList() :

Class Drawablecompatbase {    ...    public static void Settintlist (drawable drawable, colorstatelist tint) {        if (drawable instanceof Drawablewrapper) { c12/> ((drawablewrapper) drawable). Settintlist (tint);}}    

From the previous analysis, we know that the incoming drawable are DrawableWrapperDonut subclasses, so it is actually called DrawableWrapperDonut setTintList() :

@Overridepublic void Settintlist (colorstatelist tint) {    mtintlist = tint;    Updatetint (GetState ());}    Private Boolean Updatetint (int[] state) {    if (mtintlist! = NULL && Mtintmode! = null) {        final int color = Mtintlist.getcolorforstate (State, Mtintlist.getdefaultcolor ());        Final Porterduff.mode Mode = Mtintmode;        if (!mcolorfilterset | | color! = MCURRENTCOLOR | | Mode! = mcurrentmode) {            setcolorfilter (color, mode);            Mcurrentcolor = color;            Mcurrentmode = mode;            Mcolorfilterset = true;            return true;        }    } else {        Mcolorfilterset = false;        Clearcolorfilter ();    }    return false;}

See here is finally called the method of Drawable setColorFilter() . As you can see, the principle of the article mentioned here is the same as the one at the beginning, but the processing here is more detailed and considered more comprehensive.

Through the source analysis, I feel that it is possible to do Android back to the correct posture of the compatibility library . Transfer from http://www.race604.com/tint-drawable/

Drawable shading for a back-compatibility scenario

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.