Android卡片布局(圆角阴影)的几种实现及分析

前言

在开发中,为了界面美观,圆角view和阴影效果是开发中经常遇到的UI场景,比如银行卡效果,卡片式itemView布局,Banner图等,开发中我们通过各种方式实现了这种效果,但是哪种方案最好呢,接下来本文将比较几种常见的圆角阴影布局实现,并从内存占用角度分析它们的优缺点.


卡片式布局.png

解决方案

在Android开发中,有以下几种解决方案:

  • 早期开发中,可以使用shape标签为LinearLayout、RelativeLayout等添加background实现圆角阴影效果;
  • 自定义View,包括开源的RoundAngleFrameLayout以及RCRelativeLayout等解决方案也能为我们实现这种效果;
  • CardView:Android5.0以后引入了cardView来帮助我们实现圆角阴影卡片式的效果,虽然也兼容5.0以下的版本,但是5.0以下cardview会有内边距,需要处理.

存在的问题

以上几种方案都能实现圆角阴影布局的实现,但是如果出现图片顶边展示的场景时,并不能能保证图片也是圆角的,经过验证,得出以下结论并例举了几种实现方式:

  • LinearLayout等添加shape无法保证图片圆角显示;
  • RoundAngleFrameLayout+shape可以实现,不需要特殊处理图片;
  • RCRelativeLayout可以实现,不需要特殊处理图片;
  • CardView在5.0以上也可以实现,5.0以下需要处理图片圆角;
  • cardView+自定义ImageView,不需要特殊处理图片.
    圆角阴影效果的几种实现.jpg

代码实现

  • cardView :cardCornerRadius控制圆角,cardElevation控制阴影大小,值得一提的是,cardview在5.0以下展示效果会有有不同,需要特殊处理:
    //xml
    <android.support.v7.widget.CardView
            android:id="@+id/cardview"
            android:layout_width="320dp"
            android:layout_height="140dp"
            android:layout_marginTop="20dp"
            app:cardCornerRadius="10dp"
            app:cardElevation="2dp"
            app:cardBackgroundColor="@color/white"
            >
            
    //Activity,adapter等
    //5.0以下处理cardview内边距,Glide配合形变transform 处理图片圆角
    if (Build.VERSION.SDK_INT < 21) {
        //去除5.0以下cardView内间距
        cardview.setUseCompatPadding(false);
        cardview.setPreventCornerOverlap(false);
        //圆角形变,除去左上左下两个角,设置右上右下两个角为圆角
        CornerTransform transform = new CornerTransform(this, dip2px(10));
        transform.setExceptCorner(true, false, true, false);
        Glide.with(this)
                .load(url3)
                .apply(RequestOptions.centerCropTransform())//先centerCrop再设置图片圆角,否则会覆盖圆角效果
                .apply(RequestOptions.bitmapTransform(transform))
                .into(cover);
    
        return;
    }

  • RCRelativeLayout:round_corner控制圆角大小,clip_background裁切背景(必须添加才能没有背景白边)
<com.transportmm.tsportapp.mvp.ui.dsh.cornerview.RCRelativeLayout
            android:id="@+id/rclayout"
            android:layout_width="320dp"
            android:layout_height="140dp"
            android:layout_marginTop="20dp"
            app:round_corner="10dp"
            app:clip_background="true"
            android:background="@color/white">
  • CardView+RCImageView:RCImageView round_corner_xxx_xxx 控制图片四个角的圆角大小
 <com.transportmm.tsportapp.mvp.ui.dsh.cornerview.RCImageView
                android:id="@+id/coverrci"
                android:layout_width="100dp"
                app:round_corner_top_right="10dp"
                app:round_corner_bottom_right="10dp"
                android:layout_height="match_parent"
                android:layout_gravity="right"/>
  • RoundAngleFrameLayout:radius控制圆角,必须添加shape保证没有背景白边
<com.transportmm.tsportapp.mvp.ui.dsh.cornerview.RoundAngleFrameLayout
            android:id="@+id/raflayout"
            android:layout_width="320dp"
            android:layout_height="140dp"
            android:layout_marginTop="20dp"
            android:background="@drawable/shape_roundcorner"
            app:radius="5dp">

内存使用分析

通过以上几种方式实现了圆角阴影图片顶边显示的效果,但是哪一种更好呢,或者说哪一种更加适合我们的开发呢,其实简单的一些静态页面展示我认为这几种方案都不错,但是当我们在RecycleView中进行滑动时,控件的性能变得特别重要,所以使用了RecycleView模拟了他们在开发中的使用:
在RecycleView页面,我使用了95张图片,加载10页,通过AndroidStudio自带内存检测工具记录了他们在页面加前和加载十次之后的稳定内存值,使用的手机是VIVO X21A;
下面是测试结果:

解决方案 页面加载前内存值 10次加载后内存值 消耗内存
cardView 104.37 191.66 87.29
RCRelativeLayout 105.41 214.79 109.38
CardView+RCImageView 105.33 202.46 97.13
RoundAngleFrameLayout 103.85 213.43 109.58

下面是过度绘制情况:

过度绘制.jpg

可以看出,无论是在过度绘制情况方面还是内存使用角度,cardview都是性能最好的,自定义RelativeLayout或者FrameLayout则在性能上较差

