CardView简析

CardView是android L的新特性。卡片式容器,其特点是圆角和阴影效果,给扁平化元素增加了立体感。其实就是一种Material Design设计理念的代码实现。

先描述下属性:

cardBackgroundColor  整个卡片背景颜色
cardCornerRadius    卡片边角半径
cardElevation     卡片的阴影实际宽度,大家都知道 Material Design 就是引入了材质厚度的概念。实际就是通过投射阴影的原理实现这种视觉效果。
cardMaxElevation    阴影区域宽度,与实际上的padding有关。
cardUseCompatPadding     5.0以上有效,true,padding=阴影区域宽度+contentPadding
cardPreventCornerOverlap  设置是否圆角重叠。
contentPadding     cardview内容区边缘和其子view边缘的距离。padding=contentpadding+阴影宽度。setpadding在cardview里失效。
contentPaddingLeft    与上面同理
contentPaddingTop    与上面同理
contentPaddingRight     与上面同理
contentPaddingBottom    与上面同理

下面根据源码来分析一下:
public class CardView extends FrameLayout implements CardViewDelegate {
    private static final CardViewImpl IMPL;
    ...
}

继承至frameLayout,实现接口CardViewDelegate,实现cardview的 效果的处理逻辑就是在CardViewDelegate和CardViewImpl中实现得。
CardViewJellybeanMr1  21>api>=17实例化它
CardViewEclairMr1   api<17实例化它
CardViewApi21   api>=21实例化它
都是cardview的阴影和边角绘制工具,都继承CardViewImpl
CardViewJellybeanMr1  extends CardViewEclairMr1,唯一差别就是在初始化方法initStatic()中创建边框(RoundRectDrawableWithShadow)时,对canvas的方法调用的兼容。通俗点说就是对这个边框的画法不同。
android L对API的更新后,view本身就具备了绘制阴影的能力。view在绘制时,添加了对view背景的绘制drawBackground(Canvas canvas),其中引入的RenderNode是对绘制的信息以及本地接口进行封装,包括setElevation设置的elevation值最终也是通过它来调用本地接口进行绘制
cardview出现的目的,可以说是一个为了圆角和阴影效果向后兼容。

上面说了,为了兼容,cardview的各种属性设置都是通过CardViewImpl来实现。但为了解耦,CardViewDelegate就像它字面意思一样,被CardView委托,目的是为了向CardViewImpl提供必要的对象,比如背景Drawable。

它的实现并没有重写onLayout,那么他的布局方式跟framelayout一样。但onMeasure方法被重写:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (IMPL instanceof CardViewApi21 == false) {
            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            switch (widthMode) {
                case MeasureSpec.EXACTLY:
                case MeasureSpec.AT_MOST:
                    final int minWidth = (int) Math.ceil(IMPL.getMinWidth(this));
                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minWidth,
                            MeasureSpec.getSize(widthMeasureSpec)), widthMode);
                    break;
            }
 
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            switch (heightMode) {
                case MeasureSpec.EXACTLY:
                case MeasureSpec.AT_MOST:
                    final int minHeight = (int) Math.ceil(IMPL.getMinHeight(this));
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minHeight,
                            MeasureSpec.getSize(heightMeasureSpec)), heightMode);
                    break;
            }
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

其实可以看出,5.0及以上,尺寸的计算方式没变,但是5.0以下重新计算了尺寸。造成的结果是,5.0以下CardView的父容器设置wrap_content的时候,CardView即便没有子视图且它也是wrap_content,它也占据一定尺寸,因为有显示阴影和圆角边框;但在android L它的尺寸就会变成0,看不到cardview以及其阴影效果。

5.0以下版本,CardView的最小尺寸取决于IMPL.getMinWIdth(this),实际上调用的RoundRectDrawableWithShadow的getMinWidth:

float getMinWidth() {
        final float content = 2 *
                Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
        return content + (mRawMaxShadowSize + mInsetShadow) * 2;
    }

换言之,android L的CardView大小跟framelayout同理,但android L以前的版本,它的大小还跟设置的elevation和圆角半径有关。需要注意!

接下来说说这些属性需要注意的地方:

cardUseCompatPadding在5.0以下版本设置没有效果,5.0以上如果设置为false,阴影区域宽度不会加入到实际padding中。
cardElevation > cardMaxElevation时,以cardElevation为准,且阴影区域宽度=阴影实际宽度。
cardPreventCornerOverlap 当边角过大时,这个设置可能也会失效,如下图,设置了边角为300dp,可能实际宽度也就400dp左右

你可能感兴趣的:(android,material-design,view)