如何在android中制作一個(gè)方向輪盤(pán)詳解
先上效果圖

原理很簡(jiǎn)單,其實(shí)就是一個(gè)自定義的view
通過(guò)觀察,很容易發(fā)現(xiàn),我們自己的輪盤(pán)就兩個(gè)view需要繪制,一個(gè)是外面的圓盤(pán),一個(gè)就隨手指移動(dòng)的滑塊;
外面的圓盤(pán)很好繪制,內(nèi)部的滑塊則需要采集手指的位置,根據(jù)手指的位置計(jì)算出滑塊在大圓內(nèi)的位置;
最后,我們做的UI不是單純做一個(gè)UI吧,肯定還是要用于實(shí)際應(yīng)用中去,所以要加一個(gè)通用性很好的回調(diào).
計(jì)算滑塊位置的原理:
- 當(dāng)觸摸點(diǎn)在大圓與小圓的半徑差之內(nèi):
那么滑塊的位置就是觸摸點(diǎn)的位置 - 當(dāng)觸摸點(diǎn)在大圓與小圓的半徑差之外:
已知大圓圓心坐標(biāo)(cx,cy),大圓半徑rout,小圓半徑rinside,觸摸點(diǎn)的坐標(biāo)(px,py)
求小圓的圓心(ax,ay)?

作為經(jīng)過(guò)九義的你我來(lái)說(shuō),這不就是一個(gè)簡(jiǎn)簡(jiǎn)單單的數(shù)學(xué)題嘛,很容易就求解出小圓的圓心位置了。
利用三角形相似:

通用性很好的接口:
滑塊在圓中的位置,可以很好的用一個(gè)二位向量來(lái)表示,也可以用兩個(gè)浮點(diǎn)的變量來(lái)表示;

