1. 程式人生 > >Android CardView全解析(一)

Android CardView全解析(一)

**前言:前面寫了一部落格 Android訂單流程view(超簡單!)
其中用到了(CardView),之前也用過,很爽!!所以對於CardView其實很早就想去研究一下它了,於是就有了這篇部落格了,寫這篇部落格的目的呢,主要是屬性一下google的工程師們是怎麼封裝一個控制元件的,整個過程下來,還是學習到了挺多的知識的,於是打算把我學到的一些東西分享出來,算是當作學習筆記了。**

那麼CardView是幹什麼的呢?
想必有些小夥伴對於它也並不陌生。CardView是v7包中的元件(ViewGroup),主要用來設定佈局的邊框為圓角、z軸的偏移量(這個是5.0以後才有的概念,也就是陰影的效果)。看一下它的效果:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height
="match_parent" tools:context="com.yasin.round.MainActivity" android:orientation="vertical" >
<com.yasin.round.card.RoundView android:layout_width="match_parent" android:layout_height="wrap_content" app:backgroundColor="#ff00" app:conerRadius="10dp"
app:shadowSize="10dp" app:shadowStartColor="#33000000" app:shadowEndColor="#22000000" >
<RelativeLayout android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="200dp" > <TextView android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="RoundView" /> </RelativeLayout> </com.yasin.round.card.RoundView> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="10dp" app:cardBackgroundColor="#ff00" app:cardElevation="10dp" app:cardCornerRadius="10dp" > <TextView android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="v7(CardView)" /> </android.support.v7.widget.CardView> </LinearLayout>

上面的RoundView是我們照著CardView實現出來的。

執行效果:

這裡寫圖片描述

當然,系統的CardView是沒辦法修改陰影的顏色的,我們實現的RoundView對它進行了一些小小的修改,使其能支援顏色修改,當然,我也就實現了大體的部分,本篇roundview只是做演示的demo,小夥伴可不要直接拖到專案中用啊~~

廢話不多說,我們要開擼了~~~小夥伴跟緊啦!

首先明確下我們的目標:

1、可以實現圓角功能
2、可以設定陰影(z軸的偏移)

看起來很簡單,但是我們的程式碼還是比較多的,不過沒關係,我們重點看一下大神們到底是咋寫程式碼的。

首先我們定義一下我們需要用到的屬性:

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RoundView">
        <!--背景顏色-->
        <attr name="backgroundColor" format="color"></attr>
        <!--圓角的半徑-->
        <attr name="conerRadius" format="dimension"></attr>
        <!--z軸的偏移量,(陰影的大小)-->
        <attr name="shadowSize" format="dimension"></attr>
        <!--陰影的起始顏色-->
        <attr name="shadowStartColor" format="color"></attr>
        <!--陰影的結束顏色-->
        <attr name="shadowEndColor" format="color"></attr>
    </declare-styleable>
</resources>

定義好了我們的自定義屬性後,我們接下來建立一個view叫RoundView去繼承FrameLayout,然後初始化我們的控制元件獲取我們的自定義屬性:

public class RoundView extends FrameLayout {
    /**
     * 陰影的起始顏色
     */
    private int mShadowStartColor;
    /**
     * 陰影的結束顏色
     */
    private int mShadowEndColor;
    /**
     * 圓角半徑
     */
    private float mRadius;
    /**
     * 陰影的起始顏色
     */
    private float mElevation;
    /**
     * 控制元件背景顏色
     */
    private ColorStateList mBackgroundColor;
    public RoundView(Context context) {
        super(context);
        initialize(context, null, 0);
    }

    public RoundView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize(context, attrs, 0);
    }

    public RoundView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize(context, attrs, defStyleAttr);
    }
        private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {

    }
}

然後去獲取我們的自定義屬性:

