不仅仅是修改(Android EditText光标)

前言:放假三天,玩了两天了,前几天写过一篇博客 Android CardView全解析(二)好吧,本来就是想看看CardView的实现机制,然后看到5.0中实现CardView的方法,于是又感觉发现了新大陆一样,以前不懂的东西顿时又茅塞顿开了,于是打算把我看到的一点点小东西记录下来,唉唉~~我的假期啊!!!

先看看5.0以上是怎么实现CardView的。

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.support.v7.widget;

import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;

import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateVerticalPadding;
import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateHorizontalPadding;

/**
 * Very simple drawable that draws a rounded rectangle background with arbitrary corners and also
 * reports proper outline for Lollipop.
 * <p>
 * Simpler and uses less resources compared to GradientDrawable or ShapeDrawable.
 */
class RoundRectDrawable extends Drawable {
    private float mRadius;
    private final Paint mPaint;
    private final RectF mBoundsF;
    private final Rect mBoundsI;
    private float mPadding;
    private boolean mInsetForPadding = false;
    private boolean mInsetForRadius = true;

    private ColorStateList mBackground;
    private PorterDuffColorFilter mTintFilter;
    private ColorStateList mTint;
    private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN;

    public RoundRectDrawable(ColorStateList backgroundColor, float radius) {
        mRadius = radius;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        setBackground(backgroundColor);

        mBoundsF = new RectF();
        mBoundsI = new Rect();
    }

    private void setBackground(ColorStateList color) {
        mBackground = (color == null) ?  ColorStateList.valueOf(Color.TRANSPARENT) : color;
        mPaint.setColor(mBackground.getColorForState(getState(), mBackground.getDefaultColor()));
    }

    void setPadding(float padding, boolean insetForPadding, boolean insetForRadius) {
        if (padding == mPadding && mInsetForPadding == insetForPadding &&
                mInsetForRadius == insetForRadius) {
            return;
        }
        mPadding = padding;
        mInsetForPadding = insetForPadding;
        mInsetForRadius = insetForRadius;
        updateBounds(null);
        invalidateSelf();
    }

    float getPadding() {
        return mPadding;
    }

    @Override
    public void draw(Canvas canvas) {
        final Paint paint = mPaint;

        final boolean clearColorFilter;
        if (mTintFilter != null && paint.getColorFilter() == null) {
            paint.setColorFilter(mTintFilter);
            clearColorFilter = true;
        } else {
            clearColorFilter = false;
        }

        canvas.drawRoundRect(mBoundsF, mRadius, mRadius, paint);

        if (clearColorFilter) {
            paint.setColorFilter(null);
        }
    }

    private void updateBounds(Rect bounds) {
        if (bounds == null) {
            bounds = getBounds();
        }
        mBoundsF.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
        mBoundsI.set(bounds);
        if (mInsetForPadding) {
            float vInset = calculateVerticalPadding(mPadding, mRadius, mInsetForRadius);
            float hInset = calculateHorizontalPadding(mPadding, mRadius, mInsetForRadius);
            mBoundsI.inset((int) Math.ceil(hInset), (int) Math.ceil(vInset));
            // to make sure they have same bounds.
            mBoundsF.set(mBoundsI);
        }
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        updateBounds(bounds);
    }

    @Override
    public void getOutline(Outline outline) {
        outline.setRoundRect(mBoundsI, mRadius);
    }

