MagicButton

前言

感觉最近自己比较浮躁,对于Android似乎失去了以往的热情,可能是最近公司没有把重心放在APP上,比较清闲,而自己是典型的任务驱动型的程序猿。如果是跟任务相关的技能,不管多难,我都可以以百分之百的精力去专研,而如果跟任务无关,多简单的技能,都懒的去掌握 -_-||。

最近更是抛弃了Android,跑去鼓捣了一下世界上最好的开发语言—PHP,简单入门,最后完成公司官网搭建。实话说 感觉PHP对于初学者还是比较友善的,入门挺简单,而且好多语法跟JAVA挺类似的,有过JAVA基础的感觉都能很快掌握。而且免编译,即时发布即时生效的确比JAVA更加快捷方便。不过作为习惯了JAVA环境的我来说,还是更喜欢JAVA更加严谨的编程规范,而且PHP开发前后端混合在一起开发真的很是头疼,而且我很不喜欢去写那一层又一层的<div>,真的是写一个页面都让人写到烦躁,所以我果断没有在这上面再深入。。。(其实都是给自己三分钟热度的借口 😅)

出轨一时爽,正室是王道啊,所以我果断又回到了Android的怀抱。

做了两年左右的Android开发,发现自己的基础知识还是比较薄弱的,所以决定静下心来好好啃一啃自己的基础知识,第一个需要啃的硬骨头就是自定义View。

MagicButton

听这名字是不是感觉很牛X 😄 期不期待。。

接下来就是见证 Magic的时刻

。。。。

。。。

。。

….

坑爹呢!这么简单的一个控件!敢叫这么高大上的名字!😡

各位看官请息怒,气坏了身体多不好,反正你气坏了身体我也不改😄你能咋地!!

原理解析

原理其实也很简单,左右各绘制两个小圆,然后在两个小圆之间绘制两条二阶贝塞尔曲线连接。

额。。这个解析我自己都听不下去了。。还是直接上代码吧

为了方便,我将两个小圆的相关参数封装成一个对象 Circular

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

public class Circular {

private static final float C = 0.551915024494f; //常量

//圆中心坐标
float centerX;
float centerY;

//圆半径
float radius;

//圆与贝塞尔曲线相接的上顶点
float topPointX;
float topPointY;

//圆与贝塞尔曲线相接的下顶点
float bottomPointX;
float bottomPointY;

float minRadius = 0;


public void setCenter(float centerX, float centerY, float radius) {
this.centerX = centerX;
this.centerY = centerY;
this.radius = radius;
// initPoint();

}
}

关于绘制贝塞尔曲线的两个点,最初的时候选择的是圆心垂直方向与圆相交的点

1
2
3
4
5
6
7
8
//直接以圆上顶点与下顶点作为与贝塞尔曲线相切的点
private void initPoint() {
topPointX = centerX;
topPointY = -radius;

bottomPointX = centerX;
bottomPointY = radius;
}

但是这样选择的两个点绘制出的贝塞尔曲线于圆有较大的相交的部分,不平滑,如下图

magic_button1

如果选择两个圆圆心的中点与圆相切的切点作为贝塞尔曲线的绘制点,两个圆圆心的中点作为贝塞尔曲线的控制点,绘制的图形会更加平滑

magic_button2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//为了使贝塞尔曲线与圆相接的更加平滑,动态计算相切的点
public void setHalfPoint(float halfPointX, float halfPointY) {

float halfDistance = (float) Math.sqrt((halfPointX - centerX) * (halfPointX - centerX) + (halfPointY - centerY) * (halfPointY - centerY));

float a = (float) Math.sqrt(halfDistance * halfDistance - radius * radius);
float b = radius * (radius / halfDistance);
float c = radius * (a / halfDistance);

topPointX = (halfPointX - centerX) > 0 ? centerX + b : centerX - b;
topPointY = centerY - c;

bottomPointX = (halfPointX - centerX) > 0 ? centerX + b : centerX - b;
bottomPointY = centerY + c;
}