private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundView, defStyleAttr, 0);
        //判斷是否有背景顏色
        if (a.hasValue(R.styleable.RoundView_backgroundColor)) {
            mBackgroundColor = a.getColorStateList(R.styleable.RoundView_backgroundColor);
        } else {
            //獲取系統的背景顏色
            TypedArray aa = context.obtainStyledAttributes(new int[]{android.R.attr.colorBackground});
            //獲取系統的背景顏色
            int themeBackgroundColor = aa.getColor(0, 0);
            //獲取背景顏色的hvs值(h色彩、s飽和度、v顏色值)
            float[] hsv = new float[3];
            Color.colorToHSV(themeBackgroundColor, hsv);
            //當飽和度>0.5的時候,我們獲取自定義的cardview_dark_background顏色
            if (hsv[2] > 0.5) {
                mBackgroundColor = ColorStateList.valueOf(getResources().getColor(R.color.cardview_dark_background));
            } else {
                mBackgroundColor = ColorStateList.valueOf(getResources().getColor(R.color.cardview_dark_background));
            }
            aa.recycle();
        }
        mRadius = a.getDimensionPixelSize(R.styleable.RoundView_conerRadius, 0);
        mElevation = a.getDimensionPixelSize(R.styleable.RoundView_shadowSize, 0);
        mShadowStartColor = a.getColor(R.styleable.RoundView_shadowStartColor, getResources().getColor(R.color.cardview_shadow_start_color));
        mShadowEndColor = a.getColor(R.styleable.RoundView_shadowEndColor, getResources().getColor(R.color.cardview_shadow_end_color));
        a.recycle();

好啦!寫到這裡我們才完成了最基本的部分,接下來我們需要考慮(相容android不同版本、提高效能、程式碼上的高內聚低耦合)等一系列因素了。

考慮到不同的android版本實現圓角、陰影的方式不同的因素,我們應該把具體實現跟我們的roundview分離開來(整個下來有點mvp模式的感覺,你也可以理解為mvp模式吧)。

我們程式碼中分為三個部分:

  1. 建立IRoundView來處理roundview的業務邏輯
  2. RoundRectDrawableWithShadow(drawable)實現具體的功能
  3. IRoundViewDelegate(相當於roundview的代表)負責IRoundView跟drawable之間的通訊。

drawable實現好相關功能->給IRoundView->IRoundView通過IRoundViewDelegate代表類修改roundview的相關屬性。

好啦!!知道了大體的框架後,我們就開動了。

首先建立一個IRoundView類:
IRoundView

package com.yasin.round.card;

/**
 * Created by leo on 17/3/31.
 */

public interface IRoundView {
    /**
     * 初始化view
     */
    void initialize(IRoundViewDelegate roundView);

    /**
     * 設定圓角半徑
     */
    void setRadius(IRoundViewDelegate cardView, float radius);

    float getRadius(IRoundViewDelegate cardView);

    /**
     * 設定z軸的偏移量
     */
    void setElevation(IRoundViewDelegate cardView, float elevation);

    float getElevation(IRoundViewDelegate cardView);

    /**
     *圓角功能具體實現方法
     */
    void initStatic();
}

二、具體的drawable背景實現類RoundRectDrawableWithShadow

public class RoundRectDrawableWithShadow extends Drawable {

三、view的代表類IRoundViewDelegate:

package com.yasin.round.card;

import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.view.View;

/**
 * Created by leo on 17/3/31.
 */

public interface IRoundViewDelegate {
    void setCardBackground(Drawable drawable);

