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地址



  • 2018-11-17 21:08:09

    Android 单个应用的内存限制

    获取Android手机应用内存大小 手机不同其性能也不同,手机本身内存可能有大有小,所以针对每个应用的内存大小也不相同。

  • 2018-11-17 21:11:14

    Android中App可分配内存的大小

     结果:(1)未设定属性android:largeheap = "true"时,可以申请到的最大内存空间为221M。      (2)设定属性android:largeheap = "true"时, 可以申请的最大内存空间为478M,是原来的两倍多一些。

  • 2018-11-17 22:44:53

    LeakCanary,30分钟从入门到精通

    在性能优化中,内存是一个不得不聊的话题;然而内存泄漏,显示已经成为内存优化的一个重量级的方向。当前流行的内存泄漏分析工具中,不得不提的就是LeakCanary框架;这是一个集成方便, 使用便捷,配置超级简单的框架,实现的功能却是极为强大的。

  • 2018-11-17 22:53:01

    gc for alloc freed

    在数组中选择图片然后显示,然后。。。logcat不断显示GC回收。最后程序黑屏。

  • 2018-11-17 23:25:38

    Android高效内存1:一张图片占用多少内存

    在做内存优化的时候,我们发现除了解决内存泄露问题,剩下的就只有想办法减少真实的内存占用。而在App中,大部分内存可能被我们图片占用了,所以减少图片的内存占用可以带来直接的效果。本文就简单介绍一张图片到底占用多少内存,我们先假设我们有一张图片时 600 * 800 的,图片占用空间大小假设是 100KB。另外本文知识点也是面试官喜欢问的一个点,看看自己的回答到什么级别了。

  • 2018-11-18 09:06:06

    Android子线程中更新UI的3种方法

    UI的更新必须在主线程中完成,所以不管上述那种方法,都是将更新UI的消息发送到了主线程的消息对象,让主线程做处理。

  • 2018-11-19 15:10:23

    nodemailer的使用,nodejs发送邮件

    前段时间有个很普通的项目需要发邮件的功能,而且是刚开始学nodejs,所以只是搜索了下用什么好的库能实现,就找到了nodemailer了。这篇文章主要是记录一下使用的过程和经验。

  • 2018-11-21 09:07:37

    Android为每个应用分配多少内存?

    熟悉Android内存分配机制的朋友都知道,Android为每个进程分配内存时,采用弹性的分配方式,即刚开始并不会给应用分配很多的内存,而是给每一个进程分配一个“够用”的内存大小。