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左右
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。