【Android Drawable】一、Drawable 类

  • Drawable 可以方便做出一些特殊的 UI 效果,它比自定义 View 的成本要低,非图片类的 Drawable 占用空间较小,这对减小 apk 的大小很有帮助。
  • Drawable 通过 getIntrinsicWidth 和 getIntrinsicHeight 获取 Drawable 的内部宽高,比如一张图片形成的 Drawable 内部宽高就是图片的宽高,而一个颜色所形成的 Drawable 就没有内部宽高的概念,Drawable 的内部宽高不等同于他的大小。
  • 在实际开发中, Drawable 常被作为 View 的背景使用,Drawable 会被拉伸至 View 同等大小。
  • Drawable 通常被用作 View 的背景或者在 ImageView 等控件显示。
  • 在 ImageView 中显示,xml 文件中设置 'android:src' 属性,或者在代码中调用 setImageDrawable() 方法,getDrawable() 方法获取到 Drawable。
  • 作为 View 背景,在 xml 文件中设置 android:background 属性,或者在代码中调用 setBackground 方法,通过 getBackground 方法获取背景 Drawable;

Drawable

Drawable 是一个抽象类,提供了一些 API 方法去处理各种资源的绘制,但是又不具备 View 的事件与交互处理能力。再简单粗暴一点认为就是一个辅助绘制工具类,把各种东西都封装搞好以后直接给 Canvas 去画。

Drawable 类中的几个重要的方法

Drawable 类有四个抽象方法子类必须实现:

public abstract void draw(Canvas canvas);
public abstract void setAlpha(@IntRange(from=0,to=255) int alpha);
public abstract void setColorFilter(ColorFilter colorFilter);
public abstract @PixelFormat.Opacity int getOpacity();
  • draw:在 setBounds 方法设置的区域的 Canvas 中进行Drawable 的绘制,要绘制状态效果的话,可以由 setAlpha,setColorFilter 等方法控制;
  • setAlpha :给 Drawable 指定一个 alpha 值,在 0 - 255 之间;
  • setColorFilter:设置滤镜效果,有时我们会在 Drawable 内部定义一个 Paint 对象,所以该方法的实现可以为 mPaint.setColorFilter(colorFilter)
  • getOpacity :返回 Drawable 的透明度,取值为 PixelFormat.UNKNOWN,PixelFormat.TRANSLUCENT,PixelFormat.TRANSPARENT,PixelFormat.OPAQUE 中的一个;
public void setBounds(int left, int top, int right, int bottom);
public void setBounds(@NonNull Rect bounds);
protected void onBoundsChange(Rect bounds)
  • setBounds 设置绘制区域矩形边界,draw 方法调用时会用到其设置的值,不设置默认边界均为 0,所以自定义 Drawable 时要重写该方法;
  • onBoundsChange setBounds 方法中新旧 bounds 发生变化时回调,默认为空方法;
public int getIntrinsicWidth();
public int getIntrinsicHeight();

获取 Drawable 的内部宽高,包含 padding,一张图片形成的 Drawable 内部宽高就是图片的宽高,不同的 Drawable 子类是有不同的实现的,而一个颜色所形成的 Drawable 就没有内部宽高的概念,在用作 View 的 background 时自动拉伸为 View 大小。

public int getMinimumWidth() 
public int getMinimumHeight() 

返回 Drawable 建议的最小宽高,View 用作背景时要大于该最小宽高,默认的返回为内部宽高或 0;

Drawable 调用流程浅析

Drawable 一般用作 View 的背景,在基类 View 中声明了一个成员变量 Drawable mBackground 作为 View 的背景。
1、在 xml 文件中通过 android:background 属性给 View 设置背景,

    //View构造方法
    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        ......
        //获取View的属性
        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
        //定义一个局部背景变量Drawable
        Drawable background = null;
        ......
        //获取到设置的background属性,也就是我们平时给各个控件在xml中设置的android:background属性值
        case com.android.internal.R.styleable.View_background:
        background = a.getDrawable(attr);
        break;
        ......
        //当设置有背景Drawable属性时调用setBackground方法
        if (background != null) {
            setBackground(background);
        }
        ......
    }

