自定义View_一个带悬浮窗的ProgressBar(中)

2024-02-04  本文已影响0人  BraveJoy

在上篇内容【自定义View_一个带悬浮窗的ProgressBar(上)】里,我们已经完成了自定义的进度条效果,那么本篇内容我们来介绍悬浮窗如何实现,先来看一下我们今天要实现的效果:

效果分析

经过上面对效果的拆解分析,我们已经大概有了实现方案,那就是使用Path进行开发。那么Path该如何绘制?从哪里作为起点开始绘制,需要绘制那些片段,是我们需要分析重中之重。

Path轨迹

由于悬浮窗底部箭头的位置是需要根据百分比动态偏移的,它影响整个Path轨迹的绘制,也就是说它影响着较多的其他变量的变化,那么我们就把箭头的位置作为起点开始绘制,绘制过程示意如下:

动作分解

我们可以把上面的动图进行分解成下面7个片段:

①、从起点开始,向左上方画一条线段
②、紧接着往左方形画一条线段
③、再继续画一个半圆弧
④、往右画一条线段
⑤、再继续画一个半圆弧
⑥、继续往左画一条线段
⑦、闭合

如何开始?

下面的箭头,我们可以把它当成一个等边三角形,并且给它一个初始高度mArrowHeight。

再仔细看下悬浮窗的动效可以发现,底部箭头是需要根据当前的百分比而发生位置偏移的,如下:

当0%的时候,箭头在最左侧,随着百分比的增大,箭头逐渐移动到最右边:

我们需要知道,整个过程中箭头移动的总距离是多少,才能控制其偏移量。

已知箭头的高度是mArrowHeight,悬浮窗左右两个半圆弧的半径是radius。

假设底部起点坐标是(firstX, firstY),那么在上面这个过程中,我们需要明确firstX移动的范围,示意图如下:

根据示意图,也就得到了firstX移动范围是:[raidus+c , getWidth() - radius - c]
移动的总距离是:

// 箭头活动范围,对应的距离长度是:distance
float distance = getWidth() - 2 * radius - 2 * c

上面的计算中,还需要求出c的值,那么如何求出c可以使用三角函数公式:
根据:

tan30° = c / mArrowHeight

可以求出:

c =tan30° * mArrowHeight

即:

double rad = 30 * Math.PI / 180.0; // 角度转成弧度
c = (float) (Math.tan(rad) * mArrowHeight);

计算坐标,绘制

我们知道,起点坐标是动态偏移的,是和当前百分比(currentPercent)相关联的,那么当百分比在某个数值时,我们需要计算线段②的长度,即下图中的②:


上图中④的长度是等于文字的宽度的,即④=textWidth,那么②(leftDistance)就等于:

// 箭头左边的线段的长度
float leftDistance = (textWidth - 2 * c) * currentPercent;

其中,文字宽度计算如下:

// 获取当前文字
private String getText() {
    return "进度 " + df.format(this.currentPercent* 100) + " %";
}

// 计算文字的宽度
private float getTextWidth() {
    String text = getText();
    return paintProgress.measureText(text, 0, text.length());
}

接下来,我们就可以逐个计算出坐标了。

第1个坐标,即起点坐标:

// 1
float firstX = radius + c + (currentPercent* distance);
float firstY = getHeight();
path.moveTo(firstX, firstY);

第2个坐标:

// 2
float secondX = firstX - c;
float secondY = getHeight() - mArrowHeight;
path.lineTo(secondX, secondY);

第3个坐标:

// 3
float thirdX = firstX - c - leftDistance;
float thirdY = getHeight() - mArrowHeight;
path.lineTo(thirdX, thirdY);

第4个坐标:

// 4
float left = firstX - c - leftDistance - radius;
float top = 0;
float right = firstX - c - leftDistance + radius;
float bottom = getHeight() - mArrowHeight;
leftRect.set(left, top, right, bottom);
path.arcTo(leftRect, 90, 180);

第5个坐标:

// 5
float fourthX = firstX - c - leftDistance + textWidth;
float fourthY = 0;
path.lineTo(fourthX, fourthY);

第6个坐标:

//6
float left2 = firstX - c - leftDistance + textWidth - radius;
float top2 = 0;
float right2 = firstX - c - leftDistance + textWidth + radius;
float bottom2 = getHeight() - mArrowHeight;
rightRect.set(left2, top2, right2, bottom2);
path.arcTo(rightRect, -90, 180);

第7个坐标:

// 7
float fifthX = firstX + c;
float fifthY = getHeight() - mArrowHeight;
path.lineTo(fifthX, fifthY);
path.close();

绘制轨迹和文字:

//绘制轨迹-填充
canvas.drawPath(path, paintFill);
//绘制轨迹-边框
canvas.drawPath(path, paintStroke);

// 绘制文字,百分比
Paint.FontMetricsInt fontMetricsInt = paintProgress.getFontMetricsInt();
float dy2 = (fontMetricsInt.bottom - fontMetricsInt.top) / 2f - fontMetricsInt.bottom;
float baseLine = (getHeight() - mArrowHeight) / 2f + dy2;
canvas.drawText(getText(), thirdX, baseLine, paintProgress);

加动画:

/**
 * 设置当前进度的数据,带动画
 *
 * @param progress 百分比
 */
public void setProgressWithAnim(float progress) {
    ValueAnimator animator = ValueAnimator.ofFloat(0, progress).setDuration(ANIMATION_DURATION);
    animator.addUpdateListener(animation -> {
        currentPercent = (float) animation.getAnimatedValue() / 100.0f;
        invalidate();
    });
    animator.start();
}

至此,我们已经完成了今天要实现的效果。

上一篇 下一篇

猜你喜欢

热点阅读