android显示过程,android中drawable显示到view上的过程

前段时间一直整理java方面的知识了,先过渡一段时间到android上面来,后期还是会整理java相关的东西,至于整理什么方面的,还没想好。好了,先不说废话了,还是回到正片上来,说说android中用得比较多的drawable类,drawable类是一个抽象的类,其实我们平常开发的阶段用的就是它的各种子类,比如有ColorDrawable、BitmapDrawable等等,后面所有相关的Drawable都会讲到。相信大家用Bitmap也是用得比较多的,那他两有啥区别呢。

Bitmap是专门存储图片的一种形式,是对位图的每一个像素的颜色存储器,而我们的颜色值是由ARGB来标示的,因此我们常常用16进制的6位数表示一个颜色值,而颜色模式一般有下面几种:

颜色模式

说明

ARGB8888

四通道高精度(32位)

ARGB4444

四通道低精度(24位)

RGB565

三通道(16位)

Alpha8

透明通道(8位)

所以对于Bitmap的使用是需要指明Bitmap使用的颜色通道模式,一般如果没有特殊要求最好是选择三通道的就行。好了,关于Bitmap的说明就这么多,还是言归正传,说说drawable是怎么一步步显示在view上的,下面还是通过一个简单的例子,说明drawable的用法:

定义了一个drawable文件:test_back.xml

接着在activity布局中使用:

android:id="@+id/view"

android:layout_width="100dp"

android:layout_height="100dp"

android:layout_marginTop="50dp"

android:background="@drawable/test_back" />

Drawable的生成

相信大家这个代码非常熟悉了,还有个疑问就是为什么在view设置了setOnClickListener才会有view按下的效果呢,所以带着这些疑问以前看下这些问题,大家都知道view的所有属性是通过定义在attrs文件中的,而view的attrs的name是View:

50c7e1d94b3b

image.png

紧接着第一个属性就是获取background属性:

50c7e1d94b3b

image.png

那咱们可以看下drawable是怎么获取的呢,最终会到TypedArray的getDrawableForDensity方法:

@Nullable

public Drawable getDrawableForDensity(@StyleableRes int index, int density) {

//省略了代码

return mResources.loadDrawable(value, value.resourceId, density, mTheme);

}

可以看到上面调的是Resource类中的loadDrawable方法:

@NonNull

Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)

throws NotFoundException {

return mResourcesImpl.loadDrawable(this, value, id, density, theme);

}

很简单一句话,直接调了ResourcesImpl的loadDrawable方法:

@Nullable

Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,

int density, @Nullable Resources.Theme theme)

throws NotFoundException {

//省略代码

try {

final boolean isColorDrawable;

//省略代码

//如果传过来的是#开头的属性值,直接返回colorDrawable

Drawable dr;

if (isColorDrawable) {

dr = new ColorDrawable(value.data);

} else {

//如果不是则调用该方法

dr = loadDrawableForCookie(wrapper, value, id, density);

}

return dr;

} catch (Exception e) {

}

}

上面代码已经到了最精简的代码了,前面一大堆的工作判断有没有缓存的drawable,如果有直接返回cachedDrawable,如果没有接着判断是不是以#开头的clor颜色值,如果是直接返回colorDrawable,先不说colorDrawable,后面会讲到,该节只是分析drawable的显示流程。那咱们看下loadDrawableForCookie:

private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,

int id, int density) {

final String file = value.string.toString();

final Drawable dr;

try {

try {

//注意了如果drawable是一个xml文件定义的走这里

if (file.endsWith(".xml")) {

final XmlResourceParser rp = loadXmlResourceParser(

file, id, value.assetCookie, "drawable");

dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);

rp.close();

} else {

//否则从asset输入流读取

final InputStream is = mAssets.openNonAsset(

value.assetCookie, file, AssetManager.ACCESS_STREAMING);

AssetInputStream ais = (AssetInputStream) is;

dr = decodeImageDrawable(ais, wrapper, value);

}

} finally {

stack.pop();

}

} catch (Exception | StackOverflowError e) {

}

return dr;

}

从上面可以看到,我们一般写的xml都是用Drawable.createFromXmlForDensity方法来获取的,接着看下该方法:

@NonNull

public static Drawable createFromXmlForDensity(@NonNull Resources r,

@NonNull XmlPullParser parser, int density, @Nullable Theme theme)

throws XmlPullParserException, IOException {

AttributeSet attrs = Xml.asAttributeSet(parser);

//省略了判断

Drawable drawable = createFromXmlInnerForDensity(r, parser, attrs, density, theme);

return drawable;

}