    void setRadius(float radius) {
        if (radius == mRadius) {
            return;
        }
        mRadius = radius;
        updateBounds(null);
        invalidateSelf();
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    public float getRadius() {
        return mRadius;
    }

    public void setColor(@Nullable ColorStateList color) {
        setBackground(color);
        invalidateSelf();
    }

    public ColorStateList getColor() {
        return mBackground;
    }

    @Override
    public void setTintList(ColorStateList tint) {
        mTint = tint;
        mTintFilter = createTintFilter(mTint, mTintMode);
        invalidateSelf();
    }

    @Override
    public void setTintMode(PorterDuff.Mode tintMode) {
        mTintMode = tintMode;
        mTintFilter = createTintFilter(mTint, mTintMode);
        invalidateSelf();
    }

    @Override
    protected boolean onStateChange(int[] stateSet) {
        final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
        final boolean colorChanged = newColor != mPaint.getColor();
        if (colorChanged) {
            mPaint.setColor(newColor);
        }
        if (mTint != null && mTintMode != null) {
            mTintFilter = createTintFilter(mTint, mTintMode);
            return true;
        }
        return colorChanged;
    }

    @Override
    public boolean isStateful() {
        return (mTint != null && mTint.isStateful())
                || (mBackground != null && mBackground.isStateful()) || super.isStateful();
    }

    /**
     * Ensures the tint filter is consistent with the current tint color and
     * mode.
     */
    private PorterDuffColorFilter createTintFilter(ColorStateList tint, PorterDuff.Mode tintMode) {
        if (tint == null || tintMode == null) {
            return null;
        }
        final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
        return new PorterDuffColorFilter(color, tintMode);
    }
}

比如:当我们的CardView的状态变为pressed的时候,就会调用onStateChange方法:

  @Override
    protected boolean onStateChange(int[] stateSet) {
        final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
        final boolean colorChanged = newColor != mPaint.getColor();
        if (colorChanged) {
            mPaint.setColor(newColor);
        }
        if (mTint != null && mTintMode != null) {
            mTintFilter = createTintFilter(mTint, mTintMode);
            return true;
        }
        return colorChanged;
    }

然后就会去根据传入的状态获取相应的颜色值,最后赋给了mPaint,mPaint会绘制出相应的颜色背景,而这个mBackground正是我们在xml中传入的app:backgroundColor:

<com.yasin.round.card.RoundView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:backgroundColor="#ff00"
        app:conerRadius="10dp"
        app:shadowSize="10dp"
        >

好啦~~刚看这一节的小伙伴如果有点迷茫的话~可以去看看我前面的两篇博客的例子。

好啦~看到这我似乎已经明白了cardview5.0的实现方式了,但又看到:

  }
        if (mTint != null && mTintMode != null) {
            mTintFilter = createTintFilter(mTint, mTintMode);
            return true;
        }
/**
     * Ensures the tint filter is consistent with the current tint color and
     * mode.
     */
    private PorterDuffColorFilter createTintFilter(ColorStateList tint, PorterDuff.Mode tintMode) {
        if (tint == null || tintMode == null) {
            return null;
        }
        final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
        return new PorterDuffColorFilter(color, tintMode);
    }

这个又是做什么的呢??? 看名字应该是给颜色过滤的,最后设置给了mPaint :

 @Override
    public void draw(Canvas canvas) {
        final Paint paint = mPaint;

        final boolean clearColorFilter;
        if (mTintFilter != null && paint.getColorFilter() == null) {
            paint.setColorFilter(mTintFilter);
            clearColorFilter = true;
        } else {
            clearColorFilter = false;
        }

        canvas.drawRoundRect(mBoundsF, mRadius, mRadius, paint);

        if (clearColorFilter) {
            paint.setColorFilter(null);
        }
    }

很久以前就在爱哥的博客中看到了paint的setcolorfilter这个方法,那个时候也就是顺带着就看完了,觉得如果我不做图片处理的话也用不到它,确实!! 只怪当时太单纯哈~~ 如果有不懂的童鞋也可以去看看爱哥的那篇博客:
http://blog.csdn.net/aigestudio/article/details/41316141

我们看到PorterDuffColorFilter(color, tintMode)传递了两个参数,而我们5.0以上实现carview的RoundRectDrawable中的tintMode为:

private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN

所以最后根据官方的PorterDuff.Mode的算法:
SRC_IN(两个颜色相交的地方取src的颜色)
[Sa * Da, Sc * Da]

