本文共 8405 字,大约阅读时间需要 28 分钟。
Android自定义View是Android开发中比较重要的一项,也是很多开发者比较怕的一个东西。其实只要认真去学习,自定义View其实没有那么可怕;相反的,我们还能从自定义View中找到很多乐趣。
自定义一个计步器分析:
1、首先需要画一个固定不动的大圆弧 2、其次需要画一个跟着步数变化的小圆弧 3、最后画圆弧中间的步数显示的文字首先我们在values目录下新建attrs.xml文件;
然后在里面自定义属性:创建我们的StepView,继承自View并重写构造方法。
public class StepView extends View { public StepView(Context context) { this(context, null); } public StepView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public StepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } }
这里在前两个构造方法中调this,保证使用每个构造方法初始化都能走到第三个构造方法。
创建好View,重写构造方法以后就要在View中获取我们的自定义属性:
//获取自定义属性 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.StepView); mOutColor = array.getColor(R.styleable.StepView_outColor, Color.GREEN); mInnerColor = array.getColor(R.styleable.StepView_innerColor, Color.BLUE); mBorderWidth = (int) array.getDimension(R.styleable.StepView_borderWidth, 10); mStepTextSize = array.getDimensionPixelSize(R.styleable.StepView_stepTextSize, 20); mStepTextColor = array.getColor(R.styleable.StepView_stepTextColor, Color.RED); array.recycle();
然后需要重写我们的onMeasure方法,这个方法是测量并设置View的宽高,因为我们这个View比较简单,只需要保证是宽高一致,所以只需要获取我们的宽高,然后设置就行。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(Math.min(width, height), Math.min(width, height)); }
onDraw方法就是绘制View的内容,这里我们要分三步:
首先绘制固定的大圆弧:
//画外圆弧 int center = getWidth() / 2; int radius = getWidth() / 2 - mBorderWidth; int left = center - radius; int top = center - radius; int right = center + radius; int bottom = center + radius; mRectF.set(left, top, right, bottom); canvas.drawArc(mRectF, START_ANGLE, TOTAL_ANGLE, false, mPaintOuter);
首先要定义一个矩形区域mRectF,然后设置四个边界值。center就是圆心位置,radius就是半径,这里要减去圆弧的宽度。
然后调用drawArc(),这个方法主要有四个参数: oval : 生成椭圆的矩形 startAngle : 弧开始的角度 (X轴正方向为0度,顺时针弧度增大) sweepAngle : 绘制多少弧度 (注意不是结束弧度) useCenter : 是否有弧的两边 true有两边 false无两边小圆弧是会跟着当前的步数动态变化的,所以我们要根据步数计算:
//画内圆弧 float sweepAngle = (float) mCurrentStepCount / mMaxStepCount; canvas.drawArc(mRectF, START_ANGLE, sweepAngle * TOTAL_ANGLE, false, mPaintInner);
计算完成同样是调用drawArc()绘制圆弧。
接下来就是绘制显示在圆弧中心的文字就是我们的当前步数:
//画文字 if (mCurrentStepCount == 0) { return; } String currentStepText = String.valueOf(mCurrentStepCount); mPaintText.getTextBounds(currentStepText, 0, currentStepText.length(), mBounds); int startX = getWidth() / 2 - mBounds.width() / 2; Paint.FontMetricsInt fm = mPaintText.getFontMetricsInt(); int dy = (fm.bottom - fm.top) / 2 - fm.descent; int baseline = getHeight() / 2 + dy; canvas.drawText(currentStepText, startX, baseline, mPaintText);
首先根据画笔去测量文字的边界,然后计算开始的x坐标和baseline,最后调用drawText()绘制;
这里不知道怎么计算的可以转到另一篇博客,,里面有详细介绍计算推理过程。最后就是在xml中使用我们的自定义View,要让它动起来,还需要借助我们的属性动画,动态去改变当前的步数值,然后去重绘。
这里贴一下xml中使用代码:然后是在activity使用:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); StepView stepView = findViewById(R.id.stepView); stepView.setMaxStep(3000); ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0,1800); valueAnimator.setDuration(2000); valueAnimator.setInterpolator(new DecelerateInterpolator()); valueAnimator.addUpdateListener(animation -> { float animatedValue = (float) animation.getAnimatedValue(); stepView.setStep((int) animatedValue); }); valueAnimator.start(); }}
package com.px.test.view;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.RectF;import android.util.AttributeSet;import android.view.View;import com.px.test.R;import androidx.annotation.Nullable;public class StepView extends View { private static final String TAG = "StepView"; private static final int START_ANGLE = 135; private static final int TOTAL_ANGLE = 270; private int mOutColor; private int mInnerColor; private int mBorderWidth; private int mStepTextSize; private int mStepTextColor; private Paint mPaintOuter; private RectF mRectF; private Paint mPaintInner; private Paint mPaintText; private Rect mBounds; private int mMaxStepCount; private int mCurrentStepCount; public StepView(Context context) { this(context, null); } public StepView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public StepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //获取自定义属性 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.StepView); mOutColor = array.getColor(R.styleable.StepView_outColor, Color.GREEN); mInnerColor = array.getColor(R.styleable.StepView_innerColor, Color.BLUE); mBorderWidth = (int) array.getDimension(R.styleable.StepView_borderWidth, 10); mStepTextSize = array.getDimensionPixelSize(R.styleable.StepView_stepTextSize, 20); mStepTextColor = array.getColor(R.styleable.StepView_stepTextColor, Color.RED); array.recycle(); mPaintOuter = new Paint(); mPaintOuter.setAntiAlias(true); mPaintOuter.setColor(mOutColor); mPaintOuter.setStrokeWidth(mBorderWidth); mPaintOuter.setStrokeCap(Paint.Cap.ROUND); mPaintOuter.setStyle(Paint.Style.STROKE); mPaintInner = new Paint(); mPaintInner.setAntiAlias(true); mPaintInner.setColor(mInnerColor); mPaintInner.setStrokeWidth(mBorderWidth); mPaintInner.setStrokeCap(Paint.Cap.ROUND); mPaintInner.setStyle(Paint.Style.STROKE); mPaintText = new Paint(); mPaintText.setAntiAlias(true); mPaintText.setColor(mStepTextColor); mPaintText.setTextSize(mStepTextSize); mBounds = new Rect(); mRectF = new RectF(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(Math.min(width, height), Math.min(width, height)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //画外圆弧 int center = getWidth() / 2; int radius = getWidth() / 2 - mBorderWidth; int left = center - radius; int top = center - radius; int right = center + radius; int bottom = center + radius; mRectF.set(left, top, right, bottom); canvas.drawArc(mRectF, START_ANGLE, TOTAL_ANGLE, false, mPaintOuter); //画内圆弧 float sweepAngle = (float) mCurrentStepCount / mMaxStepCount; canvas.drawArc(mRectF, START_ANGLE, sweepAngle * TOTAL_ANGLE, false, mPaintInner); //画文字 if (mCurrentStepCount == 0) { return; } String currentStepText = String.valueOf(mCurrentStepCount); mPaintText.getTextBounds(currentStepText, 0, currentStepText.length(), mBounds); int startX = getWidth() / 2 - mBounds.width() / 2; Paint.FontMetricsInt fm = mPaintText.getFontMetricsInt(); int dy = (fm.bottom - fm.top) / 2 - fm.descent; int baseline = getHeight() / 2 + dy; canvas.drawText(currentStepText, startX, baseline, mPaintText); } public void setMaxStep(int maxStepCount) { mMaxStepCount = maxStepCount; } public void setStep(int currentStepCount) { mCurrentStepCount = currentStepCount; invalidate(); }}
整个实现过程并不复杂,有时候有些事情并没有我们想的那么难,只要我们去做了,就会发现其实就那样。
转载地址:http://jzsni.baihongyu.com/