自定义View(5)玩转字体变色-进度条、形状切换

上几篇已经讲了自定义View即自定义View变色的基本使用,为了更熟悉自定义View的使用,上篇结尾有两个任务,这篇就来讲讲是怎么实现的,包括打造炫酷的进度条、仿58同城数据加载。
在这里插入图片描述

1. 打造炫酷的进度条

1.1 思路

我们要做的炫酷的进度条,可以继承View,也可以继承TextView,这里以继承TextView为例来实现,其实分为三部分,外圈、内圈、中间的字符串,这个跟《自定义View(3)仿QQ运动步数进度》可谓是非常相似了。不过对于外圈,除了可以用弧形表示外,也可以用圆来表示。

1.2 自定义View-炫酷的进度条

1.2.1 新建attrs.xml

首先还是新建attrs.xml,定义需要用到的自定义属性。

<!-- /CustomView/view5/app/src/main/res/values/attrs.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ProgressView">
        <attr name="outColor" format="color"/>
        <attr name="innerColor" format="color"/>
        <attr name="borderWidget" format="dimension"/>
    </declare-styleable>
</resources>

1.2.2 新建ProgressView继承TextView

// /CustomView/View5/app/src/main/java/com/example/view5/ProgressView.java
public class ProgressView extends androidx.appcompat.widget.AppCompatTextView {
    private Paint mOuterPaint;  // 总进度条画笔
    private int mOuterColor = Color.RED;  // 总进度条颜色,并设置默认
    private int mStartAngle = 0;  // 弧度开始角度
    private float mEndAngle = 360f;   // 弧度结束角度

    private Paint mInnerPaint;  // 已走画笔
    private int mInnerColor = Color.BLUE;  // 已走颜色,并设置默认

    private float mBorderWidget = 40;  // 默认圆弧宽度

    private Paint mTextPaint;  // 文字画笔
    private float mProgress = 0;
    private String mProgressText;   // 当前进度

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

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

    public ProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mProgressText = getText().toString();
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ProgressView);
        mBorderWidget = ta.getDimension(R.styleable.ProgressView_borderWidget, mBorderWidget);
        mOuterColor = ta.getColor(R.styleable.ProgressView_outColor, mOuterColor);
        mInnerColor = ta.getColor(R.styleable.ProgressView_innerColor, mInnerColor);
        ta.recycle();  // 回收

        mOuterPaint = new Paint(); // 创建总画笔
        mOuterPaint.setAntiAlias(true); // 取消锯齿
        mOuterPaint.setColor(mOuterColor);  // 设置颜色
        mOuterPaint.setStrokeWidth(mBorderWidget);  // 设置圆弧宽度
        mOuterPaint.setStyle(Paint.Style.STROKE);  // 设置圆弧Style
        mOuterPaint.setStrokeCap(Paint.Cap.ROUND);  // 设置起始,结束圆角

        mInnerPaint = new Paint();  // 创建进度画笔
        mOuterPaint.setAntiAlias(true);  // 抗锯齿
        mInnerPaint.setColor(mInnerColor);  // 设置颜色
        mInnerPaint.setStrokeWidth(mBorderWidget);  // 设置圆弧宽度
        mInnerPaint.setStyle(Paint.Style.STROKE);  // 设置圆弧Style
        mInnerPaint.setStrokeCap(Paint.Cap.ROUND);  // 设置起始,结束圆角

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);   // 设置抗锯齿
        mTextPaint.setDither(true);      // 设置放抖动
        mTextPaint.setTextSize(getTextSize());  // 设置文字的大小,就是xml设置的大小
        mTextPaint.setColor(Color.RED);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);

        // 画外圆弧
        // 问题:边缘显示不全   描边有宽度  mBorderWidget
        float half = mBorderWidget / 2;
        // 描边有宽度borderWidget,如果不重新算rectF,绘制就会显示不全。这里将整个图多绘制半个宽度后,就刚刚好了。
        @SuppressLint("DrawAllocation") RectF rectF = new RectF(half, half, getWidth() - half, getHeight() - half);
        // canvas.drawArc(rectF, mStartAngle, mEndAngle, false, mOuterPaint); // 这里如果是True的话,下面就是闭合的
        int center = getWidth()/2;
        canvas.drawCircle(center,center,center-half,mOuterPaint);  // 画一个360°的圆弧,或者画一个圆都行

        // 画内圆弧
        canvas.drawArc(rectF, mStartAngle, mEndAngle/100*mProgress, false, mInnerPaint);
        // canvas.drawArc(rectF, mStartAngle, mProgress, false, mInnerPaint);  // 第二种方法

        // 画文本
        mProgressText = mProgress + "%";
        // mProgressText = Math.round(mProgress / mEndAngle * 100) + "%";  // 第二种方法
        // 获取文字排版信息
        Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
        int baseLine = getHeight() / 2 + (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;  // 计算基线
        // 获取文本宽度
        float textWidget = mTextPaint.measureText(mProgressText);
        int dx = (int) (getWidth() / 2 - textWidget / 2);  // 控件的一半减去文字的一半,算出文字的初始绘制值
        canvas.drawText(mProgressText, dx, baseLine, mTextPaint);
    }

    public synchronized void setProgress(int progress) {
        this.mProgress = progress;
        //不断绘制 invalidate中会调用onDraw方法
        invalidate();
    }

    public synchronized void setAnimatorStep(int progress) {
        // 属性动画
        ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0,progress);  // 从0变化到1
        valueAnimator.setDuration(3000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                float currentStep = (float) animation.getAnimatedValue();
                setProgress((int) currentStep);
            }
        });
        valueAnimator.start();
    }
}