    Drawable getCardBackground();
    View getCardView();
    int getShadowStartColor();
    int getShadowEndColor();
    ColorStateList getBackgroundColor();
    float getRadius();
    float getElevation();
}

定義好了各個模組程式碼後,我們首先需要在我們的roundview中做處理了,我們在roundview中需要建立我們的代表類IRoundViewDelegate:

/**
 * Created by leo on 17/3/31.
 */

public class RoundView extends FrameLayout {
.....
private IRoundViewDelegate mRoundViewDelegate = new IRoundViewDelegate() {
        private Drawable bgDrawable;

        @Override
        public void setCardBackground(Drawable drawable) {
            this.bgDrawable = drawable;
            setBackgroundDrawable(drawable);
        }

        @Override
        public Drawable getCardBackground() {
            return bgDrawable;
        }

        @Override
        public View getCardView() {
            return RoundView.this;
        }

        @Override
        public int getShadowStartColor() {
            return RoundView.this.getShadowStartColor();
        }

        @Override
        public int getShadowEndColor() {
            return RoundView.this.getShadowEndColor();
        }

        @Override
        public ColorStateList getBackgroundColor() {
            return RoundView.this.getBackgroundColor();
        }

        @Override
        public float getRadius() {
            return RoundView.this.getRadius();
        }

        @Override
        public float getElevation() {
            return RoundView.this.getElevation();
        }
    };
}

然後我們需要把我們的mRoundViewDelegate物件在roundview初始化的時候傳遞給IRoundView(我們的邏輯處理類):

於是我們的roundivew全部程式碼就是:

package com.yasin.round.card;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;

import com.yasin.round.R;

/**
 * Created by leo on 17/3/31.
 */

public class RoundView extends FrameLayout {
    /**
     * 陰影的起始顏色
     */
    private int mShadowStartColor;
    /**
     * 陰影的結束顏色
     */
    private int mShadowEndColor;
    /**
     * 圓角半徑
     */
    private float mRadius;
    /**
     * 陰影的起始顏色
     */
    private float mElevation;
    /**
     * 控制元件背景顏色
     */
    private ColorStateList mBackgroundColor;
    private static IRoundView roundViewImp;
    static {
        if (Build.VERSION.SDK_INT >= 17) {
            roundViewImp = new RoundViewJellyBeanMr();
        } else {
            roundViewImp = new RoundViewLowImp();
        }
        roundViewImp.initStatic();
    }

    public RoundView(Context context) {
        super(context);
        initialize(context, null, 0);
    }

    public RoundView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize(context, attrs, 0);
    }

    public RoundView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize(context, attrs, defStyleAttr);
    }

    private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundView, defStyleAttr, 0);
        //判斷是否有背景顏色
        if (a.hasValue(R.styleable.RoundView_backgroundColor)) {
            mBackgroundColor = a.getColorStateList(R.styleable.RoundView_backgroundColor);
        } else {
            //獲取系統的背景顏色
            TypedArray aa = context.obtainStyledAttributes(new int[]{android.R.attr.colorBackground});
            //獲取系統的背景顏色
            int themeBackgroundColor = aa.getColor(0, 0);
            //獲取背景顏色的hvs值(h色彩、s飽和度、v顏色值)
            float[] hsv = new float[3];
            Color.colorToHSV(themeBackgroundColor, hsv);
            //當飽和度>0.5的時候,我們獲取自定義的cardview_dark_background顏色
            if (hsv[2] > 0.5) {
                mBackgroundColor = ColorStateList.valueOf(getResources().getColor(R.color.cardview_dark_background));
            } else {
                mBackgroundColor = ColorStateList.valueOf(getResources().getColor(R.color.cardview_dark_background));
            }
            aa.recycle();
        }
        mRadius = a.getDimensionPixelSize(R.styleable.RoundView_conerRadius, 0);
        mElevation = a.getDimensionPixelSize(R.styleable.RoundView_shadowSize, 0);
        mShadowStartColor = a.getColor(R.styleable.RoundView_shadowStartColor, getResources().getColor(R.color.cardview_shadow_start_color));
        mShadowEndColor = a.getColor(R.styleable.RoundView_shadowEndColor, getResources().getColor(R.color.cardview_shadow_end_color));
        a.recycle();
        roundViewImp.initialize(mRoundViewDelegate);
    }


    public int getShadowStartColor() {
        return mShadowStartColor;
    }

    public void setShadowStartColor(int shadowStartColor) {
        this.mShadowStartColor = shadowStartColor;
    }

    public int getShadowEndColor() {
        return mShadowEndColor;
    }

    public void setShadowEndColor(int shadowEndColor) {
        this.mShadowEndColor = shadowEndColor;
    }

    public float getRadius() {
        return mRadius;
    }

    public void setRadius(float mRadius) {
        this.mRadius = mRadius;
    }

    public float getElevation() {
        return mElevation;
    }

    public void setElevation(float mElevation) {
        this.mElevation = mElevation;
    }

    public ColorStateList getBackgroundColor() {
        return mBackgroundColor;
    }

    public void setBackgroundColor(ColorStateList backgroundColor) {
        this.mBackgroundColor = backgroundColor;
    }

    private IRoundViewDelegate mRoundViewDelegate = new IRoundViewDelegate() {
        private Drawable bgDrawable;

        @Override
        public void setCardBackground(Drawable drawable) {
            this.bgDrawable = drawable;
            setBackgroundDrawable(drawable);
        }

        @Override
        public Drawable getCardBackground() {
            return bgDrawable;
        }

        @Override
        public View getCardView() {
            return RoundView.this;
        }

        @Override
        public int getShadowStartColor() {
            return RoundView.this.getShadowStartColor();
        }

        @Override
        public int getShadowEndColor() {
            return RoundView.this.getShadowEndColor();
        }

        @Override
        public ColorStateList getBackgroundColor() {
            return RoundView.this.getBackgroundColor();
        }

        @Override
        public float getRadius() {
            return RoundView.this.getRadius();
        }

        @Override
        public float getElevation() {
            return RoundView.this.getElevation();
        }
    };
}

