周五的节奏真的心情爽啊,看看书啊,看看源码啊,时间都这样毫不经意间过去了,爽啦啦!题外话就不说那么多了啊。下面进入正题,带你分析CardView
这种带阴影、圆角的View
是怎么一步步的实现的。
来看看CardView
构造器吧:
public CardView(Context context) {
super(context);
initialize(context, null, 0);
}
public CardView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs, 0);
}
public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs, defStyleAttr);
}
哈哈哈,还是逃不过自定义View的法则啊。再去看看initialize
方法吧:
private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
R.style.CardView);
ColorStateList backgroundColor;
//看xml有没有cardBackgroundColor属性
if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
} else {
// There isn't one set, so we'll compute one based on the
//找colorBackground属性
theme
final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
final int themeColorBackground = aa.getColor(0, 0);
aa.recycle();
// If the theme colorBackground is light, use our own light color, otherwise dark
final float[] hsv = new float[3];
Color.colorToHSV(themeColorBackground, hsv);
//没有就根据这两个颜
backgroundColor = ColorStateList.valueOf(hsv[2] > 0.5f
? getResources().getColor(R.color.cardview_light_background)
: getResources().getColor(R.color.cardview_dark_background));
}
float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0);
mCompatPadding = a.getBoolean(R.styleable.CardView_cardUseCompatPadding, false);
mPreventCornerOverlap = a.getBoolean(R.styleable.CardView_cardPreventCornerOverlap, true);
int defaultPadding = a.getDimensionPixelSize(R.styleable.CardView_contentPadding, 0);
mContentPadding.left = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingLeft,
defaultPadding);
mContentPadding.top = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingTop,
defaultPadding);
mContentPadding.right = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingRight,
defaultPadding);
mContentPadding.bottom = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingBottom,
defaultPadding);
if (elevation > maxElevation) {
maxElevation = elevation;
}
mUserSetMinWidth = a.getDimensionPixelSize(R.styleable.CardView_android_minWidth, 0);
mUserSetMinHeight = a.getDimensionPixelSize(R.styleable.CardView_android_minHeight, 0);
a.recycle();
//这个地方很重要,这里我们重点看
IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
elevation, maxElevation);
}
在这里看到有一个变量IMPL
,那去看看该变量是在哪生成的吧:
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();
}
这里有几个版本的分支,根据不同的版本生成不同的CardViewImpl
,其实看设计模式的筒子们。这里是工厂模式的一种,根据不同的情况生产不同的CardViewImpl(接口)
,那咋们先去看高版本的吧:
CardViewApi21
实现类,还是照常跟进CardViewApi21
的initialize
方法:
@Override
public void initialize(CardViewDelegate cardView, Context context,
ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
//生成一个RoundRectDrawable,看名字就知道是个带圆角矩形的drawable啊
final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
cardView.setCardBackground(background);
View view = cardView.getCardView();
view.setClipToOutline(true);
view.setElevation(elevation);
setMaxElevation(cardView, maxElevation);
}
这里cardView是什么东东啊,别急,咱们回到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;
}
};
可以看到它是CardView
内部的一个final类型的内部类啊,这里主要是来看下setCardBackground
方法,这里是将IMPL
中drawable
回传过来了,该处也没做什么,只是调用了CardView
的setBackgroundDrawable
方法啊,也即是我们的ViewGroup
的setBackgroundDrawable
方法。看过设计模式的朋友其实不难发现,mCardViewDelegate
内部类是不是Builder
的设计模式呢,Builder
设计模式最大的特点就是将外部类的一些行为封装到内部类中。其实如果外部类的属性不是很多的话,也没必要封装成Builder
模式。你们觉得呢?
上面提到了CardViewApi21
的initialize
方法中RoundRectDrawable
变量,也提到了它是一个Drawable
,那咱们看看是怎么自定义一个Drawable
吧:
1.对RoundRectDrawable
的大小进行修正啊:
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
updateBounds(bounds);
}
private void updateBounds(Rect bounds) {
if (bounds == null) {
bounds = getBounds();
}
mBoundsF.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
mBoundsI.set(bounds);
//默认是true的
if (mInsetForPadding) {
float vInset = calculateVerticalPadding(mPadding, mRadius, mInsetForRadius);
float hInset = calculateHorizontalPadding(mPadding, mRadius, mInsetForRadius);
mBoundsI.inset((int) Math.ceil(hInset), (int) Math.ceil(vInset));
// to make sure they have same bounds.
mBoundsF.set(mBoundsI);
}
}
上面对mBoundsI
通过padding
和mRadius
缩小,mBoundsF
同样也是缩小。
下面是对RoundRectDrawable
的绘制部分了,绘制的话,就简单了:
@Override
public void draw(Canvas canvas) {
final Paint paint = mPaint;
final boolean clearColorFilter;
if (mTintFilter != null && paint.getColorFilter() == null) {
//画笔的过滤
paint.setColorFilter(mTintFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
}
//绘制带圆角的矩形
canvas.drawRoundRect(mBoundsF, mRadius, mRadius, paint);
if (clearColorFilter) {
paint.setColorFilter(null);
}
}
这里只是分析21之上的代码,具体低版本的代码看下CardViewJellybeanMr1
和CardViewGingerbread