相交的地方的透明度为:Sa为src的透明度*目标对象的透明度
相交的地方的颜色为:src的颜色值*目标对象的透明度

所以最后呈现出来的效果为:两个颜色相交的地方取src的颜色

好啦~说完了上一节中遗落的一点点内容,好吧!!感觉自己说了一堆堆废话,终于还是要进入我们今天的主题了(修改EditText的光标的颜色)

修改EditText在 Android 3.1 (API 12) 就已经支持了,但是呢,我们只能在xml中修改其drawable:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.yasin.tintdemo.MainActivity"
    android:orientation="vertical"
    >

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我是默认的edittext"
        />
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textCursorDrawable="@null"
        android:text="我是光标颜色跟text的颜色一样的edittext"
        />
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我是通过xml修改过光标的edittext"
        android:textCursorDrawable="@drawable/cursor"
        />
</LinearLayout>

这里写图片描述

当然你还可以修改成一张自己的图片:

这里写图片描述

好啦~ 我们去EditText中找找看一下我们设置的textCursorDrawable属性去哪了,于是我们打开EditText,发现EditText并没有这个属性,于是我们找到它父类TextView:

我们看到赋给了一个叫mCursorDrawableRes的字段:

 case com.android.internal.R.styleable.TextView_textCursorDrawable:
                mCursorDrawableRes = a.getResourceId(attr, 0);
                break;

然后我们发现mCursorDrawableRes在TextView中并没有被引用啊,这就尴尬了哈,于是我们在它的onDraw方法中找到了这么一段代码:

if (mEditor != null) {
            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
        } else {
            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
        }

然后我们进到mEditor的onDraw方法中,然后看到其调用了:

 private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
        final boolean translate = cursorOffsetVertical != 0;
        if (translate) canvas.translate(0, cursorOffsetVertical);
        for (int i = 0; i < mCursorCount; i++) {
            mCursorDrawable[i].draw(canvas);
        }
        if (translate) canvas.translate(0, -cursorOffsetVertical);
    }

mCursorDrawable是一个drawable数组:

    final Drawable[] mCursorDrawable = new Drawable[2];

于是我们又找啊找,看到我们最终在xml设置的cursorDrawable被赋给了这个drawable数组:

 if (mCursorDrawable[cursorIndex] == null)
            mCursorDrawable[cursorIndex] = mTextView.getContext().getDrawable(
                    mTextView.mCursorDrawableRes);

好啦~! 说到这,我们只需要拿到TextView中的mCursorDrawableRes字段,然后获取到相应的drawable,然后再获取到Editor的mCursorDrawable数组,然后把通过着色器修改drawable颜色,最后赋给Editor的mCursorDrawable数组就可以了,这样说起来是有点点抽象了哈,没关系,我们接下来就来实现一下。

在此之前我们有点小疑惑哈,我们都知道我们在xml中设置的cursorDrawable最后给了TextView的mCursorDrawableRes字段,如果我们需要根据这个字段获取到我们的drawable对象,然后去修改其颜色,如果我们不设置cursorDrawable的话,那么不是无法修改其的光标颜色了吗??? 好吧,一开始我也是这么觉得的哈,但是我们看到TextView的mCursorDrawableRes字段上面有一行注释:

  // Although these fields are specific to editable text, they are not added to Editor because
    // they are defined by the TextView's style and are theme-dependent.
    int mCursorDrawableRes;

这些属性会随着textview的style改变而改变,所以即使我们没有在zml中设置光标drawable,系统还是会根据theme来修改我们的textview的style的,所以TextView的mCursorDrawableRes字段总是会有值的。

嗯嗯,我们看到我们的默认的EditText我们是没有设置其光标,然后我们就用我们上面的思路修改其颜色为红色:

package com.yasin.tintdemo;

import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.app.AppCompatActivity;
import android.widget.EditText;
import android.widget.TextView;

import java.lang.reflect.Field;