其中我們看到這麼一段程式碼:

 static {
        if (Build.VERSION.SDK_INT >= 17) {
            roundViewImp = new RoundViewJellyBeanMr();
        } else {
            roundViewImp = new RoundViewLowImp();
        }
        roundViewImp.initStatic();
    }

RoundViewJellyBeanMr跟RoundViewLowImp類就是具體的業務邏輯類了,
為什麼要做這個判斷呢?

因為我們知道我們畫圓角的時候一般都是呼叫:

canvas.drawRoundRect(bounds,cornerRadius,cornerRadius,paint);

但是drawRoundRect方法在api11-16的時候繪製效率很低,所以考慮到效能我們分為兩個類。

public class RoundViewLowImp implements IRoundView {
...
}

高版本中無非就是需要實現的圓角方式不同罷了,所以我們只需要繼承低版本RoundViewLowImp然後去實現圓角方法initStatic就可以了:

package com.yasin.round.card;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;

/**
 * Created by leo on 17/4/1.
 */

public class RoundViewJellyBeanMr extends RoundViewLowImp {

    @Override
    public void initStatic() {
 }

**我們先看RoundViewLowImp中咋實現。
我們需要把我們從view中獲取到的屬性傳遞給我們的具體實現類RoundRectDrawableWithShadow中:**

package com.yasin.round.card;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;

/**
 * Created by leo on 17/4/1.
 */

public class RoundViewLowImp implements IRoundView {
    @Override
    public void initialize(IRoundViewDelegate roundView) {
        RoundRectDrawableWithShadow backgroundDrawable = createDrawable(roundView);
        roundView.setCardBackground(backgroundDrawable);
    }

    private RoundRectDrawableWithShadow createDrawable(IRoundViewDelegate roundView) {
        return new RoundRectDrawableWithShadow(
                roundView.getBackgroundColor(),
                roundView.getShadowStartColor(),
                roundView.getShadowEndColor(),
                roundView.getElevation(),
                roundView.getRadius());
    }

    @Override
    public void initStatic() {
        RoundRectDrawableWithShadow.mRoundRectHelper = new RoundRectDrawableWithShadow.RoundRectHelper() {
            @Override
            public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint) {

            }
        };
    }

    @Override
    public void setRadius(IRoundViewDelegate cardView, float radius) {

    }

    @Override
    public float getRadius(IRoundViewDelegate cardView) {
        return 0;
    }

    @Override
    public void setElevation(IRoundViewDelegate cardView, float elevation) {

    }

    @Override
    public float getElevation(IRoundViewDelegate cardView) {
        return 0;
    }

    private RoundRectDrawableWithShadow getShadowBackground(IRoundViewDelegate cardView) {
        return (RoundRectDrawableWithShadow) cardView.getCardBackground();
    }
}

有了我們的介面後,程式碼很容易看懂跟實現的對吧!

好啦!做了那麼多前置工作,終於是要到我們的具體實現類中了,我們這裡的實現類為RoundRectDrawableWithShadow(drawable):

package com.yasin.round.card;

import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;

/**
 * Created by leo on 17/4/1.
 */

