望远山,知近路,而后自得其乐!

Android Drawable介绍

1.Drawable简介

Android系统中将可绘制对象被抽象为Drawable,不同的绘制资源对应着不同的Drawable类型。Android FrameWork提供了常用的Drawable,Android控件的绘制资源基本都是通过Drawable形式实现的。一般情况下,开发者是不会直接接触Drawable的具体实现的,Drawable资源一般都放在res/drawable目录下,用户通过图片,xml格式的Drawable资源来使用。

Android内置的比较常用的Drawable类型包括:ColorDrawableGradientDrawableShapeDrawableBitmapDrawableNinePatchDrawableInsetDrawableClipDrawableScaleDrawableRotateDrawableAnimationDrawableLayerDrawableLevelListDrawableStateListDrawableTransitionDrawable

2.Drawable 特性

Drawable 比 View 更轻量级,它只是 View 绘制中过程被调用的一个东西(背景,前景)。
自定义 View 的时候,如果只要改变背景的话,最好不要去重写 draw 函数,Drawable 这个类已经被抽象出来负责背景的绘制了。

Drawable 有许许多多的子类,但最复杂的还是 Drawable 本身,作为父类的它要考虑到方方面面。

1).setBounds

最基本的形状是什么,是矩形。
无论 Drawable 最终呈现在画布上是什么形状的,它总是被限定在一个矩形当中。

void setBackgroundBounds() {  
       if (mBackgroundSizeChanged && mBackground != null) {  
           mBackground.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);  
           mBackgroundSizeChanged = false;  
           rebuildOutline();  
       }  
   } 

在绘制背景时,会将 View 的坐标转换为 bounds,所以 Drawable 都会被拉伸至 View 的大小。

canvas.drawRect(getBounds(), mPaint); 

2).setCallBack

public static interface Callback {  
       public void invalidateDrawable(Drawable who);  
  
       public void scheduleDrawable(Drawable who, Runnable what, long when);  
  
       public void unscheduleDrawable(Drawable who, Runnable what);  
   }

View 在设置背景时,drawable 会把 callback 指向 View。

background.setCallback(this);

Drawable 在需要重绘时,会调用:

public void invalidateSelf() {  
     final Callback callback = getCallback();  
     if (callback != null) {  
         callback.invalidateDrawable(this);  
     }  
 } 
@Override  
 public void invalidateDrawable(@NonNull Drawable drawable) {  
     if (verifyDrawable(drawable)) {  
         final Rect dirty = drawable.getDirtyBounds();  
         final int scrollX = mScrollX;  
         final int scrollY = mScrollY;  
  
         invalidate(dirty.left + scrollX, dirty.top + scrollY,  
                 dirty.right + scrollX, dirty.bottom + scrollY);  
         rebuildOutline();  
     }  
 }  

这一步说明了,Drawable 是 View 附属,它不会仅仅重画自己。

scheduleSelf 是定时重绘自己的意思,就是动画的意思。
unscheduleSelf 则是取消上一步动画的意思,很多时候我们需要让动画戛然而止。

3).setState

View 有很多 State,比较常见的是 EditText 的 foucs,以及 Button 的 press。
Drawable 是否根据 View 的状态改变而做出相应的变化,就被称为 isStateful。
触发过程如下:
view.refreshDrawableState -> view.drawableStateChanged -> bg.setState -> bg.onStateChanged

3.Drawable的定义形式

一般情况下,除了图片资源是直接放在res/drawable下(android studio中图片放在res/minmap下),其他的Drawable都是以xml格式实现的,开发者通过在xml中使用shape,selector,level-list等标签来实现对应的Drawable,从而实现相应的可绘制资源的定义,最终view通过绘制这些Drawable来实现我们想要的显示效果。

Drawable中xml标签与Drawable对象的对应关系如以下表格所示:

xml标签Drawable对象
<selector>StateListDrawable
<level-list>LevelListDrawable
<layer-list>LayerDrawable
<transition>TransitionDrawable
<color>ColorDrawable
<shape>GradientDrawable
<scale>ScaleDrawable
<clip>ClipDrawable
<rotate>RotateDrawable
<anination-list>AnimationDrawable
<inset>InsetDrawable
<bitmap>BitmapDrawable
<nine-patch>NinePatchDrawable

4.从源码探索Drawable的加载过程

下面我们通过Framework的源代码来探索一下Drawable的加载过程,一般情况下,我们通过getResources().getDrawable(int id);的方式去加载。顺着这条线往下看,getDrawable->loadDrawable->loadDrawableForCookie->Drawable.createFromXml->createFromXmlInner,来看androidgraphicsdrawableDrawable.java的createFromXmlInner函数的实现代码。

