This article follows the analysis of the previous two articles, mainly analyzes the process of displaying background to view, and the process of displaying foregrounds on view. It also introduces the foreground display effect and how to define the color of foreground water wave effect. If you haven’t seen the first two videos, let’s look at the first two videos:

  • The process by which a drawable is displayed on a View in Android
  • Analysis of foreground water wave realization process in Android

State initialization of StateListDrawable

The inflate method of the drawable is called, as you remember in the first article that the inflate method is generated from the various tag names of the XML. Let’s look in this direction at what the inflate method does. Look directly at the inflate method under StateListDrawable:

@Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable); super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible); updateStateFromTypedArray(a); updateDensity(r); a.recycle(); InflateChildElements (r, parser, attrs, theme); OnStateChange (getState())); }Copy the code

Look at comment 1, which calls the inflateChildElements method:

private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
        Theme theme) throws XmlPullParserException, IOException {
    final StateListState state = mStateListState;
    final int innerDepth = parser.getDepth() + 1;
    int type; int depth; // go through each item tagwhile ((type= parser.next()) ! = XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth ||type! = XmlPullParser.END_TAG)) {if (type! = XmlPullParser.START_TAG) {continue; } // Exit the loop if the tag inside is not itemif(depth > innerDepth || ! parser.getName().equals("item")) {
            continue; } final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawableItem); // If the current attribute value is a drawable directly, instead of an XML file, Direct return to the drawable drawable Dr = al-qeada etDrawable (R.s tyleable. StateListDrawableItem_drawable); a.recycle(); Final int[] States = extractStateSet(attrs);if(Dr == null) {// If the drawable attribute is a separate XML file, Still have to continue to parse the drawable Dr = drawable. The following XML file createFromXmlInner (r, parser, attrs, theme); } state. AddStateSet (States, Dr); }}Copy the code

The extraStateSet method is used to parse each item tag and return Dr If the value of a drawable in the tag is a value, rather than a drawable XML file. Obtain the corresponding state value of true or false in each state:

int[] extractStateSet(AttributeSet attrs) {
    int j = 0;
    final int numAttrs = attrs.getAttributeCount();
    int[] states = new int[numAttrs];
    for (int i = 0; i < numAttrs; i++) {
        final int stateResId = attrs.getAttributeNameResource(i);
        switch (stateResId) {
            caseZero:break; // If the attribute is drawable or id, do not use itcase R.attr.drawable:
            case R.attr.id:
                continue; Default: / / through the properties of Boolean value, return the corresponding state_ integer value of the * * * states [j++] = attrs. GetAttributeBooleanValue (I,false)? stateResId : -stateResId; } } states = StateSet.trimStateSet(states, j);return states;
}
Copy the code

If the item tag contains a drawable or id attribute, do not use it. If the retrieved state_*** is true, then the id of its corresponding state_*** is returned; if false, its corresponding -ID is returned.

Get the id of the corresponding state_*** and put it in the array States. If the above definition of drawable value cannot through access to the above, then through drawable. CreateFromXmlInner method. StateListState is a subclass of StateListDrawable. StateListState stores the drawable under each state. In the mDrawables variable of the parent class, put state in the mStateSets. Here is an example of the above API: write a selector drawable named test_back.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/colorAccent" android:state_pressed="true" />
    <item android:drawable="@color/colorAccent" android:state_selected="true" />
    <item android:drawable="@color/colorPrimary" />
</selector>
Copy the code

ColorDrawable, and then set the background of the view in the layout:

state_selected

state_pressed
states
extractStateSet
stateResId=0
extractStateSet

final View view2 = findViewById(R.id.view2);
Drawable background = view2.getBackground();
StateListDrawable.DrawableContainerState constantState =
        (StateListDrawable.DrawableContainerState) background.getConstantState();