该方法很简单,直接是调用了createFromXmlInnerForDensity方法:

@NonNull

static Drawable createFromXmlInnerForDensity(@NonNull Resources r,

@NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,

@Nullable Theme theme) throws XmlPullParserException, IOException {

return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attire

density, theme);

}

可以看到通过resources.getDrawableInflater().inflateFromXmlForDensity方法返回的drawable对象,可以直接看下resources.getDrawableInflater()返回的对象:

50c7e1d94b3b

image.png

很一目了然,获取的是DrawableInfalter对象,还记得LayoutInflater对象吧,它是用来加载布局的,可以看出来各种xml都是通过各种****Inlfater加载出来的,直接看DrawableInfalter的inflateFromXmlForDensity方法:

@NonNull

Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,

@NonNull AttributeSet attrs, int density, @Nullable Theme theme)

throws XmlPullParserException, IOException {

//如果xml中根标签是drawable,那么直接解析它的class属性,一般如果是自定义drawable可以这么玩

if (name.equals("drawable")) {

name = attrs.getAttributeValue(null, "class");

if (name == null) {

throw new InflateException(" tag must specify class attribute");

}

}

//此处是关键

Drawable drawable = inflateFromTag(name);

if (drawable == null) {

drawable = inflateFromClass(name);

}

drawable.setSrcDensityOverride(density);

//该处也很重要,后面讲各种drawable的时候会讲到该方法

drawable.inflate(mRes, parser, attrs, theme);

return drawable;

}

上面代码逻辑很清晰,如果获取到的标签是drawable,通过class属性获取到drawable对象,如果标签不是drawable通过inflateFromTag方法获取:

private Drawable inflateFromTag(@NonNull String name) {

switch (name) {

case "selector":

return new StateListDrawable();

case "animated-selector":

return new AnimatedStateListDrawable();

case "level-list":

return new LevelListDrawable();

case "layer-list":

return new LayerDrawable();

case "transition":

return new TransitionDrawable();

case "ripple":

return new RippleDrawable();

case "adaptive-icon":

return new AdaptiveIconDrawable();

case "color":

return new ColorDrawable();

case "shape":

return new GradientDrawable();

case "vector":

return new VectorDrawable();

case "animated-vector":

return new AnimatedVectorDrawable();

case "scale":

return new ScaleDrawable();

case "clip":

return new ClipDrawable();

case "rotate":

return new RotateDrawable();

case "animated-rotate":

return new AnimatedRotateDrawable();

case "animation-list":

return new AnimationDrawable();

case "inset":

return new InsetDrawable();

case "bitmap":

return new BitmapDrawable();

case "nine-patch":

return new NinePatchDrawable();

case "animated-image":

return new AnimatedImageDrawable();

default:

return null;

}

}

我去,这也太明显了吧,整个drawable的子类都放出来了,像不像工厂方法呢,是的,没错,DrawableInflater类就是Drawable的工厂类,通过标签的name,返回不同的drawable。如果标签的名字是drawable的话,会调用inflateFromClass方法来生成drawable的:

@NonNull

private Drawable inflateFromClass(@NonNull String className) {

try {

Constructor extends Drawable> constructor;

synchronized (CONSTRUCTOR_MAP) {

constructor = CONSTRUCTOR_MAP.get(className);

if (constructor == null) {

final Class extends Drawable> class =

mClassLoader.loadClass(className).asSubclass(Drawable.class);

constructor = clazz.getConstructor();

CONSTRUCTOR_MAP.put(className, constructor);

}

}

return constructor.newInstance();

} catch (NoSuchMethodException e) {

final InflateException ie = new InflateException(

"Error inflating class " + className);

ie.initCause(e);

throw i.e

} catch (ClassCastException e) {

// If loaded class is not a Drawable subclass.

final InflateException ie = new InflateException(

"Class is not a Drawable " + className);

ie.initCause(e);

throw i.e

} catch (ClassNotFoundException e) {

// If loadClass fails, we should propagate the exception.

final InflateException ie = new InflateException(

"Class not found " + className);

ie.initCause(e);

throw i.e

} catch (Exception e) {

final InflateException ie = new InflateException(

"Error inflating class " + className);

ie.initCause(e);

throw i.e

}

}

这里就不做过多的解释了,通过反射生成Drawable对象的,关于反射大家可以看我前面写的java反射整理

紧接着调用了drawable.inflate方法。该方法对于后面分析各种Drawable有很大的帮助。

