2021-08-12

Posted fakerXuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021-08-12相关的知识,希望对你有一定的参考价值。

一个简单的记录步数自定义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()容易产生过度绘制的问题
    • 如果是自己画不用系统的布局嵌套 可以减少绘制次数 但是开发效率又低了

    原作者课程链接

    03自定义View - 仿QQ运动步数进度效果

以上是关于2021-08-12的主要内容,如果未能解决你的问题,请参考以下文章

2021-08-12

2021-08-12

今日份的学习笔记:2021.08.12

2021-08-12

2021-08-12

2021-08-12 jenkins + pytest + allure