Android Drawable完全解析(一):Drawable源碼分析(中)

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 !

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容