TV开发之如何绘制真正的圆角布局控件

一开始是这样的,热门的控件(LabeView),是三角形的,

图片是圆角的.

然后下面的TextView带背景颜色的是长方形的。

都不是圆角.

 

【如何绘制真正的圆角矩形控件?】

    一般 ImageView 使用 OnDraw,虽然能弄成圆角,比如在 FrameLayout( 就是继承ViewGroup的控件)下,它显示是正常的圆角。但是,如果再放一个文本(设置背景颜色)或者按钮,layout_width 占满 FrameLayout控件的话,你就发现,只是ImageView圆角了,Button或者文本 超出了ImageView的圆角,FrameLayout 并不是真正的圆角.

    这样看来,这并不是我想要的效果。

    继续分析源码.

【绘制过程分析】

ImageView.java

@Override 

protected void onDraw(Canvas canvas) {

    super.onDraw(canvas); 

    if (mDrawable == null) {

        return; // couldn't resolve the URI

    }

    ... ...

    mDrawable.draw(canvas);

    ... ...

}

如果你使用 setBackgroundDrawable 设置ImageView,那么mDrawable就是null.

setBackgroundDrawable 是设置的背景,具体看 View.java.

public ImageView(Context context, AttributeSet attrs, int defStyle) {

    ... ...

    Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);

    if (d != null) {

        setImageDrawable(d);

    ... ...

}

以上代码发现 src="@dra..."

如果你使用 setImageDrawable 设置ImageView,那么就

将 mDrawable 绘制出来, 是我们设置ImageView 图片或者资源图片时候赋的.

从上面的代码也分析的出来,ImageView的背景和src还有有区别的噢.

有何种区别,继续看看源码.

FrameLayout.java

FrameLayout 并没有重写 onDraw 函数, 看看继承的ViewGroup.

public void setWillNotDraw(boolean willNotDraw) {

    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);

}

ViewGroup.java

ViewGroup 也没有重写 onDraw函数,再看看继承的View.

View.java

View 找到了 onDraw 函数.

protected void onDraw(Canvas canvas) {

}

不过它只是一个空函数,什么事情都没有做.

来找找看,谁调用了 onDraw吧 ,不错就是 draw 函数.

这是draw函数给出的注释。

       /*

         * Draw traversal performs several drawing steps which must be executed

         * in the appropriate order:

         *

         *      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)

         */

public void draw(Canvas canvas) {

    // Step 1, draw the background, if needed :  第一步,绘制背景.

    ... ...

    if (!dirtyOpaque) { // (A4)

        final Drawable background = mBackground; // (A5)

    ... ...

    // Step 3, draw the content:绘制本身的内容(一般继承viewGroup无).

    if (!dirtyOpaque) onDraw(canvas);

    // Step 4, draw the children:绘制子控件.

    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers:渐变

    ... ...

    // Step 6, draw decorations (scrollbars):绘制滚动条.

    onDrawScrollBars(canvas);

}

如果不需要绘制渐变,则跳过第2和5步。

A4:dirtyOpaque 表示 dirty 区是否是不透明 为 True, 透明为 false【一般Android的几乎都是透明的,都为false】.

        如果View系统不支持Alpha通道,不需要绘制背景,因为视图本身会占满整个区域,背景会完全被挡住。

A5**:mBackground 就是设置背景时候传入的.**

dispatchDraw 内部绘制子控件,也是调用的childView.draw(... ...


通过上面的分析,我们大概知道了流程。

android是如何绘制控件,以及子控件出来的。

我们又知道clipPath这个函数,也知道 Path 这个函数.

然后就可以开始着手写代码了.


为了实现真正的圆角,我重写了 draw.

@Override

public void draw(Canvas canvas) {

        // 设置圆角.

        canvas.save();

        if (mIsDrawShape) {

            canvas.clipPath(getShapePath());

        }

        super.draw(canvas);

        canvas.restore();

}

clipPath 可以让绘制的内容只在限制的区域内显示.

那么 super.draw 绘制的控件内容,只能在 clipPath的区域显示了.

如果你的区域是一个圆角的矩形,哈哈哈.

现在无论放什么,都OK了.

要了解更多,https://github.com/FrozenFreeFall/Android-tv-widget 

下载源码 查看 ReflectItemView.java吧.

来看看最后的效果吧.