小节

xml中定义的drawable是通过DrawableInflater生成不同的drawable,如果标签直接定义drawable,去解析class属性,class属性是drawable的全类名;否则解析相应的标签生成不同的drawable,比如ColorDrawable、StateListDrawable、GradientDrawable。

通过上面的分析,例子中的Drawable实际是一个StateListDrawable,下面一起来看看他是如何显示到view上的

Drawable与view的关系

上面已经讲了drawable是如何通过xml生成drawable,下面要将的是Drawable是怎么作用到view上,在前面说到view中通过TypeArray.getDrawable获取到Background是一个StateListDrawable,后面在view四个参数的构造方法中设置了background:

if (background != null) {

setBackground(background);

}

接着调用了下面该方法:

public void setBackground(Drawable background) {

//noinspection deprecation

setBackgroundDrawable(background);

}

@Deprecated

public void setBackgroundDrawable(Drawable background) {

computeOpaqueFlags();

if (background == mBackground) {

return;

}

boolean requestLayout = false;

mBackgroundResource = 0;

//1.销毁之前用到的drawable

if (mBackground != null) {

if (isAttachedToWindow()) {

mBackground.setVisible(false, false);

}

mBackground.setCallback(null);

unscheduleDrawable(mBackground);

}

if (background != null) {

//2.需要刷新view的位置时候

if (mBackground == null

|| mBackground.getMinimumHeight() != background.getMinimumHeight()

|| mBackground.getMinimumWidth() != background.getMinimumWidth()) {

requestLayout = true;

}

mBackground = background;

if (background.isStateful()) {

//3.getDrawableState主要是获取到drawable状态的全集

background.setState(getDrawableState());

}

if (isAttachedToWindow()) {

background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);

}

//添加着色的代码

applyBackgroundTint();

// 对当前的drawable设置回调

background.setCallback(this);

} else {

//如果当前background为空,需要重新更新view在页面上的位置

mBackground = null;

requestLayout = true;

}

computeOpaqueFlags();

if (requestLayout) {

requestLayout();

}

mBackgroundSizeChanged = true;

//重新走绘制

invalidate(true);

invalidateOutline();

}

可以看到上面先是对之前的background进行销毁,调用了drawable.setCallback(null),可以看到将当前view实例传给了drawable对象:

public final void setCallback(@Nullable Callback cb) {

mCallback = cb != null ? new WeakReference<>(cb) : null;

}

此处看到了没,将view实例通过弱引用包装起来了,防止drawable长时间不释放view实例,所以在不用drawable的时候务必调用下setCallback(null)防止内存泄漏

紧接着注释三处通过drawable.isStateful来判断需要给drawale加各种状态不,在drawable默认中实现了isStateful方法:

/**

* 该drawable是否根据state来更改样式

*

*/

public boolean isStateful() {

return false;

}

咋们看下ColorDrawable和StateListDrawable下是怎么实现该方法的:

//stateLIstDrawable直接返回true,说明它是根据状态改变样式的drawable

@Override

public boolean isStateful() {

return true;

}

//colorDrawable会根据mColorState.mTint.isStateful()来判断是不是根据状态来变样式

//mColorState.mTint是colorStateLIst对象

@Override

public boolean isStateful() {

return mColorState.mTint != null && mColorState.mTint.isStateful();

}

@Override

public boolean isStateful() {

return mStateSpecs.length >= 1 && mStateSpecs[0].length > 0;

}

判断mStateSpecs长度大于1,并且第一个length大于0

public ColorStateList(int[][] states, @ColorInt int[] colors) {

mStateSpecs = states;

mColors = colors;

onColorsChanged();

}

mStateSpecs数组是根据states二维数组传过来的,这里写一个简单的例子来看看isStateful方法的说明:

int pressed = Color.RED;

int focused = Color.RED;

int normal = Color.BLACK;

int unable = Color.GRAY;

//颜色数组值要和状态值对应上

int[] colors = new int[]{pressed, focused, normal, focused, unable, normal}

int[][] states = new int[6][];

//定义6个状态的数组

states[0] = new int[]{android.R.attr.state_pressed, android.R.attr.state_en

states[1] = new int[]{android.R.attr.state_enabled, android.R.attr.state_fo

states[2] = new int[]{android.R.attr.state_enabled};

states[3] = new int[]{android.R.attr.state_focused};

states[4] = new int[]{android.R.attr.state_window_focused};

states[5] = new int[]{};

ColorStateList colorStateList = new ColorStateList(states, colors);

ColorDrawable colorDrawable = new ColorDrawable();

//可以通过该方法设置colorStateList,先只需要知道用就行,后面会讲到

colorDrawable.setTintMode(PorterDuff.Mode.ADD);

colorDrawable.setTintList(colorStateList);

View test = findViewById(R.id.test);

test.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

}

});

