参考
常用属性
CardView 的阴影的实现方式是通过为 CardView 设置 backgroundDrawable,drawable 自身带有阴影部分。
cardPreventCornerOverlap
是否防止角落重叠。默认时该属性值为 true。在 5.0 以下的手机将该值设置 true 和 false 的效果分别如下:

其中的外层红色边框就是为 CardView 设置的背景 drawable。
从中可以看出:
-
为 true 时,内容不会覆盖住圆角。因为 drawable 会自动添加 padding 值——该值通过 cornerRadius 进行计算而得——这导致边框与内容之意有间隔。
-
为 false 时,内部内容会覆盖在圆角上。因为不会为 drawable 添加 padding 值;
-
内容是否覆盖住圆角,主要是通过为 drawable 添加 padding 实现,而 cardPreventCornerOverlap 则控制着是否会为该 drawable 添加 padding ,true 时添加,false 不添加。
cardUseCompatPadding
设置内边距,控制 API 21及以上的版本是否和之前的版本具有一样的计算方式。默认值为 false。
在 API 21 之前的版本中,为了避免内容与覆盖住圆角,CardView 会添加 padding 。但 API 21 及以后版本中,系统会对内容进行裁剪以达到不覆盖圆角的效果。因此,同样的设置在 21 以后和以前的系统中显示的效果不一样。
-
为 true 时,在 21 及以上的版本中,也会类似于 21 以前的版本一样,添加一个 padding 值。
-
为 false 时,不会添加 padding 值。
常用类
-
CardView : 主类。外界在使用时,就使用的是该类。
-
CardViewImpl : 一个接口类,会根据手机版本的不同,采用不同的方式为 CardView 设置阴影效果
-
CardViewGingerbread、CardViewJellybeanMr1 与 CardViewApi21:CardViewImpl 的实现类。其中 CardViewJellybeanMr1 继承于 CardViewGingerbread,但只重写了 initStatic() 方法。
-
CardViewDelegate :CardView 代理类,用于 CardView 与 CardViewImple 之间通信,其具体子类在 CardView 内部。
-
RoundRectDrawableWithShadow 与 RoundRectDrawable:都继承于 Drawable,CardViewImpl 不同的子类根据自己的需要生成两个类的实例,然后设置为 CardView 的 backgroundDrawable。
-
CardViewApi21 使用 RoundRectDrawable
-
CardViewGingerbread 与 CardViewJellybeanMr1 使用RoundRectDrawableWithShadow 。
-
思路解析
静态代码块
CardView 首先在静态代码块中根据版本初始化 CardViewImpl 对象,并赋值给 IMPL 属性,同时调用 CardViewImpl #initStatic() 方法:
private static final CardViewImpl IMPL;
static {
if (Build.VERSION.SDK_INT >= 21) {
IMPL = new CardViewApi21();
} else if (Build.VERSION.SDK_INT >= 17) {
IMPL = new CardViewJellybeanMr1();
} else {
IMPL = new CardViewGingerbread();
}
IMPL.initStatic();
}
其中 CardViewApi21#initStatic() 方法是空实现,不进行任何操作;而剩余两个会为
RoundRectDrawableWithShadow#sRoundRectHelper 属性进行赋值。
构造函数
CardView 的构造函数中会调用 CardView#initialize() 方法 。该方法会调用 CardViewImpl#initialize() 方法:
CardViewGingerbread#initialize() 如下:
@Override
public void initialize(CardViewDelegate cardView, Context context,
ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
elevation, maxElevation);
background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
// 其实际上调用了 CardView#setBackgroundDrawable() 方法
cardView.setCardBackground(background);
updatePadding(cardView);
}
private RoundRectDrawableWithShadow createBackground(Context context,
ColorStateList backgroundColor, float radius, float elevation,
float maxElevation) {
return new RoundRectDrawableWithShadow(context.getResources(), backgroundColor, radius,
elevation, maxElevation);
}
可以看出,只是创建了一个 RoundRectDrawableWithShadow 对象,并设置给 CardView作为 background。
CardViewApi21的initialize() 如下:
public void initialize(CardViewDelegate cardView, Context context,
ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
// 也是生成一个 drawable,然后设置给 CardView 作为 backgroundDrawable
cardView.setCardBackground(background);
View view = cardView.getCardView();
view.setClipToOutline(true); // api 21以上才有的功能
view.setElevation(elevation);// api 21 以上才有的功能
setMaxElevation(cardView, maxElevation);
}
因为 API 21 以上,系统已经现实了 elevation 属性,所以不需要像 21 以前一样通过画出阴影模拟高度,而是可以简单的调用了 View#setElevation() 方法。
网友评论