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

    xiaoxiao2021-03-25  270

    前言:放假三天,玩了两天了,前几天写过一篇博客 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

    vv_小虫 认证博客专家 JavaScript CSS 前端框架 6 年开发经验,前端架构师,目前主要负责企业级应用前端技术平台建设工作,在前端工程化实现、Node 应用开发、Android 技术、Vue 技术、React 技术、移动开发等方向有丰富实践。
    转载请注明原文地址: https://ju.6miu.com/read-159.html

    最新回复(0)