這個(gè)接口就可以很好的表示了小圓在大圓的位置了,他們的取值范圍是[-1,1]
小技巧:
為了小圓能始終在脫手后回到終點(diǎn)位置,我們?cè)O(shè)計(jì)了一個(gè)動(dòng)畫(huà),當(dāng)然,實(shí)際情況中有一種情況是,你移動(dòng)到某個(gè)位置后,脫手后位置不能動(dòng),那你禁用這個(gè)動(dòng)畫(huà)即可。
代碼部分
tips:代碼部分的變量名與原理的變量名有出入
public class ControllerView extends View implements View.OnTouchListener {
private Paint borderPaint = new Paint();//大圓的畫(huà)筆
private Paint fingerPaint = new Paint();//小圓的畫(huà)筆
private float radius = 160;//默認(rèn)大圓的半徑
private float centerX = radius;//大圓中心點(diǎn)的位置cx
private float centerY = radius;//大圓中心點(diǎn)的位置cy
private float fingerX = centerX, fingerY = centerY;//小圓圓心的位置(ax,ay)
private float lastX = fingerX, lastY = fingerY;//小圓自動(dòng)回歸中點(diǎn)動(dòng)畫(huà)中上一點(diǎn)的位置
private float innerRadius = 30;//默認(rèn)小圓半徑
private float radiusBorder = (radius - innerRadius);//大圓減去小圓的半徑
private ValueAnimator positionAnimator;//自動(dòng)回中的動(dòng)畫(huà)
private MoveListener moveListener;//移動(dòng)回調(diào)的接口
public ControllerView(Context context) {
super(context);
init(context, null, 0);
}
public ControllerView(Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public ControllerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
//初始化
private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ControllerView);
int fingerColor = typedArray.getColor(R.styleable.ControllerView_fingerColor,
Color.parseColor("#3fffffff"));
int borderColor = typedArray.getColor(R.styleable.ControllerView_borderColor,
Color.GRAY);
radius = typedArray.getDimension(R.styleable.ControllerView_radius, 220);
innerRadius = typedArray.getDimension(R.styleable.ControllerView_fingerSize, innerRadius);
borderPaint.setColor(borderColor);
fingerPaint.setColor(fingerColor);
lastX = lastY = fingerX = fingerY = centerX = centerY = radius;
radiusBorder = radius - innerRadius;
typedArray.recycle();
}
setOnTouchListener(this);
positionAnimator = ValueAnimator.ofFloat(1);
positionAnimator.addUpdateListener(animation -> {
Float aFloat = (Float) animation.getAnimatedValue();
changeFingerPosition(lastX + (centerX - lastX) * aFloat, lastY + (centerY - lastY) * aFloat);
});
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(getActualSpec(widthMeasureSpec), getActualSpec(heightMeasureSpec));
}
//處理wrapcontent的測(cè)量
//默認(rèn)wrapcontent,沒(méi)有做matchParent,指定大小的適配
//view實(shí)際的大小是通過(guò)大圓半徑確定的
public int getActualSpec(int spec) {
int mode = MeasureSpec.getMode(spec);
int len = MeasureSpec.getSize(spec);
switch (mode) {
case MeasureSpec.AT_MOST:
len = (int) (radius * 2);
break;
}
return MeasureSpec.makeMeasureSpec(len, mode);
}
//繪制
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(centerX, centerY, radius, borderPaint);
canvas.drawCircle(fingerX, fingerY, innerRadius, fingerPaint);
}
@Override public boolean onTouch(View v, MotionEvent event) {
float evx = event.getX(), evy = event.getY();
float deltaX = evx - centerX, deltaY = evy - centerY;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//圓外按壓不生效
if (deltaX * deltaX + deltaY * deltaY > radius * radius) {
break;
}
case MotionEvent.ACTION_MOVE:
//如果觸摸點(diǎn)在圓外
if (Math.abs(deltaX) > radiusBorder || Math.abs(deltaY) > radiusBorder) {
float distance = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
changeFingerPosition(centerX + (deltaX * radiusBorder / distance),
centerY + (deltaY * radiusBorder / distance));
} else { //如果觸摸點(diǎn)在圓內(nèi)
changeFingerPosition(evx, evy);
}
positionAnimator.cancel();
break;
case MotionEvent.ACTION_UP:
positionAnimator.setDuration(1000);
positionAnimator.start();
break;
}
return true;
}
/**
* 改變位置的回調(diào)出來(lái)
*/
private void changeFingerPosition(float fingerX, float fingerY) {
this.fingerX = fingerX;
this.fingerY = fingerY;
if (moveListener != null) {
float r = radius - innerRadius;
if (r == 0) {
invalidate();
return;
}
moveListener.move((fingerX - centerX) / r, (fingerY - centerY) / r);
}
invalidate();
}
@Override protected void finalize() throws Throwable {
super.finalize();
positionAnimator.removeAllListeners();
}
public void setMoveListener(
MoveListener moveListener) {
this.moveListener = moveListener;
}
/**
*回調(diào)事件的接口
*
**/
public interface MoveListener {
void move(float dx, float dy);
}
}
style.xml
<declare-styleable name="ControllerView"> <attr name="fingerColor" format="color" /> <attr name="borderColor" format="color" /> <attr name="fingerSize" format="dimension" /> <attr name="radius" format="dimension" /> </declare-styleable>
寫(xiě)在最后:
這個(gè)是一個(gè)智能小車(chē)的安卓控制端的一部分demo,到此這篇關(guān)于如何在android中制作一個(gè)方向輪盤(pán)的文章就介紹到這了,更多相關(guān)android制作方向輪盤(pán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)右邊抽屜Drawerlayout效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)右邊抽屜Drawerlayout效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
android實(shí)現(xiàn)圖片閃爍動(dòng)畫(huà)效果的兩種實(shí)現(xiàn)方式(實(shí)用性高)
本文通過(guò)兩種方法給大家講解了android實(shí)現(xiàn)圖片閃爍動(dòng)畫(huà)效果,實(shí)用性非常高,對(duì)這兩種方法感興趣的朋友一起通過(guò)本文學(xué)習(xí)吧2016-09-09
Android項(xiàng)目開(kāi)發(fā) 教你實(shí)現(xiàn)Periscope點(diǎn)贊效果
這篇文章主要為大家分享了Android項(xiàng)目開(kāi)發(fā),一步一步教你實(shí)現(xiàn)Periscope點(diǎn)贊效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-12-12
最常見(jiàn)的猜拳小游戲Android代碼實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了最常見(jiàn)的猜拳小游戲Android代碼實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08
Java程序員轉(zhuǎn)Android開(kāi)發(fā)必讀經(jīng)驗(yàn)一份
小編最近幾日偷偷的發(fā)現(xiàn)部分Java程序員想轉(zhuǎn)安卓開(kāi)發(fā),故此加緊補(bǔ)充知識(shí),為大家搜集資料,積極整理前人的經(jīng)驗(yàn),希望可以給正處于困惑中的你,帶來(lái)些許的幫助。2017-11-11
Android使用Intent發(fā)送短信的實(shí)現(xiàn)方法
這篇文章主要介紹了Android使用Intent發(fā)送短信的實(shí)現(xiàn)方法,結(jié)合簡(jiǎn)單實(shí)例形式分析了Android短信發(fā)送功能的實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-07-07
基于Android實(shí)現(xiàn)定時(shí)刷新功能
定時(shí)刷新是一種常見(jiàn)的應(yīng)用需求,例如自動(dòng)加載新數(shù)據(jù)、定時(shí)更新 UI、動(dòng)畫(huà)循環(huán)播放、實(shí)時(shí)監(jiān)控等場(chǎng)景中都需要定時(shí)刷新頁(yè)面,Android 平臺(tái)提供了多種實(shí)現(xiàn)定時(shí)刷新的方式,本文將結(jié)合實(shí)例詳細(xì)講解如何實(shí)現(xiàn)定時(shí)刷新功能,需要的朋友可以參考下2025-04-04
Android中imageView圖片放大縮小及旋轉(zhuǎn)功能示例代碼
這篇文章主要介紹了Android中imageView圖片放大縮小及旋轉(zhuǎn)功能示例代碼,需要的朋友可以參考下2017-08-08