获取到 xml 中设置的背景后最终调用 setBackground 方法;
2、我们也可以在代码中直接调用一系列 setBackgroundXXX 方法:

public void setBackgroundColor(@ColorInt int color);
public void setBackgroundResource(@DrawableRes int resid);
public void setBackground(Drawable background)

最终其实都是调用了 setBackgroundDrawable 方法:

public void setBackgroundDrawable(Drawable background) {
        ......
        //每次设置新的background后就进行复位操作
        if (mBackground != null) {
            if (isAttachedToWindow()) {
                //Drawable设置为不可见(这部就用上上面Drawable的分析了么)
                mBackground.setVisible(false, false);
            }
            //Drawable回调断开(这部就用上上面Drawable的分析了么)
            mBackground.setCallback(null);
            //移除Drawable绘制队列,实质触发回调的该方法(这部就用上上面Drawable的分析了么)
            unscheduleDrawable(mBackground);
        }

        if (background != null) {
            //当有设置背景Drawable
            ......
            //给Drawable设置View当前的布局方向(这部就用上上面Drawable的分析了么)
            background.setLayoutDirection(getLayoutDirection());
            //判断当前Drawable是否设置有padding(这部就用上上面Drawable的分析了么,有padding则返回true)
            if (background.getPadding(padding)) {
                //依据Drawable的这个padding去给当前View相关padding属性建议修改
                ......
            }
            //比较上次旧的(可能没有)和现在设置的Drawable的最小宽高,发现不一样就预示着我们接下来需要对View再次layout,做标记
            if (mBackground == null
                    || mBackground.getMinimumHeight() != background.getMinimumHeight()
                    || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
                requestLayout = true;
            }
            //把要新设置的Drawable正式赋值给View的mBackground成员
            mBackground = background;
            //判断当状态改变时当前Drawable是否需要切换图片,一般在StateListDrawable实现中为true(这部就用上上面Drawable的分析了么)
            if (background.isStateful()) {
                //设置Drawable状态(这部就用上上面Drawable的分析了么)
                background.setState(getDrawableState());
            }
            //如果View已经attached to window了就把Drawable设置为可见(这部就用上上面Drawable的分析了么)
            if (isAttachedToWindow()) {
                background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
            }
            //设置Drawable的callback,在View继承关系中有实现Drawable的callback
            background.setCallback(this);
            ......
        } else {
            //当没有设置背景Drawable时清空背景Drawable,然后设置View的重新layout标志
            mBackground = null;
            ......
            requestLayout = true;
        }
        ......
        //需要重新布局,触发重新布局
        if (requestLayout) {
            requestLayout();
        }
        //通知重新绘制刷新操作
        mBackgroundSizeChanged = true;
        invalidate(true);
        invalidateOutline();
    }

每逢我们对控件通过 xml 或者 java 设置 background 后触发的其实就是上面这一堆操作,实质最后触发的就是 layout 或者 draw。在 View 的 draw 方法中调用 drawBackground 方法:

public void draw(Canvas canvas) {
        /*
         *      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)
         */
        ......
        drawBackground(canvas);
        ......
    }

private void drawBackground(Canvas canvas) {
        ......
        //实质调用了Drawable的setBounds方法,把当前View测量好的矩形区域顶点赋值给Drawable,说明接下来Drawable绘制区域与View大小相同。
        //该方法实质:mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
        setBackgroundBounds();
        //简单粗暴理解就是View要是能滑动,化到哪Drawable背景画到哪(其实就是每次滑动都按可见区域绘制Drawable,因为canvas已经被依据滑动平移了)
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            //这不就是Drawable的draw方法么,和前面分析的一样,最后View框架会调用我们Drawable的draw方法,传入的是当前View的canvas而已。
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }

最终调用了 mBackground 的 draw 方法完成绘制。
参考:
Android 应用层开发 Drawable 的一些叨叨絮

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,548评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,069评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,985评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,305评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,324评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,030评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,639评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,552评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,081评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,194评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,327评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,004评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,688评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,188评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,307评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,667评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,337评论 2 358

推荐阅读更多精彩内容