介紹
轉載請說明出處!網易雲音樂大家都很熟悉,今天主要是來模仿以下他的音效均衡器的調節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多行代碼,其實還是藏着很多重要的知識點的。也是面試容易問到的。
希望大家根據自己的理解去繪製,自己的圖形,直接抄來的就失去他原有的意義。