2021-08-12
Posted fakerXuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021-08-12相关的知识,希望对你有一定的参考价值。
一个简单的记录步数自定义View
一个简单的记录步数自定义View
最终效果
代码
- 自定义View
package com.example.viewapplication;
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 androidx.annotation.Nullable;
public class QQStepView extends View {
private int mOuterColor = Color.RED;
private int mInnerColor =Color.BLUE;
private int mBorderWidth = 20; //20px
private int mStepTextSize;
private int mStepTextColor;
private Paint mOutPaint,mInnerPaint,mTextPaint;
// 总共的 和 当前的步数
private int mMaxStep = 0;
private int mCurrentStep = 0;
public QQStepView(Context context) {
this(context, null);
}
public QQStepView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QQStepView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 1.分析效果
// 2。确定自定义属性 编写attr.xml
// 3。在布局中使用
// 4。在自定义View中获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);
mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);
mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth, mBorderWidth);
mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize, mStepTextSize);
mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
array.recycle();
mOutPaint = new Paint();
mOutPaint.setAntiAlias(true);
mOutPaint.setStrokeWidth(mBorderWidth);
mOutPaint.setColor(mOuterColor);
mOutPaint.setStrokeCap(Paint.Cap.ROUND); // 弧线末尾有圆角
mOutPaint.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);
// 5。onMeasure()
// 6。 画外圆弧 内圆弧 文字
// 7。其他
}
@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));
}
// 6.画外圆弧 内圆弧 文字
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 因为弧线本身是有宽度的 设置外接矩形时要考虑到这一点 否则圆弧会画到外接矩形外面
// 其实只要弧有宽度就会画外接矩形外面 这里意思是设置外接矩形时 就减去半个弧线宽度的长度
// 这样圆弧的宽高和预设的就是一致的
RectF rectF = new RectF(mBorderWidth / 2, mBorderWidth / 2, getWidth() - mBorderWidth, getHeight() - mBorderWidth);
canvas.drawArc(rectF, 135, 270, false, mOutPaint);
if (mMaxStep == 0) return;
// 画内圆弧 怎么画不能写死 由使用者设置属性 从外面传
float sweepAngle = (float) mCurrentStep / mMaxStep;
canvas.drawArc(rectF,135, sweepAngle * 270, false, mInnerPaint);
// 画文字 注意画文字时设置基线的问题
String stepText = mCurrentStep + "";
Rect textBound = new Rect();
mTextPaint.getTextBounds(stepText, 0, stepText.length(), textBound);
// 计算基线位置
int dx = getWidth() / 2 - textBound.width() / 2;
Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
int baseLine = getHeight() / 2 + dy;
canvas.drawText(stepText, dx, baseLine, mTextPaint);
}
public synchronized void setmMaxStep(int stepMax){
this.mMaxStep = stepMax;
}
public synchronized void setmCurrentStep(int currentStep){
this.mCurrentStep = currentStep;
// 不断绘制 onDraw() invalidate中会调用ondraw()方法
invalidate();
}
}
- 自定义属性
<?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="stepTextSize" format="dimension"/>
<attr name="stepTextColor" format="color"/>
</declare-styleable>
</resources>
-
layout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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=".MainActivity" android:orientation="vertical"> <com.example.viewapplication.QQStepView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/step_view" app:outerColor="@color/teal_200" app:innerColor="@color/purple_200" app:borderWidth="20dp" app:stepTextColor="@color/black" app:stepTextSize="30sp" /> </LinearLayout>
-
Activity
package com.example.viewapplication; import androidx.appcompat.app.AppCompatActivity; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.os.Bundle; import android.view.animation.DecelerateInterpolator; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final QQStepView qqStepView = (QQStepView) findViewById(R.id.step_view); qqStepView.setmMaxStep(4000); // 属性动画 ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 3000); valueAnimator.setDuration(2000); valueAnimator.setInterpolator(new DecelerateInterpolator()); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float currentStep = (float) animation.getAnimatedValue(); qqStepView.setmCurrentStep((int) currentStep); } }); valueAnimator.start(); } }
一些理解
- Invalidate()
p.invalidateChild(this, damage); Parent父类 parent = parent.invalidateChildInParent(location, dirty); if (view != null) { // Account for transform on current parent Matrix m = view.getMatrix(); if (!m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set((int) Math.floor(boundingRect.left), (int) Math.floor(boundingRect.top), (int) Math.ceil(boundingRect.right), (int) Math.ceil(boundingRect.bottom)); } } } while (parent != null); mView.draw(canvas);
Invalidate() 流程:一路往上寻找父view,跑到最外层
Draw() -> dispatchDraw() 一路往下画 最终画到当前调用invalidate到view的onDraw()方法
invalidate牵连着整个layout布局中的view
- 为什么不能在子线程中更新UI
开了线程 更新UI会会调用到 ViewRootImpl.checkThread()
checkThread() 用来检测线程
// if (mThread != Thread.currentThread()) { // Thread.currentThread()是子线程 mThread 在构造函数中初始化的 Thread.currentThread() 主线程mainThread if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); }
- 设置UI时经常会用到的setText() setImageView()等方法会调用onInvalidate()容易产生过度绘制的问题
- 如果是自己画不用系统的布局嵌套 可以减少绘制次数 但是开发效率又低了
原作者课程链接
以上是关于2021-08-12的主要内容,如果未能解决你的问题,请参考以下文章