test.setBackground(colorDrawable);

boolean stateful = colorDrawable.isStateful();

Log.d(TAG, "stateful:" + stageful);

此时获取到的stateful是true,再来看不加状态值的时候,代码改成如下:

ColorDrawable colorDrawable = new ColorDrawable(Color.BLACK);

View test = findViewById(R.id.test);

test.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

}

});

test.setBackground(colorDrawable);

boolean stateful = colorDrawable.isStateful();

Log.d(TAG, "stateful:" + stageful);

看到上面获取的stateful为false,因为此时我们没有传state的数组,所以验证了上面的代码。关于isStateful方法说明到这里,下面继续回到

setBackgroundDrawable方法的background.setState(getDrawableState())这一行,大家可以看下drawable.setState方法:

public boolean setState(@NonNull final int[] stateSet) {

if (!Arrays.equals(mStateSet, stateSet)) {

mStateSet = stateSet;

return onStateChange(stateSet);

}

return false;

}

该方法表示当前drawable是什么状态的,如果状态不一样,则会触发到onStateChange方法,drawable默认的onStateChange是一个空的实现,因此需要子类自己实现,大家这会只需要知道这么个流程,后面会仔细介绍drawable的子类时候再讲该方法,继续看view的getDrawableState方法:

public final int[] getDrawableState() {

//如果已经获取过mDrawableState的状态值,并且mPrivateFlags等于标志

if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {

return mDrawableState;

} else {

mDrawableState = onCreateDrawableState(0);

mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;

return mDrawableState;

}

}

刚开始mDrawableState变量为空,那么此时通过onCreateDrawableState方法来获取mDrawableState:

protected int[] onCreateDrawableState(int extraSpace) {

int[] drawableState;

int privateFlags = mPrivateFlags;

int viewStateIndex = 0;

//下面这些操作都是通过当前view的状态来给viewStateIndex设置不同状态的值

if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED

if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENAB

if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;

if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECT

if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;

if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIV

if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&

ThreadedRenderer.isAvailable()) {

viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;

}

if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED

final int privateFlags2 = mPrivateFlags2;

if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {

viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;

}

if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {

viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;

}

//获取到上面的不同状态的值后,获取到当前drawable的状态,以此来设置drawable在不同状态下的样式

drawableState = StateSet.get(viewStateIndex);

if (extraSpace == 0) {

return drawableState;

}

//省略代码

}

上面的操作是通过全局mPrivateFlags标志获取到各种状态下的索引,并且位运算或放到viewStateIndex中,最后通过StateSet.get(viewStateIndex)赋值给drawableState。关于StateSet.get(viewStateIndex)获取到的都是R.attr.state_****,关于通过view的状态设置drawable的状态就是这么来的,下面继续回到view的setBackgroundDrawable方法,接着会调用applyBackgroundTint方法,

private void applyBackgroundTint() {

if (mBackground != null && mBackgroundTint != null) {

//如果view设置了backgroundTint属性,那么mBackgroundTint就不会为空

final TintInfo tintInfo = mBackgroundTint;

if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {

mBackground = mBackground.mutate();

if (tintInfo.mHasTintList) {

//传入colorStateList实现效果

mBackground.setTintList(tintInfo.mTintList);

}

if (tintInfo.mHasTintMode) {

//设置tintMode

mBackground.setTintMode(tintInfo.mTintMode);

}

if (mBackground.isStateful()) {

mBackground.setState(getDrawableState());

}

}

}

}

看到上面的代码是不是很熟悉上面事例中setTintList和setTintMode的使用,没错上面代码也可以通过xml来实现setTintList的效果:

//定义一个drawable文件,实质是一个StateListDrawable,名字叫test.xml:

在相应的view布局上使用如下:

android:id="@+id/test"

android:layout_width="100dp"

android:layout_height="100dp"

android:background="@color/black"

android:backgroundTint="@drawable/test"

android:backgroundTintMode="add"/>