使用中的选择

自定义RelativeLayout或者FrameLayout的方式上面被验证为性能不够优秀,那么CardView的两种实现即5.0以上cardview直接实现和5.0以下cardview+glide特殊处理图片及cardview+RcImage两种那种更好呢,

  • cardview 分5.0和5.0以下分版本处理的好处是内存使用开销小,但是需要版本处理;
  • cardview+RCImageView的方式无需关注系统版本,但是内存开销较大;
    不妨看一下现在的安卓版本分布(2018年10月26号数据)
    版本分布.png

可以看出,5.0以下设备占比仅一成左后,所以5.0以下的代码执行占比较低,综合来看,选用cardView+glide5.0以下特殊处理图片的方式更好一些

源码分析

自定义cardView 打造一个通用的新闻标签视图 PictureTextCardView(V1.0)

自定义cardView
  • 支持图片顶部,靠左,靠右顶边展示
  • 提供各个子view的获取方法及动态设置各个子view的填充内容;
  • 除了cardview本身的属性以外,xml中额外支持添加以下属性;
    • app:img_width="xxdp"和app:img_height="xxdp",图片的宽高;默认100dp宽度和150dp高度;(图片top时宽度match_parent)
    • text_margin 控制文本的外边距,默认10dp;
    • cardCornerRadius控制圆角大小,默认5dp;
    • img_gravity(left,right,top)控制图片位置 ,默认靠右显示;
    • titleColor 标题文字颜色
    • contentColor 副标题文字颜色
    • remarkColor 补充文字颜色

继承自cardView

public class PictureTextCardView extends CardView implements ICardInterface{
    ...
    ...
}

初始化属性

    public PictureTextCardView(Context context) {
        this(context, null);
    }

    public PictureTextCardView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PictureTextCardView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        if (attrs != null) {
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PictureTextCardView);
            radius = ta.getDimension(R.styleable.PictureTextCardView_cardCornerRadius, dip2px(5));
            imgHeight = ta.getDimension(R.styleable.PictureTextCardView_img_height, dip2px(150));
            imgWidth = ta.getDimension(R.styleable.PictureTextCardView_img_width, dip2px(100));
            textMargin = ta.getDimension(R.styleable.PictureTextCardView_text_marign, dip2px(10));
            titleColor = ta.getColor(R.styleable.PictureTextCardView_title_color, Color.parseColor("#000000"));
            contentColor = ta.getColor(R.styleable.PictureTextCardView_content_color,Color.parseColor("#666666"));
            remarkColor = ta.getColor(R.styleable.PictureTextCardView_remark_color,Color.parseColor("#999999"));
            imgGravity = ta.getInt(R.styleable.PictureTextCardView_img_gravity,0);
            ta.recycle();
        }

        init(context);

    }

提供各个子view的获取方法及一些设置

public interface  ICardInterface{
    /* 获得控件 */
    TextView getTitle();
    TextView getContents();
    TextView getRemark();
    ImageView getImageView();

    /* 可见/不可见状态 */
    void setTitleVisible(boolean visible);
    void setContentsVisible(boolean visible);
    void setRemarkVisible(boolean visible);
    void setImageVisible(boolean visible);

    /* 设置控件填充内容 */
    void setImageView(Context context,String str);
    void setTitleText(String str);
    void setContentsText(String str);
    void setRemarkText(String str);
    void setTitle(String title,int textSize,int color);
    void setContent(String content,int textSize,int color);
    void setRemark(String remark,int textSize,int color);

}

使用同cardView一样 建议通过xml控制一些布局属性

<com.dsh.mydemos.myview.PictureTextCardView
            android:id="@+id/ptcardview_left"
            android:layout_width="320dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            app:cardCornerRadius="10dp"
            app:text_marign="10dp"
            app:img_width="100dp"
            app:img_height="150dp"
            app:img_gravity="left"
            app:cardElevation="2dp"
            app:cardBackgroundColor="@color/white"
            android:layout_marginBottom="10dp"
            />
/>

配合代码填充布局内容(如果xml中设置了img_gravity属性,setViewsLayout方法不要执行,防止view重绘)

    //图片居左展示
    ptcardviewLeft.setViewsLayout(PictureTextCardView.POSITION_LEFT);
    ptcardviewLeft.setTitle("自定义cardview:PictureTextCardView",24,getResources().getColor(R.color.red));
    ptcardviewLeft.setContent("图片靠左展示",20,getResources().getColor(R.color.blue));
    ptcardviewLeft.setRemark("2018-11-23",16,getResources().getColor(R.color.gray));
    ptcardviewRight.setImageView(this,url4);
        

内存使用分析:

解决方案 页面加载前内存值 10次加载后内存值 消耗内存
cardView 104.37 183.20 78.83
RCRelativeLayout 105.41 214.79 109.38
CardView+RCImageView 105.33 202.46 97.13
RoundAngleFrameLayout 103.85 213.43 109.58
PictureTextCardView 104.34 191.15 86.81

可以看出,PictureTextCardView性能介于原生CardView和CardView+RCImageView之间,过度绘制表现方面跟其他自定义view一致.

项目demo地址

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

推荐阅读更多精彩内容