周五的節(jié)奏真的心情爽啊,看看書啊,看看源碼啊,時(shí)間都這樣毫不經(jīng)意間過(guò)去了,爽啦啦!題外話就不說(shuō)那么多了啊。下面進(jìn)入正題,帶你分析CardView這種帶陰影、圓角的View是怎么一步步的實(shí)現(xiàn)的。
來(lái)看看CardView構(gòu)造器吧:
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);
}
哈哈哈,還是逃不過(guò)自定義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有沒(méi)有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);
//沒(méi)有就根據(jù)這兩個(gè)顏
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();
//這個(gè)地方很重要,這里我們重點(diǎn)看
IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
elevation, maxElevation);
}
在這里看到有一個(gè)變量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();
}
這里有幾個(gè)版本的分支,根據(jù)不同的版本生成不同的CardViewImpl,其實(shí)看設(shè)計(jì)模式的筒子們。這里是工廠模式的一種,根據(jù)不同的情況生產(chǎn)不同的CardViewImpl(接口),那咋們先去看高版本的吧:
CardViewApi21實(shí)現(xiàn)類,還是照常跟進(jìn)CardViewApi21的initialize方法:
@Override
public void initialize(CardViewDelegate cardView, Context context,
ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
//生成一個(gè)RoundRectDrawable,看名字就知道是個(gè)帶圓角矩形的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內(nèi)部的一個(gè)final類型的內(nèi)部類啊,這里主要是來(lái)看下setCardBackground方法,這里是將IMPL中drawable回傳過(guò)來(lái)了,該處也沒(méi)做什么,只是調(diào)用了CardView的setBackgroundDrawable方法啊,也即是我們的ViewGroup的setBackgroundDrawable方法??催^(guò)設(shè)計(jì)模式的朋友其實(shí)不難發(fā)現(xiàn),mCardViewDelegate內(nèi)部類是不是Builder的設(shè)計(jì)模式呢,Builder設(shè)計(jì)模式最大的特點(diǎn)就是將外部類的一些行為封裝到內(nèi)部類中。其實(shí)如果外部類的屬性不是很多的話,也沒(méi)必要封裝成Builder模式。你們覺(jué)得呢?
上面提到了CardViewApi21的initialize方法中RoundRectDrawable變量,也提到了它是一個(gè)Drawable,那咱們看看是怎么自定義一個(gè)Drawable吧:
1.對(duì)RoundRectDrawable的大小進(jìn)行修正啊:
@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);
//默認(rèn)是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);
}
}
上面對(duì)mBoundsI通過(guò)padding和mRadius縮小,mBoundsF同樣也是縮小。
下面是對(duì)RoundRectDrawable的繪制部分了,繪制的話,就簡(jiǎn)單了:
@Override
public void draw(Canvas canvas) {
final Paint paint = mPaint;
final boolean clearColorFilter;
if (mTintFilter != null && paint.getColorFilter() == null) {
//畫筆的過(guò)濾
paint.setColorFilter(mTintFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
}
//繪制帶圓角的矩形
canvas.drawRoundRect(mBoundsF, mRadius, mRadius, paint);
if (clearColorFilter) {
paint.setColorFilter(null);
}
}
這里只是分析21之上的代碼,具體低版本的代碼看下CardViewJellybeanMr1和CardViewGingerbread