public class RoundRectDrawableWithShadow extends Drawable {
  @Override
    public void setAlpha(int alpha) {

    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
    @Override
    public void draw(Canvas canvas) {
    }
}

我們去繼承一個drawable類,然後需要重寫這麼幾個方法,其它幾個看不懂也沒關係,我們重點要看並且要實現的就是draw方法:

說了那麼多東西了,我們都還沒有測試的,我們來測試一下我們的程式碼(我們在左上角畫一個弧度):

首先引入roundview:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.yasin.round.MainActivity"
    android:orientation="vertical"
    >

    <com.yasin.round.card.RoundView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:backgroundColor="#ff00"
        app:conerRadius="10dp"
        app:shadowSize="10dp"
        app:shadowStartColor="#33000000"
        app:shadowEndColor="#22000000"
        >
        <RelativeLayout
            android:layout_gravity="center"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            >
            <TextView
                android:layout_centerInParent="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="RoundView"
                />
        </RelativeLayout>
    </com.yasin.round.card.RoundView>
    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        app:cardBackgroundColor="#ff00"
        app:cardElevation="10dp"
        app:cardCornerRadius="10dp"
        >
        <TextView
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="v7(CardView)"
            />
    </android.support.v7.widget.CardView>
</LinearLayout>

然後修改RoundRectDrawableWithShadow中程式碼,畫一個弧度:

package com.yasin.round.card;

import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;

/**
 * Created by leo on 17/4/1.
 */

public class RoundRectDrawableWithShadow extends Drawable {
    private static final float SHADOW_MULTIPLIER = 1.5f;
    public static RoundRectHelper mRoundRectHelper;
    private ColorStateList mBgColor;
    private int mShadowStartColor;
    private int mShadowEndColor;
    private float mCornerRadius;
    private float mShadowSize;

    private boolean mDirty = true;
    private RectF mCardBounds = new RectF();
    private Paint mPaint;
    private Paint mCornerShadowPaint;
    private Paint mEdgeShadowPaint;
    private Path mCornerShadowPath;

    public RoundRectDrawableWithShadow(ColorStateList bgColor, int shadowStartColor, int shadowEndColor, float shadowSize, float radius) {
        this.mShadowStartColor = shadowStartColor;
        this.mShadowEndColor = shadowEndColor;
        this.mShadowSize = shadowSize;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        setBackground(bgColor);
        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mCornerShadowPaint.setStyle(Paint.Style.FILL);
        this.mCornerRadius = (int) (radius + .5f);
        mCardBounds = new RectF();
        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
        mEdgeShadowPaint.setAntiAlias(false);
    }

    private void setBackground(ColorStateList color) {
        mBgColor = (color == null) ? ColorStateList.valueOf(Color.TRANSPARENT) : color;
        mPaint.setColor(mBgColor.getColorForState(getState(), mBgColor.getDefaultColor()));
    }

    @Override
    public void draw(Canvas canvas) {
        if (mDirty) {
            buildComponents(getBounds());
            mDirty = false;
        }
        canvas.translate(0, mShadowSize / 2);
        drawShadow(canvas);
        canvas.translate(0, -mShadowSize / 2);
        }
         private void drawShadow(Canvas canvas) {
        final float edgeShadowTop = -mCornerRadius - mShadowSize;
        final float inset = mCornerRadius+ mShadowSize / 2;
        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
        // LT
        int saved = canvas.save();
        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        }
         private void buildComponents(Rect bounds) {
        final float verticalOffset = mShadowSize * SHADOW_MULTIPLIER;
        mCardBounds.set(bounds.left + mShadowSize, bounds.top + verticalOffset,
                bounds.right - mShadowSize, bounds.bottom - verticalOffset);
        buildShadowCorners();
    }

    private void buildShadowCorners() {
        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
        RectF outerBounds = new RectF(innerBounds);
        outerBounds.inset(-mShadowSize, -mShadowSize);

        if (mCornerShadowPath == null) {
            mCornerShadowPath = new Path();
        } else {
            mCornerShadowPath.reset();
        }
        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
        mCornerShadowPath.moveTo(-mCornerRadius, 0);
        mCornerShadowPath.rLineTo(-mShadowSize, 0);
        // outer arc
        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
        // inner arc
        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
        mCornerShadowPath.close();
        float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
                new float[]{0f, startRatio, 1f}
                , Shader.TileMode.CLAMP));