Drawable[] children = constantState.getChildren();
for (int i = 0; i < children.length; i++) {
    Drawable child = children[i];
    if (child instanceof ColorDrawable) {
        ColorDrawable colorDrawable = (ColorDrawable) child;
        int color = colorDrawable.getColor();
        Log.d(TAG, "color:" + toHexEncoding(color));
    } else {
        Log.d(TAG, "drawable:"+ children[i]); }}Copy the code

<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>
Copy the code

Illustrates the more so, in StateListDrawable. StateListState class through addStateSet method, under the different state corresponding to the drawable in mDrawable [] array, but some curious, why in print log, In the StateSet class, the states_ *** state has 10 colorDrawable values and empty drawable values. In the StateSet class, the states_ *** state has 10 drawable values:

StateListDrawable.StateListState
addStateSet
DrawableContainer
addChild

Public void growArray(int oldSize, int newSize) {//newSize=10, oldSize=0 Drawable[] newDrawables = new Drawable[newSize]; // arrayCopy copies mDrawables to newDrawables. Arraycopy (mDrawables, 0, newDrawables, 0, oldSize); mDrawables = newDrawables; }Copy the code

MDrawables =10 newDrawables =10 newDrawables =10 newDrawables So it confirms the print result of length =10 output in the above log.

StateListDrawable drawing process

Drawable (action_DOWN, action_UP) : drawable (action_DOWN, action_UP) : drawable (action_DOWN, action_UP) : drawable (action_DOWN, action_UP) : drawable (action_DOWN, action_UP) : drawable (action_DOWN, action_UP)

public boolean setState(@NonNull final int[] stateSet) {
    if(! Arrays.equals(mStateSet, stateSet)) { mStateSet = stateSet;return onStateChange(stateSet);
    }
    return false;
}
Copy the code

StateSet specifies the state of the current drawable. By default, mStateSet is an empty array, so the array.equals (mStateSet, stateSet) is definitely not equal. StateSet is assigned to mStateSet, the callback is assigned to the onStateChange method, and the drawable method is empty below, so you can see that the state change is left to the subclasses:

@override protected Boolean onStateChange(int[] stateSet) {final Boolean changed = super.onStateChange(stateSet); / / by the state from the int value in R file to gain independence idx int independence idx = mStateListState. IndexOfStateSet (stateSet);if (idx < 0) {
        idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
    }
    returnselectDrawable(idx) || changed; } @override protected Boolean onStateChange(int[] state) {mLastDrawable and mCurrDrawable are nullif(mLastDrawable ! = null) {return mLastDrawable.setState(state);
    }
    if(mCurrDrawable ! = null) {return mCurrDrawable.setState(state);
    }
    return false;
}
Copy the code

Can be seen above the onStateChange method first calls the superclass onStateChange method, and through mStateListState indexOfStateSet get independence idx value, finally call the parent class selectDrawable (independence idx) method, Through the log, we get the following log when pressing:

mStateListState.indexOfStateSet
StateListDrawable.StateListState
mStateSets

Note that if the default item is placed in the second position and the pressed item is placed in the third position, the drawable is no longer displayed when pressedint idx = mStateListState.indexOfStateSet(stateSet);Drawable (idx=1); drawable (idx=1); drawable (idx=1);

The last call to selectDrawable is in the parent class: selectDrawable (selectDrawable);

public boolean selectDrawable(int index) {
    if (index == mCurIndex) {
        return false; } final long now = SystemClock.uptimeMillis(); / / by default mDrawableContainerState. MExitFadeDuration = 0if (mDrawableContainerState.mExitFadeDuration > 0) {
        if(mLastDrawable ! = null) { mLastDrawable.setVisible(false.false);
        }
        if(mCurrDrawable ! = null) { mLastDrawable = mCurrDrawable; mLastIndex = mCurIndex; mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration; }else{ mLastDrawable = null; mLastIndex = -1; mExitAnimationEnd = 0; }}else if(mCurrDrawable ! = null) { mCurrDrawable.setVisible(false.false); } / / actually see here, the index is always > = 0, and mDrawableContainerState. MNumChildren = 10if(the index > = 0 && index < mDrawableContainerState. MNumChildren) {/ / get the current drawable final drawable d = mDrawableContainerState.getChild(index); // assign d to mCurrDrawable mCurrDrawable = d; mCurIndex = index;if(d ! = null) {if(mDrawableContainerState.mEnterFadeDuration > 0) { mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; } initializeDrawableForDisplay(d); }}else{ mCurrDrawable = null; mCurIndex = -1; } // The default is 0if(mEnterAnimationEnd ! = 0 || mExitAnimationEnd ! = 0) {if (mAnimationRunnable == null) {
            mAnimationRunnable = new Runnable() {
                @Override public void run() {
                    animate(true); invalidateSelf(); }}; }else {
            unscheduleSelf(mAnimationRunnable);
        }
        // Compute first frame and schedule next animation.
        animate(true); } // this will trigger its own draw method invalidateSelf();return true;
}
Copy the code

