Android 踩坑记定义控件性能规范

为什么 Shape 不起作用

2016-05-07  本文已影响2222人  南腔北调集合
基础知识

Android里,我们经常会用shape去定义View的形状。如下是在xml里定义一个简单shape的代码:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <solid android:color="#00FF00" />
    <corners
        android:bottomLeftRadius="10dp"
        android:bottomRightRadius="10dp"
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp" />
</shape>

使用时,将它设置在view 的背景上,有的同学这样问,如下使用shape,为什么不起作用?
第一例, 不起作用:

 <ImageView
    android:id="@+id/img_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/round_corner_rectangle"
    android:scaleType="fitXY"
    android:src="@drawable/img"/>

第二例,不起作用,看不到圆角效果

<FrameLayout    
   android:layout_width="wrap_content"   
   android:layout_height="wrap_content"    
   android:background="@drawable/round_corner_rectangle">    
   <TextView        
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content" />
</FrameLayout>

第三例,TextView 有圆角,正常

<TextView
    android:background="@drawable/round_corner_rectangle"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content" />

首先,shape是什么?

以圆角矩形<shape>为例,其中 shape 标签在解析后对应于 GradientDrawable类(注意不是ShapeDrawable),即在xml里定义<shape>,运行期间会生成对应的GradientDrawable对象,同时传入xml里定义的圆角属性值。

查看GradientDrawable 源码,将看到在xml里<shape>设定的各个角圆角弧度,被传入并保存在数组mRadiusArray:

private void updateDrawableCorners(TypedArray a) {
......
            setCornerRadii(new float[] {
                    topLeftRadius, topLeftRadius,
                    topRightRadius, topRightRadius,
                    bottomRightRadius, bottomRightRadius,
                    bottomLeftRadius, bottomLeftRadius
            });
}

所以,设定shape标签即设生成drawable 对象。

继续查看GradientDrawable源码,其绘制过程是基本图形绘制,涉及:Canvas、Path、 Paint。其中path 定义封闭形状,并设定好圆角,paint 画笔设置颜色等,最终在canvas 画布上画出图形,步骤如下:

    private void buildPathIfDirty() {
        final GradientState st = mGradientState;
        if (mPathIsDirty) {
            ensureValidRect();
            mPath.reset();
            mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
            mPathIsDirty = false;
        }
    }
switch (st.mShape) {
            case RECTANGLE:
                if (st.mRadiusArray != null) {
                    buildPathIfDirty();
                    // 画线及填充
                    canvas.drawPath(mPath, mFillPaint);
                    if (haveStroke) {
                        // 描边
                        canvas.drawPath(mPath, mStrokePaint);
                    }
                }

以上分析了定义一个 圆角矩形时,GradientDrawable 将在 canvas上自我绘制的过程。

View设置各种drawable为背景,怎么起作用的?

以第三例为例,设置TextView的background,先了解以下基础:

  1. TextView 继承自View基类
  2. 设置各种背景都将转化为drawable对象
  3. View 里有一个公用画布 canvas

查看View源码,View 里背景和内容的绘制步骤:

  1. 首先绘制底部 background
  2. 绘制具体的内容,通过onDraw 通知继承View类子类绘制具体内容。
   
 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) 的源码及注释 16153 行:

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

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // 接着,绘制内容,dispatchDraw 通知该 View 上的子结点进行自我绘制。

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // we're done...
            return;
        }

绘制Background 的过程,简化一下即为drawable 直接调用自身 draw 方法,在同一画布上进行绘制。

  private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
…

            background.draw(canvas);

    }

以上分析解释了View绘制背景和内容的区别,同时,也顺便可以解释Imageview 的 background 和 src 的不同之处:

  1. 本质上无区别,都是各种不同类型的drawable,本质上都通过自身的draw方法在canvas上绘制。
  2. background 是背景,首先会在View基类的draw里被绘制。src 是内容,随后在子类ImageView的 ondraw 里被绘制。

这里验证一下,如果将 android:background=“@null” 会发生什么

  <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@null"
        android:text="此处背景透明"
        android:layout_alignParentBottom="true"/>

会发现,将会取和设置 transparent 也是一样透明的效果。

将会看到,是和设置背景为 transparent 一样透明的效果。

回头来看文中开头处提到的shape不起作用的例子:
第二例
其中TextView为外部 FrameLayout 的子结点,外部FrameLayout设置的的标签与TextView无关,TextView的绘制范围仅宽高受FrameLayout的影响,标签只代表了一个图像,不影响子节点。
解决方法:
FrameLayout 设置padding, 或者TextView设置 margin,padding要大于等于sqrt(r),其中r为所设圆角半径值,并且两者背景颜色一致。为何为sqrt(r),请自行画图计算。

第一例
ImageView 设置圆角为何不起作用。参见 ImageView 里源码,src 对应 mDrawable,绘制时,将覆盖底层 background,即设置了圆角的drawable。

    private void updateDrawable(Drawable d) {
……
       mDrawable = d;
}

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
…
            mDrawable.draw(canvas);

    }

解决办法
那该怎么给ImageView 画圆角呢?办法是通过paint 的SRC_IN模式:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

SRC_IN 模式设置后,将两个绘制的效果叠加后取交集后展现,比如:第一个绘制的是个圆形,第二个绘制的是个Bitmap,于是交集为圆形,就实现了圆形图片效果。

而且,android Tint 也是靠 SRC_IN 来自动变成我们想要的背景颜色,来达到Material Design的效果。

上一篇下一篇

猜你喜欢

热点阅读