绘制贝塞尔曲线的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 绘制贝塞尔曲线
*
* @param canvas
*/
private void drawBezier(Canvas canvas) {
Path path = new Path();
path.moveTo(leftCircular.topPointX, leftCircular.topPointY);

path.quadTo((rightCircular.centerX + leftCircular.centerX) / 2, 0, rightCircular.topPointX, rightCircular.topPointY);
path.lineTo(rightCircular.bottomPointX, rightCircular.bottomPointY);
path.quadTo((rightCircular.centerX + leftCircular.centerX) / 2, 0, leftCircular.bottomPointX, leftCircular.bottomPointY);
path.close();

canvas.drawPath(path, mainPaint);
}

这样View的onDraw(Canvas canvas)方法中就比较简单了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

// canvas.drawColor(Color.GRAY);
//绘制辅助坐标系
// drawCoordinateSystem(canvas);
canvas.translate(centerX, centerY);

//绘制背景
drawbgLine(canvas);


mainPaint.setColor(paintColor);
//绘制小圆
if (rightCircular.isNeedDraw())
canvas.drawCircle(rightCircular.centerX, rightCircular.centerY, rightCircular.radius, mainPaint);

if (leftCircular.isNeedDraw())
canvas.drawCircle(leftCircular.centerX, leftCircular.centerY, leftCircular.radius, mainPaint);

//绘制圆中间的贝塞尔曲线
if (rightCircular.isNeedDraw() && leftCircular.isNeedDraw())
drawBezier(canvas);
}

点击切换的时候使用 ValueAnimator 来进行动态变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 切换动画
*/
private void startAnimator() {
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private float leftCenterX;
private float rightCenterX;
private float leftRadius;
private float rightRadius;

@Override
public void onAnimationUpdate(ValueAnimator animation) {
float curValue = (float) animation.getAnimatedValue();

if (!isChecked) {
rightRadius = curValue * (height / 2 - 4);
leftRadius = (1 - curValue) * (height / 2 - 4);

rightCenterX = (-width / 2 + height) + (width - height / 2 * 3) * curValue;
leftCenterX = (-width / 2 + height / 2) + (height / 2) * curValue;
} else {
rightRadius = (1 - curValue) * (height / 2 - 4);
leftRadius = curValue * (height / 2 - 4);

rightCenterX = (width / 2 - height / 2) - (height / 2) * curValue;
leftCenterX = (width / 2 - height) - (width - height / 2 * 3) * curValue;
}

rightCircular.setCenter(rightCenterX, 0, rightRadius);
leftCircular.setCenter(leftCenterX, leftCircular.centerY, leftRadius);

rightCircular.setHalfPoint((leftCircular.centerX + leftCircular.radius + rightCircular.centerX - rightCircular.radius) / 2, (leftCircular.centerY + rightCircular.centerY) / 2);
leftCircular.setHalfPoint((leftCircular.centerX + leftCircular.radius + rightCircular.centerX - rightCircular.radius) / 2, (leftCircular.centerY + rightCircular.centerY) / 2);
postInvalidate();
}
});

animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isChecked = !isChecked;
if (onCheckChangeListenter != null) {
onCheckChangeListenter.onCheck(isChecked);
}
isAnimatorStart = false;
}
});


ValueAnimator colorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), isChecked ? checkedColor : unCheckedColor, isChecked ? unCheckedColor : checkedColor);
colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
paintColor = (int) animation.getAnimatedValue();
}
});

colorAnimator.setDuration(duration);
animator.setDuration(duration);

if (!isAnimatorStart) {
colorAnimator.start();
animator.start();
isAnimatorStart = true;
}

}

小结

自定义View还是有一些套路的,例如自定义属性的解析,宽高的计算获取,都基本上差不多的套路。感觉自定义View最麻烦的地方就是大量的数学几何运算😂,可怜我连一个最简单的三角函数问题都忘了,百度了半天才解决😂。。。。

哎。。慢慢啃吧。。。

项目github地址:https://github.com/lichenming0516/MagicButton-master

坚持原创技术分享,您的支持将鼓励我继续创作!