        // we offset the content shadowSize/2 pixels up to make it more realistic.
        // this is why edge shadow shader has some extra space
        // When drawing bottom edge shadow, we use that extra space.
        mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
                -mCornerRadius - mShadowSize,
                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
        mEdgeShadowPaint.setAntiAlias(false);
    }

    @Override
    public void setAlpha(int alpha) {

    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    static interface RoundRectHelper {
        void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
    }
}

看別管我是咋實現的哈,先看看效果:

這裡寫圖片描述

可以看到,我們的roundview左上角有了一個圓角弧度,然後我們再加加程式碼:

 private void drawShadow(Canvas canvas) {
        final float edgeShadowTop = -mCornerRadius - mShadowSize;
        final float inset = mCornerRadius+ mShadowSize / 2;
        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
        // LT
        int saved = canvas.save();
        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawHorizontalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.width() - 2 * inset, -mCornerRadius,
                    mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
        }

這裡寫圖片描述

然後寫完各個方向的draw方法:

 private void drawShadow(Canvas canvas) {
        final float edgeShadowTop = -mCornerRadius - mShadowSize;
        final float inset = mCornerRadius+ mShadowSize / 2;
        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
        // LT
        int saved = canvas.save();
        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawHorizontalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.width() - 2 * inset, -mCornerRadius,
                    mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
        // RB
        saved = canvas.save();
        canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
        canvas.rotate(180f);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawHorizontalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.width() - 2 * inset, -mCornerRadius ,
                    mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
        // LB
        saved = canvas.save();
        canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
        canvas.rotate(270f);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawVerticalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
        // RT
        saved = canvas.save();
        canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
        canvas.rotate(90f);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawVerticalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
    }

執行程式碼:

相關推薦

Android CardView解析()

**前言:前面寫了一部落格 Android訂單流程view(超簡單!) 其中用到了(CardView),之前也用過,很爽!!所以對於CardView其實很早就想去研究一下它了,於是就有了這篇部落格了,寫這篇部落格的目的呢,主要是屬性一下google的工程師們是

篇就夠了系列之Android Manifest解析

前言: 前面幾篇介紹了android四大元件的知識,可以發現,四大元件都必須在一個叫AndroidManifest.xml檔案中進行註冊,那麼該檔案的作用是什麼呢?你們的內容各有什麼意義呢?帶著這些疑問,來開始下面內容的學習。官方文件,很詳細 作用:

前端入門之(vue-router解析)

前言: 一直想著啥時候能把vue全家桶的東西原始碼全部擼一遍,可惜無奈能力有限啊(哈哈,最近一直在補js基礎),看vuex程式碼的時候覺得也還好,基本上是邊學邊解析原始碼的方式把vuex擼了一遍(畢竟整個vuex加起來也沒多少程式碼),可當碰到vue-router的時候,首先就被它那幾千

Android DataBinding解析,你該用這個框架了

前言: Data binding 在2015年7月釋出的Android Studio v1.3.0 版本上引入,在2016年4月Android Studio v2.0.0 上正式支援。目前為止,Data Binding 已經支援雙向綁定了。 Databind

Android TagFlowLayout完全解析 款針對Tag的佈局(針對多個條目的單選操作)

目錄(?)[+] 一、概述 本文之前,先提一下關於上篇博文的100多萬訪問量請無視,博文被刷,我也很鬱悶,本來想把那個文章放到草稿箱,結果放不進去,還把日期弄更新了,實屬無奈。 因為本身FlowLayout本身的預期是提供一種新的佈局的方式,但是呢,在實際的開發中

Android Volley完全解析(),初識Volley的基本用法

1. Volley簡介我們平時在開發Android應用的時候不可避免地都需要用到網路技術,而多數情況下應用程式都會使用HTTP協議來發送和接收網路資料。Android系統中主要提供了兩種方式來進行HTTP通訊,HttpURLConnection和HttpClient,幾乎在任

Android Binder 解析(1) -- 概述