简单来说,backgroundTint是往background上着色,相当于网上涂层,后面细讲drawable的时候,会说到他的各种子类的情况是如何控制backgroundTint的。继续回到view的setBackgroundDrawable方法上来,上面说完了applyBackgroundTint方法,后面紧接着到了background.setCallback(this),此处是控制drawable绘制的回调,将view当前的实例传给drawable。此时drawable的setState、setTintList、setCallback都已经完成了,紧接着就是绘制了,因此在最后调用了invalidate(true),最终会触发view的重新绘制了。

view绘制怎么绘制drawable

大家知道view的绘制方法在draw---->onDraw,draw里面做的就是系统的一些绘制,而onDraw是view的子类绘制的方法,所以我们一般写的绘制都是在onDraw里面,下面来看看draw里面绘制background,可以看到draw里面有一句drawBackground(canvas):

private void drawBackground(Canvas canvas) {

final Drawable background = mBackground;

if (background == null) {

return;

}

//设置drawable的大小,这个很重要

setBackgroundBounds();

//省略代码

final int scrollX = mScrollX;

final int scrollY = mScrollY;

//最终会走drawable.draw方法,因此drawable的绘制,其实是drawable自己完成的

if ((scrollX | scrollY) == 0) {

background.draw(canvas);

} else {

canvas.translate(scrollX, scrollY);

background.draw(canvas);

canvas.translate(-scrollX, -scrollY);

}

}

上面绘制drawable很简单,首先调用了setBackgroundBounds方法,然后调用了drawable.draw(canvas),最终的绘制还是交给了drawable自己去绘制。下面看下setBackgroundBounds方法:

void setBackgroundBounds() {

if (mBackgroundSizeChanged && mBackground != null) {

//设置drawable的大小,大小其实就是view的大小

mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);

mBackgroundSizeChanged = false;

rebuildOutline();

}

}

可以看到setBackgroundBounds其实是调用了mBackground.setBounds方法,在drawable的setBounds方法中只是设置了全局的mBounds变量:

public void setBounds(int left, int top, int right, int bottom) {

Rect oldBounds = mBounds;

if (oldBounds == ZERO_BOUNDS_RECT) {

oldBounds = mBounds = new Rect();

}

if (oldBounds.left != left || oldBounds.top != top ||

oldBounds.right != right || oldBounds.bottom != bottom) {

if (!oldBounds.isEmpty()) {

// first invalidate the previous bounds

invalidateSelf();

}

mBounds.set(left, top, right, bottom);

onBoundsChange(mBounds);

}

}

所以说drawable中只是设置了个全局变量mBounds,供子类使用,所以从这里看得出来,如果要使用drawable必须调用drawable的setBounds方法,要不然在绘制drawable的时候显示不出来。到最后就是background的绘制了,也就是drawable.draw(canvas)方法,可以看出来drawable是不负责绘制的,绘制工作都交给了自己的子类。

view点击的时候drawable状态改变

view的点击都是在ontouchEvent里面,我们主要看关键点就行

//手指在抬起的时候,并且mPrivateFlags等于PFLAG_PRESSED标志

if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {

setPressed(false);

}

这里可以看出来,如果手抬起来了,并且状态是PFLAG_PRESSED,调用了setPressed(false),看看该方法:

public void setPressed(boolean pressed) {

//这句标明在按下的时候needsRefresh=true,因为刚开始mPrivateFlags&PFLAG_PRESSED!=PFLAG_PRESSED

//因此后面为false,当pressed=true的时候,那么needsRefresh=true,反之pressed=false的时候,

//因此此时mPrivateFlags&PFLAG_PRESSED=PFLAG_PRESSED,因此needsRefresh还是为true

//所以在按下和松开view的时候needsRefresh都为true

final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

//如果按下了mPrivateFlags|PFLAG_PRESSED,那此时mPrivateFlags=PFLAG_PRESSED了

//反之松开的时候mPrivateFlags!=PFLAG_PRESSED了

if (pressed) {

mPrivateFlags |= PFLAG_PRESSED;

} else {

mPrivateFlags &= ~PFLAG_PRESSED;

}

//如果needsRefresh为true,此时需要刷新drawable

if (needsRefresh) {

refreshDrawableState();

}

dispatchSetPressed(pressed);

}

注释写得很清楚了,抬起和按下view都会触发refreshDrawableState方法,并且在按下的时候mPrivateFlags= PFLAG_PRESSED,抬起的时候mPrivateFlags!= PFLAG_PRESSED,下面来看看refreshDrawableState方法:

public void refreshDrawableState() {

mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;

drawableStateChanged();

ViewParent parent = mParent;

if (parent != null) {

parent.childDrawableStateChanged(this);

}

}

