如何在android中制作一個(gè)方向輪盤詳解
先上效果圖
原理很簡(jiǎn)單,其實(shí)就是一個(gè)自定義的view
通過觀察,很容易發(fā)現(xiàn),我們自己的輪盤就兩個(gè)view需要繪制,一個(gè)是外面的圓盤,一個(gè)就隨手指移動(dòng)的滑塊;
外面的圓盤很好繪制,內(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)過九義的你我來(lái)說,這不就是一個(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)畫,當(dāng)然,實(shí)際情況中有一種情況是,你移動(dòng)到某個(gè)位置后,脫手后位置不能動(dòng),那你禁用這個(gè)動(dòng)畫即可。
代碼部分
tips:代碼部分的變量名與原理的變量名有出入
public class ControllerView extends View implements View.OnTouchListener { private Paint borderPaint = new Paint();//大圓的畫筆 private Paint fingerPaint = new Paint();//小圓的畫筆 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)畫中上一點(diǎn)的位置 private float innerRadius = 30;//默認(rèn)小圓半徑 private float radiusBorder = (radius - innerRadius);//大圓減去小圓的半徑 private ValueAnimator positionAnimator;//自動(dòng)回中的動(dòng)畫 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,沒有做matchParent,指定大小的適配 //view實(shí)際的大小是通過大圓半徑確定的 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>
寫在最后:
這個(gè)是一個(gè)智能小車的安卓控制端的一部分demo,到此這篇關(guān)于如何在android中制作一個(gè)方向輪盤的文章就介紹到這了,更多相關(guān)android制作方向輪盤內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
AndroidStudio中AVD虛擬機(jī)設(shè)備空間不足調(diào)試過程出現(xiàn)的黑屏問題及解決方案
這篇文章主要介紹了解決AndroidStudio中AVD虛擬機(jī)設(shè)備空間不足調(diào)試過程出現(xiàn)的黑屏問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Android AIDL實(shí)現(xiàn)進(jìn)程間通信探索
這篇文章主要為大家詳細(xì)介紹了Android AIDL實(shí)現(xiàn)進(jìn)程間通信的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09簡(jiǎn)單實(shí)現(xiàn)Android本地音樂播放器
這篇文章主要為大家詳細(xì)介紹了如何簡(jiǎn)單實(shí)現(xiàn)Android本地音樂播放器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Android提高之TelephonyManager功能探秘
這篇文章主要介紹了Android的TelephonyManager功能,可以幫助讀者更好的理解Java反射機(jī)制,需要的朋友可以參考下2014-08-08Android Rreact Native 常見錯(cuò)誤總結(jié)
這篇文章主要介紹了Android Rreact Native 常見錯(cuò)誤總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-06-06XListView實(shí)現(xiàn)多條目網(wǎng)絡(luò)數(shù)據(jù)刷新加載 網(wǎng)絡(luò)加載圖片
這篇文章主要為大家詳細(xì)介紹了XListView實(shí)現(xiàn)多條目網(wǎng)絡(luò)數(shù)據(jù)刷新加載,網(wǎng)絡(luò)加載圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11android RecycleView實(shí)現(xiàn)下拉刷新和上拉加載
這篇文章主要為大家詳細(xì)介紹了android RecycleView實(shí)現(xiàn)下拉刷新和上拉加載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06Android IPC機(jī)制利用Messenger實(shí)現(xiàn)跨進(jìn)程通信
這篇文章主要介紹了Android IPC機(jī)制中 Messager 實(shí)現(xiàn)跨進(jìn)程通信的知識(shí),對(duì)Android學(xué)習(xí)通信知識(shí)非常重要,需要的同學(xué)可以參考下2016-07-07