For all the code above, just look at this logic: If (index > = 0 && index < mDrawableContainerState mNumChildren), you can see the current access to the drawable mCurrDrawable variable has been assigned to, InvalidateSelf (drawable, StateListDrawable, drawable, drawable, drawable, drawable) The draw method is in the DrawContainer:

@Override
public void draw(Canvas canvas) {
    if(mCurrDrawable ! = null) { mCurrDrawable.draw(canvas); }if (mLastDrawable != null) {
        mLastDrawable.draw(canvas);
    }
}
Copy the code

In plain English, the second and third colorDrawable values in the array above are used when pressing and lifting, assigning drawable to mCurrDrawable, so that the drawable will look like mCurrDrawable.

In selectDrawable method above is that the if (mDrawableContainerState. MExitFadeDuration > 0), which if can be done by XML or setExitFadeDuration methods:

The interval between fades in and out when drawable leaves

To see the effect of setting this property, here is the effect when pressed and released:

public boolean selectDrawable(int index) {
    if (index == mCurIndex) {
        return false; } final long now = SystemClock.uptimeMillis(); // It will go hereif (mDrawableContainerState.mExitFadeDuration > 0) {
        if(mLastDrawable ! = null) { mLastDrawable.setVisible(false.false); } // The second mCurrDrawable is not null,if(mCurrDrawable ! = null) { mLastDrawable = mCurrDrawable; mLastIndex = mCurIndex; / / set the animation to maintain time mExitAnimationEnd = now + mDrawableContainerState. MExitFadeDuration; }else{ mLastDrawable = null; mLastIndex = -1; mExitAnimationEnd = 0; }}else if(mCurrDrawable ! = null) { mCurrDrawable.setVisible(false.false); } // The default is 0if(mEnterAnimationEnd ! = 0 || mExitAnimationEnd ! = 0) {if (mAnimationRunnable == null) {
            mAnimationRunnable = new Runnable() {
                @Override public void run() {animate() {animate()true); invalidateSelf(); }}; }else{// Free task unscheduleSelf(mAnimationRunnable); } // This will trigger animation to perform animate(true); } // this will trigger its own draw method invalidateSelf();return true;
}
Copy the code

If set up mDrawableContainerState mExitFadeDuration > 0, mCurrDrawable drawable under the normal state, thus will give lastDrawable currentDrawable assignment, MExitAnimationEnd is the time we set to fade in and out, followed by the execution of mAnimationRunnable in the animate method:

void animate(boolean schedule) {
    mHasAlpha = true;

    final long now = SystemClock.uptimeMillis();
    boolean animating = false; // If you set the enterAnimationEnd property, it will go there. // In fact, the animation that enters is an animation that keeps changing mCurrDrawable, and mLastDrawable is empty. When pressed, the alpha of the pressed drawable is 0 to 255 // When lifted, the normal drawable is also 0 to 255if(mCurrDrawable ! = null) {if(mEnterAnimationEnd ! = 0) {if (mEnterAnimationEnd <= now) {
                mCurrDrawable.setAlpha(mAlpha);
                mEnterAnimationEnd = 0;
            } else {
                int animAlpha = (int)((mEnterAnimationEnd-now)*255)
                        / mDrawableContainerState.mEnterFadeDuration;
                mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
                animating = true; }}}else {
        mEnterAnimationEnd = 0;
    }
    if(mLastDrawable ! = null) {if(mExitAnimationEnd ! = 0) {if (mExitAnimationEnd <= now) {
                mLastDrawable.setVisible(false.false);
                mLastDrawable = null;
                mLastIndex = -1;
                mExitAnimationEnd = 0;
            } else{// Actually change the opacity of mLastDrawable, which is 0 by mExitAnimationEnd, MCurrDrawable int animAlpha = (int)((mexitAnimationend-now)*255) / mDrawableContainerState.mExitFadeDuration; mLastDrawable.setAlpha((animAlpha*mAlpha)/255); animating =true; }}}else {
        mExitAnimationEnd = 0;
    }

    if(schedule && animating) { scheduleSelf(mAnimationRunnable, now + 1000 / 60); }}Copy the code

If mExitAnimationEnd>0, lastDrawable is a normal drawable and the alpha of lastDrawable is 255 to 0. The effect is that the normal lastDrawable on mExitAnimationEnd will fade out, and the drawable will not be displayed until now reaches mExitAnimationEnd. And when you’re lifting up, lastDrawable is the drawable that was pressed down, and the alpha of lastDrawable is 255 to 0, so what you’re seeing is that the drawable that was pressed down gets thinner.

If mEnterAnimationEnd>0, lastDrawable is null and only currentDrawable is drawn, and currentDrawable has alpha from 0 to 255, so when pressed, the drawable will be watered down to display. When lifted, currentDrawable is a normal drawable, so a normal drawable will fade to display.

EnterFadeDuration Sets the fade time of the drawable to display. ExitFadeDuration Sets the expiration time of the drawable.

EnterFadeDuration is not shown in the example above, so you can try it yourself.

In the above example, currentDrawable and lastDrawable are actually colorDrawable, because the drawable we defined in this example is only a color value, so let’s see how the colorDrawable is actually drawn. Before we talk about drawing a colorable, let’s remember from the first video on Android how a drawable is displayed on a view. The applyBackgroundTint method was used for coloring, SetColorFilter (new PorterDuffColorFilter(color,mode));

setColorFilter

ColorFiler has three subclasses: ColorMatrixColorFilter, LightingColorFilter, PorterDuffColorFilter:

ColorMatrixColorFilter

ColorMatrix is a ColorMatrix, it needs a ColorMatrix object, ColorMatrix needs to set the ColorMatrix, the following through a demo to illustrate the problem:

public class ColorFilterView extends View {
    private static final String TAG = ColorFilterView.class.getSimpleName();
    Paint mPaint;

    public ColorFilterView(Context context) {
        this(context, null);
    }

    public ColorFilterView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ColorFilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        int color = Color.parseColor("# 666666"); //R=102,G=102,B=102,A=255 int red = Color.red(color); int green = Color.green(color); int blue = Color.blue(color); Log.d(TAG,"red:" + red);
        Log.d(TAG, "green:" + green);
        Log.d(TAG, "blue:" + blue);
        Log.d(TAG, "alpha:" + Color.alpha(color));
        mPaint.setColor(color);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d(TAG, "onDraw"); ColorMatrix ColorMatrix = new ColorMatrix(newfloat[] {0.5 f, 0, 0, 0, 0, / / R = 0.5 * 102 + 102 + 0 0 * * * 255 + 102 + 0 0 = 0, 51 0.5 f, 0, 0, 0, / / G = 0 + 0.5 * 102 * 102 * 102 + 0 + 0 * 255 + 0 = 0, 0, 0.5 f, = 0, 0, 0, / / B * 102 + 0 + 0.5 * 102 * 255 * 102 + 0 + 0 = 0, 0, 0, 1, 0, / / B = 0 * 102 + 0 + 1 * 255 * 102 * 102 + 0 + 0 = 255}); ColorMatrix colorMatrix1 = new ColorMatrix(newfloat[]{ 1, 0, 0, 0, 0,//R=1*102+0*102+0*102+0*255+0=102 0, 1, 0, 0, 0,//G=0*102+1*102+0*102+0*255+0=102 0, 0, 1, 0, 0,//B=0*102+0*102+1*102+0*255+0=102 0, 0, 0, 1, 0,//A=0*102+0*102+0*102+1*255+0=255 }); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); Canvas. DrawCircle (240, 600/2, 200, mPaint); }}Copy the code

In the example, the color value is #666666, and the color value is R=102, G=102, B=102, A=255. During the colorMatrix operation, the result of the first row of the matrix is the value of R. The algorithm process is to multiply each number of each row by the color value and then add it. So the result of the first matrix is :R=51, G=51, B=51, A=255, and the corresponding color is #333333; In the second matrix, the color bits of each row multiplied by each number are exactly the original value of 102, so the second color matrix evaluates to the original value of 102. Here’s a picture from the Internet:

Here is how the ColorMatrix works in the case of bitmap. Let’s take a look at the pictures in android Studio.

// ColorMatrix colorMatrixR = new ColorMatrix(newfloat[] {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,}); // ColorMatrix colorMatrixG = new ColorMatrix(newfloat[] {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,}); ColorMatrix colorMatrixRB = new ColorMatrix(newfloat[] {1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,}); // ColorMatrix colorMatrixB = new ColorMatrix(newfloat[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,}); ColorMatrix colorMatrixA = new ColorMatrix(newfloat[] {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0.5 f, 0,}); // LightingColorFilter lightingColorFilter = new LightingColorFilter(0x0000ff, 0x000000); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixR)); Canvas. DrawCircle (240, 600/2, 200, mPaint); canvas.drawBitmap(bitmap, 100, 100, null); canvas.drawBitmap(bitmap, 300, 100, mPaint); mPaint.setColorFilter(null); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixRB)); canvas.drawBitmap(bitmap, 500, 100, mPaint); mPaint.setColorFilter(null); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixB)); canvas.drawBitmap(bitmap, 700, 100, mPaint); mPaint.setColorFilter(null); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixG)); canvas.drawBitmap(bitmap, 900, 100, mPaint); mPaint.setColorFilter(null); mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixA)); canvas.drawBitmap(bitmap, 100, 300, mPaint);Copy the code

ColorMatrix other methods:

SetRGB2YUV bias the channel Settings to red

SetSaturation is set

SetScale sets the scale ratio for each channel

setRotate

ColorMatrix = new ColorMatrix(); colorMatrix1.setSaturation(100); ColorMatrix colorMatrix2 = new ColorMatrix(); colorMatrix2.setRGB2YUV(); ColorMatrix colorMatrix3 = new ColorMatrix(); colorMatrix3.setYUV2RGB(); ColorMatrix colorMatrix4 = new ColorMatrix(); colorMatrix4.setScale(50, 50, 50, 50); ColorMatrix ColorMatrix colorMatrix5 = new ColorMatrix(); colorMatrix5.setRotate(0, 180); // How many degrees do I rotate around a channel? 1 is around channel G, 0 is around channel R, 2. It's around channel BCopy the code

So much about the ColorMatrix, you just need to remember the calculation formula, to bias the channel, set the value of that channel to be larger, the rest of the channels do not care about or adjust small.

LightingColorFilter

< span style = “box-color: border-box; color: RGB (51, 51, 51); display: block; line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;”

// Lighting colorFilter // The color value of the first parameter is set to the multiples of each channel through ARGB // the second parameter is set to the offset of each channel // It should be seen below that the drawn color is biased towards the R channel, LightingColorFilter = new LightingColorFilter(0x66660000, 0x00000000); mPaint.setColorFilter(null); mPaint.setColorFilter(lightingColorFilter); canvas.drawBitmap(bitmap, 300, 300, mPaint);Copy the code

Very simple, if you want the color to be a channel color, directly set the channel color value larger, offset parameters can be appropriately set to the corresponding channel. So much about LightingColorFilter, basically know the meaning of two parameters ok.

PorterDuffColorFilter

ColorFilter porterDuff.mode colorFilter colorFilter porterDuff.mode colorFilter porterDuff.mode colorFilter SRC represents the color of the first parameter of the current PorterDuffColorFilter. DST represents the pattern used in paint. The following demo uses DST to draw a bitmap:

// Draw the color SRC, DST PorterDuffColorFilter srcPorterDuffColorFilter = new PorterDuffColorFilter(color.red, porterduff.mode.src); mPaint.setColorFilter(null); mPaint.setColorFilter(srcPorterDuffColorFilter); canvas.drawBitmap(bitmap, 500, 300, mPaint); PorterDuffColorFilter PorterDuffColorFilter = new PorterDuffColorFilter(color. RED, porterduff.mode.dst); mPaint.setColorFilter(null); mPaint.setColorFilter(porterDuffColorFilter); canvas.drawBitmap(bitmap, 700, 300, mPaint);Copy the code

Let’s take a look at android real case using PorterDuffColorFilter drawing overlap, in fact, in imageView setColorFilter is using PorterDuffColorFilter API, Drawable. SetColorFilter is finally called:

PorterDuffColorFilter
MULTIPLY
Color.GRAY
MULTIPLY

public class ManagerBookShelfHolder extends ViewHolderImpl<BookShelfItem> {

    private ImageView bookCoverimg;
    private TextView bookName;
    private ImageView select;

    @Override
    protected int getItemLayoutId() {
        return R.layout.book_shelf_item;
    }

    @Override
    public void initView() {
        bookCoverimg = findById(R.id.bookCoverimg);
        bookName = findById(R.id.bookName);
        select = findById(R.id.select);
    }

    @Override
    public void onBind(BookShelfItem data, int pos) {
        Glide.with(getContext()).load(data.bookCoverimg).listener(new RequestListener<Drawable>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                return false;
            }

            @Override
            public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                bookCoverimg.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
                return false;
            }
        }).into(bookCoverimg);
        if (data.select) {
            select.setImageResource(R.mipmap.select);
            bookCoverimg.clearColorFilter();
        } else{ bookCoverimg.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY); select.setImageResource(R.mipmap.un_select); } bookName.setText(data.bookName); select.setVisibility(View.VISIBLE); }}Copy the code