接着看drawableStateChanged方法:

protected void drawableStateChanged() {

//可以看到先去根据getDrawableState获取到drawable的状态,然后调用了各个drawable的setState方法

final int[] state = getDrawableState();

boolean changed = false;

//首先就是设置background的state

final Drawable bg = mBackground;

if (bg != null && bg.isStateful()) {

changed |= bg.setState(state);

}

//省略代码

//foreground的state,后面会讲到foreground水波效果是怎么形成的

final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;

if (fg != null && fg.isStateful()) {

changed |= fg.setState(state);

}

//省略代码

//最后刷新view来调用view的draw方法,最后调用drawable的draw方法

if (changed) {

invalidate();

}

}

前面讲过,getDrawableState方法会根据当前view的状态的flag值,去StateSet类中去找各个状态下对应的drawable的state,找到后,调用drawable的setState方法,在setState中会通过mStateSet和传进来的stateSet对比,如果两者相等,那么触发drawable的onStateChange方法,后面讲drawable的时候细讲,先明白是设置state就行。上面可以看到foreground设置背景也是这么干的,后面会介绍foreground是怎么来的。最后调用了invalidate方法,重新绘制drawable。上面在整个ontouchEvent的up事件中调用了setPress(false),那什么时候调用的setPress(true)的呢,很简单,在view的down事件中触发的,回到ontouchEvent里面,在里面有这么一句if (clickable || (viewFlags & TOOLTIP) == TOOLTIP)也就是说只有在clickable为true的情况下,drawable点击才有效果,所以在开篇的例子中,只有设置了view的监听器,drawable点击样式才起效果。关于view的事件传递可以看我之前写的该篇android中view事件传递,在view的down事件中有这么一句setPressed(true, x, y),也就是会触发drawable按下的时候样式。

在上面我们提到过drawable.,setbackCall(this),这句代码,此处是把view的事例传给了drawable,在drawale的invalidateSelf方法中会回调到view:

//该方法是drawale的重绘方法,回调到view的invalidateDrawable方法

public void invalidateSelf() {

final Callback callback = getCallback();

if (callback != null) {

callback.invalidateDrawable(this);

}

}

看下view中回调的invalidateDrawable方法:

@Override

public void invalidateDrawable(@NonNull Drawable drawable) {

if (verifyDrawable(drawable)) {

final Rect dirty = drawable.getDirtyBounds();

final int scrollX = mScrollX;

final int scrollY = mScrollY;

invalidate(dirty.left + scrollX, dirty.top + scrollY,

dirty.right + scrollX, dirty.bottom + scrollY);

rebuildOutline();

}

}

看到了没,实际上调用了view的重绘,而view的重绘又会调用drawable的draw方法,因此上面的drawable.setbackCall(this),最终是绘制自己。好了,介绍到这里,drawable显示到view上基本梳理完了,后面还会介绍foreground是怎么有波纹点击效果,以及介绍drawable在xml中定义的state_****是如何加载到drawale的state上来的,这个就涉及到xml解析了,其实跟xml布局的解析是差不多的,后面还会介绍各种drawable的使用,以及如何自定义一个drawable。

总结

xml中的各种drawable,其实每一个标签对应了各种drawable,该工作交给了DrawableInflater来完成的

解析到各种drawable后,会触发drawable的inflate方法,后面会讲drawable的state获取时会讲到

view中background,foreground其实都是对应的drawable,如果我们设置的background只是一个color,那么此时background就是一个colorDrawable,如果是一个selector标签的话,此时是一个StateListDrawable。

background的isStateful方法判断drawable是不是带有state的drawl。.;

drawable的setState方法是设置drawable当前的state的

drawable.setTintList是设置drawable的着色,它的实质是给paint设置setColorFilter,关于ColorFilter有三个子类,后面会讲到

drawable.setTintMode是设置上面着色的模式,关于模式后面也会讲到

在view按下和松开的时候会通过view的FLAG_PRESSED来给drawble设置state_pressed状态,最终调用到drawable的draw方法,drawable的draw方法需要子类自己去实现。

drawable.setCallback(this)是将view的事例传到drawable的WeakReference弱引用包装起来了,在invalidateSelf方法中会触发callback的invalidateDrawable方法,从而回调到view的重绘,而最终会重新调用了drawable的draw方法,从而达到drawble的重新绘制。

关于drawable的打造可以看我另外一篇实战仿苹果版小黄车(ofo)app主页菜单效果

.;.;.;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值