1.2.3 修改activity_main.xml

<!-- /CustomView/View5/app/src/main/res/layout/activity_main.xml -->
<?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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.example.view5.ProgressView
        android:layout_gravity="center"
        android:id="@+id/progress_view"
        android:background="@color/grey"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:text="Hello World!"
        android:textSize="40sp"
        app:outColor="@color/white"
        app:innerColor="@color/black"
        />

</LinearLayout>

1.2.4 修改MainActivity.java

// /CustomView/View5/app/src/main/java/com/example/view5/MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        @SuppressLint({"MissingInflatedId", "LocalSuppress"}) final ProgressView progressView = (ProgressView) findViewById(R.id.progress_view);
        progressView.setAnimatorStep(100);
//        progressView.setAnimatorStep(360);  第二种方法
    }
}

1.2.5 效果展示

在这里插入图片描述

2. 仿58同城数据加载

2.1 思路

画三个图形,分别是正方形、三角形、圆形。然后每秒钟画其中一个。这里的正方形和圆形都有现存的方法可以调用,而三角形相对比较复杂,需要用到Path。
在这里插入图片描述

2.2 自定义View-仿58同城数据加载

2.2.1 添加attrs.xml

<!-- /CustomView/View5/app/src/main/res/values/attrs.xml -->
    ...
    <declare-styleable name="ShapeView">
        <attr name="squareColor" format="color"/>
        <attr name="triangleColor" format="color"/>
        <attr name="circleColor" format="color"/>
    </declare-styleable>

2.2.2 新建ShapeView继承View

// /CustomView/View5/app/src/main/java/com/example/view5/ShapeView.java
public class ShapeView extends View {
    private Paint mSquarePaint;
    private int mSquareColor = Color.RED;
    private Paint mTrianglePaint;
    private int mTriangleColor = Color.YELLOW;
    private Paint mCirclePaint;
    private int mCircleColor = Color.BLUE;
    private String mProgress = "";
    private Path mPath;
    private final String[] shape = {"Square","Triangle","Circle"};
    int currentIndex = 0;
    int aimIndex = 0;
    public ShapeView(Context context) {
        this(context, null);
    }

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

    public ShapeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ShapeView);
        mSquareColor = ta.getColor(R.styleable.ShapeView_squareColor, mSquareColor);
        mTriangleColor = ta.getColor(R.styleable.ShapeView_triangleColor, mTriangleColor);
        mCircleColor = ta.getColor(R.styleable.ShapeView_circleColor, mCircleColor);
        ta.recycle();

        mSquarePaint = new Paint();
        mSquarePaint.setAntiAlias(true); // 取消锯齿
        mSquarePaint.setStyle(Paint.Style.FILL);  // 实心,不设置其实默认也是实心
        mSquarePaint.setColor(mSquareColor);  // 设置颜色

        mTrianglePaint = new Paint();
        mTrianglePaint.setAntiAlias(true); // 取消锯齿
        mTrianglePaint.setColor(mTriangleColor);  // 设置颜色

        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true); // 取消锯齿
        mCirclePaint.setColor(mCircleColor);  // 设置颜色
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (Objects.equals(mProgress, "")) {return;}
        switch (mProgress){
            case "Square":
                canvas.drawRect(0,0,getWidth(),getHeight(),mSquarePaint);
                break;
            case "Triangle":
                float y = (float) (0.5 * Math.sqrt(3) * getHeight());
                if (mPath == null) {
                    // 添加if判断,不需要反复new Path(),减少开销
                    mPath = new Path();  // 通过路线来画,依次传入三个点的坐标,连起来。
                    mPath.moveTo(0, y);
                    mPath.lineTo(getWidth()/2, 0);
                    mPath.lineTo(getWidth(), y);
                    mPath.close();   // 路径闭合
                }
                canvas.drawPath(mPath, mTrianglePaint);
                break;
            case "Circle":
                int center = getWidth()/2;
                canvas.drawCircle(center,center,center,mCirclePaint);  // 画一个360°的圆弧,或者画一个圆都行
                break;
        }
    }

    public synchronized void setProgress(String progress) {
        this.mProgress = progress;
        //不断绘制 invalidate中会调用onDraw方法
        invalidate();
    }

    public synchronized void setAnimatorStep() {
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        Runnable task = new Runnable() {
            @Override
            public void run() {
                setProgress(shape[currentIndex]);
                currentIndex = (currentIndex + 1) % shape.length; // 循环数组
                aimIndex++;
                // 数组处理完毕,关闭执行器
                if (aimIndex >= 100) {
                    // executorService.shutdown();
                }
            }
        };

        // 每秒执行一次任务
        executorService.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
    }
}

2.2.3 修改activity_main.xml

<!-- /CustomView/View5/app/src/main/res/layout/activity_main.xml -->
    ...
    <TextView
        android:layout_width="match_parent"
        android:layout_height="10dp" />

    <com.example.view5.ShapeView
        android:layout_gravity="center"
        android:id="@+id/shape_view"
        android:layout_width="200dp"
        android:layout_height="200dp" />

2.2.4 修改

// /CustomView/View5/app/src/main/java/com/example/view5/MainActivity.java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        final ShapeView shapeView = findViewById(R.id.shape_view);
        shapeView.setAnimatorStep();
    }

2.2.5 效果展示

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值