CardView是用于实现卡片式布局效果的重要控件,由appcompat-v7库提供。实际上CardView也是一个FrameLayout,只是额外提供圆角和阴影等效果。
玩Android这么久了,几个应用都使用了CardView。今天在改一个项目时,留意到两个使用CardView的地方,具有类似的代码,但是实现的的效果不太一样,因此还是决定写篇文章来理清CardView开发过程中的坑。
在app的build.gradle文件中添加依赖。
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:cardview-v7:26.1.0'
在xml布局文件中使用CardView。
app:cardBackGroundColor 设置背景颜色
app:cardCornerRadius 设置圆角大小
app:cardElevation 设置z轴阴影效果
app:cardMaxElevation 设置z轴的最大高度值
app:contentPadding 内容距离边界的距离
app:contentPaddingXXX 设置局部的内边距,代替Padding,在CardView中设置padding不起作用
app:cardUseCompatPadding 是否使用CompatPadding,如果需要将CardView与其他视图对齐,在21以下,可以将此标记为true,在21以上平台,添加相同的填充值。
app:cardPreventCornerOverlap 是否裁剪边界以防止重叠
CardView常用属性就是这些,看似不多,实质很多坑。
CardView是在Android 5.0(Lollipop)也就是API 21时推出的控件,因此在Android 5.0前使用CardView要考虑适配的问题。
在Android 5.0之前的系统,CardView会自动添加一些额外的Padding控件来绘制阴影部分,导致了Android 5.0前后的不同系统上CardView的大小会不一样。为了解决这个问题,可以有如下方法:
①使用不同API版本的dimension资源匹配,也就是借助vales和values-21文件夹中不同的dimens.xml文件
②使用setUseCompadding熟悉,设置为ture,可以让CardView在不同系统中使用相同的padding值。
在API21前CradView不会裁剪内容来满足圆角的需求,而是使用了padding的代替方案,设置了内边距,免得裁剪内容。API21以后,CardView会自动裁剪内容元素以适应圆角。
在CardView中,有个app:cardPreventCornerOverlap 的属于,设置该属性为true后,CardView将不会添加Padding。
在Material Design中有一个叫触碰抬起的交互效果,也就是在z轴上发生位移,产生一个阴影加深的效果。lift-on-touch触碰抬起效果是通过改变translationZ值,沿着Z轴发生变化,就会导致阴影效果的变化:
因为在API21中有个新的属性android:stateListAnimator,可以在API21以上系统实现lift-on-touch效果。
在CardView属性中添加:
android:stateListAnimator="@drawable/lift_on_touch"
在drawable中添加lift-on-touch.xml文件
-
-
使用场景如下:
在Item中有个CheckBox选择,根据CheckBox的状态设置背景颜色,通过setbackDrawable设置。
if (task.getFinished()) {
holder.view.setBackgroundDrawable(holder.view.getResources().getDrawable(R.drawable.list_completed_touch_feedback));
} else {
holder.view.setBackgroundDrawable(holder.view.getResources().getDrawable(R.drawable.touch_feedback));
}
设置了背景后,导致了图一的效果:CardView的圆角、阴影等效果完全实现不了。经过测试,如图二所示:在CardView中设置了layout_margin后,部分CardView效果能显示。如图三所示,不设置backgroundDrawable和layout_margin后,可以显示CardView的圆角和阴影,但是无法根据CheckBox的状态高亮显示任务是否完成。
查看CardView的代码:
private final CardViewDelegate mCardViewDelegate = new CardViewDelegate() {
private Drawable mCardBackground;
@Override
public void setCardBackground(Drawable drawable) {
mCardBackground = drawable;
setBackgroundDrawable(drawable);
}
@Override
public boolean getUseCompatPadding() {
return CardView.this.getUseCompatPadding();
}
@Override
public boolean getPreventCornerOverlap() {
return CardView.this.getPreventCornerOverlap();
}
@Override
public void setShadowPadding(int left, int top, int right, int bottom) {
mShadowBounds.set(left, top, right, bottom);
CardView.super.setPadding(left + mContentPadding.left, top + mContentPadding.top,
right + mContentPadding.right, bottom + mContentPadding.bottom);
}
@Override
public void setMinWidthHeightInternal(int width, int height) {
if (width > mUserSetMinWidth) {
CardView.super.setMinimumWidth(width);
}
if (height > mUserSetMinHeight) {
CardView.super.setMinimumHeight(height);
}
}
@Override
public Drawable getCardBackground() {
return mCardBackground;
}
@Override
public View getCardView() {
return CardView.this;
}
};
可以看到,所有对于圆角背景颜色等操作,都是代理给mCardViewDelegate来完成的,而通过mCardViewDelegate获取背景Drawable是它保存的mCardBackground实例,一旦直接给CardView设置BackgroundDrawable后,这个drawable和mCardViewDelegate保存的mCardBackground就不再是同一个了,那么后续对背景的操作都没有效果,改变的只是mCardBackground的属性,而真正的cardView的属性没有改变。
为了实现上述效果,将CheckBox改变的背景颜色改为CardView内部中RelativeLayout的背景,从而不影响CardView的效果,也就是说CardView实现了圆角、阴影、lift-on-touch等效果后,再根据CheckBox的状态来改变RelativeLayout的背景颜色。
...