public class MainActivity extends AppCompatActivity {
    private EditText et1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et1= (EditText) findViewById(R.id.id_et1);
        tintCursorDrawable(et1, Color.BLUE);
    }
    public static void tintCursorDrawable(EditText editText, int color) {
        try {
            //获取TextView的mCursorDrawableRes字段
            Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
            fCursorDrawableRes.setAccessible(true);
            //获取mCursorDrawableRes字段
            int mCursorDrawableRes = fCursorDrawableRes.getInt(editText);
            //获取TextView的mEditor字段
            Field fEditor = TextView.class.getDeclaredField("mEditor");
            fEditor.setAccessible(true);
            //获取EditText的mEditor对象
            Object editor = fEditor.get(editText);
            Class<?> clazz = editor.getClass();
            //获取editor对象的mCursorDrawable数组
            Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");
            fCursorDrawable.setAccessible(true);
            if (mCursorDrawableRes <= 0) {
                return;
            }
            //根据光标的resid获取到相应的drawable
            Drawable cursorDrawable = editText.getContext().getResources().getDrawable(mCursorDrawableRes);
            if (cursorDrawable == null) {
                return;
            }
            //然后给cursorDrawable着色
            Drawable tintDrawable  = tintDrawable(cursorDrawable, ColorStateList.valueOf(color));
            //把着色后的cursorDrawable给editor的mCursorDrawable数组
            Drawable[] drawables = new Drawable[] {tintDrawable, tintDrawable};
            fCursorDrawable.set(editor, drawables);
        } catch (Throwable ignored) {
        }
    }
    public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
        //给相应的drawable着色(v4包中的DrawableCompat着色)
        final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTintList(wrappedDrawable, colors);
        return wrappedDrawable;
    }
}

这里写图片描述

好啦~看到v4包中的这个方法:

public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
        //给相应的drawable着色(v4包中的DrawableCompat着色)
        final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTintList(wrappedDrawable, colors);
        return wrappedDrawable;
    }

厉害了我的哥,我们是不是可以修改在EditText的默认背景呢? 我们也修改为蓝色:

package com.yasin.tintdemo;

import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.app.AppCompatActivity;
import android.widget.EditText;
import android.widget.TextView;

import java.lang.reflect.Field;

public class MainActivity extends AppCompatActivity {
    private EditText et1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et1= (EditText) findViewById(R.id.id_et1);
        tintCursorDrawable(et1, Color.BLUE);
        Drawable background = et1.getBackground();
        et1.setBackgroundDrawable(tintDrawable(background,ColorStateList.valueOf(Color.BLUE)));
    }
    public static void tintCursorDrawable(EditText editText, int color) {
        try {
            //获取TextView的mCursorDrawableRes字段
            Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
            fCursorDrawableRes.setAccessible(true);
            //获取mCursorDrawableRes字段
            int mCursorDrawableRes = fCursorDrawableRes.getInt(editText);
            //获取TextView的mEditor字段
            Field fEditor = TextView.class.getDeclaredField("mEditor");
            fEditor.setAccessible(true);
            //获取EditText的mEditor对象
            Object editor = fEditor.get(editText);
            Class<?> clazz = editor.getClass();
            //获取editor对象的mCursorDrawable数组
            Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");
            fCursorDrawable.setAccessible(true);
            if (mCursorDrawableRes <= 0) {
                return;
            }
            //根据光标的resid获取到相应的drawable
            Drawable cursorDrawable = editText.getContext().getResources().getDrawable(mCursorDrawableRes);
            if (cursorDrawable == null) {
                return;
            }
            //然后给cursorDrawable着色
            Drawable tintDrawable  = tintDrawable(cursorDrawable, ColorStateList.valueOf(color));
            //把着色后的cursorDrawable给editor的mCursorDrawable数组
            Drawable[] drawables = new Drawable[] {tintDrawable, tintDrawable};
            fCursorDrawable.set(editor, drawables);
        } catch (Throwable ignored) {
        }
    }
    public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
        //给相应的drawable着色(v4包中的DrawableCompat着色)
        final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTintList(wrappedDrawable, colors);
        return wrappedDrawable;
    }
}

