本篇文章将这几个类放在一块儿,也是有缘由的,由于这几个是存在多个层级的;并且两个ListDrawable并非Drawable的直接子类。java
看名字能够分析出这是一个可绘制的层级关系。从官方注释中,也对其有明确的说明:其由一个Drawable数组组成,而且按照数组中的顺序绘制,因此最后一个会被绘制在最顶层。 说白了,就和FrameLayout很相似,是一层一层盖上去的。android
由于LayerDrawable是数组形式保存的Drawable,在其内部定义了静态类ChildDrawable用于存储每个Drawable的状态。数组
咱们先来看一下这个类的定义bash
static class ChildDrawable {
public Drawable mDrawable; // 持有一个Drawable的引用
public int[] mThemeAttrs;
public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
// 须要注意的是,这几个参数并不会传递给mDrawable,是和mDrawable里面的padding是相互独立的
public int mInsetL, mInsetT, mInsetR, mInsetB;// 每个Drawable和左上右下边界的距离,有对应的function
public int mInsetS = INSET_UNDEFINED; // 这里S表明Start,即对应LtR/RtL时使用
public int mInsetE = INSET_UNDEFINED; // E表示End
public int mWidth = -1;
public int mHeight = -1;
public int mGravity = Gravity.NO_GRAVITY;
public int mId = View.NO_ID; // 当前Drawable的ID
……………………
}
复制代码
这些属性便可以在xml中定义,也能够在代码中经过对应的function来设置,好比ide
/**
* @param index 须要调整的layer层
* @param l 距离左边界的像素值
*/
public void setLayerInsetLeft(int index, int l) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];// LayerState中有一个ChildDrawable数组,用于保存所有layer
childDrawable.mInsetL = l;
}
// 一样的,也存在对应的get方法。须要注意的是,这些API只有在>=23才能使用
复制代码
和以前讲述的State差别不大,只是内部多了几个变量,用于保存要绘制的Drawable数量/Drawable数组等。动画
static class LayerState extends ConstantState {
int mNumChildren;
ChildDrawable[] mChildren;
// 总体的padding值
int mPaddingTop = -1;
int mPaddingBottom = -1;
int mPaddingLeft = -1;
int mPaddingRight = -1;
int mPaddingStart = -1;
int mPaddingEnd = -1;
……
}
复制代码
经过<layer-list>标签进行配置,代码以下ui
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:paddingMode="nest">
<item android:drawable="@drawable/color_drawable" android:width="300dp" android:height="300dp"/>
<item android:drawable="@drawable/gradient_drawable" android:width="100dp" android:height="100dp"/>
</layer-list>
复制代码
在上述代码片断中,须要注意的有几点:this
当第一层不设置本身的padding时,建议读者尝试一下切换两种mode,看看是否有区别spa
类比于xml中的设置,Java代码的示例代码以下3d
// 这里为了示例方便才用的d0/d1的命名方式
d0 = getResources().getDrawable(R.drawable.shape_drawable);
d1 = getResources().getDrawable(R.drawable.color_drawable);
// drawable = (LayerDrawable) getResources().getDrawable(R.drawable.layer_drawable);
Drawable[] ds = new Drawable[2];
ds[0] = d0;
ds[1] = d1;
drawable = new LayerDrawable(ds);
drawable.setLayerHeight(0, 300);// 设置第0层的高度
drawable.setLayerWidth(0, 300);
drawable.setLayerHeight(1, 450);
drawable.setLayerWidth(1, 150);
drawable.setPaddingMode(LayerDrawable.PADDING_MODE_NEST);
复制代码
因而可知,对于drawable这类视图资源仍是使用xml定义更好
咱们先来看一下这个接口是作什么的?
// 当你须要实现动画效果时,须要设置这个回调
public interface Callback {
// 须要重绘时回调
void invalidateDrawable(@NonNull Drawable who);
// 执行下一帧动画时调用
void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);
// 取消以前scheduleDrawable要执行的动做
void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
}
复制代码
也就是说,诸如进度条这一类须要利用动画进行视图变化时,就会触发该回调接口。
对于LayerDrawable,其最经常使用的就是SeekBar的进度条背景颜色,通常经常使用两层分别表示progress和secondaryProgress,若是还须要缓冲,则再添加一个background便可。 固然,SeekBar里面有不少坑,在后续会专门讲一下系统提供的UI,在作业务时应该如何自定义更改。
对于每一层的drawable,其内部属性和建立时相关。经过代码等设置的left/right等参数只是改变其在LayerDrawable中的表现
从名字能够看出,这个类是和“等级/级别”相关。
当某一个展现内容根据设置的级别不一样,展现不一样的效果,最多见的就是手机电量。当电量充足时是绿色,电量一半时是黄色,手机快没电时是红色。这就能够经过LevelListDrawable来实现,经过设置不一样的等级从而实现切换显示。
该类并非Drawable的直接子类,而是DrawableContainer的子类,经过中间层实现状态的选择
该类是一个帮助类,用于存储Drawables并选择其中一个进行展现。所以其和LayerList的差异就在于:LayerList是把全部的Drawable按从前到后的顺序依次铺在视图上;DrawableContainer的子类则是从中选择一个Drawable进行展现。
和LayerList不一样的是,其没有ChildDrawable这一辅助类存储每个子Drawable,而是直接使用ConstantState进行保存,由于每次只须要展现一个Drawable,因此不须要把全部的数据都一次性存储到类中。
这里也有和以往不一样的,以前咱们遇到的都是ConstantState的实子类,而这一次是抽象子类,其只提供基本属性,DrawableContainer的每个子类中,又继承DrawableContainerState进行对应的拓展。
除了以前讲过的一些方法外,这里新增长了一些方法。
// 当切换Drawable时,进入动画时长。一样的也有设置退出时长的方法
public final void setEnterFadeDuration(int duration) {
mEnterFadeDuration = duration;
}
// 切换时会调用的方法
public final Drawable getChild(int index) {
// 获取到数组中对应的Drawable
final Drawable result = mDrawables[index];
// 若是存在,则直接返回
if (result != null) {
return result;
}
// 若是Drawable不存在,可是对应的State存在
if (mDrawableFutures != null) {
// mDrawableFutures是一个稀疏数组
final int keyIndex = mDrawableFutures.indexOfKey(index);
if (keyIndex >= 0) {
final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
// 根据保存的ConstantState建立Drawable
final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes));
mDrawables[index] = prepared;
mDrawableFutures.removeAt(keyIndex);
if (mDrawableFutures.size() == 0) {
mDrawableFutures = null;
}
return prepared;
}
}
// 若是都不存在,则返回null
return null;
}
对应的,还有addChild方法,这里略过。原理是想通的。
复制代码
这是个啥?咱们先来看一下它的实现
private static class BlockInvalidateCallback implements Drawable.Callback {
private Drawable.Callback mCallback;
// 包装callback
public BlockInvalidateCallback wrap(Drawable.Callback callback) {
mCallback = callback;
return this;
}
public Drawable.Callback unwrap() {
final Drawable.Callback callback = mCallback;
mCallback = null;
return callback;
}
@Override
public void invalidateDrawable(@NonNull Drawable who) {
// Ignore invalidation.空实现,缘由后续会讲
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
if (mCallback != null) {
mCallback.scheduleDrawable(who, what, when);
}
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
if (mCallback != null) {
mCallback.unscheduleDrawable(who, what);
}
}
}
复制代码
经过代码咱们能够发现,它只是将原来的callback保存为内部的mCallback。当调用wrap方法的时候,drawable设置的callback就是BlockInvalidateCallback,当触发invalidateDrawable时,由于是空实现,因此就不会产生重绘;而当另外两个回调触发时,因为callback被赋值给了mCallback,因此会正常触发。可是为何要来这么一手呢?看一下应用场景。
private void initializeDrawableForDisplay(Drawable d) {
if (mBlockInvalidateCallback == null) {
mBlockInvalidateCallback = new BlockInvalidateCallback();
}
// 替换callback为Block中的mCallback
d.setCallback(mBlockInvalidateCallback.wrap(d.getCallback()));
try {
// 省略了大量的d.setXXX方法
} finally {
// 最后再换回原来的callback
d.setCallback(mBlockInvalidateCallback.unwrap());
}
}
复制代码
以前的Block实现中,咱们能够看到,其invalidateDrawable是一个空实现,将callback替换以后,每次触发重绘就会使用BlockInvalidateCallback中的invalidateDrawable方法,从而阻止了重绘的发生。而在initializeDrawableForDisplay方法中,为了不初始化过程当中触发invalidate,因此使用BlockInvalidateCallback来包装一层,避免触发重绘操做(setXXX方法最终会触发invalidateDrawable回调方法)。
在使用过程当中,咱们知道一个level是存在上下界的,当level处于区间内时,就会切换到对应level的视图。在前面咱们也讲过,LevelListState是DrawableContainerState的子类,其内部只新增了两个变量,很明显,是保存每一个drawable的上下界的数组变量
private int[] mLows;
private int[] mHighs;
复制代码
也所以,其对应的新增长了几个方法
private void mutate() {
mLows = mLows.clone();
mHighs = mHighs.clone();
}
public void addLevel(int low, int high, Drawable drawable) {
// 调用父类方法,若是大小不够,则数组大小增长10,因为多态就会调用LevelListDrawable中的growArray方法,更新了mLows和mHighs大小
// 该方法会触发mutate方法,由于多态特性就会调用LevelListDrawable中的mutate方法,进而调用state中的mutate方法
int pos = addChild(drawable);
mLows[pos] = low;
mHighs[pos] = high;
}
public int indexOfLevel(int level) {
final int[] lows = mLows;
final int[] highs = mHighs;
final int N = getChildCount();
for (int i = 0; i < N; i++) {
// 这里能够看出,返回的是第一个符合条件的drawable
if (level >= lows[i] && level <= highs[i]) {
return i;
}
}
return -1;
}
复制代码
在代码的开头注释中,就写到通常是ImageView的setImageLevel中使用,那么咱们就按照这个流程看一下
// ImageView.java
public void setImageLevel(int level) {
mLevel = level;
if (mDrawable != null) {
mDrawable.setLevel(level);// 调用Drawable#setLevel方法,为啥不是多态?请看后文
resizeFromDrawable();// 调整大小
}
}
// Drawable.java
// 由于是final方法,因此没法重写
public final boolean setLevel(@IntRange(from=0,to=10000) int level) {
if (mLevel != level) {
mLevel = level;
return onLevelChange(level);// 这里会有多态哦
}
return false;
}
// LevelListDrawable.java
protected boolean onLevelChange(int level) {
int idx = mLevelListState.indexOfLevel(level);
if (selectDrawable(idx)) {// 若是选择成功,则返回true
return true;
}
return super.onLevelChange(level);
}
// DrawableContainer.java
// 这里就是最后一步,展现出了对应的drawable
public boolean selectDrawable(int index) {
// 若是没变,则不进行处理,节约资源
if (index == mCurIndex) {
return false;
}
final long now = SystemClock.uptimeMillis();
if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + index
+ ": exit=" + mDrawableContainerState.mExitFadeDuration
+ " enter=" + mDrawableContainerState.mEnterFadeDuration);
// 动画时间相关
if (mDrawableContainerState.mExitFadeDuration > 0) {
......
} else if (mCurrDrawable != null) {
......
}
// 若是index是存在的,则准备对应的属性
if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
final Drawable d = mDrawableContainerState.getChild(index);
mCurrDrawable = d;
mCurIndex = index;
if (d != null) {
if (mDrawableContainerState.mEnterFadeDuration > 0) {
mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
}
initializeDrawableForDisplay(d);
}
} else {
mCurrDrawable = null;
mCurIndex = -1;
}
// 若是是有动画的,则经过animate方法实现切换,内部是经过alpha变化实现的
if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
@Override public void run() {
animate(true);
invalidateSelf();
}
};
} else {
unscheduleSelf(mAnimationRunnable);
}
// Compute first frame and schedule next animation.
animate(true);
}
invalidateSelf();// 重绘
return true;
}
复制代码
类比前面的LayerListDrawable,这里一样的在xml中使用<level-list>来实现,示例代码以下
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/shape_drawable" android:minLevel="1" android:maxLevel="20"/>
<item android:drawable="@drawable/color_drawable" android:minLevel="21" android:maxLevel="60"/>
</level-list>
复制代码
当level处于1-20之间时,使用shape_drawable,当level处于21-60时,使用color_drawable。这就对应了电池电量的应用场景。 对于Java代码,仍旧是不太推荐动态设置,除非是万不得已须要动态设置时才使用。
至此,LevelListDrawable的源码流程就简略的分析完毕了。还有一些细节问题暂时不须要咱们去考虑。
通常来说,LevelListDrawable用于根据不一样时机展现不一样视图的场景,典型的就是电池电量场景、涉及到同一个组件不一样时机(非状态响应)时,均可以使用LevelListDrawable来处理。
看了前面的LevelListDrawable以后,StateListDrawable就能够类比来看。LevelList是根据level进行切换的,那么StateList就是根据state进行切换的。
在xml中使用时,经过<selector>标签进行选择,其中有不少状态能够设置
StateListDrawable_visible
StateListDrawable_variablePadding
StateListDrawable_constantSize
DrawableStates_state_focused
DrawableStates_state_window_focused
DrawableStates_state_enabled
DrawableStates_state_checkable
DrawableStates_state_checked
DrawableStates_state_selected
DrawableStates_state_activated
DrawableStates_state_active
DrawableStates_state_single
DrawableStates_state_first
DrawableStates_state_middle
DrawableStates_state_last
DrawableStates_state_pressed
复制代码
以上的属性经过名字能够很明显的知道其触发条件,那么若是我写了所有的条件,有什么触发顺序限制么?
类比levelListState,其内部也新增了其切换视图所需的变量
int[][] mStateSets;
复制代码
而当触发某一种状态时,则会返回第一个匹配的index,这也就解决了前面提出的疑问:当几个状态同时匹配时,应该显示哪个的问题。
protected boolean onStateChange(int[] stateSet) {
// 省略部分代码
return selectDrawable(idx) || changed;
}
int indexOfStateSet(int[] stateSet) {
final int[][] stateSets = mStateSets;
final int N = getChildCount();
for (int i = 0; i < N; i++) {
// 一样的,第一个符合state条件的返回
if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
return i;
}
}
// 若是没有则返回-1
return -1;
}
// StateSet.java
public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) {
// 大量逻辑判断是否符合条件,有须要的同窗能够去看所有源码
}
复制代码
至此,StateListDrawable须要了解的内容就这么多,由于其和LevelListDrawable类似的内容较多,故而不过多赘述。 因为本人水平欠佳,有不正确的地方或者不清楚的地方,欢迎拍砖。 下一篇将讲述十分重要的bitmap以及相关的Drawable,敬请期待!