《转》为什么 Shape 不起作用, android的倒角不起作用

https://www.jianshu.com/p/90a264d5c610

 

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 对象。

  • Drawable 可以理解为:二维平面上,能画出来的图形图像,如:BitmapDrawable, ShapeDrawable, PictureDrawable, LayerDrawable, 等等派生类。Drawable 都有自己的draw() 方法,来操纵 canvas
  • canvas 画布是透明的,可以在上面涂抹任意形状,并填充上颜色、渐变等,即 Drawable

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

  • path定义封闭形状代码如下:
    private void buildPathIfDirty() {
        final GradientState st = mGradientState;
        if (mPathIsDirty) {
            ensureValidRect();
            mPath.reset();
            mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
            mPathIsDirty = false;
        }
    }
  • 画线及填充,558行:
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的效果。



作者:南腔北调集合
链接:https://www.jianshu.com/p/90a264d5c610
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发表。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值