自定义View--仿QQ运动步数

本文详细介绍了在Android中创建一个自定义View,用于显示步数的环形进度条。通过自定义属性,设置了外圈颜色、内圈颜色、步数颜色和字体大小等。在onMeasure()方法中确定宽高,onDraw()方法中进行绘制,同时提供了通过属性动画更新步数的功能。
摘要由CSDN通过智能技术生成

   自定义View三部曲:onMeasure()\onDraw()\onTouch()

一、实现分析

    1.1 固定不动的大圆弧 color borderWidth
    1.2 可以变化的小圆弧 color borderWidth
    1.3 中间的步数文字 coloe textSIze
    自定义View,为了调用者方便可以使用自定义属性

二、实现步骤:

       2.1 自定义属性

		<?xml version="1.0" encoding="utf-8"?>
		<resources>

			<declare-styleable name="QQTepView">
				<attr name="outerColor" format="color"/>
				<attr name="indoorColor" format="color"/>
				<attr name="borderWidth" format="dimension"/>
				<attr name="mTextSize" format="dimension"/>
				<attr name="stepColor" format="color"/>
			</declare-styleable>
		</resources>

      2.2 使用属性

       <com.example.qqview.QQTepView
			android:id="@+id/qqTepView"
			android:layout_width="match_parent"
			android:layout_height="366dp"
			app:borderWidth="30dp"
			app:indoorColor="@color/design_default_color_error"
			app:mTextSize="15sp"
			app:outerColor="@color/design_default_color_primary"
			app:stepColor="@color/design_default_color_error">
		</com.example.qqview.QQTepView>

     2.3 通过构造函数获取属性值、初始化画笔

    public QQTepView(Context context) {
        this(context,null);
    }

    public QQTepView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public QQTepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    public QQTepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.QQTepView);
        indoorColor = typedArray.getColor(R.styleable.QQTepView_indoorColor,indoorColor);
        outColor = typedArray.getColor(R.styleable.QQTepView_outerColor,outColor);
        stepColor = typedArray.getColor(R.styleable.QQTepView_stepColor,stepColor);
        //设计到字体大小的需要进行类型强转,dp————》sp,这里没有进行强转
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.QQTepView_mTextSize,mTextSize);
        borderWidth = typedArray.getDimensionPixelSize(R.styleable.QQTepView_borderWidth,borderWidth);
        typedArray.recycle();
        //1.分析效果
        //2.确定自定义属性 编写attrs.xml
        //3.在布局中使用
        //4.在自定义View中获取属性
        //5.onMeasure():确定 宽高
        //6画图
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(outColor);
        paint.setStrokeWidth(borderWidth);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeCap(Paint.Cap.ROUND);

        indoorpaint = new Paint();
        indoorpaint.setStrokeWidth(borderWidth);
        indoorpaint.setColor(indoorColor);
        indoorpaint.setStyle(Paint.Style.STROKE);
        indoorpaint.setAntiAlias(true);
        paint.setStrokeCap(Paint.Cap.ROUND);

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(stepColor);
        textPaint.setTextSize(mTextSize);

    }

     2.4 确定宽高、进行绘制

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //1.调用着在布局文件中可能出现的布局为 Wrap_content,但这种情况不允许
        //获取模式进行判断
        //高度和宽度不一致,取最小值,确保是个正方形
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if(widthMode == MeasureSpec.AT_MOST){
            width = 80;
            Log.d(TAG,"不允许");
        }
        if(heightMode == MeasureSpec.AT_MOST){
            height = 80;
            Log.d(TAG,"不允许");
        }

        //三元运算
        setMeasuredDimension(width>height?height:width,width>height?height:width);


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        RectF rect = new RectF(borderWidth,borderWidth,getWidth()- borderWidth,getHeight()-borderWidth);
        canvas.drawArc(rect,135,270,false,paint);
//               if(stepMax == 0)return;
        if(stepMax != 0) {
            float sweepAngle = currentStep / (float)stepMax*270;
            Log.d(TAG, "sweepAngle:" + currentStep * stepMax);
            Log.d(TAG, "sweepAngle:"+sweepAngle );
            canvas.drawArc(rect,135,sweepAngle,false,indoorpaint);
        }
//        canvas.drawArc(rect,135,sweepAngle,false,indoorpaint);

        canvas.drawText(String.valueOf(currentStep),getWidth()/2-String.valueOf(currentStep).length()/2,getHeight()/2,textPaint);
        Log.d(TAG,"currentStep"+currentStep);

    }

     2.5 添加动画效果

    //设置步数,让步数充满变化
    public synchronized void setCurrentStep(int currentStep) {
        this.currentStep = currentStep;
        invalidate();
    }

    public synchronized void setStepMax(int stepMax) {
        this.stepMax = stepMax;
    }

    public int getCurrentStep() {
        return currentStep;
    }

    public int getStepMax() {
        return stepMax;
    }
        //使用属性动画,让步数充满变化
        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 animatedValue = (float) valueAnimator.getAnimatedValue();
                qqTepView.setCurrentStep((int) animatedValue);
            }
        });
        valueAnimator.start();

