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