Android Drawable彻底解析(一):Drawable源码分析(上) Android Drawable彻底解析(一):Drawable源码分析(中) Android Drawable彻底解析(一):Drawable源码分析(下)android
呃...我不是故意要凑篇幅写个什么上下篇,实在是由于Drawable源码有点长,一篇写不下啦O(∩_∩)O~canvas
鉴于源码通常较长,之后全部源码分析的部分,英文注释非必要状况都再也不保留! ####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类自己源码先写到这儿,接着往下看。app
####3:Drawable绘制流程 看过Drawable源码,其实咱们仍是不清楚: Drawable实例究竟是如何被绘制到屏幕上面? Drawable源码中的那些方法又是何时被谁调用的?ide
咱们回想一下,使用Drawable最一般的步骤: 经过Resource获取Drawable实例 将获取的Drawable实例当作背景设置给View或者做为ImageView的src进行显示:oop
下面就逐步分析理解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)方法绘制到屏幕上。this
继续查看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 !