美文网首页Android知识Android开发Android技术知识
【Android Drawable】一、Drawable 类

【Android Drawable】一、Drawable 类

作者: 秀花123 | 来源:发表于2017-04-23 11:16 被阅读999次
  • 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 的一些叨叨絮

相关文章

网友评论

    本文标题:【Android Drawable】一、Drawable 类

    本文链接:https://www.haomeiwen.com/subject/qggtottx.html