介绍
转载请说明出处!网易云音乐大家都很熟悉,今天主要是来模仿以下他的音效均衡器的调节UI 效果如图:
若想实现这个效果我们需要做什么呢?我们现分析一下:
1. 首先定好我们需要画图的固定点的坐标(图中蓝色圈)。
2. 过固定点用’平滑曲线‘把这些坐标连接起来 ,必须是平滑的哦。
3. 看到效果我们首先想到的就是贝塞尔曲线。
4. 首先我们先把固定点链接起来,不让他动,然后再去实现ontouch()事件,修改坐标点。
我们先绘制过定点的曲线
让指定的坐标点使用平滑的曲线链接起来,这个问题之前看过很多文章,去年都已经做了这样的需求,只是一直没时间记录,过了这么就也忘了差不多,及时把它记下来,如图:
由图蓝色点是给的坐标点,然后我们根据给的坐标点把,她们平滑的链接起来。这里就用到了贝塞尔曲线,如果对自定义view还不是很熟悉的同事,可以去看我的其他文章关于自定义view的专栏。
具体什么是贝塞尔曲线,这里就不说了,推荐大家去看一个文章写的很好:自带美感的贝塞尔曲线原理与实战——Android高级UI
之前我也参考了很多文章,都讲了很多的数学公式。
其实我们只要记住1点就行:
任意相邻的三个点之间第一个点和第三个点的连线和第二个点的切线的斜率是相等的
如图我们任取其中一段曲线:
注释:蓝色是给的定点,绿色是我们求得的贝塞尔曲线控制点,具体怎么求接下来我们会说,先理解我们的概念
求解控制点
1. 我们知道蓝色的点是我们已知的坐标点(x,y),初中学过 y = kx + b; 既然我们定点坐标已知,那我们把这个定点的斜率求出来,然后再根据 (已知斜率 过定点 求一条 直线)这个应该比较容易懂。
2. 通过1 我们能把直线求出来,具体取直线上那一点我们根据曲线的弯曲程度,我们可以定一个比率rate,比如我把比率调的大一点如图:
明显我们能够看出绿色的坐标点变长,曲线变得更加弯曲。
所以我们开始计算我们的控制点坐标:
private List<PointF> getControlPoints(PointF[] points){
// 计算斜率 y = kx + b => k = (y - b)/x
if (points.length < 3){
return null;
}
List<PointF> pointFList = new ArrayList<>();
float rate = 0.7f;
//从第一个控制点开始计算
float cx1 = points[0].x + (points[1].x - points[0].x)*rate;
float cy1 = points[0].y;
pointFList.add(new PointF(cx1,cy1));
for (int i =1;i<points.length-1;i++){
//第二个点
float k = (points[i+1].y - points[i-1].y)/(points[i+1].x - points[i-1].x);
float b = points[i].y - k*points[i].x;
//左边控制点
float cxLeft = points[i].x - (points[i].x - points[i-1].x)*rate;
float cyLeft = k*cxLeft + b;
pointFList.add(new PointF(cxLeft,cyLeft));
//右边控制点
float cxRight = points[i].x + (points[i+1].x - points[i].x)*rate;
float cyRight = k*cxRight + b;
pointFList.add(new PointF(cxRight,cyRight));
}
//最后一个点
float cxLast = points[points.length - 1].x - (points[points.length - 1].x - points[points.length - 2].x)*rate;
float cyLast = points[points.length - 1].y;
pointFList.add(new PointF(cxLast,cyLast));
return pointFList;
}
得到控制点的坐标然后绘制
这里用到了 path 路径不理解的可以去看看我以前的文章 Android自定义view --Path 的高级用法之-搜索按钮动画
绘制:
for (int i=0;i<mPoints.length-1;i++){
mPath.moveTo(mPoints[i].x,mPoints[i].y);
mPath.cubicTo(
fList.get(i*2).x,fList.get(i*2).y,
fList.get(i*2+1).x,fList.get(i*2+1).y,
mPoints[i+1].x,mPoints[i+1].y
);
}
到这里平滑曲线我们已经绘制好了,接下来就是ontouch()事件处理拉
onTouch()事件处理
要处理onTouch()事件首先了解的是:
1. View事件分发
2. View 拦截
3. 手指 Action_down 的时候 事件检测
4. View 事件滑动冲突(这里不存在这个问题)
5. View 消费事件
不熟悉的同学可以去搜索相关博文,这里就不作详细的讲解
事件分发
事件分发我们首先要想到的方法是 dispatchTouchEvent(MotionEvent event) 在 action_down 的时候 判断我们的事件该由那个View来消费
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int a = event.getAction();
switch (a) {
case MotionEvent.ACTION_DOWN:
x = event.getX();
y = event.getY();
// 检测是否需要拦截事件
if (!checkClickPosition(x,y)){
return false;
}
}
return super.dispatchTouchEvent(event);
}
记住一句话:任何事件都是从 ACTION_DOWN 开始的,所以不管手指 滑动到何处 ,事件的作用对象 始终是 action_down 处 的 View(如果被当前View拦截的话)
如何检测(3)
检测其实就是检测我们的 ACTION_DOWN 位置 是否在我们事件处理的范围,比如图
我们手指点的位置是否在我们的圆环位置內即:手指ACTION_DOWN的坐标(x,y)是否在圆环当前左上角和右下角的(x,y)坐标內,如何计算:
private boolean checkClickPosition(float x,float y){
for (int i =0;i<mPoints.length;i++){
if ((x < mPoints[i].x +20 && x >mPoints[i].x-20) && (y < mPoints[i].y +20 && y > mPoints[i].y-20)){
index = i;
return true;
}
}
return false;
}
这段代码是我们这个例子的坐标检测,好好理解理解
View事件消费
消费就其实就是我们调用onTouchEnvent()方法 return true,事件不往下传递。
@Override
public boolean onTouchEvent(MotionEvent event) {
int a = event.getAction();
switch (a) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//处理消费事件的地方
mPoints[index].y = event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP:
break;
}
// return true 才会消费
return true;
}
消费比较好理解,就不多说了,到此就是我们仿照网易云音乐的均衡器效果了
看似简单100多行代码,其实还是藏着很多重要的知识点的。也是面试容易问到的。
希望大家根据自己的理解去绘制,自己的图形,直接抄来的就失去他原有的意义。