效果圖
這是一個自定義的弧形菜單控件咬崔,手指滑動可以對其進行旋轉(zhuǎn),點擊圖標(biāo)可以做一些操作烦秩,功能就是這樣垮斯,下面介紹是如何實現(xiàn)的。
功能實現(xiàn)
自定義屬性
要實現(xiàn)這樣一個控件只祠,首先要知道這個圓弧的半徑mRadius兜蠕,以及初始可見的圖標(biāo)個數(shù)mVisiableItemCount(這里是5個)。我們來設(shè)置兩個自定義屬性抛寝,在attrs.xml中添加如下代碼:
<declare-styleable name="ArcDragMenu">
<attr name="mradius" format="dimension" />
<attr name="visibleitemcount" format="integer" />
</declare-styleable>
這樣我們就可以在布局文件中設(shè)置自定義的屬性熊杨。
<com.example.arcmenu.view.ArcDragMenu
android:id="@+id/arcdragmenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:mradius="360dp"
app:visibleitemcount="5"/>
在ArcDragMenu的構(gòu)造方法中獲取自定義屬性的值。
public ArcDragMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 獲取自定義屬性的值
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.ArcDragMenu, defStyleAttr, 0);
mRadius = (int) a.getDimension(R.styleable.ArcDragMenu_mradius, TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 360,
getResources().getDisplayMetrics()));
mVisiableItemCount = (int) a.getInteger(R.styleable.ArcDragMenu_visibleitemcount, 5);
a.recycle();
}
計算角度和位置
如圖盗舰,我們把整個圓弧的角度分成mVisiableItemCount份(這里是5份)晶府,那么圖中藍∠占1份,黃∠占2份钻趋,黑∠占2.5份川陆。黑∠的對邊為VIew寬度的一半,斜邊為圓弧半徑mRadius蛮位,由此可得:
黑∠ = Math.asin((getMeasuredWidth()/2.0)/mRadius);
藍∠的角度的大小angleDelay為:
angleDelay = Math.asin((getMeasuredWidth()/2.0)/mRadius)*2/ mVisiableItemCount;
第一個圖標(biāo)初始角度mInitialAngle的值(即黃∠):
//這里加負(fù)號表示位于中心軸的左邊
mInitialAngle = angleDelay *(-(mVisiableItemCount /2.0 - 0.5));
第二個圖標(biāo)的角度為mInitialAngle+angleDelay 较沪,其他以此類推。
知道了角度土至,計算位置就很簡單了购对,這里就不一一計算了,直接看代碼陶因。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
angleDelay = Math.asin((getMeasuredWidth()/2.0)/mRadius)*2/ mVisiableItemCount;
mInitialAngle = angleDelay *(-(mVisiableItemCount /2.0-0.5));
if(mCurrAngle ==0){
mCurrAngle = mInitialAngle;
}
double angle = mCurrAngle;
int count = getChildCount();
for (int i = 0; i < count; i++){
View child = getChildAt(i);
//子View的左上角坐標(biāo)(cl,ct)
int cl = (int) (mRadius * Math.sin(angle)) + getMeasuredWidth()/2 - child.getMeasuredWidth()/2;
int ct = (int) (mRadius * Math.cos(angle)) ;
//測量的子View的寬骡苞,高
int cWidth = child.getMeasuredWidth();
int cHeight = child.getMeasuredHeight();
//設(shè)置子view的位置
child.layout(cl, ct, cl + cWidth, ct + cHeight);
angle += angleDelay;
}
}
滑動
由上面的代碼可以看出,圖標(biāo)的位置是由當(dāng)前角度mCurrAngle來計算的楷扬,所以我們只需改變mCurrAngle的值即可滑動控件解幽。我們要計算出手指按下的角度,手指移動過程中角度烘苹,從而計算出移動了多少角度躲株,然后加到mCurrAngle上。部分代碼如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float x = ev.getRawX();
float y = ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
/**
* 獲得開始的角度
*/
float start = getAngle(mLastX, mLastY);
/**
* 獲得當(dāng)前的角度
*/
float end = getAngle(x, y);
float dr = end - start;
//防止超出范圍镣衡,左滑到最后一個霜定,右滑到第一個就不能再滑了
if(mCurrAngle + dr <= mInitialAngle && mCurrAngle + dr >= mInitialAngle - (mMenuItemCount- mVisiableItemCount)*angleDelay){
mCurrAngle += dr;
}
// 重新布局
requestLayout();
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
private float getAngle(float xTouch, float yTouch) {
double x = xTouch - getMeasuredWidth()/2;
double y = yTouch;
return (float) (Math.asin(x / Math.hypot(x, y)));//其中Math.hypot(x, y)為sqrt(x2 +y2)
}
這樣圖標(biāo)就可以隨著手指一起滑動了档悠,但是你可能會覺得太生硬了,手指松開就立刻停了望浩,如果快速滑動時讓它Fling一會就好了辖所。
Fling
當(dāng)手指抬起時,我們計算一下移動的角的速度磨德。
// 計算每秒移動的角度
float anglePerSecond = mTmpAngle * 1000 / (System.currentTimeMillis() - mDownTime);
我們開一個任務(wù)去慢慢遞減anglePerSecond 的值缘回,同時去改變mCurrAngle的值,這樣手指抬起后還能繼續(xù)滑動典挑,代碼如下:
/**
* 記錄上一次的x酥宴,y坐標(biāo)
*/
private float mLastX;
private float mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float x = ev.getRawX();
float y = ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
mDownTime = System.currentTimeMillis();
mTmpAngle = 0;
// 如果當(dāng)前已經(jīng)在快速滾動
if (isFling){
// 移除快速滾動的回調(diào)
removeCallbacks(mFlingRunnable);
isFling = false;
return true;
}
break;
case MotionEvent.ACTION_MOVE:
/**
* 獲得開始的角度
*/
float start = getAngle(mLastX, mLastY);
/**
* 獲得當(dāng)前的角度
*/
float end = getAngle(x, y);
float dr = end - start;
//防止超出范圍,左滑到最后一個您觉,右滑到第一個就不能再滑了
if(mCurrAngle + dr <= mInitialAngle && mCurrAngle + dr >= mInitialAngle - (mMenuItemCount- mVisiableItemCount)*angleDelay){
mCurrAngle += dr;
}
mTmpAngle += end - start;
// 重新布局
requestLayout();
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
// 計算每秒移動的角度
float anglePerSecond = mTmpAngle * 1000
/ (System.currentTimeMillis() - mDownTime);
// 如果達到該值認(rèn)為是快速移動
if (Math.abs(anglePerSecond) > FLINGABLE_VALUE && !isFling) {
// post一個任務(wù)拙寡,去自動滾動
post(mFlingRunnable = new AutoFlingRunnable(anglePerSecond));
return true;
}
// 如果當(dāng)前旋轉(zhuǎn)角度超過NOCLICK_VALUE屏蔽點擊
if (Math.abs(mTmpAngle) > NOCLICK_VALUE || System.currentTimeMillis()-mDownTime >500) {
return true;
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
private float getAngle(float xTouch, float yTouch) {
double x = xTouch - getMeasuredWidth()/2;
double y = yTouch;
return (float) (Math.asin(x / Math.hypot(x, y)));//其中Math.hypot(x, y)為sqrt(x2 +y2)
}
/**
* 自動滾動的任務(wù)
*/
private class AutoFlingRunnable implements Runnable{
private float angelPerSecond;
public AutoFlingRunnable(float velocity)
{
this.angelPerSecond = velocity;
}
public void run(){
// 如果小于0.1,則停止
if (Math.abs(angelPerSecond) < 0.1f){
isFling = false;
return;
}
isFling = true;
// 不斷改變mCurrAngle ,讓其滾動顾犹,/60為了避免滾動太快
float dr = (angelPerSecond / 60);
if(mCurrAngle + dr <= mInitialAngle && mCurrAngle + dr >= mInitialAngle - (mMenuItemCount- mVisiableItemCount)*angleDelay){
mCurrAngle += dr;
}else if(mCurrAngle + dr <= mInitialAngle){
mCurrAngle = mInitialAngle - (mMenuItemCount- mVisiableItemCount)*angleDelay;
}else if(mCurrAngle + dr >= mInitialAngle - (mMenuItemCount- mVisiableItemCount)*angleDelay){
mCurrAngle = mInitialAngle;
}
// 逐漸減小這個值
angelPerSecond /= 1.066f;
postDelayed(this, 10);
// 重新布局
requestLayout();
}
}
到此已經(jīng)全部結(jié)束了倒庵,有哪些做的不對的地方,希望大家多多指點炫刷。
源碼