这里写图片描述

完美,接着我们看看v4包中的:

public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
        //给相应的drawable着色(v4包中的DrawableCompat着色)
        final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTintList(wrappedDrawable, colors);
        return wrappedDrawable;
    }

我们先看看 DrawableCompat.setTintList(wrappedDrawable, colors);
方法:

 /**
     * Specifies a tint for {@code drawable} as a color state list.
     *
     * @param drawable The Drawable against which to invoke the method.
     * @param tint     Color state list to use for tinting this drawable, or null to clear the tint
     */
    public static void setTintList(@NonNull Drawable drawable, @Nullable ColorStateList tint) {
        IMPL.setTintList(drawable, tint);
    }

然后再找到IMPL.setTintList(drawable, tint);:

static final DrawableImpl IMPL;
    static {
        final int version = android.os.Build.VERSION.SDK_INT;
        if (version >= 23) {
            IMPL = new MDrawableImpl();
        } else if (version >= 21) {
            IMPL = new LollipopDrawableImpl();
        } else if (version >= 19) {
            IMPL = new KitKatDrawableImpl();
        } else if (version >= 17) {
            IMPL = new JellybeanMr1DrawableImpl();
        } else if (version >= 11) {
            IMPL = new HoneycombDrawableImpl();
        } else {
            IMPL = new BaseDrawableImpl();
        }
    }

是不是跟我们前面两篇博客中的cardview实现方式有点类似哈(知识点哈~~),我们点进BaseDrawableImpl找到setTintList方法:

@Override
        public void setTintList(Drawable drawable, ColorStateList tint) {
            DrawableCompatBase.setTintList(drawable, tint);
        }

继续往下走:

 public static void setTintList(Drawable drawable, ColorStateList tint) {
        if (drawable instanceof TintAwareDrawable) {
            ((TintAwareDrawable) drawable).setTintList(tint);
        }
    }

好吧,只有drawable属于TintAwareDrawable才能进行着色,所以回到我们最初的地方:

public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
        //给相应的drawable着色(v4包中的DrawableCompat着色)
        final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTintList(wrappedDrawable, colors);
        return wrappedDrawable;
    }

我们调用setTintList之前还敢了一步操作:

//给相应的drawable着色(v4包中的DrawableCompat着色)
        final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);

我们来看看wrap方法,猜都猜到了肯定是吧drawable包装成一个TintAwareDrawable对象:

 public static Drawable wrap(@NonNull Drawable drawable) {
        return IMPL.wrap(drawable);
    }

继续往下走:

  @Override
        public Drawable wrap(Drawable drawable) {
            return DrawableCompatBase.wrapForTinting(drawable);
        }
 public static Drawable wrapForTinting(Drawable drawable) {
        if (!(drawable instanceof TintAwareDrawable)) {
            return new DrawableWrapperGingerbread(drawable);
        }
        return drawable;
    }

最后终于找到了这个类:

DrawableWrapperGingerbread

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.support.v4.graphics.drawable;

import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.annotation.TargetApi;

/**
 * Drawable which delegates all calls to it's wrapped {@link Drawable}.
 * <p/>
 * Also allows backward compatible tinting via a color or {@link ColorStateList}.
 * This functionality is accessed via static methods in {@code DrawableCompat}.
 */