public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
           Theme theme) throws XmlPullParserException, IOException {
        final Drawable drawable;

        final String name = parser.getName();
        if (name.equals("selector")) {
            drawable = new StateListDrawable();
        } else if (name.equals("animated-selector")) {
            drawable = new AnimatedStateListDrawable();
        } else if (name.equals("level-list")) {
            drawable = new LevelListDrawable();
        } else if (name.equals("layer-list")) {
            drawable = new LayerDrawable();
        } else if (name.equals("transition")) {
            drawable = new TransitionDrawable();
        } else if (name.equals("ripple")) {
            drawable = new RippleDrawable();
        } else if (name.equals("color")) {
            drawable = new ColorDrawable();
        } else if (name.equals("shape")) {
            drawable = new GradientDrawable();
        } else if (name.equals("vector")) {
            drawable = new VectorDrawable();
        } else if (name.equals("animated-vector")) {
            drawable = new AnimatedVectorDrawable();
        } else if (name.equals("scale")) {
            drawable = new ScaleDrawable();
        } else if (name.equals("clip")) {
            drawable = new ClipDrawable();
        } else if (name.equals("rotate")) {
            drawable = new RotateDrawable();
        } else if (name.equals("animated-rotate")) {
            drawable = new AnimatedRotateDrawable();
        } else if (name.equals("animation-list")) {
            drawable = new AnimationDrawable();
        } else if (name.equals("inset")) {
            drawable = new InsetDrawable();
        } else if (name.equals("bitmap")) {
            //noinspection deprecation
            drawable = new BitmapDrawable(r);
            if (r != null) {
               ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
            }
        } else if (name.equals("nine-patch")) {
            drawable = new NinePatchDrawable();
            if (r != null) {
                ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
             }
        } else {
            throw new XmlPullParserException(parser.getPositionDescription() +
                    ": invalid drawable tag " + name);
        }
     
        drawable.inflate(r, parser, attrs, theme);
        return drawable;
    }

上面的对应关系就从这里来的,现在是不是一下子明朗了呢。在函数结尾,Drawable通过条用inflate函数解析AttributeSet中的属性信息,并设置Drawable相应的属性。看一下BitmapDrawable吧,

public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
            throws XmlPullParserException, IOException {
        super.inflate(r, parser, attrs, theme);
 
        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
        updateStateFromTypedArray(a);
        verifyState(a);
        a.recycle();
    }

继续看updateStateFromTypedArray

private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
        final Resources r = a.getResources();
        final BitmapState state = mBitmapState;
 
        // Account for any configuration changes.
        state.mChangingConfigurations |= a.getChangingConfigurations();
 
        // Extract the theme attributes, if any.
        state.mThemeAttrs = a.extractThemeAttrs();
 
        final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
        if (srcResId != 0) {
            final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId);
            if (bitmap == null) {
                throw new XmlPullParserException(a.getPositionDescription() +
                        ": <bitmap> requires a valid src attribute");
            }
 
            state.mBitmap = bitmap;
        }
 
        state.mTargetDensity = r.getDisplayMetrics().densityDpi;
 
        final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
        setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
 
        state.mAutoMirrored = a.getBoolean(
                R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
        state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
 
        final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
        if (tintMode != -1) {
            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
        }
 
        final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
        if (tint != null) {
            state.mTint = tint;
        }
 
        final Paint paint = mBitmapState.mPaint;
        paint.setAntiAlias(a.getBoolean(
                R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
        paint.setFilterBitmap(a.getBoolean(
                R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
        paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
 
        setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
 
        final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
        if (tileMode != TILE_MODE_UNDEFINED) {
            final Shader.TileMode mode = parseTileMode(tileMode);
            setTileModeXY(mode, mode);
        }
 
        final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
        if (tileModeX != TILE_MODE_UNDEFINED) {
            setTileModeX(parseTileMode(tileModeX));
        }
 
        final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
        if (tileModeY != TILE_MODE_UNDEFINED) {
            setTileModeY(parseTileMode(tileModeY));
        }
 
        // Update local properties.
        initializeWithState(state, r);
    }

updateStateFromTypedArray函数中,BitmapDrawable获取到src属性后,通过BitmapFactory加载图像文件,并读取用户配置的属性,设置BitmapDrawable的其他属性。其他的Drawable的实现方式类似。

文章评论已关闭!