**前言:前面写了一博客 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实现出来的。
运行效果:
当然,系统的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模式吧)。
我们代码中分为三个部分:
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);
}
}
看别管我是咋实现的哈,先看看效果:
可以看到,我们的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);
}
运行代码:
(代码我待会做解析)可以看到,我们的阴影算是实现了。
跟系统自带的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);
}
};
}
}
最后运行代码:
可以看到,我们的roundview算是基本上实现了~~~~
好啦!!后面后只是给小伙伴们看一下具体实现流程跟测试,所以就没对代码做注释,下一节我们重点关注一下实现方法哈~~~
本节先到这里,未完待续……