@RequiresApi(9)
@TargetApi(9)
class DrawableWrapperGingerbread extends Drawable
        implements Drawable.Callback, DrawableWrapper, TintAwareDrawable {

    static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;

    private int mCurrentColor;
    private PorterDuff.Mode mCurrentMode;
    private boolean mColorFilterSet;

    DrawableWrapperState mState;
    private boolean mMutated;

    Drawable mDrawable;

    DrawableWrapperGingerbread(@NonNull DrawableWrapperState state, @Nullable Resources res) {
        mState = state;
        updateLocalState(res);
    }

    /**
     * Creates a new wrapper around the specified drawable.
     *
     * @param dr the drawable to wrap
     */
    DrawableWrapperGingerbread(@Nullable Drawable dr) {
        mState = mutateConstantState();
        // Now set the drawable...
        setWrappedDrawable(dr);
    }

    /**
     * Initializes local dynamic properties from state. This should be called
     * after significant state changes, e.g. from the One True Constructor and
     * after inflating or applying a theme.
     */
    private void updateLocalState(@Nullable Resources res) {
        if (mState != null && mState.mDrawableState != null) {
            final Drawable dr = newDrawableFromState(mState.mDrawableState, res);
            setWrappedDrawable(dr);
        }
    }

    /**
     * Allows us to call ConstantState.newDrawable(*) is a API safe way
     */
    protected Drawable newDrawableFromState(@NonNull Drawable.ConstantState state,
            @Nullable Resources res) {
        return state.newDrawable(res);
    }

    @Override
    public void draw(Canvas canvas) {
        mDrawable.draw(canvas);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        if (mDrawable != null) {
            mDrawable.setBounds(bounds);
        }
    }

    @Override
    public void setChangingConfigurations(int configs) {
        mDrawable.setChangingConfigurations(configs);
    }

    @Override
    public int getChangingConfigurations() {
        return super.getChangingConfigurations()
                | (mState != null ? mState.getChangingConfigurations() : 0)
                | mDrawable.getChangingConfigurations();
    }

    @Override
    public void setDither(boolean dither) {
        mDrawable.setDither(dither);
    }

    @Override
    public void setFilterBitmap(boolean filter) {
        mDrawable.setFilterBitmap(filter);
    }

    @Override
    public void setAlpha(int alpha) {
        mDrawable.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mDrawable.setColorFilter(cf);
    }

    @Override
    public boolean isStateful() {
        final ColorStateList tintList = (isCompatTintEnabled() && mState != null)
                ? mState.mTint
                : null;
        return (tintList != null && tintList.isStateful()) || mDrawable.isStateful();
    }

    @Override
    public boolean setState(final int[] stateSet) {
        boolean handled = mDrawable.setState(stateSet);
        handled = updateTint(stateSet) || handled;
        return handled;
    }

    @Override
    public int[] getState() {
        return mDrawable.getState();
    }

    @Override
    public Drawable getCurrent() {
        return mDrawable.getCurrent();
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart);
    }

    @Override
    public int getOpacity() {
        return mDrawable.getOpacity();
    }

    @Override
    public Region getTransparentRegion() {
        return mDrawable.getTransparentRegion();
    }

    @Override
    public int getIntrinsicWidth() {
        return mDrawable.getIntrinsicWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        return mDrawable.getIntrinsicHeight();
    }

    @Override
    public int getMinimumWidth() {
        return mDrawable.getMinimumWidth();
    }

    @Override
    public int getMinimumHeight() {
        return mDrawable.getMinimumHeight();
    }

    @Override
    public boolean getPadding(Rect padding) {
        return mDrawable.getPadding(padding);
    }

    @Override
    @Nullable
    public ConstantState getConstantState() {
        if (mState != null && mState.canConstantState()) {
            mState.mChangingConfigurations = getChangingConfigurations();
            return mState;
        }
        return null;
    }

    @Override
    public Drawable mutate() {
        if (!mMutated && super.mutate() == this) {
            mState = mutateConstantState();
            if (mDrawable != null) {
                mDrawable.mutate();
            }
            if (mState != null) {
                mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null;
            }
            mMutated = true;
        }
        return this;
    }

    /**
     * Mutates the constant state and returns the new state.
     * <p>
     * This method should never call the super implementation; it should always
     * mutate and return its own constant state.
     *
     * @return the new state
     */
    @NonNull
    DrawableWrapperState mutateConstantState() {
        return new DrawableWrapperStateBase(mState, null);
    }

    /**
     * {@inheritDoc}
     */
    public void invalidateDrawable(Drawable who) {
        invalidateSelf();
    }

    /**
     * {@inheritDoc}
     */
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
        scheduleSelf(what, when);
    }

    /**
     * {@inheritDoc}
     */
    public void unscheduleDrawable(Drawable who, Runnable what) {
        unscheduleSelf(what);
    }

    @Override
    protected boolean onLevelChange(int level) {
        return mDrawable.setLevel(level);
    }

    @Override
    public void setTint(int tint) {
        setTintList(ColorStateList.valueOf(tint));
    }

    @Override
    public void setTintList(ColorStateList tint) {
        mState.mTint = tint;
        updateTint(getState());
    }

    @Override
    public void setTintMode(PorterDuff.Mode tintMode) {
        mState.mTintMode = tintMode;
        updateTint(getState());
    }

    private boolean updateTint(int[] state) {
        if (!isCompatTintEnabled()) {
            // If compat tinting is not enabled, fail fast
            return false;
        }

        final ColorStateList tintList = mState.mTint;
        final PorterDuff.Mode tintMode = mState.mTintMode;

        if (tintList != null && tintMode != null) {
            final int color = tintList.getColorForState(state, tintList.getDefaultColor());
            if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
                setColorFilter(color, tintMode);
                mCurrentColor = color;
                mCurrentMode = tintMode;
                mColorFilterSet = true;
                return true;
            }
        } else {
            mColorFilterSet = false;
            clearColorFilter();
        }
        return false;
    }

    /**
     * Returns the wrapped {@link Drawable}
     */
    public final Drawable getWrappedDrawable() {
        return mDrawable;
    }

    /**
     * Sets the current wrapped {@link Drawable}
     */
    public final void setWrappedDrawable(Drawable dr) {
        if (mDrawable != null) {
            mDrawable.setCallback(null);
        }

        mDrawable = dr;

        if (dr != null) {
            dr.setCallback(this);
            // Only call setters for data that's stored in the base Drawable.
            setVisible(dr.isVisible(), true);
            setState(dr.getState());
            setLevel(dr.getLevel());
            setBounds(dr.getBounds());
            if (mState != null) {
                mState.mDrawableState = dr.getConstantState();
            }
        }

        invalidateSelf();
    }

    protected boolean isCompatTintEnabled() {
        // It's enabled by default on Gingerbread
        return true;
    }

    protected static abstract class DrawableWrapperState extends Drawable.ConstantState {
        int mChangingConfigurations;
        Drawable.ConstantState mDrawableState;

        ColorStateList mTint = null;
        PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;

        DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
            if (orig != null) {
                mChangingConfigurations = orig.mChangingConfigurations;
                mDrawableState = orig.mDrawableState;
                mTint = orig.mTint;
                mTintMode = orig.mTintMode;
            }
        }

        @Override
        public Drawable newDrawable() {
            return newDrawable(null);
        }

        public abstract Drawable newDrawable(@Nullable Resources res);

        @Override
        public int getChangingConfigurations() {
            return mChangingConfigurations
                    | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0);
        }

        boolean canConstantState() {
            return mDrawableState != null;
        }
    }

    private static class DrawableWrapperStateBase extends DrawableWrapperState {
        DrawableWrapperStateBase(
                @Nullable DrawableWrapperState orig, @Nullable Resources res) {
            super(orig, res);
        }

        @Override
        public Drawable newDrawable(@Nullable Resources res) {
            return new DrawableWrapperGingerbread(this, res);
        }
    }
}

