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 !