Android CardView全解析(一)

**前言:前面写了一博客 Android订单流程view(超简单!)
其中用到了(CardView),之前也用过,很爽!!所以对于CardView其实很早就想去研究一下它了,于是就有了这篇博客了,写这篇博客的目的呢,主要是属性一下google的工程师们是怎么封装一个控件的,整个过程下来,还是学习到了挺多的知识的,于是打算把我学到的一些东西分享出来,算是当作学习笔记了。**

那么CardView是干什么的呢?
想必有些小伙伴对于它也并不陌生。CardView是v7包中的组件(ViewGroup),主要用来设置布局的边框为圆角、z轴的偏移量(这个是5.0以后才有的概念,也就是阴影的效果)。看一下它的效果:


<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实现出来的。

运行效果:

Android CardView全解析(一)_第1张图片

当然,系统的CardView是没办法修改阴影的颜色的,我们实现的RoundView对它进行了一些小小的修改,使其能支持颜色修改,当然,我也就实现了大体的部分,本篇roundview只是做演示的demo,小伙伴可不要直接拖到项目中用啊~~

废话不多说,我们要开撸了~~~小伙伴跟紧啦!

首先明确下我们的目标:

1、可以实现圆角功能
2、可以设置阴影(z轴的偏移)

看起来很简单,但是我们的代码还是比较多的,不过没关系,我们重点看一下大神们到底是咋写代码的。

首先我们定义一下我们需要用到的属性:

attrs.xml


<resources>
    <declare-styleable name="RoundView">
        
        <attr name="backgroundColor" format="color">attr>
        
        <attr name="conerRadius" format="dimension">attr>
        
        <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:


<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);
    }
}

看别管我是咋实现的哈,先看看效果:

Android CardView全解析(一)_第2张图片

可以看到,我们的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);
        }

Android CardView全解析(一)_第3张图片

然后写完各个方向的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全解析(一)_第4张图片

(代码我待会做解析)可以看到,我们的阴影算是实现了。

跟系统自带的cardview相比,就只差我们中间部分的背景color 了,因为api不同所以我们实现drawRoundRect方式不同的原因,于是我们暴露出一个方法,让不同版本api实现各自的drawRoundRect方法:

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);
        mRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
    }

    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);
    }

    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);
    }
}

然后在我们的IRoundView(业务逻辑处理类中)给drawable中的RoundRectHelper具体实现:

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() {
        RoundRectDrawableWithShadow.mRoundRectHelper=new RoundRectDrawableWithShadow.RoundRectHelper() {
            @Override
            public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint) {
              canvas.drawRoundRect(bounds,cornerRadius,cornerRadius,paint);
            }
        };
    }
}

最后运行代码:

Android CardView全解析(一)_第5张图片

可以看到,我们的roundview算是基本上实现了~~~~

好啦!!后面后只是给小伙伴们看一下具体实现流程跟测试,所以就没对代码做注释,下一节我们重点关注一下实现方法哈~~~
本节先到这里,未完待续……

你可能感兴趣的:(Android进阶)