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 !