太多了,其它的方法我就不解释了,我们前面说了,我们调用给drawable着色的方法为:

public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
        //给相应的drawable着色(v4包中的DrawableCompat着色)
        final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTintList(wrappedDrawable, colors);
        return wrappedDrawable;
    }

然后setTintList里面又是:

public static void setTintList(Drawable drawable, ColorStateList tint) {
        if (drawable instanceof TintAwareDrawable) {
            ((TintAwareDrawable) drawable).setTintList(tint);
        }
    }

最后还是调用了我们DrawableWrapperGingerbread类的setTintList方法:


@RequiresApi(9)
@TargetApi(9)
class DrawableWrapperGingerbread extends Drawable
        implements Drawable.Callback, DrawableWrapper, TintAwareDrawable {
    ....
    @Override
    public void setTintList(ColorStateList tint) {
        mState.mTint = tint;
        updateTint(getState());
    }
    ....
}

好啦,核心方法来了:

private boolean updateTint(int[] state) {
        if (!isCompatTintEnabled()) {
            // If compat tinting is not enabled, fail fast
            return false;
        }

        final ColorStateList tintList = mState.mTint;
        final PorterDuff.Mode tintMode = mState.mTintMode;

        if (tintList != null && tintMode != null) {
            final int color = tintList.getColorForState(state, tintList.getDefaultColor());
            if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
                setColorFilter(color, tintMode);
                mCurrentColor = color;
                mCurrentMode = tintMode;
                mColorFilterSet = true;
                return true;
            }
        } else {
            mColorFilterSet = false;
            clearColorFilter();
        }
        return false;
    }