摘要 如果各位玩過《爐石傳說》,那麼可能對法師的職業卡「不穩定的傳送門」很有印象,特別是沒有歐洲玩家,經常能夠拿到其他職業的強力單卡。Android 也提供了傳送門,讓我們可以像使用本地方法一樣,呼叫其他程序的方法,他有一個響亮的名字,Binder! Binder 在 Android 是如此的重要,承當

Android Init流程解析

***************************************************************************         ******************************************************

zencart解析()

Index.php 1. Load application_top.php - see {@tutorial initsystem} 2. Set main language directory based on $_SESSION['language'] 3. Load

Android圖片載入框架最解析),Glide的基本用法

現在Android上的圖片載入框架非常成熟,從最早的老牌圖片載入框架UniversalImageLoader,到後來Google推出的Volley,再到後來的新興軍Glide和Picasso,當然還有Facebook的Fresco。每一個都非常穩定,功能也都十分強大。但是它們

Android事件匯流排()EventBus3.0用法解析

前言 EventBus是一款針對Android優化的釋出/訂閱事件匯流排。簡化了應用程式內各元件間、元件與後臺執行緒間的通訊。優點是開銷小,程式碼更優雅,以及將傳送者和接收者解耦。如果Activity和Activity進行互動還好說,如果Fragmen

Android圖片載入框架最解析)Glide的基本用法

現在Android上的圖片載入框架非常成熟,從最早的老牌圖片載入框架UniversalImageLoader,到後來Google推出的Volley,再到後來的新興軍Glide和Picasso,當然還有Facebook的Fresco。每一個都非常穩定,功能也都

(郭霖)Android圖片載入框架最解析),Glide的基本用法

本文同步發表於我的微信公眾號,掃一掃文章底部的二維碼或在微信搜尋 郭霖 即可關注,每天都有文章更新。 現在Android上的圖片載入框架非常成熟,從最早的老牌圖片載入框架UniversalImageLoader,到後來Google推出的Volley,再到後來的新興軍Glide和Picas

篇好文之Android資料庫 SQLite解析

這篇文章是資料庫系列篇文章的第一篇,主要講Android Sqlite資料庫儲存,後面陸續出GreenDao,LitePal, Realm,wcdb的文章,一如既往,如果遇到任何關於Android中SQLite的問題,都可以直接在我的文章底部留言,或者直接在我

Android異步載入解析之開篇瞎扯淡

com des turn pro 能夠 eat launch 卡頓 ring Android異步載入概述 Android異步載入在Android中使用的很廣泛,除了是由於避免在主線程中做網絡操作。更是為了避免在顯示時由於時間太長而造成ANR,添加顯示的流暢性,特別是像Li

Android: 在native中訪問assets解析

lock mp4 cpp sets 這樣的 內容 jniexport opencl href 本文總結在Android Native C++開發中訪問APK中的assets資源的方法 在CMake中添加相關NDK LIB的 依賴 因為我們接下來用到的一些函數實現在NDK庫l

Android框架原始碼解析之()Volley

前幾天面試CVTE,HR面掛了。讓內部一個學長幫我查看了一下面試官評價,發現二面面試官的評價如下: 廣度OK,但缺乏深究能力,深度與實踐不足 原始碼:只能說流程,細節程式碼不清楚,retrofit和volley都是。 感覺自己一方面:自己面試技巧有待提高吧(框

Android 建立與解析XML()—— 概述

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Fragment解析系列():那些年踩過的坑

本篇主要介紹一些最常見的Fragment的坑以及官方Fragment庫的那些自身的BUG,並給出解決方案;這些BUG在你深度使用時會遇到,比如Fragment巢狀時或者單Activity+多Fragment架構時遇到的坑。 Fragment是可以讓你的app縱享絲滑的設計,如果你的app想在

Android圖片載入框架最解析(四),玩轉Glide的回撥與監聽(筆記)

參考原文:Android圖片載入框架最全解析(四),玩轉Glide的回撥與監聽 回撥的原始碼實現 的Target物件傳入到GenericRequest當中,而Glide在圖片載入完成之後又會回撥GenericRequest的onResourceReady()方法,onReso