As for other modes, we will not try to draw them by ourselves according to the mode diagram below.

SetXmode can be used in android paint. SetXmode. This mode can be used in android ofo’s home menu.

Drawable setColorFilter = drawable setColorFilter = drawable setColorFilter = drawable setColorFilter = drawable setColorFilter = drawable setColorFilter = drawable setColorFilter = drawable setColorFilter = drawable setColorFilter = drawable setColorFilter

public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
    if (getColorFilter() instanceof PorterDuffColorFilter) {
        PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
        if (existing.getColor() == color && existing.getMode() == mode) {
            return; }}setColorFilter(new PorterDuffColorFilter(color, mode));
}
Copy the code

SetColorFilter (new PorterDuffColorFilter(color, mode)) Drawable is an abstract method, so we can look at the setColorFilter method in colorable drawable:

@Override
public void setColorFilter(ColorFilter colorFilter) {
    mPaint.setColorFilter(colorFilter);
}
Copy the code

Well, that simple call to piant.setColorFilter will eventually trigger the drawable from the view.

TODO

  • I wanted to add StateListDrawable andRippleDrawablePut together, limited by space, willRippleDrawableIn the next section, I will talk about the implementation of the water wave effect and how to implement the water wave effect when defining the view.
  • ColorMatrixColorFilter extension explanation.

If you’re not familiar with the drawable process on a view, take a look at the previous two sections:

  • The process by which a drawable is displayed on a View in Android
  • Analysis of foreground water wave realization process in Android