Drawable
来源:互联网 发布:棋院围棋软件 编辑:程序博客网 时间:2024/05/20 20:21
<View android:id="@+id/view" android:layout_width="200dp" android:layout_height="200dp" android:background="@android:color/holo_red_light" />
屏幕上会出现一个红色的正方形。
Drawable bg = findViewById(R.id.view).getBackground();断点调试一下
bg is a ColorDrawable。
ColorDrawable 有点复杂,写一个简化的版本。
public class SimpleColorDrawable extends Drawable { private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); public SimpleColorDrawable(int color) { mPaint.setColor(color); } @Override public void draw(@NonNull Canvas canvas) { canvas.drawRect(getBounds(), mPaint); } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter colorFilter) { } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; }}
SimpleColorDrawable 也能正常工作。
Drawable newBg = new SimpleColorDrawable(Color.BLUE); findViewById(R.id.view).setBackground(newBg);这样就能看到蓝色的正方形了。
绘制 View 的过程中会绘制背景。
view.draw(canvas) -> view.drawBackground -> background.draw(canvas)
Drawable 有几个重要的方法。
setBounds
设置绘制的区域,比如 SimpleColorDrawable 中的:
canvas.drawRect(getBounds(), mPaint);
(如果 bounds 大于 View 的区域会怎么样?不会绘制。)
setStateDrawable 无法接受事件,但仍需要和用户交互,比如常见的按钮按下变色。
View 就成了用户和 Drawable 之间的媒介,具体过程等到 StateListDrawable 再分析。
setLevel
这个方法和 setState 很像,一个是留给用户用的交互入口,一个是留给我们写代码用的。
setCallback
稍后再说。
DrawableContainer
A helper class that contains several {@link Drawable}s and selects which one to use
@Override public void draw(Canvas canvas) { if (mCurrDrawable != null) { mCurrDrawable.draw(canvas); } if (mLastDrawable != null) { mLastDrawable.draw(canvas); } }它会引用多个 Drawable,最后绘制时会选择其中一个。
StateListDrawable 是 DrawableContainer 的子类。
设计意图:当 StateListDrawable 的 State 改变时,切换不同 Drawable 显示。
关键代码:
public void addState(int[] stateSet, Drawable drawable) { if (drawable != null) { mStateListState.addStateSet(stateSet, drawable); // in case the new state matches our current state... onStateChange(getState()); } } @Override public boolean isStateful() { return true; } @Override protected boolean onStateChange(int[] stateSet) { final boolean changed = super.onStateChange(stateSet); int idx = mStateListState.indexOfStateSet(stateSet); if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states " + Arrays.toString(stateSet) + " found " + idx); if (idx < 0) { idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); } return selectDrawable(idx) || changed; }当 View 的状态改变时会调用 drawableStateChanged
protected void drawableStateChanged() { final int[] state = getDrawableState(); boolean changed = false; final Drawable bg = mBackground; if (bg != null && bg.isStateful()) { changed |= bg.setState(state); }...}所以 isStateful 方法需要重载。
setState 会调用 onStateChange,比较保存的 stateSet ,切换到合适的 drawable 后重画。
怎么切换 Drawable 的
public boolean selectDrawable(int index) { invalidateSelf(); return true; }
public void invalidateSelf() { final Callback callback = getCallback(); if (callback != null) { callback.invalidateDrawable(this); } }View 在设置背景时,会执行如下代码:
background.setCallback(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(); } }会直接重画自身,背景部分也自然会被重画了。
有时 Callback 也会不是 View ,稍后分析。
LevelListDrawable 也是 DrawableContainer 的子类,但这个类设计得有些费解,跳过了。
AnimationDrawable 也是 DrawableContainer 的子类。
适合实现那种转圈圈的效果,需要 12 张图片的那种。
不同于 StateListDrawable,它不需要和用户交互,预留了 start 和 stop 两个方法。
数据结构是,一个 duration 对应一个 drawable (StateListDrawable 是一个 StateSet 对应一个 Drawable)。
start 时会 setFrame 为第一帧。
private void setFrame(int frame, boolean unschedule, boolean animate) { selectDrawable(frame); scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]); }
public void scheduleSelf(@NonNull Runnable what, long when) { final Callback callback = getCallback(); if (callback != null) { callback.scheduleDrawable(this, what, when); } }如果 callback 是 view
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { if (verifyDrawable(who) && what != null) { final long delay = when - SystemClock.uptimeMillis(); if (mAttachInfo != null) { mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed( Choreographer.CALLBACK_ANIMATION, what, who, Choreographer.subtractFrameDelay(delay)); } else { // Postpone the runnable until we know // on which thread it needs to run. getRunQueue().postDelayed(what, delay); } } }往队列里加一个 Runnable, duration 秒后执行。
这个 Runnable 就是显示下一帧。
@Override public void run() { nextFrame(false); }这样就实现按 duration 不断切换 drawable 的效果了。
Drawable 的另一个子类,DrawableWrapper。
Drawable container with only one child element.
@Overridepublic void draw(@NonNull Canvas canvas) { if (mDrawable != null) { mDrawable.draw(canvas); }}和 DrawableContainer 一样,也不负责直接绘制。
ClipDrawable 是 DrawableWrapper 的子类。
设计意图:对绘制的 drawable 裁剪,只绘制一部分,常用于实现进度条。
@Override public void draw(Canvas canvas) { final Drawable dr = getDrawable(); if (dr.getLevel() == 0) { return; } final Rect r = mTmpRect; final Rect bounds = getBounds(); final int level = getLevel(); int w = bounds.width(); final int iw = 0; //mState.mDrawable.getIntrinsicWidth(); if ((mState.mOrientation & HORIZONTAL) != 0) { w -= (w - iw) * (MAX_LEVEL - level) / MAX_LEVEL; } int h = bounds.height(); final int ih = 0; //mState.mDrawable.getIntrinsicHeight(); if ((mState.mOrientation & VERTICAL) != 0) { h -= (h - ih) * (MAX_LEVEL - level) / MAX_LEVEL; } final int layoutDirection = getLayoutDirection(); Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); if (w > 0 && h > 0) { canvas.save(); canvas.clipRect(r); dr.draw(canvas); canvas.restore(); } }ClipDrawable 有两个参数,orientation 决定垂直还是水平, gravity 决定左到右还是右到左(水平情况下)。
clipRect 也个 native 方法,没啥好研究的。
InsetDrawable是 DrawableWrapper 的子类。
它没重载 draw 方法,而是
@Override protected void onBoundsChange(Rect bounds) { final Rect r = mTmpRect; r.set(bounds); r.left += mState.mInsetLeft; r.top += mState.mInsetTop; r.right -= mState.mInsetRight; r.bottom -= mState.mInsetBottom; // Apply inset bounds to the wrapped drawable. super.onBoundsChange(r); }就是压缩了所要绘制的 drawable 的 bounds,感觉没什么地方用的到。
剩下两个子类 RotateDrawable 和 ScaleDrawable 也大同小异。
当 callback 不是 view
Drawable 比较神奇的地方就是可以一层套一层,因为都是 Drawable 嘛(递归思想还是常用的。)
ColorDrawable 套一个 ClipDrawable 显示 90%,再套一个 RotateDrawable 顺时针旋转 90 度。
那么问题来了,当你改变最内部 ColorDrawable 颜色时(虽然没这个方法。。),外部是如何知道它需要重画呢。
DrawableWrapper 是实现 Callback 接口的,
@Override public void invalidateDrawable(@NonNull Drawable who) { final Callback callback = getCallback(); if (callback != null) { callback.invalidateDrawable(this); } }并且 setDrawable 时已经把内部 drawable 的 callback 设置为 DrawableWrapper
public void setDrawable(@Nullable Drawable dr) { if (mDrawable != null) { mDrawable.setCallback(null); } mDrawable = dr; if (dr != null) { dr.setCallback(this);}...}所以当 ColorDrawable invidateSelf 时,
public void invalidateSelf() { final Callback callback = getCallback(); if (callback != null) { callback.invalidateDrawable(this); } }colorDrawable.invalidateSelf -> clipDrawable.invalidateDrawble -> rotateDrawable.invalidateDrawable -> view.invalidate
会完整的向上冒泡,直到 View 重画,然后在往下走一遍。(这和事件分发,还有 js 事件冒泡很像)
LayerDrawable 是 Drawable 的另一个子类。
A Drawable that manages an array of other Drawables. These are drawn in array
order, so the element with the largest index will be drawn on top.
LayerDrawable 也引用多个 drawable,但它全部都绘制。
@Override public void draw(Canvas canvas) { final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; for (int i = 0; i < N; i++) { final Drawable dr = array[i].mDrawable; if (dr != null) { dr.draw(canvas); } } }
TransitionDrawable 是 LayerDrawable 的子类
Drawable d; d = array[0].mDrawable; if (crossFade) { d.setAlpha(255 - alpha); } d.draw(canvas); if (crossFade) { d.setAlpha(0xFF); } if (alpha > 0) { d = array[1].mDrawable; d.setAlpha(alpha); d.draw(canvas); d.setAlpha(0xFF); }关键代码如上,就是根据根据时间流逝(0,duration)->(0,255),不断改变 alpha。
然后设置两个内部 drawable 的透明度 alpha 和 255-alpha,一起绘制。
关键是内部 drawable 要实现了 setAlpha 方法。
ShapeDrawable
把绘制工作交给了 Shape 类。
protected void onDraw(Shape shape, Canvas canvas, Paint paint) { shape.draw(canvas, paint); }
RectShape
canvas.drawRect(mRect, paint);
ArcShape
canvas.drawArc(rect(), mStart, mSweep, true, paint);
OvalShape
canvas.drawOval(rect(), paint);
复杂的 Shape
RoundRectShape
protected void onResize(float w, float h) { super.onResize(w, h); RectF r = rect(); mPath.reset(); if (mOuterRadii != null) { mPath.addRoundRect(r, mOuterRadii, Path.Direction.CW); } else { mPath.addRect(r, Path.Direction.CW); } if (mInnerRect != null) { mInnerRect.set(r.left + mInset.left, r.top + mInset.top, r.right - mInset.right, r.bottom - mInset.bottom); if (mInnerRect.width() < w && mInnerRect.height() < h) { if (mInnerRadii != null) { mPath.addRoundRect(mInnerRect, mInnerRadii, Path.Direction.CCW); } else { mPath.addRect(mInnerRect, Path.Direction.CCW); } } } }可以画出类似空心环的东西,用到 Path,注意下 CW 和 CCW 顺逆时针,以后应该会专门学学 Path。
- Drawable
- Drawable
- Drawable
- Drawable
- drawable
- Drawable
- Drawable
- Drawable
- Drawable
- Drawable
- android.graphics.drawable.Drawable
- android drawable Transition Drawable
- Drawable之Shape drawable
- drawable-mdpi、drawable-ldpi、drawable-hdpi区别
- drawable-mdpi、drawable-ldpi、drawable-hdpi区别
- drawable-hdpi,drawable-ldpi,drawable-mdpi区别?
- Drawable资源
- Drawable资源
- 从零开始学spring-boot(1)-Hello spring boot!
- 2016.10.29【初中部 NOIP提高组 】模拟赛C题解
- async.js 接口文档以及测试用例
- Android 闹钟实现
- 如何在 Android 应用中使用 FontAwesome 图标
- Drawable
- 欢迎使用CSDN-markdown编辑器
- 2016.10.27总结
- C#控件及常用属性
- 前后端分离ajax接收文件流的实践
- 分布式对象存储Ambry(1)简介与集群部署
- MT过安全狗增加用户
- MMO移动游戏性能分析报告:渲染、UI、逻辑代码和内存
- 纪中2016.10.29比赛总结