1. 说明
基于前边两次的自定义入门、自定义TextView的知识,这节课我们来实现一个效果,就是仿QQ运动步数效果,效果图如下:
图片.png
2.分析
如图所示,可以分为3个部分来实现:
QQ运动步数效果分析.png
第1部分:固定不动的大圆弧(蓝色),color、borderWidth;
第2部分:变化的小圆弧(红色),color、borderWidth;
第3部分:中间变化的文字,textSize、textColor;
3. 代码分析
3.1 创建attrs.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 自定义属性 -->
<declare-styleable name="QQStepView">
<attr name="outerColor" format="color"/>
<attr name="innerColor" format="color"/>
<attr name="borderWidth" format="dimension"/>
<attr name="stepTextColor" format="color"/>
<attr name="stepTextSize" format="dimension"/>
</declare-styleable>
</resources>
3.2 自定义QQStepView.java
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/3/18 8:10
* Version 1.0
* Params:
* Description: 仿QQ运动步数
*/
public class QQStepView extends View {
// 外圆弧颜色
private int mOurterColor = Color.RED ;
// 内圆弧颜色
private int mInnerColor = Color.BLUE ;
// 圆弧宽度
private int mBorderWidth = 20 ; //20px
// 文字大小
private int mStepTextSize ;
// 文字颜色
private int mStepTextColor ;
// 外圆弧画笔、内圆弧画笔、文字画笔
private Paint mOuterPaint , mInnerPaint , mTextPaint ;
// 总共步数
private int mStepMax = 0 ;
// 当前步数
private int mCurrentStep = 0 ;
public QQStepView(Context context) {
this(context,null);
}
public QQStepView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public QQStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 1. 创建attrs.xml文件,编写自定义属性
// 2. 在布局中使用
// 3. 在自定义View中获取自定义属性
// 4. 测量onMeasure()
// 5. 绘制固定圆弧、变化圆弧、文字
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
mOurterColor = array.getColor(R.styleable.QQStepView_outerColor , mOurterColor) ;
mInnerColor = array.getColor(R.styleable.QQStepView_innerColor , mInnerColor) ;
mBorderWidth = array.getDimensionPixelSize(R.styleable.QQStepView_borderWidth , mBorderWidth) ;
mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize , mStepTextSize) ;
mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor , mStepTextColor) ;
array.recycle();
// 外圆弧画笔
mOuterPaint = new Paint() ;
mOuterPaint.setAntiAlias(true);
mOuterPaint.setStrokeWidth(mBorderWidth);
mOuterPaint.setColor(mOurterColor);
mOuterPaint.setStrokeCap(Paint.Cap.ROUND);
mOuterPaint.setStyle(Paint.Style.STROKE); //画笔空心
// 内圆弧画笔
mInnerPaint = new Paint() ;
mInnerPaint.setAntiAlias(true);
mInnerPaint.setStrokeWidth(mBorderWidth);
mInnerPaint.setColor(mInnerColor);
mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
mInnerPaint.setStyle(Paint.Style.STROKE); //画笔空心
// 文字画笔
mTextPaint = new Paint() ;
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mStepTextColor);
mTextPaint.setTextSize(mStepTextSize);
}
// 画外圆弧、内圆弧、文字
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 调用者可能会在布局文件中给宽高设置 wrap_content
// 这里确保:如果宽高不一致,则取最小值,确保是一个正方形
int width = MeasureSpec.getSize(widthMeasureSpec) ;
int height = MeasureSpec.getSize(heightMeasureSpec) ;
setMeasuredDimension(width>height?height:width , width>height?height:width);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 画外圆弧
RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2
,getWidth()-mBorderWidth/2,getHeight()-mBorderWidth/2);
canvas.drawArc(rectF , 135 , 270 , false , mOuterPaint);
if (mStepMax ==0) return;
// 画内圆弧 肯定不能写死,使用百分比,让使用者从外边传递
float sweepAngle = (float) mCurrentStep/mStepMax ;
canvas.drawArc(rectF , 135 , sweepAngle*270 , false , mInnerPaint);
// 画文字
String stepText = mCurrentStep+"" ;
Rect rect = new Rect() ;
mTextPaint.getTextBounds(stepText,0,stepText.length(),rect);
int dx = getWidth()/2 - rect.width()/2 ;
// 基线baseLine
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom ;
int baseLine = getHeight()/2 + dy ;
canvas.drawText(stepText , dx , baseLine , mTextPaint);
}
/**
* 最大步数
* 在这里添加 synchronized,目的就是防止多线程操作
*/
public synchronized void setStepMax(int stepMax){
this.mStepMax = stepMax ;
}
/**
* 当前步数
* 在这里添加 synchronized,目的就是防止多线程操作
*/
public synchronized void setCurrentStep(int currentStep){
this.mCurrentStep = currentStep ;
// 根据效果可以看到,圆弧和文字一直在变,所以就知道,是不断的重新绘制
invalidate();
}
}
3.3 MainActivity.java代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final QQStepView step_view = (QQStepView) findViewById(R.id.step_view);
step_view.setStepMax(4000);
// 属性动画,让圆弧动起来
ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 3000);
valueAnimator.setDuration(1000) ;
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 不断获取当前角度,不断设置当前角度,不断重新去绘制
float currentStep = (float) animation.getAnimatedValue();
step_view.setCurrentStep((int) currentStep);
}
});
valueAnimator.start();
}
}
3.4 activity_main.xml布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jackchen.view_day03_2.MainActivity">
<com.jackchen.view_day03_2.QQStepView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:borderWidth="20dp"
app:outerColor="@color/colorPrimary"
app:innerColor="@color/colorAccent"
app:stepTextSize="30sp"
app:stepTextColor="@color/colorAccent"
android:id="@+id/step_view"
/>
</RelativeLayout>
具体代码已上传至github:
https://github.com/shuai999/View_day03_2.git