源碼系列篇之CardView

周五的節(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)CardViewApi21initialize方法:

@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方法,這里是將IMPLdrawable回傳過(guò)來(lái)了,該處也沒(méi)做什么,只是調(diào)用了CardViewsetBackgroundDrawable方法啊,也即是我們的ViewGroupsetBackgroundDrawable方法??催^(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é)得呢?

上面提到了CardViewApi21initialize方法中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ò)paddingmRadius縮小,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之上的代碼,具體低版本的代碼看下CardViewJellybeanMr1CardViewGingerbread

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 7,391評(píng)論 0 17
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,715評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,366評(píng)論 25 708
  • CardView 擴(kuò)展 FrameLayout 類并讓您能夠顯示卡片內(nèi)的信息,這些信息在整個(gè)平臺(tái)中擁有一致的呈現(xiàn)方...
    輕云時(shí)解被占用了閱讀 7,158評(píng)論 4 22
  • 十一月的北京,今年的風(fēng)有點(diǎn)冷 緊了緊大衣,扔掉了隨手的煙… 有時(shí)候,人總是還是向往美好;向往一種和自己原有生活不擦...
    馮英雄閱讀 322評(píng)論 0 0

友情鏈接更多精彩內(nèi)容