简化高仿以及源码分析Android 5.0的CardView

xiaoxiao2021-02-28  30

需求: 为了实现定制化的CardView效果,想要定制每一个角落都是圆弧或者直角的需求,需要了解CardView的绘制原理。

 CardView核心思想:

像版本控制就不讲了,只分析如何绘制圆角和阴影的,以下是源码的注释,整体看起来很复杂,但核心步骤就几行代码:

主要涉及这个类: RoundRectDrawableWithShadow

public void draw(Canvas canvas) { // 开始绘制drawable,mDirty为了防止两次绘制,影响效率,在编程的时候,可以借鉴,没有变化就不要再次计算和绘制了。 if(this.mDirty) { this.buildComponents(this.getBounds());// 设置内容区域和阴影区域,最好是单步调试一下,看看各个坐标 this.mDirty = false; } canvas.translate(0.0F, this.mRawShadowSize / 2.0F); this.drawShadow(canvas); canvas.translate(0.0F, -this.mRawShadowSize / 2.0F); sRoundRectHelper.drawRoundRect(canvas, this.mCardBounds, this.mCornerRadius, this.mPaint); } // 这个是核心,在构造了绘制的封闭扇形区域以后,绘制左上角的情况,之后再通过移动画布,旋转画布,构造4个角的效果。 private void drawShadow(Canvas canvas) { float edgeShadowTop = -this.mCornerRadius - this.mShadowSize; float inset = this.mCornerRadius + this.mInsetShadow + this.mRawShadowSize / 2.0F; boolean drawHorizontalEdges = this.mCardBounds.width() - 2.0F * inset > 0.0F; boolean drawVerticalEdges = this.mCardBounds.height() - 2.0F * inset > 0.0F; int saved = canvas.save(); canvas.translate(this.mCardBounds.left + inset, this.mCardBounds.top + inset); canvas.drawPath(this.mCornerShadowPath, this.mCornerShadowPaint); // 绘制圆角的阴影 if(drawHorizontalEdges) { // 绘制边线的阴影 canvas.drawRect(0.0F, edgeShadowTop, this.mCardBounds.width() - 2.0F * inset, -this.mCornerRadius, this.mEdgeShadowPaint); } canvas.restoreToCount(saved); saved = canvas.save(); canvas.translate(this.mCardBounds.right - inset, this.mCardBounds.bottom - inset); canvas.rotate(180.0F); canvas.drawPath(this.mCornerShadowPath, this.mCornerShadowPaint); if(drawHorizontalEdges) { canvas.drawRect(0.0F, edgeShadowTop, this.mCardBounds.width() - 2.0F * inset, -this.mCornerRadius + this.mShadowSize, this.mEdgeShadowPaint); } canvas.restoreToCount(saved); saved = canvas.save(); canvas.translate(this.mCardBounds.left + inset, this.mCardBounds.bottom - inset); canvas.rotate(270.0F); canvas.drawPath(this.mCornerShadowPath, this.mCornerShadowPaint); if(drawVerticalEdges) { canvas.drawRect(0.0F, edgeShadowTop, this.mCardBounds.height() - 2.0F * inset, -this.mCornerRadius, this.mEdgeShadowPaint); } canvas.restoreToCount(saved); // 这个注释的意思是,不明白原理的时候,可以通过删除改变源码,来看理解的效果,这个方法也很重要。 // saved = canvas.save(); // canvas.translate(this.mCardBounds.right - inset, this.mCardBounds.top + inset); // canvas.rotate(90.0F); // canvas.drawPath(this.mCornerShadowPath, this.mCornerShadowPaint); // if(drawVerticalEdges) { // canvas.drawRect(0.0F, edgeShadowTop, this.mCardBounds.height() - 2.0F * inset, -this.mCornerRadius, this.mEdgeShadowPaint); // } // // canvas.restoreToCount(saved); } private void buildShadowCorners() { // 仔细看,是负值,往左往外构造了一个内部区域,记住这个半径 RectF innerBounds = new RectF(-this.mCornerRadius, -this.mCornerRadius, this.mCornerRadius, this.mCornerRadius); RectF outerBounds = new RectF(innerBounds); outerBounds.inset(-this.mShadowSize, -this.mShadowSize); // 往外又扩大了一层区域,就是想构造两个弧形中间夹杂的一个扇形区域,用path的形式连接起来,这个就是阴影的区域 if(this.mCornerShadowPath == null) { this.mCornerShadowPath = new Path(); } else { this.mCornerShadowPath.reset(); } this.mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD); this.mCornerShadowPath.moveTo(-this.mCornerRadius, 0.0F); // 可以跟踪坐标,自己画个图,就知道了区域,移动到一个坐标点 this.mCornerShadowPath.rLineTo(-this.mShadowSize, 0.0F); // 基于上一个点,往右连接一条线 this.mCornerShadowPath.arcTo(outerBounds, 180.0F, 90.0F, false); // 基于右边连线后,绘制一个圆弧,180->转到90度绘制圆弧, this.mCornerShadowPath.arcTo(innerBounds, 270.0F, -90.0F, false); // 记住上边的方向,是基于最后一个点来连接新的弧,这个是270度往左下回来绘制的 this.mCornerShadowPath.close(); // 构造一个封闭的扇形区域 float startRatio = this.mCornerRadius / (this.mCornerRadius + this.mShadowSize); // 这个是非常有技巧的,记住这个比例,因为是扇形上绘制阴影,从扇形的内边开始绘制,到扇形的 // 外边结束,而这两个扇形的半径是知道的,所以可以计算比例,这个比例是在圆形渐变使用的,从哪里开始渐变,这个非常重要。 this.mCornerShadowPaint.setShader(new RadialGradient(0.0F, 0.0F, this.mCornerRadius + this.mShadowSize, new int[]{this.mShadowStartColor, this.mShadowStartColor, this.mShadowEndColor}, new float[]{0.0F, startRatio, 1.0F}, Shader.TileMode.CLAMP)); // 这个是线性渐变,可以比如最下边的阴影就是线性渐变设置的 this.mEdgeShadowPaint.setShader(new LinearGradient(0.0F, -this.mCornerRadius + this.mShadowSize, 0.0F, -this.mCornerRadius - this.mShadowSize, new int[]{this.mShadowStartColor, this.mShadowStartColor, this.mShadowEndColor}, new float[]{0.0F, 0.5F, 1.0F}, Shader.TileMode.CLAMP)); } private void buildComponents(Rect bounds) { float verticalOffset = this.mMaxShadowSize * 1.5F; // 设置阴影的垂直方向的距离,1.5是经验值,我感觉可以根据UI的经验来调节 // 这个就是内容区域,往里缩小了一些,给阴影部分流出空间 this.mCardBounds.set((float)bounds.left + this.mMaxShadowSize, (float)bounds.top + verticalOffset, (float)bounds.right - this.mMaxShadowSize, (float)bounds.bottom - verticalOffset); // 计算阴影的圆角,这部分比较核心,也是有技巧的,仔细看注释 this.buildShadowCorners(); }

我的cardview的效果:

我的cardview的源码:

public class CardFramelayout extends View { private Paint mShaderPaint; private Paint mStrokePaint; private Paint mContentPaint; private Paint mBgPaint; private int mColorStart = Color.parseColor("#37000000"); private int mColorEnd = Color.parseColor("#03000000"); private int mColorHorizentalStart = Color.parseColor("#eeeeee"); private int mColorHorizentalEnd = Color.parseColor("#ffffffff"); private int []mColors = new int[] { Color.parseColor("#eeeeee"), Color.parseColor("#efefef"), Color.parseColor("#f0f0f0"), Color.parseColor("#f1f1f1"), Color.parseColor("#f2f2f2"), Color.parseColor("#f3f3f3"), Color.parseColor("#f4f4f4"), Color.parseColor("#f5f5f5"), Color.parseColor("#f6f6f6"), Color.parseColor("#f7f7f7"), Color.parseColor("#f8f8f8"),Color.parseColor("#f9f9f9"), Color.parseColor("#fefefe")}; private float [] mLocations; private int offset = 40; private int radius = 60; private int mWidth; private int mHeight; private Rect mRect, mContentRect, mShaderRect, mLeftRect, mRightRect; private LinearGradient mLinearGradient, mLeftGradient, mRightGradient; private Rect mRectLeftBottom; private Paint mLeftBottomPaint, mLeftPaint; private RadialGradient mLeftBottomGradient; public CardFramelayout(@NonNull Context context) { super(context); init (); } public CardFramelayout(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init (); } public CardFramelayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init (); } private void init () { mShaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mShaderPaint.setStyle(Paint.Style.FILL); mShaderPaint.setColor(Color.BLUE); mLeftPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mLeftPaint.setStyle(Paint.Style.FILL); mLeftPaint.setColor(Color.BLUE); mContentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mContentPaint.setStyle(Paint.Style.FILL); mContentPaint.setColor(Color.WHITE); mLeftBottomPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mLeftBottomPaint.setStyle(Paint.Style.FILL); mLeftBottomPaint.setColor(Color.WHITE); mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBgPaint.setStyle(Paint.Style.FILL); mBgPaint.setColor(Color.parseColor("#eeeeee")); mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setColor(Color.RED); // mColors[0] mStrokePaint.setStrokeWidth(3); mLocations = new float[mColors.length]; float step = 1.0f / mColors.length; for (int i = 0; i < mColors.length; i++) { mLocations[i] = step * i; } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; int bigRadius = offset + radius; int centerX = bigRadius; int centerY = mHeight - bigRadius; mRect = new Rect (0, 0, w, h); mContentRect = new Rect (offset, offset, w - offset, h - offset); mShaderRect = new Rect (centerX, h - offset, w , h); mLeftRect = new Rect (0, offset, offset, centerY); int contentWidth = w - offset * 2; mRightRect = new Rect (offset + contentWidth, offset, w, h - offset); // 绘制线性渐变,bottom的 mLinearGradient = new LinearGradient(offset , h - offset, offset, h, new int[]{mColorStart, mColorEnd}, new float[] {0, 1}, Shader.TileMode.CLAMP); // mLeftGradient = new LinearGradient(0, offset, offset, offset, new int[]{mColorHorizentalEnd, mColorStart}, new float[] {0, 1}, Shader.TileMode.CLAMP); // mRightGradient = new LinearGradient(offset + contentWidth, offset, w, offset, new int[]{mColorStart, mColorHorizentalEnd}, new float[] {0, 1}, Shader.TileMode.CLAMP); // 绘制左边的线性渐变 mLeftGradient = new LinearGradient(0, offset, offset, offset, new int[]{mColorEnd, mColorStart}, new float[] {0, 1}, Shader.TileMode.CLAMP); // mLeftGradient = new LinearGradient(0, offset, offset, offset, new int[]{mColorStart, mColorEnd}, new float[] {0, 1}, Shader.TileMode.CLAMP); mRightGradient = new LinearGradient(offset + contentWidth, offset, w, offset, new int[]{mColorHorizentalStart, mColorHorizentalEnd}, new float[] {0, 1}, Shader.TileMode.CLAMP); // mLinearGradient = new LinearGradient(0, h - offset, 0, h, mColors, mLocations, Shader.TileMode.CLAMP); mLeftPaint.setShader(mLeftGradient); mShaderPaint.setShader(mLinearGradient); mRectLeftBottom = new Rect (0, h - offset * 2, offset * 2, h); // int bigRadius = offset + radius; // int centerX = bigRadius; // int centerY = mHeight - bigRadius; // mLeftBottomGradient = new RadialGradient(offset , h - offset, offset, new int[]{Color.RED, Color.GREEN}, new float[] {0, 1}, Shader.TileMode.CLAMP); // 这个比较核心,当绘制弧形渐变的时候,要设置两个圆形的比例,再绘制圆形就行了。 float startRatio = radius * 1.0f / bigRadius; mLeftBottomGradient = new RadialGradient(centerX, centerY, bigRadius, new int[]{Color.TRANSPARENT, mColorStart, mColorEnd}, new float[] {0, startRatio, 1}, Shader.TileMode.CLAMP); // mLeftBottomGradient = new RadialGradient(offset , h - offset, offset, new int[]{mColorStart, mColorEnd}, new float[] {0, 1}, Shader.TileMode.CLAMP); mLeftBottomPaint.setShader(mLeftBottomGradient); } Path path = new Path(); Path shaderPath = new Path(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // canvas.drawRect(0, 0, mWidth, mHeight, mBgPaint); // canvas.drawRect(mRectLeftBottom, mLeftBottomPaint); // canvas.drawRect(mContentRect, mContentPaint); mShaderPaint.setShader(mLinearGradient); canvas.drawRect(mShaderRect, mShaderPaint); canvas.drawRect(mLeftRect, mLeftPaint); int bigRadius = offset + radius; int centerX = bigRadius; int centerY = mHeight - bigRadius; path.moveTo(centerX, centerY); // 绘制弧形 center (bigRadius, mHeight - bigRadius); RectF arc = new RectF (); int l = bigRadius - radius; int t = mHeight - bigRadius - radius; int r = bigRadius + radius; int b = mHeight - bigRadius + radius; arc.set(l, t, r, b); // canvas.drawArc(arc, 90, 90, true, mStrokePaint); // shaderPath.setFillType(Path.FillType.EVEN_ODD); shaderPath.reset(); shaderPath.moveTo(0, centerY); shaderPath.lineTo(offset, centerY); // mStrokePaint.setStrokeWidth(20); // canvas.drawPoint(offset, mHeight - centerY, mStrokePaint); // mStrokePaint.setStrokeWidth(4); shaderPath.addArc(arc, 180, -90); shaderPath.moveTo(centerX, mHeight - offset); shaderPath.lineTo(centerX, mHeight); RectF outRect = new RectF(); outRect.set(centerX - bigRadius, centerY - bigRadius, centerX + bigRadius, centerY + bigRadius); shaderPath.addArc(outRect, 90, 90); shaderPath.moveTo(offset, centerY); shaderPath.close(); // mStrokePaint.setShader(mLeftBottomGradient); // canvas.drawPath(shaderPath, mStrokePaint); // mLeftBottomPaint mStrokePaint RectF circle = new RectF(); circle.set(0, mHeight - centerY * 2, centerY * 2, mHeight); canvas.drawArc(outRect, 90, 90, true, mLeftBottomPaint); // mShaderPaint.setShader(mRightGradient); // canvas.drawRect(mRightRect, mShaderPaint); } }
转载请注明原文地址: https://www.6miu.com/read-1950322.html

最新回复(0)