三、附件

package com.example.qqview;

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.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

public class QQTepView extends View{

    private static final String TAG ="QQStepView" ;
    private int indoorColor = Color.RED;
    private int outColor = Color.BLUE;
    private int stepColor = Color.GREEN;
    private int mTextSize;
    private int borderWidth;
    private Paint paint,indoorpaint,textPaint;


    private int stepMax = 0;
    private int currentStep = 0;

    public QQTepView(Context context) {
        this(context,null);
    }

    public QQTepView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public QQTepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    public QQTepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.QQTepView);
        indoorColor = typedArray.getColor(R.styleable.QQTepView_indoorColor,indoorColor);
        outColor = typedArray.getColor(R.styleable.QQTepView_outerColor,outColor);
        stepColor = typedArray.getColor(R.styleable.QQTepView_stepColor,stepColor);
        //设计到字体大小的需要进行类型强转,dp————》sp,这里没有进行强转
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.QQTepView_mTextSize,mTextSize);
        borderWidth = typedArray.getDimensionPixelSize(R.styleable.QQTepView_borderWidth,borderWidth);
        typedArray.recycle();
        //1.分析效果
        //2.确定自定义属性 编写attrs.xml
        //3.在布局中使用
        //4.在自定义View中获取属性
        //5.onMeasure():确定 宽高
        //6画图
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(outColor);
        paint.setStrokeWidth(borderWidth);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeCap(Paint.Cap.ROUND);

        indoorpaint = new Paint();
        indoorpaint.setStrokeWidth(borderWidth);
        indoorpaint.setColor(indoorColor);
        indoorpaint.setStyle(Paint.Style.STROKE);
        indoorpaint.setAntiAlias(true);
        paint.setStrokeCap(Paint.Cap.ROUND);

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(stepColor);
        textPaint.setTextSize(mTextSize);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //1.调用着在布局文件中可能出现的布局为 Wrap_content,但这种情况不允许
        //获取模式进行判断
        //高度和宽度不一致,取最小值,确保是个正方形
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if(widthMode == MeasureSpec.AT_MOST){
            width = 80;
            Log.d(TAG,"不允许");
        }
        if(heightMode == MeasureSpec.AT_MOST){
            height = 80;
            Log.d(TAG,"不允许");
        }

        //三元运算
        setMeasuredDimension(width>height?height:width,width>height?height:width);


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        RectF rect = new RectF(borderWidth,borderWidth,getWidth()-borderWidth,getHeight()-borderWidth);
        canvas.drawArc(rect,135,270,false,paint);
//               if(stepMax == 0)return;
        if(stepMax != 0) {
            float sweepAngle = currentStep / (float)stepMax*270;
            Log.d(TAG, "sweepAngle:" + currentStep * stepMax);
            Log.d(TAG, "sweepAngle:"+sweepAngle );
            canvas.drawArc(rect,135,sweepAngle,false,indoorpaint);
        }
//        canvas.drawArc(rect,135,sweepAngle,false,indoorpaint);

        canvas.drawText(String.valueOf(currentStep),getWidth()/2-String.valueOf(currentStep).length()/2,getHeight()/2,textPaint);
        Log.d(TAG,"currentStep"+currentStep);

    }


    //设置步数,让步数充满变化
    public synchronized void setCurrentStep(int currentStep) {
        this.currentStep = currentStep;
        invalidate();
    }

    public synchronized void setStepMax(int stepMax) {
        this.stepMax = stepMax;
    }

    public int getCurrentStep() {
        return currentStep;
    }

    public int getStepMax() {
        return stepMax;
    }
}
package com.example.qqview;

import androidx.appcompat.app.AppCompatActivity;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;

public class MainActivity extends AppCompatActivity {

    private QQTepView qqTepView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        qqTepView = findViewById(R.id.qqTepView);
        qqTepView.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 animatedValue = (float) valueAnimator.getAnimatedValue();
                qqTepView.setCurrentStep((int) animatedValue);
            }
        });
        valueAnimator.start();
    }
}
<?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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.qqview.QQTepView
        android:id="@+id/qqTepView"
        android:layout_width="match_parent"
        android:layout_height="366dp"
        app:borderWidth="30dp"
        app:indoorColor="@color/design_default_color_error"
        app:mTextSize="15sp"
        app:outerColor="@color/design_default_color_primary"
        app:stepColor="@color/design_default_color_error"></com.example.qqview.QQTepView>

</LinearLayout>

 附言:平时在向别人前辈学习,案例可能相同,但都是自己手敲的,欢迎一起学习~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Abednego_子瑜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值