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

2020-03-21 00:11:38

下面讲了三种实现圆角阴影的方案,因为现在手机安卓版本都不低了,所以我是直接采用了cardView都方案,简单,方便

参考地址 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次加载后内存值消耗内存
cardView104.37191.6687.29
RCRelativeLayout105.41214.79109.38
CardView+RCImageView105.33202.4697.13
RoundAngleFrameLayout103.85213.43109.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次加载后内存值消耗内存
cardView104.37183.2078.83
RCRelativeLayout105.41214.79109.38
CardView+RCImageView105.33202.4697.13
RoundAngleFrameLayout103.85213.43109.58
PictureTextCardView104.34191.1586.81

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

项目demo地址



  • 2020-01-03 00:36:00

    break和continue详解for循环

    1. break:直接跳出当前循环体(while、for、do while)或程序块(switch)。其中switch case执行时,一定会先进行匹配,匹配成功返回当前case的值,再根据是否有break,判断是否继续输出,或是跳出判断(可参考switch的介绍)。 2. continue:不再执行循环体中continue语句之后的代码,直接进行下一次循环。

  • 2020-01-04 08:14:56

    input上传图片,获取图片上传尺寸

    onchange触发,获取当前file对象(这里可以获取图片的大小、类型、修改时间等) reader去读取文件 塞到页面,获取图片的宽高 移出图片节点

  • 2020-01-04 08:19:28

    flex 布局子内容p元素被撑开

    父元素 flex 布局,子元素有一行文字,将其设置为不换行隐藏后显示省略号,但实际并不是想象的那样,而导致布局变形。改怎么办?