Android Drawable完全解析(一):Drawable源码分析(上) Android Drawable完全解析(一):Drawable源码分析(中) Android Drawable完全解析(一):Drawable源码分析(下)
呃...我不是故意要凑篇幅写个什么上下篇,实在是因为Drawable源码有点长,一篇写不下啦O(∩_∩)O~
鉴于源码一般较长,以后所有源码分析的部分,英文注释非必要情况都不再保留! ####2:Drawable源码分析/翻译 继续上Drawable源码:
package android.graphics.drawable;
public abstract class Drawable {
****
略
****
/**
这个方法很重要,故保留英文注释!
调用mutate(),使当前Drawable实例mutable,这个操作不可逆。
一个mutable的Drawable实例不会和其他Drawable实例共享它的状态。
当你需要修改一个从资源文件加载的Drawable实例时,mutate()方法尤其有用。
默认情况下,所有加载同一资源文件生成的Drawable实例都共享一个通用的状态,
如果你修改了其中一个Drawable实例,所有的相关Drawable实例都会发生同样的变化。
这个方法在[其实你不懂:Drawable着色(tint)的兼容方案 源码解析]
这篇文章里有过介绍,就是为了限定Drawable实例的编辑生效范围仅限于自身。
* Make this drawable mutable. This operation cannot be reversed. A mutable
* drawable is guaranteed to not share its state with any other drawable.
* This is especially useful when you need to modify properties of drawables
* loaded from resources. By default, all drawables instances loaded from
* the same resource share a common state; if you modify the state of one
* instance, all the other instances will receive the same modification.
*
* Calling this method on a mutable Drawable will have no effect.
*
* @return This drawable.
* @see ConstantState
* @see #getConstantState()
*/
public @NonNull Drawable mutate() {
return this;
}
/**
被隐匿
* @hide
*/
public void clearMutated() {
// Default implementation is no-op.
}
//下面几个方法介绍了通过不同的方式创建Drawable实例:
//流、XML、文件地址
public static Drawable createFromStream(InputStream is, String srcName) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
try {
return createFromResourceStream(null, null, is, srcName);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
public static Drawable createFromResourceStream(Resources res, TypedValue value,
InputStream is, String srcName) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
try {
return createFromResourceStream(res, value, is, srcName, null);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
public static Drawable createFromResourceStream(Resources res, TypedValue value,
InputStream is, String srcName, BitmapFactory.Options opts) {
if (is == null) {
return null;
}
Rect pad = new Rect();
if (opts == null) opts = new BitmapFactory.Options();
opts.inScreenDensity = Drawable.resolveDensity(res, 0);
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
if (bm != null) {
byte[] np = bm.getNinePatchChunk();
if (np == null || !NinePatch.isNinePatchChunk(np)) {
np = null;
pad = null;
}
final Rect opticalInsets = new Rect();
bm.getOpticalInsets(opticalInsets);
return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
}
return null;
}
public static Drawable createFromXml(Resources r, XmlPullParser parser)
throws XmlPullParserException, IOException {
return createFromXml(r, parser, null);
}
public static Drawable createFromXml(Resources r, XmlPullParser parser, Theme theme)
throws XmlPullParserException, IOException {
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
//noinspection StatementWithEmptyBody
while ((type=parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Empty loop.
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
Drawable drawable = createFromXmlInner(r, parser, attrs, theme);
if (drawable == null) {
throw new RuntimeException("Unknown initial tag: " + parser.getName());
}
return drawable;
}
public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
return createFromXmlInner(r, parser, attrs, null);
}
public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
return r.getDrawableInflater().inflateFromXml(parser.getName(), parser, attrs, theme);
}
public static Drawable createFromPath(String pathName) {
if (pathName == null) {
return null;
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
try {
Bitmap bm = BitmapFactory.decodeFile(pathName);
if (bm != null) {
return drawableFromBitmap(null, bm, null, null, null, pathName);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
return null;
}
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
inflate(r, parser, attrs, null);
}
/**
从XML文件中加载Drawable实例,Drawable实例接受主题设置的风格
*/
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable);
mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible);
a.recycle();
}
/**
从XML文件中加载Drawable实例
*/
void inflateWithAttributes(@NonNull @SuppressWarnings("unused") Resources r,
@NonNull @SuppressWarnings("unused") XmlPullParser parser, @NonNull TypedArray attrs,
@AttrRes int visibleAttr) throws XmlPullParserException, IOException {
mVisible = attrs.getBoolean(visibleAttr, mVisible);
}
/**
这段注释很重要,故保留英文注释!
ConstantState这个抽象类被用于存储 多个Drawable实例间 共享的 常量状态值及数据。
如从同一个图片资源创建的多个BitmapDrawable实例,它们将共享
同一个存储在它们的ConstantState中的Bitmap。
* This abstract class is used by {@link Drawable}s to store shared constant state and data
* between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
* share a unique bitmap stored in their ConstantState.
*
newDrawable可以运用ConstantState创建一个新的Drawable实例
* <p>
* {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
* from this ConstantState.
* </p>
*
Drawable#getConstantState可以获取一个Drawable关联的ConstantState。
调用Drawable#mutate(),则将为新创建的Drawable实例单独关联一个ConstantState。
* Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
* {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
* Drawable.
*/
public static abstract class ConstantState {
/**
运用ConstantState创建一个新的Drawable实例
*/
public abstract @NonNull Drawable newDrawable();
/**
运用ConstantState创建一个新的Drawable实例
*/
public @NonNull Drawable newDrawable(@Nullable Resources res) {
return newDrawable();
}
/**
运用ConstantState创建一个新的Drawable实例
*/
public @NonNull Drawable newDrawable(@Nullable Resources res,
@Nullable @SuppressWarnings("unused") Theme theme) {
return newDrawable(res);
}
/**
返回会影响Drawable实例的一个bit掩码变化设置
*/
public abstract @Config int getChangingConfigurations();
/**
返回所有的像素数
public int addAtlasableBitmaps(@NonNull Collection<Bitmap> atlasList) {
return 0;
}
/** @hide */
protected final boolean isAtlasable(@Nullable Bitmap bitmap) {
return bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888;
}
/**
返回当前共享状态是否可以设置主题
*/
public boolean canApplyTheme() {
return false;
}
}
/**
返回当前Drawable的用于存储共享状态值的ConstantState实例
*/
public @Nullable ConstantState getConstantState() {
return null;
}
//通过Bitmap实例创建Drawable实例
private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
Rect pad, Rect layoutBounds, String srcName) {
if (np != null) {
return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
}
return new BitmapDrawable(res, bm);
}
/**
确保色彩过滤器和当前色彩与色彩模式一致
*/
@Nullable PorterDuffColorFilter updateTintFilter(@Nullable PorterDuffColorFilter tintFilter,
@Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) {
if (tint == null || tintMode == null) {
return null;
}
final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
if (tintFilter == null) {
return new PorterDuffColorFilter(color, tintMode);
}
tintFilter.setColor(color);
tintFilter.setMode(tintMode);
return tintFilter;
}
/**
如果主题有效,则从中获取样式属性,
如果主题无效,则返回没有样式的资源。
*/
static @NonNull TypedArray obtainAttributes(@NonNull Resources res, @Nullable Theme theme,
@NonNull AttributeSet set, @NonNull int[] attrs) {
if (theme == null) {
return res.obtainAttributes(set, attrs);
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
/**
根据 原始像素值,资源单位密度和目标设备单位密度 获得一个float像素值
*/
static float scaleFromDensity(float pixels, int sourceDensity, int targetDensity) {
return pixels * targetDensity / sourceDensity;
}
static int scaleFromDensity(
int pixels, int sourceDensity, int targetDensity, boolean isSize) {
if (pixels == 0 || sourceDensity == targetDensity) {
return pixels;
}
final float result = pixels * targetDensity / (float) sourceDensity;
if (!isSize) {
return (int) result;
}
final int rounded = Math.round(result);
if (rounded != 0) {
return rounded;
} else if (pixels > 0) {
return 1;
} else {
return -1;
}
}
//获取单位密度
static int resolveDensity(@Nullable Resources r, int parentDensity) {
final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
}
static void rethrowAsRuntimeException(@NonNull Exception cause) throws RuntimeException {
final RuntimeException e = new RuntimeException(cause);
e.setStackTrace(new StackTraceElement[0]);
throw e;
}
/**
通过解析tintMode属性枚举值获得一个PorterDuff.Mode
被隐匿
* @hide
*/
public static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) {
switch (value) {
case 3: return Mode.SRC_OVER;
case 5: return Mode.SRC_IN;
case 9: return Mode.SRC_ATOP;
case 14: return Mode.MULTIPLY;
case 15: return Mode.SCREEN;
case 16: return Mode.ADD;
default: return defaultMode;
}
}
}
Drawable类本身源码先写到这儿,接着往下看。
####3:Drawable绘制流程 看过Drawable源码,其实我们还是不清楚: Drawable实例到底是如何被绘制到屏幕上面? Drawable源码中的那些方法又是什么时候被谁调用的?
我们回想一下,使用Drawable最通常的步骤: 通过Resource获取Drawable实例 将获取的Drawable实例当做背景设置给View或者作为ImageView的src进行显示:
下面就逐步分析理解Drawable的绘制流程。 ######3.1:通过Resource获取Drawable实例 最常用写法:getResources().getDrawable(int id),看下关键代码:
public class Resources {
****
public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
final Drawable d = getDrawable(id, null);
*****
return d;
}
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
//
impl.getValue(id, value, true);
//将获取到的Drawable实例返回
return impl.loadDrawable(this, value, id, theme, true);
} ****
}
}
一路追踪下去:
public class ResourcesImpl {
//Resource实例,TypedValue,资源ID,Theme实例,true
@Nullable
Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
boolean useCache) throws NotFoundException {
try {
********
//是否属于ColorDrawable
final boolean isColorDrawable;
//Drawable缓存
final DrawableCache caches;
final long key;
//判断资源是否属于颜色资源
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
caches = mColorDrawableCache;
key = value.data;
} else {
//如果是加载一张普通的图片,不属于颜色资源
isColorDrawable = false;
caches = mDrawableCache;
key = (((long) value.assetCookie) << 32) | value.data;
}
if (!mPreloading && useCache) {
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
//如果在Drawable缓存里面未找到资源ID对应的Drawable实例,继续
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
//如果不属于颜色资源,则从sPreloadedDrawables中查询
//sPreloadedDrawables只有在执行cacheDrawable方法时
//才会进行数据添加:而第一次加载图片时候还未执行cacheDrawable
//所以此时cs = null.
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
//当第一次加载图片资源时候,cs=null且不属于颜色资源,
//实际是通过loadDrawableForCookie来获取Drawable实例
dr = loadDrawableForCookie(wrapper, value, id, null);
}
*********
}
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
Resources.Theme theme) {
****
final String file = value.string.toString();
****
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
if (file.endsWith(".xml")) {
//如果是从xml文件加载Drawable
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(wrapper, rp, theme);
rp.close();
} else {
//从图片资源加载Drawable,执行Drawable.createFromResourceStream
//获取Drawable实例
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
} catch (Exception e) {
****
}
****
return dr;
}
}
一路追踪下去:
public abstract class Drawable {
public static Drawable createFromResourceStream(Resources res, TypedValue value,
InputStream is, String srcName, BitmapFactory.Options opts) {
****
return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
****
}
private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
Rect pad, Rect layoutBounds, String srcName) {
if (np != null) {
//如果加载的图片资源是.9 PNG,返回NinePatchDrawable实例
return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
}
//对于普通图片资源,返回BitmapDrawable
return new BitmapDrawable(res, bm);
}
}
由此可见,通过Resource实例加载一张资源图片: .9图返回1个NinePatchDrawable实例; 普通图片返回1个BitmapDrawable实例。 ######3.2:将获取的Drawable实例当做背景设置给View 最常用写法:targetView.setBackgroundDrawable(Drawable bg), 同样看一下关键代码
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
****
public void setBackgroundDrawable(Drawable background) {
****
if (background == mBackground) {
//如果当前背景和background相同,直接return
return;
}
boolean requestLayout = false;
mBackgroundResource = 0;
if (mBackground != null) {
if (isAttachedToWindow()) {
//如果当前View实例已经被绘制到屏幕上,则首先取消
//该View实例原始背景Drawable的动画
mBackground.setVisible(false, false);
}
//移除该View实例原始背景Drawable的动画监听接口
mBackground.setCallback(null);
//取消该View实例原始背景Drawable的所有事件
unscheduleDrawable(mBackground);
}
if (background != null) {
****
//设置background的布局方向和View实例一致,
//Drawable.setLayoutDirection见上一篇文章
background.setLayoutDirection(getLayoutDirection());
if (background.getPadding(padding)) {
//如果Drawable实例background有padding
resetResolvedPaddingInternal();
switch (background.getLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
//布局方向从右至左
mUserPaddingLeftInitial = padding.right;
mUserPaddingRightInitial = padding.left;
internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
break;
case LAYOUT_DIRECTION_LTR:
default:
//布局方向从左至右
mUserPaddingLeftInitial = padding.left;
mUserPaddingRightInitial = padding.right;
//internalSetPadding会将四个参数值和View实例的padding进行比对,若不同则会重新布局+重建View的外部轮廓
internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
}
mLeftPaddingDefined = false;
mRightPaddingDefined = false;
}
if (mBackground == null
|| mBackground.getMinimumHeight() != background.getMinimumHeight()
|| mBackground.getMinimumWidth() != background.getMinimumWidth()) {
requestLayout = true;
}
//设置当前View实例的背景为传入的Drawable实例 background
mBackground = background;
if (background.isStateful()) {
//如果background会根据状态值变更外观,则设置其状态为
//当前View实例的state
background.setState(getDrawableState());
}
if (isAttachedToWindow()) {
//如果当前View实例已经被绘制到屏幕上
//且实例和实例的父控件及递归获得的根布局都处于可见状态,
//则设置background开启动画效果
background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
}
applyBackgroundTint();
//设置background动画接口监听为View实例本身(View实现了 Drawable.Callback):
//public class View implements Drawable.Callback
background.setCallback(this);
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
//需要重新布局
requestLayout = true;
}
} else {
mBackground = null;
if ((mViewFlags & WILL_NOT_DRAW) != 0
&& (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
requestLayout = true;
}
computeOpaqueFlags();
if (requestLayout) {
//重新布局
requestLayout();
}
mBackgroundSizeChanged = true;
//重绘View实例
invalidate(true);
//重建View实例的外部轮廓
invalidateOutline();
}
}
由此可见,setBackgroundDrawable方法,调用了Drawable实例的一系列方法,最终引发了View实例的重新布局(requestLayout())重绘(invalidate(true))及重建View实例的外部轮廓(invalidateOutline())。 invalidate会触发draw方法,我们继续看View.draw方法的关键代码:
public void draw(Canvas canvas) {
****
/*
翻译可能不甚准确,欢迎英语好的同学留言指正O(∩_∩)O~
Draw方法会执行以下几个步骤,且必须按顺序执行:
1:绘制View实例的背景
2:如有必要,保存画布图层以备褪色
3:绘制View实例的内容
4:绘制View实例的中的子控件
5:如有必要,绘制边缘并恢复图层
6:绘制滚动条
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
//从步骤顺序上看,和Drawable相关的就是第1步,只看第1步代码
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
//绘制背景
drawBackground(canvas);
}
****
}
一路追踪下去:
private void drawBackground(Canvas canvas) {
//mBackground就是之前setBackgroundDrawable传入的Drawable实例
final Drawable background = mBackground;
if (background == null) {
return;
}
//设置background绘制范围为View实例的所在范围
setBackgroundBounds();
****
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
//最终调用Drawable.draw(Canvas canvas)将Drawable实例
//绘制到屏幕上
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
//最终调用Drawable.draw(Canvas canvas)将Drawable实例
//绘制到屏幕上
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
由此可见,View实例的背景Drawable实例最终还是调用自身的Drawable.draw(@NonNull Canvas canvas)方法绘制到屏幕上。
继续查看Drawable.draw方法:
public abstract class Drawable {
//Drawable中的draw是一个抽象方法,应该是为了众多的
//子类Drawable拥有自定义的绘制逻辑进行重写
public abstract void draw(@NonNull Canvas canvas);
}
在分析Resource.getDrawable时候已经知道,
对于普通的图片资源,获取到的是一个BitmapDrawable实例,
我们就来看看BitmapDrawable的draw具体的绘制逻辑:
public class BitmapDrawable extends Drawable {
@Override
public void draw(Canvas canvas) {
****
if (shader == null) {
****
//最终调用了Canvas.drawBitmap方法,将Drawable实例中的bitmap绘制到View实例关联的画布上
canvas.drawBitmap(bitmap, null, mDstRect, paint);
if (needMirroring) {
canvas.restore();
}
} ****
}
}
至此,将获取的Drawable实例当做背景设置给View,和Drawable相关的一系列逻辑就分析完了,大致如下:
- 1:setBackgroundDrawable方法,调用了Drawable的一系列方法,设置了Drawable实例一系列属性值,最终引发了View实例的重新布局(requestLayout()),重绘(invalidate(true))及重建View实例的外部轮廓(invalidateOutline())
- 2:在View实例重绘过程的第一步,将得到的Drawable实例(View实例的背景)绘制到屏幕上,实质是调用了Drawable.draw(@NonNull Canvas canvas)
- 3:Drawable.draw本身是个抽象方法,绘制具体逻辑由其子类实现。 我们以之前获得的BitmapDrawable为例进行分析: 最终调用了Canvas.drawBitmap方法,将Drawable实例中的bitmap绘制到View实例关联的画布上
Drawable绘制流程今天先写到这儿,现在是2017/03/07 20:41,加班码字到现在有点累了,明后天继续把ImageView和Drawable关联的部分写完吧!
未完待续...
以上就是个人分析的一点结果,若有错误,请各位同学留言告知!
That's all !