Fresco 源码分析(四) 后台数据返回到前台的处理 - Drawable体系的介绍(2)
2015-10-05 16:02
716 查看
在上篇中我们介绍了drawble类的一些内容(链接地址: http://blog.csdn.net/ieyudeyinji/article/details/48879647),下面我们会介绍drawable的核心部分
Android的drawables在编写程序时是相当有用的.Drwable是一个插件化的绘制容器,一般是跟view进行关联的.比如说,BitmapDrawable是用来显示图片的,ShapeDrawable是用来绘制形状和元素的.我们甚至都可以来结合使用它们来创建复杂的显示效果.
Drawables允许我们在不继承它们的情况下,方便的定制控件的显示效果.实际上,因为drawables使用这么方便,在Android大部分原生的app中和控件中使用了drawables;在Android的Framework层大概用到了700处drawables.由于Drwables在系统中广泛的使用,Andorid在从资源中加载时,做了优化.例如,在每次我们创建Button的时候,就从Framework层的资源库(ndroid.R.drawable.btn_default)中加载了一个新的drawable.这意味着,在应用中所有的按钮使用的是不同的drawable作为他们的背景.但是,这些drawables却是拥有相同的状态,称作”constant state”.state中包含的内容是根据使用的drawable类型而定的,但是通常情况下,是包含我们在资源中定义的所有的属性.以Button为例,恒定的状态”constant state”包含一个bitmap image.通过这种方式.在所有的应用中所有的按钮共享一个bitmap,这样节省了很多的内存.
下面的图片展示了在我们指定相同的图片资源作为不同的两个view的背景时,创建了那些类.正如我们所见,创建了两个drawable,但是他们共享着相同的状态,因此使用了相同的bitmap.
共享状态的特征对于避免浪费内存而言,是不错的,但是当你尝试修改drawable的状态时,会带来一些问题的.假如: 一个应用有一个书籍的列表,每本书的名字后面有一颗星,当用户标记为喜欢的时候,是完全不透明的,在用户没有标记为喜欢的时候,是透明的.为了实现这个效果,我们很可能在adapter的getView()的方法中书写如下的代码
不幸的是,这部分代码会产生一个奇怪的结果,所有的drawables都是有用相同的透明度.
用”constant state” 可以解释这样的结果.尽管,我们为每一项得到的是一个新的drawable,但是constant state 仍然是相同的,对于BitmapDrawable而言,透明度是”constant state”的一部分.因此,更改一个drawable的透明度,就会更改所有其他的drawable的透明度.更糟糕的是,想要在Android 1.0 和Android1.1版本上解决这个问题,是没那么容易的.
Android 1.5提供了一个合适的方法来解决这个方法,那就是使用mutate()方法.当你调用drawable的这个方法时,更改当前drawable的”constant state”,是不会影响其他的drawables.注意: 就算是mutate()了一个drawable,bitmaps仍然是共享的.下图显示了当我们调用mutate()之后,drawable产生了什么变化.
既然是这样的,那么我们就用mutate()来更改之前的程序
为了使用方便,mutate()方法返回了drawable自身,这样允许我们使用链式调用.但是这不会产生一个新的drawable实例.采用上述的代码后,我们的应用会表现的正常.
以上的部分是个人的翻译.
这个只是google在developer中的介绍,那么在程序中是如何体现出来的呢?这个我们就来看看android中是如何加载drawable的xml文件即可.
这里我们还要看之前看过的一段程序
Resources.loadDrawable(TypedValue value, int id)部分源码分析
再次分析这段逻辑部分
1. 从缓存中获取drawable,如果不是null,直接返回
2. 如果是null,从PreloadedDrawables中寻找这个key
3. 如果找到了,那么根据ConstantState创建一个新的这样的drawable
4. 如果没有找到,执行5-6以下的逻辑
5. 如果是颜色,生成ColorDrawable
6. 如果不是颜色,根据xml或者assets的类型做对应的解析
7. 如果这时drawable不是null的话,设置drawable的状态,并且缓存drawable,如果是preloading,那么缓存到sPreloadedDrawables中,否则,缓存到sDrawableCache中(在android的系统启动中preloading是true的,缓存的是系统级别的drawable:sPreloadedDrawables,否则,正常应用启动时,preloading是false的,缓存的就是应用级别的drawable:sDrawableCache,至于详细的分析逻辑请参见私房菜的博客:android 系统资源的加载和获取 : http://blog.csdn.net/shift_wwx/article/details/39502463)
挑选的呢,就选择BitmapDrawable和StateListDrawable吧,因为这两个使用的频率是很高的.
BitmapDrawable.draw(Canvas canvas)分析
1. 获取当前的bitmap
2. 如果bitmap为null,不做逻辑的操作
3. bitmap不为null时,做如下的操作
4. 获取当前的BitmapState
5. 如果需要重新构造Shader,做6-8如下的操作
6. 获取到x轴底纹和y轴底纹,如果都是null的话,设置mPaint的Shader为null
7. 否则,重新生成一个BitmapShader,并且给mPaint
8. 设置是否要重新构造Shader为false,并且将边界拷贝到mDstRect中
9. 获取到mPaint的Shader,如果shader是null,做10-11如下的操作,否则,做12-13如下的操作
10. 如果要应用Gravity,那么计算Gravity,并且赋值给mDstRect
11. 在canvas中用mPaint绘制mDstRect大小的bitmap
12. 如果要应用Gravity,那么设置mDstRect大小为边界的大小,并且设置应用Gravity为false
13. 用画笔在canvas中绘制目标区域即可
DrawableContainer
–| AnimationDrawable 帧动画
–| LevelListDrawable 层级的
–| StateListDrawable 状态相关
分析子类之前,还是要先了解一下父类做的操作的
既然是一个容器,那么肯定有增删改查的一些操作,但是由于Drawable是用于填充的后台操作,也就不需要删除的操作,所以,在这里,有的是Drawable的增加,修改和查询的操作
增加 : 容器中增加一个drawable,用于填充drawable时的操作
修改: 修改当前展示的drawable.对于客户端而言,通知外界的更改方式,然后内部实现更改展示的drawable即可.
查询:获得当前的所有和单个的drawable.对于客户端而言,不需要知道所有的drawable,只需要获取当前展示的drawable即可(这也是为什么基类Drawable中会出现一个方法:getCurrent()).
那么从以上的三个方面而言,我们最关心的就是修改的实现,这便是位于DrawableContainer的selectDrawable(int idx)方法
DrawableContainer.selectDrawable(int idx)分析
如果下标不变,返回false即可
如果下标更改,下标不符合要求后,设置当前的Drawable为不显示,然后设置当前的drawable为null,并且有效的下标为-1
如果下标符合要求,那么设置当前的drawable为不显示,更改当前显示的drawable为传递而来下标的drawable,并且更改这个drawable的状态
通知回调函数,当前的状态已经更改
返回true
说归说,但是在DrawableContainer类中没有发现调用这个方法的地方,这也是设计的优雅之处,我只是向外提供了这样的一个方法,告诉你如何通知容器发生了变化,但是何时调用,这个是需要具体的类来实现的.
下面我们就以StateListDrawable为例,来分析这个过程.
Drawable.setState(final int[] stateSet)源码分析
如果当前的状态未更改,返回的是false
如果当前的状态更改了,返回的是onStateChange
那么我们需要关心的就是onStateChange的方法喽,在StateListDrawable中复写了这个方法,我们来看看
StateListDrawable.onStateChange(int[] stateSet)源码分析
通知state计算下标
如果下标为0,转化为通用的下标
调用DrawableContainer.selectDrawable(int idx),如果是true,返回即可
如果是false,返回基类的onStateChange
其实,分析到这里,我们还没有提到是如何绘制的,由于DrawableContainer是个容器,其实直接调用当前的drawable的绘制方法即可,只是一个传递的作用.
扩展: 看到这里,我们便可以想象到,LevelListDrawable实现的核心方法就是onLevelChange的时候,判断是否调用DrawableContainer.selectDrawable(int idx)的方法即可,实时确实如此.
介绍了这么多比较抽象的内容,下篇博客我们介绍drawable的相关使用范例.
3.4.6 mutate()方法
muatate方法是drawble比较有意思的一个方法,这个方法也是需要我们格外注意的一个方法,以下内容为个人翻译自google developer的文章(原文链接地址:http://android-developers.blogspot.com/2009/05/drawable-mutations.html):Android的drawables在编写程序时是相当有用的.Drwable是一个插件化的绘制容器,一般是跟view进行关联的.比如说,BitmapDrawable是用来显示图片的,ShapeDrawable是用来绘制形状和元素的.我们甚至都可以来结合使用它们来创建复杂的显示效果.
Drawables允许我们在不继承它们的情况下,方便的定制控件的显示效果.实际上,因为drawables使用这么方便,在Android大部分原生的app中和控件中使用了drawables;在Android的Framework层大概用到了700处drawables.由于Drwables在系统中广泛的使用,Andorid在从资源中加载时,做了优化.例如,在每次我们创建Button的时候,就从Framework层的资源库(ndroid.R.drawable.btn_default)中加载了一个新的drawable.这意味着,在应用中所有的按钮使用的是不同的drawable作为他们的背景.但是,这些drawables却是拥有相同的状态,称作”constant state”.state中包含的内容是根据使用的drawable类型而定的,但是通常情况下,是包含我们在资源中定义的所有的属性.以Button为例,恒定的状态”constant state”包含一个bitmap image.通过这种方式.在所有的应用中所有的按钮共享一个bitmap,这样节省了很多的内存.
下面的图片展示了在我们指定相同的图片资源作为不同的两个view的背景时,创建了那些类.正如我们所见,创建了两个drawable,但是他们共享着相同的状态,因此使用了相同的bitmap.
共享状态的特征对于避免浪费内存而言,是不错的,但是当你尝试修改drawable的状态时,会带来一些问题的.假如: 一个应用有一个书籍的列表,每本书的名字后面有一颗星,当用户标记为喜欢的时候,是完全不透明的,在用户没有标记为喜欢的时候,是透明的.为了实现这个效果,我们很可能在adapter的getView()的方法中书写如下的代码
Book book = ...; TextView listItem = ...; listItem.setText(book.getTitle()); Drawable star = context.getResources().getDrawable(R.drawable.star); if (book.isFavorite()) { star.setAlpha(255); // opaque } else { star.setAlpha(70); // translucent }
不幸的是,这部分代码会产生一个奇怪的结果,所有的drawables都是有用相同的透明度.
用”constant state” 可以解释这样的结果.尽管,我们为每一项得到的是一个新的drawable,但是constant state 仍然是相同的,对于BitmapDrawable而言,透明度是”constant state”的一部分.因此,更改一个drawable的透明度,就会更改所有其他的drawable的透明度.更糟糕的是,想要在Android 1.0 和Android1.1版本上解决这个问题,是没那么容易的.
Android 1.5提供了一个合适的方法来解决这个方法,那就是使用mutate()方法.当你调用drawable的这个方法时,更改当前drawable的”constant state”,是不会影响其他的drawables.注意: 就算是mutate()了一个drawable,bitmaps仍然是共享的.下图显示了当我们调用mutate()之后,drawable产生了什么变化.
既然是这样的,那么我们就用mutate()来更改之前的程序
Drawable star = context.getResources().getDrawable(R.drawable.star); if (book.isFavorite()) { star.mutate().setAlpha(255); // opaque } else { star. mutate().setAlpha(70); // translucent }
为了使用方便,mutate()方法返回了drawable自身,这样允许我们使用链式调用.但是这不会产生一个新的drawable实例.采用上述的代码后,我们的应用会表现的正常.
以上的部分是个人的翻译.
这个只是google在developer中的介绍,那么在程序中是如何体现出来的呢?这个我们就来看看android中是如何加载drawable的xml文件即可.
这里我们还要看之前看过的一段程序
Resources.loadDrawable(TypedValue value, int id)部分源码分析
再次分析这段逻辑部分
1. 从缓存中获取drawable,如果不是null,直接返回
2. 如果是null,从PreloadedDrawables中寻找这个key
3. 如果找到了,那么根据ConstantState创建一个新的这样的drawable
4. 如果没有找到,执行5-6以下的逻辑
5. 如果是颜色,生成ColorDrawable
6. 如果不是颜色,根据xml或者assets的类型做对应的解析
7. 如果这时drawable不是null的话,设置drawable的状态,并且缓存drawable,如果是preloading,那么缓存到sPreloadedDrawables中,否则,缓存到sDrawableCache中(在android的系统启动中preloading是true的,缓存的是系统级别的drawable:sPreloadedDrawables,否则,正常应用启动时,preloading是false的,缓存的就是应用级别的drawable:sDrawableCache,至于详细的分析逻辑请参见私房菜的博客:android 系统资源的加载和获取 : http://blog.csdn.net/shift_wwx/article/details/39502463)
/*package*/ Drawable loadDrawable(TypedValue value, int id) throws NotFoundException { //start log part ...... //end log part final long key = (((long) value.assetCookie) << 32) | value.data; Drawable dr = getCachedDrawable(key); if (dr != null) { return dr; } Drawable.ConstantState cs = sPreloadedDrawables.get(key); if (cs != null) { dr = cs.newDrawable(this); } else { if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { dr = new ColorDrawable(value.data); } if (dr == null) { if (value.string == null) { throw new NotFoundException( "Resource is not a Drawable (color or path): " + value); } String file = value.string.toString(); ...... if (file.endsWith(".xml")) { try { XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "drawable"); dr = Drawable.createFromXml(this, rp); rp.close(); } catch (Exception e) { NotFoundException rnf = new NotFoundException( "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id)); rnf.initCause(e); throw rnf; } } else { try { InputStream is = mAssets.openNonAsset( value.assetCookie, file, AssetManager.ACCESS_STREAMING); dr = Drawable.createFromResourceStream(this, value, is, file, null); is.close(); } catch (Exception e) { NotFoundException rnf = new NotFoundException( "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id)); rnf.initCause(e); throw rnf; } } } } if (dr != null) { dr.setChangingConfigurations(value.changingConfigurations); cs = dr.getConstantState(); if (cs != null) { if (mPreloading) { sPreloadedDrawables.put(key, cs); } else { synchronized (mTmpValue) { //Log.i(TAG, "Saving cached drawable @ #" + // Integer.toHexString(key.intValue()) // + " in " + this + ": " + cs); mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); } } } } return dr; }
3.4.5 如何绘制当前的drawable
以上介绍了那么多,其实都只是知识的铺垫而已,drawable最核心的还是要绘制一些东西.翻看Drawable类的draw(Canvas canvas)发现这个方法是抽象的,这是理所当然的事情喽,因为Drawable根本不知道应该绘制什么,至于需要绘制出什么效果,其实是应该交由具体的子类来实现的.Drawable的子类很多,我们就挑选两个来分析一下.挑选的呢,就选择BitmapDrawable和StateListDrawable吧,因为这两个使用的频率是很高的.
3.4.5.1 BitmapDrawable的绘制
我们直接来分析BitmapDrawable的绘制方法,中间遇到一些技术点,再看相关的技术点BitmapDrawable.draw(Canvas canvas)分析
1. 获取当前的bitmap
2. 如果bitmap为null,不做逻辑的操作
3. bitmap不为null时,做如下的操作
4. 获取当前的BitmapState
5. 如果需要重新构造Shader,做6-8如下的操作
6. 获取到x轴底纹和y轴底纹,如果都是null的话,设置mPaint的Shader为null
7. 否则,重新生成一个BitmapShader,并且给mPaint
8. 设置是否要重新构造Shader为false,并且将边界拷贝到mDstRect中
9. 获取到mPaint的Shader,如果shader是null,做10-11如下的操作,否则,做12-13如下的操作
10. 如果要应用Gravity,那么计算Gravity,并且赋值给mDstRect
11. 在canvas中用mPaint绘制mDstRect大小的bitmap
12. 如果要应用Gravity,那么设置mDstRect大小为边界的大小,并且设置应用Gravity为false
13. 用画笔在canvas中绘制目标区域即可
@Override public void draw(Canvas canvas) { Bitmap bitmap = mBitmap; if (bitmap != null) { final BitmapState state = mBitmapState; if (mRebuildShader) { Shader.TileMode tmx = state.mTileModeX; Shader.TileMode tmy = state.mTileModeY; if (tmx == null && tmy == null) { state.mPaint.setShader(null); } else { Shader s = new BitmapShader(bitmap, tmx == null ? Shader.TileMode.CLAMP : tmx, tmy == null ? Shader.TileMode.CLAMP : tmy); state.mPaint.setShader(s); } mRebuildShader = false; copyBounds(mDstRect); } Shader shader = state.mPaint.getShader(); if (shader == null) { if (mApplyGravity) { Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight, getBounds(), mDstRect); mApplyGravity = false; } canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); } else { if (mApplyGravity) { mDstRect.set(getBounds()); mApplyGravity = false; } canvas.drawRect(mDstRect, state.mPaint); } } }
3.4.5.2 StateListDrawable的绘制
在查看Drawable类的体系结构时,发现StateListDrawable并非直接继承自Drawable,而是继承自DrawableContainer,然后发现DrawableContainer的子类有几个DrawableContainer
–| AnimationDrawable 帧动画
–| LevelListDrawable 层级的
–| StateListDrawable 状态相关
分析子类之前,还是要先了解一下父类做的操作的
3.4.5.2.1 DrawableContainer的分析
DrawableContainer正如其名字,是一个Drawable的容器,继承自这个类,可以实现多个drawable的切换,但是在外界调用者看来,是没有什么区别的.既然是一个容器,那么肯定有增删改查的一些操作,但是由于Drawable是用于填充的后台操作,也就不需要删除的操作,所以,在这里,有的是Drawable的增加,修改和查询的操作
增加 : 容器中增加一个drawable,用于填充drawable时的操作
修改: 修改当前展示的drawable.对于客户端而言,通知外界的更改方式,然后内部实现更改展示的drawable即可.
查询:获得当前的所有和单个的drawable.对于客户端而言,不需要知道所有的drawable,只需要获取当前展示的drawable即可(这也是为什么基类Drawable中会出现一个方法:getCurrent()).
那么从以上的三个方面而言,我们最关心的就是修改的实现,这便是位于DrawableContainer的selectDrawable(int idx)方法
DrawableContainer.selectDrawable(int idx)分析
如果下标不变,返回false即可
如果下标更改,下标不符合要求后,设置当前的Drawable为不显示,然后设置当前的drawable为null,并且有效的下标为-1
如果下标符合要求,那么设置当前的drawable为不显示,更改当前显示的drawable为传递而来下标的drawable,并且更改这个drawable的状态
通知回调函数,当前的状态已经更改
返回true
public boolean selectDrawable(int idx) { if (idx == mCurIndex) { return false; } if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { Drawable d = mDrawableContainerState.mDrawables[idx]; if (mCurrDrawable != null) { mCurrDrawable.setVisible(false, false); } mCurrDrawable = d; mCurIndex = idx; if (d != null) { d.setVisible(isVisible(), true); d.setAlpha(mAlpha); d.setDither(mDrawableContainerState.mDither); d.setColorFilter(mColorFilter); d.setState(getState()); d.setLevel(getLevel()); d.setBounds(getBounds()); } } else { if (mCurrDrawable != null) { mCurrDrawable.setVisible(false, false); } mCurrDrawable = null; mCurIndex = -1; } invalidateSelf(); return true; }
说归说,但是在DrawableContainer类中没有发现调用这个方法的地方,这也是设计的优雅之处,我只是向外提供了这样的一个方法,告诉你如何通知容器发生了变化,但是何时调用,这个是需要具体的类来实现的.
下面我们就以StateListDrawable为例,来分析这个过程.
3.4.5.2.1 StateListDrawable的分析
这个在分析之前,我们可以想象一下,StateListDrawable,就是一群状态的结合,最常用的方式,就是在drawable的xml中,我们写selector的xml.也就是说状态在更改后,会通知外界更改.那么响应state的变化,在Drawable的类中,看到使用的是如下的方法.;Drawable.setState(final int[] stateSet)源码分析
如果当前的状态未更改,返回的是false
如果当前的状态更改了,返回的是onStateChange
/** * Specify a set of states for the drawable. These are use-case specific, * so see the relevant documentation. As an example, the background for * widgets like Button understand the following states: * [{@link android.R.attr#state_focused}, * {@link android.R.attr#state_pressed}]. * * <p>If the new state you are supplying causes the appearance of the * Drawable to change, then it is responsible for calling * {@link #invalidateSelf} in order to have itself redrawn, <em>and</em> * true will be returned from this function. * * <p>Note: The Drawable holds a reference on to <var>stateSet</var> * until a new state array is given to it, so you must not modify this * array during that time.</p> * * @param stateSet The new set of states to be displayed. * * @return Returns true if this change in state has caused the appearance * of the Drawable to change (hence requiring an invalidate), otherwise * returns false. */ public boolean setState(final int[] stateSet) { if (!Arrays.equals(mStateSet, stateSet)) { mStateSet = stateSet; return onStateChange(stateSet); } return false; }
那么我们需要关心的就是onStateChange的方法喽,在StateListDrawable中复写了这个方法,我们来看看
StateListDrawable.onStateChange(int[] stateSet)源码分析
通知state计算下标
如果下标为0,转化为通用的下标
调用DrawableContainer.selectDrawable(int idx),如果是true,返回即可
如果是false,返回基类的onStateChange
@Override protected boolean onStateChange(int[] stateSet) { int idx = mStateListState.indexOfStateSet(stateSet); if (idx < 0) { idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); } if (selectDrawable(idx)) { return true; } return super.onStateChange(stateSet); }
其实,分析到这里,我们还没有提到是如何绘制的,由于DrawableContainer是个容器,其实直接调用当前的drawable的绘制方法即可,只是一个传递的作用.
扩展: 看到这里,我们便可以想象到,LevelListDrawable实现的核心方法就是onLevelChange的时候,判断是否调用DrawableContainer.selectDrawable(int idx)的方法即可,实时确实如此.
3.5 view与drawable的关系
view与drawable的关系,其实就类似于调用者和被调用者一样.view中的一些绘制信息,如background和ImageView的src的图片绘制,会交给Drawable来实现,然后呢,view的一些状态的更改,如可见,不可见,选中,未选中,透明度等等状态信息的更改会通知Drawable,然后不同的drawable会相应不同的变化,并且判断是否要通知callback来做对应的更改即可.view实现这些callback的方法,然后view校验drawable的信息,并且判断是否要重绘当前的view.介绍了这么多比较抽象的内容,下篇博客我们介绍drawable的相关使用范例.
相关文章推荐
- 10 个 Node.js 常见面试题
- 顺序栈(数组实现)
- LINUX VIM快捷键
- swift2.0 - 渐来的美好(也许应该要收回我之前说的话了)
- java:代理测试
- GO 学习笔记 (二) for , if , switch
- Nginx中location配置[转]
- Longest Common Prefix
- 高斯消元 【模板】
- 信息系统项目管理师2015年9月28日作业
- Android:Material Design(一) 概述
- UITapGestureRecognizer 的用法
- 八皇后问题--递归和非递归
- 读万卷书
- StudentDao
- 搜索
- 堆(优先队列)优化dijkstra(邻接矩阵)
- 阅读科研文献心得分享(一)
- JDBC示例程序
- 银行业务调度系统