最后调用了drawable对象的:

 setColorFilter(color, tintMode);

方法,我们看看setColorFilter方法:

 public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
        setColorFilter(new PorterDuffColorFilter(color, mode));
    }

好吧,历经千辛万苦终于是找到了这么一行代码(好幸酸啊)。

那么我们传递给这个方法的color,mode又是什么呢?

 private boolean updateTint(int[] state) {
     。。。
        final ColorStateList tintList = mState.mTint;
        final PorterDuff.Mode tintMode = mState.mTintMode;
。。。       
    }

然后我们点进mState的mTintMode字段:

        PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;

那么默认的mode又是什么呢?

    static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;

是的,是PorterDuff.Mode.SRC_IN,

还记得我们最初说的PorterDuff.Mode.SRC_IN的作用吗?

private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN

所以最后根据官方的PorterDuff.Mode的算法:
SRC_IN(两个颜色相交的地方取src的颜色)
[Sa * Da, Sc * Da]

也就是说我们在MainActivity中调用:

 public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
        //给相应的drawable着色(v4包中的DrawableCompat着色)
        final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTintList(wrappedDrawable, colors);
        return wrappedDrawable;
    }

最后通过PorterDuffColorFilter的SRC_IN模式,显示出colors我们设置的颜色值。。

好啦!!不知道说了那么多小伙伴有没有掌握呢??

尼玛,我看到这的时候似乎又想起了什么,就是在imageview中也有这样几个方法:

 public final void setColorFilter(int color, PorterDuff.Mode mode) {
        setColorFilter(new PorterDuffColorFilter(color, mode));
    }

    /**
     * Set a tinting option for the image. Assumes
     * {@link PorterDuff.Mode#SRC_ATOP} blending mode.
     *
     * @param color Color tint to apply.
     * @attr ref android.R.styleable#ImageView_tint
     */
    @RemotableViewMethod
    public final void setColorFilter(int color) {
        setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
    }

    public final void clearColorFilter() {
        setColorFilter(null);
    }

我们来试试:

我们把一个ImageView只保留红色通道:

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"
            />
        <ImageView
            android:id="@+id/id_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"
            />

    </LinearLayout>
public class MainActivity extends AppCompatActivity {
    private EditText et1;
    private ImageView img;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        img= (ImageView) findViewById(R.id.id_image);
        img.setColorFilter(new LightingColorFilter(0xffff0000,0));
    }

这里写图片描述

对LightingColorFilter不懂的童鞋可以去看爱哥的那篇博客哈。

我们利用我们的PorterDuffColorFilter把img变成红色:

new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);

这里写图片描述

还有些其它的模式哈,比如SRC_OUT(相交以外的地方显示红色)

        img.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_OUT));

这里写图片描述

其它的模式小伙伴自己去尝试哈,也不知道小伙伴都懂了没,欢迎入群:qq群号511276976

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值