概述

双圆圈菜单是之前做的一个项目的需求,写完以后boss决定不用了,提取以后把它贴出来,第一次发技术博客,希望各位前辈多多指教。

先看一下效果图。

需求就是
* 外圈放置菜单选项,内圈为圆形大图;
* 当内圈小白点对准外圈选项时,内圈切换对应的选项图案;
* 移动内圈时,内圈图案随转动角度旋转;
* 转动内外圈时,若没有转到对应角度,自动回弹或者弹向对应角度。


实现细节

这个DoubleCircleLayout我是仿照张鸿洋前辈的《 Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单》写的,在这里说明一下。鸿洋前辈的博客讲得已经很明白很细致了,我就不献丑了,主要贴一些我自己写的不一样的地方。
(http://blog.csdn.net/lmj623565791/article/details/43131133)


DoubleCircleLayout

Activity及布局文件都非常简单,这里就不写了,有兴趣的可以到github上download我的项目,地址是(https://github.com/loommo/DoubleCircleLayout)。

DoubleCircleLayout是一个自定义的ViewGroup,主要需要重写ViewGroup的onLayout(),onMeasure(),dispatchTouchEvent()方法。
* OnMeasure()测量布局容器及子视图的宽高,可以根据我们需要设定各个视图的宽高;
* OnLayout()设置各个子视图的位置;
* dispatchTouchEvent()编写转动内外圈不同的视图转动效果。


OnLayout()

为了实现需求需要的效果,首先要设定这个不完整的双环的位置,内环圆心在界面水平中心线靠左的位置,x坐标约为1/5屏幕宽度,而外圈圆心item距离约1/2屏幕宽度位置摆放。初始内外圈转动角度(mOuterStartAngle,mInnerStartAngle)为0。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {int layoutRadius = mRadius;int focusRadius = (int) (820 / 1520f * mRadius);int fWidth = (int) (layoutRadius * RADIO_DEFAULT_FOCUSITEM_DIMENSION);// Laying out the child viewsfinal int childCount = getChildCount();int left, top;// menu item 的尺寸int cWidth = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION_WIDTH);int cHeight = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION);// 根据menu item的个数,计算角度float angleDelay;if (getChildCount() > 2) {angleDelay = 360 / (getChildCount() - 2);} else {angleDelay = 360;}mOneAngle = angleDelay;// 遍历去设置menuitem的位置for (int i = 0; i < childCount; i++) {final View child = getChildAt(i);// 中心child与focus跳过if (child.getId() == R.id.id_circle_menu_item_center || child.getId() == R.id.id_circle_menu_item_focus)continue;if (child.getVisibility() == GONE) {continue;}mOuterStartAngle %= 360;mInnerStartAngle %= 360;// 计算,中心点到menu item中心的距离float tmp = layoutRadius / 2f - cHeight / 2;// tmp cos 即menu item中心点的横坐标left = mRadius * 200 / 1520+ (int) Math.round(tmp* Math.cos(Math.toRadians(mOuterStartAngle)) - 1 / 2f* cHeight);// tmp sin 即menu item的纵坐标top = mRadius/ 2+ (int) Math.round(tmp* Math.sin(Math.toRadians(mOuterStartAngle)) - 1 / 2f* cHeight);child.layout(left, top, left + cWidth, top + cHeight);// 叠加尺寸mOuterStartAngle += angleDelay;}// focus位置View fView = findViewById(R.id.id_circle_menu_item_focus);if (fView != null) {float tmp = focusRadius / 2f;int fl = mRadius * 259 / 1520 + (int) Math.round(tmp* Math.cos(Math.toRadians(mInnerStartAngle)) - 1 / 2f* fWidth);int fr = mRadius / 2 + (int) Math.round(tmp* Math.sin(Math.toRadians(mInnerStartAngle)) - 1 / 2f* fWidth);fView.layout(fl, fr, fl + fWidth, fr + fWidth);}// 找到中心的view,如果存在设置onclick事件View cView = findViewById(R.id.id_circle_menu_item_center);if (cView != null) {cView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if (mOnMenuItemClickListener != null) {mOnMenuItemClickListener.itemCenterClick(v);}}});// 设置center item位置float cl = mRadius * RADIO_DEFAULT_CENTERITEM_LEFT - cView.getMeasuredWidth() / 2;int cr = mRadius / 2 - cView.getMeasuredWidth() / 2;cView.layout((int) cl, cr, (int) (cl + cView.getMeasuredWidth()), cr + cView.getMeasuredWidth());}
}

dispatchTouchEvent()

这是最重要的一个方法,为了实现外圈转动,对准小白点内圈切换图片,用户按下屏幕(ACTION_DOWN),记录点击x,y坐标,根据x,y坐标计算点击范围是在内圈或者外圈(isBigCircle());当用户手指挪动(ACTION_MOVE)时,计算当前x,y坐标(getAngle())与原先x,y坐标差别,得到转动角度,由转动角度重新布局item位置及内圈图片角度,使得效果如同item随手指转动;用户抬起手指(ACTION_UP)时,计算当前内外圈转动角度,计算当前是否匹配(对转小白点)及匹配的是哪个item(matchItem()),切换内圈图片,若不匹配,启动FlingRunnable,回弹至最近的匹配角度。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {float x = ev.getX();float y = ev.getY();switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:// 初始点击xy值mLastX = x;mLastY = y;// 初始化mOuterTmpAngle = 0;mInnerTmpAngle = 0;// 判断内外圈isBig = isBigCircle(mLastX, mLastY);break;case MotionEvent.ACTION_MOVE:// 获得开始的角度float start = getAngle(mLastX, mLastY);// 获得当前的角度float end = getAngle(x, y);double tmpAngle;if (isBig) {tmpAngle = mOuterStartAngle;} else {tmpAngle = mInnerStartAngle;}// 如果是一、四象限,则直接end-start,角度值都是正值if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4) {tmpAngle += end - start;mInnerTmpAngle += end - start;} else {// 二、三象限,色角度值是负值tmpAngle += start - end;mInnerTmpAngle += start - end;}if (isBig) {mOuterStartAngle = tmpAngle;} else {mInnerStartAngle = tmpAngle;mOuterTmpAngle += end - start;}// 重新布局requestLayout();// 同时设置中心圈角度setMenuCentreAngle(mOuterTmpAngle);mLastX = x;mLastY = y;break;case MotionEvent.ACTION_UP:// 处理当前角度为0~360double wCircleAngle = mOuterStartAngle;double nCircleAngle = mInnerStartAngle;wCircleAngle %= 360;wCircleAngle = wCircleAngle >= 0 ? wCircleAngle : (360 + wCircleAngle);nCircleAngle %= 360;nCircleAngle = nCircleAngle >= 0 ? nCircleAngle : (360 + nCircleAngle);// 判断内外圈是否match,在一份+-0.2均MATCHdouble dvalue = Math.abs(wCircleAngle - nCircleAngle) % mOneAngle;if (dvalue < mOneAngle * 0.05 | mOneAngle - dvalue < mOneAngle * 0.05) {// match,更换中心图片matchItem(wCircleAngle, nCircleAngle, isBig);} else if (isBig) {// 外圈不匹配,回弹float anglew = (float) (wCircleAngle % mOneAngle);if (anglew < mOneAngle * 0.5) {post(mOuterFlingRunnable = new OuterFlingRunnable(-anglew));} else {post(mOuterFlingRunnable = new OuterFlingRunnable(mOneAngle - anglew));}return true;} else {// 内圈不匹配,回弹float anglen = (float) (nCircleAngle % mOneAngle);if (anglen < mOneAngle * 0.5) {post(mInnerFlingRunnable = new InnerFlingRunnable(-anglen));} else {post(mInnerFlingRunnable = new InnerFlingRunnable(mOneAngle - anglen));}return true;}// 如果当前旋转角度超过NOCLICK_VALUE屏蔽点击if (Math.abs(mInnerTmpAngle) > NOCLICK_VALUE) {return true;}break;}return super.dispatchTouchEvent(ev);
}/*
*根据触摸的位置,计算角度
* */
private float getAngle(float xTouch, float yTouch) {double x = xTouch - (mRadius * (259 / 1520d));double y = yTouch - (mRadius / 2d);return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
}/** 判断旋转外圈还是内圈* */
private boolean isBigCircle(float xTouch, float yTouch) {double x = xTouch - (mRadius * (259 / 1520d));double y = yTouch - (mRadius / 2d);if (Math.hypot(x, y) > (mRadius * (994 / 1520f) / 2)) {return true;// 外圈}return false;// 内圈
}private void matchItem(double wCircleAngle, double nCircleAngle, boolean isBig) {if (mMenuItemCount == 0) {return;}//计算当前itemint w = (int) (Math.round(wCircleAngle / mOneAngle) % mMenuItemCount);if (w != 0) {w = getChildCount() - 2 - w;}int n = (int) (Math.round(nCircleAngle / mOneAngle) % mMenuItemCount);int item = w + n + mMenuItemCount;item = item % mMenuItemCount;// 当前对应item数setMenuCentre(item);
}

完整代码

最后附上DoubleCircleLayout的完整代码。
DoubleCircleLayout.java:

package com.loommo.circlelayout.ui.widget;import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;import com.loommo.circlelayout.R;
import com.loommo.circlelayout.listener.OnMenuItemClickListener;import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;import java.util.List;public class DoubleCircleLayout extends ViewGroup {/*item的图片id*/
private List<Integer> resIds;
/*载入数据item数*/
private int mMenuItemCount;/*容器宽度*/
private int mRadius;/*child item的默认尺寸占radius百分比 */
private static final float RADIO_DEFAULT_CHILD_DIMENSION = 220 / 1520f;
private static final float RADIO_DEFAULT_CHILD_DIMENSION_WIDTH = 500 / 1520f;
/*focusitem的默认尺寸占radius百分比 */
private static final float RADIO_DEFAULT_FOCUSITEM_DIMENSION = 20 / 1520f;
/*中心child的默认尺寸占radius百分比 */
private static final float RADIO_DEFAULT_CENTERITEM_DIMENSION = 994 / 1520f;
/*中心child圆点距离左侧距离占radius百分比*/
private static final float RADIO_DEFAULT_CENTERITEM_LEFT = 259 / 1520f;/*item最大数目*/
private static final int ITEM_MAX_CONNT = 8;/*如果移动角度达到该值,则屏蔽点击*/
private static final int NOCLICK_VALUE = 3;
/*回弹每秒滚动速度*/
private static final int FLINGABLE_SPEED = 8;
/*外圈布局时的开始角度 */
private double mOuterStartAngle = 0;
/*内圈布局时的开始角度 */
private double mInnerStartAngle = 0;
/*检测按下到抬起时旋转的角度*/
private float mOuterTmpAngle;//内圈旋转角度
private float mInnerTmpAngle;//外圈
/*记录上一次的x,y坐标*/
private float mLastX;
private float mLastY;
private float mOneAngle;
private boolean isBig = true;
/* 自动滚动的Runnable/外圈*/
private OuterFlingRunnable mOuterFlingRunnable;
/* 自动滚动的Runnable/内圈*/
private InnerFlingRunnable mInnerFlingRunnable;/*MenuItem的点击事件接口*/
private OnMenuItemClickListener mOnMenuItemClickListener;public DoubleCircleLayout(Context context, AttributeSet attrs) {super(context, attrs);setPadding(0, 0, 0, 0);
}@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {int layoutRadius = mRadius;int focusRadius = (int) (820 / 1520f * mRadius);int fWidth = (int) (layoutRadius * RADIO_DEFAULT_FOCUSITEM_DIMENSION);// Laying out the child viewsfinal int childCount = getChildCount();int left, top;// menu item 的尺寸int cWidth = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION_WIDTH);int cHeight = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION);// 根据menu item的个数,计算角度float angleDelay;if (getChildCount() > 2) {angleDelay = 360 / (getChildCount() - 2);} else {angleDelay = 360;}mOneAngle = angleDelay;// 遍历去设置menuitem的位置for (int i = 0; i < childCount; i++) {final View child = getChildAt(i);// 中心child与focus跳过if (child.getId() == R.id.id_circle_menu_item_center || child.getId() == R.id.id_circle_menu_item_focus)continue;if (child.getVisibility() == GONE) {continue;}mOuterStartAngle %= 360;mInnerStartAngle %= 360;// 计算,中心点到menu item中心的距离float tmp = layoutRadius / 2f - cHeight / 2;// tmp cos 即menu item中心点的横坐标left = mRadius * 200 / 1520+ (int) Math.round(tmp* Math.cos(Math.toRadians(mOuterStartAngle)) - 1 / 2f* cHeight);// tmp sin 即menu item的纵坐标top = mRadius/ 2+ (int) Math.round(tmp* Math.sin(Math.toRadians(mOuterStartAngle)) - 1 / 2f* cHeight);child.layout(left, top, left + cWidth, top + cHeight);// 叠加尺寸mOuterStartAngle += angleDelay;}// focus位置View fView = findViewById(R.id.id_circle_menu_item_focus);if (fView != null) {float tmp = focusRadius / 2f;int fl = mRadius * 259 / 1520 + (int) Math.round(tmp* Math.cos(Math.toRadians(mInnerStartAngle)) - 1 / 2f* fWidth);int fr = mRadius / 2 + (int) Math.round(tmp* Math.sin(Math.toRadians(mInnerStartAngle)) - 1 / 2f* fWidth);fView.layout(fl, fr, fl + fWidth, fr + fWidth);}// 找到中心的view,如果存在设置onclick事件View cView = findViewById(R.id.id_circle_menu_item_center);if (cView != null) {cView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if (mOnMenuItemClickListener != null) {mOnMenuItemClickListener.itemCenterClick(v);}}});// 设置center item位置float cl = mRadius * RADIO_DEFAULT_CENTERITEM_LEFT - cView.getMeasuredWidth() / 2;int cr = mRadius / 2 - cView.getMeasuredWidth() / 2;cView.layout((int) cl, cr, (int) (cl + cView.getMeasuredWidth()), cr + cView.getMeasuredWidth());}
}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int resWidth = 0;int resHeight = 0;// 根据传入的参数,分别获取测量模式和测量值int width = MeasureSpec.getSize(widthMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 如果宽或者高的测量模式非精确值if (widthMode != MeasureSpec.EXACTLY|| heightMode != MeasureSpec.EXACTLY) {// 主要设置为背景图的高度resWidth = getSuggestedMinimumWidth();// 如果未设置背景图片,则设置为屏幕宽高的默认值resWidth = resWidth == 0 ? getDefaultWidth() : resWidth;resHeight = getSuggestedMinimumHeight();// 如果未设置背景图片,则设置为屏幕宽高的默认值resHeight = resHeight == 0 ? getDefaultWidth() : resHeight;} else {// 若设置为精确值resWidth = width;resHeight = height;}setMeasuredDimension(resWidth, resHeight);// 获得半径mRadius = resHeight;// child数量final int count = getChildCount();// menu item尺寸int childSize = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION_WIDTH);// menu item测量模式int childMode = MeasureSpec.EXACTLY;// 迭代测量for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (child.getVisibility() == GONE) {continue;}// 计算menu item的尺寸;以及和设置好的模式,去对item进行测量int makeMeasureSpec = -1;if (child.getId() == R.id.id_circle_menu_item_center) {makeMeasureSpec = MeasureSpec.makeMeasureSpec((int) (mRadius * RADIO_DEFAULT_CENTERITEM_DIMENSION),childMode);} else if (child.getId() == R.id.id_circle_menu_item_focus) {makeMeasureSpec = MeasureSpec.makeMeasureSpec((int) (mRadius * RADIO_DEFAULT_FOCUSITEM_DIMENSION),childMode);} else {makeMeasureSpec = MeasureSpec.makeMeasureSpec(childSize,childMode);}child.measure(makeMeasureSpec, makeMeasureSpec);}}@Override
public boolean dispatchTouchEvent(MotionEvent ev) {float x = ev.getX();float y = ev.getY();switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:// 初始点击xy值mLastX = x;mLastY = y;// 初始化mOuterTmpAngle = 0;mInnerTmpAngle = 0;// 判断内外圈isBig = isBigCircle(mLastX, mLastY);break;case MotionEvent.ACTION_MOVE:// 获得开始的角度float start = getAngle(mLastX, mLastY);// 获得当前的角度float end = getAngle(x, y);double tmpAngle;if (isBig) {tmpAngle = mOuterStartAngle;} else {tmpAngle = mInnerStartAngle;}// 如果是一、四象限,则直接end-start,角度值都是正值if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4) {tmpAngle += end - start;mInnerTmpAngle += end - start;} else {// 二、三象限,色角度值是负值tmpAngle += start - end;mInnerTmpAngle += start - end;}if (isBig) {mOuterStartAngle = tmpAngle;} else {mInnerStartAngle = tmpAngle;mOuterTmpAngle += end - start;}// 重新布局requestLayout();// 同时设置中心圈角度setMenuCentreAngle(mOuterTmpAngle);mLastX = x;mLastY = y;break;case MotionEvent.ACTION_UP:// 处理当前角度为0~360double wCircleAngle = mOuterStartAngle;double nCircleAngle = mInnerStartAngle;wCircleAngle %= 360;wCircleAngle = wCircleAngle >= 0 ? wCircleAngle : (360 + wCircleAngle);nCircleAngle %= 360;nCircleAngle = nCircleAngle >= 0 ? nCircleAngle : (360 + nCircleAngle);// 判断内外圈是否match,在一份+-0.2均MATCHdouble dvalue = Math.abs(wCircleAngle - nCircleAngle) % mOneAngle;if (dvalue < mOneAngle * 0.05 | mOneAngle - dvalue < mOneAngle * 0.05) {// match,更换中心图片matchItem(wCircleAngle, nCircleAngle, isBig);} else if (isBig) {// 外圈不匹配,回弹float anglew = (float) (wCircleAngle % mOneAngle);if (anglew < mOneAngle * 0.5) {post(mOuterFlingRunnable = new OuterFlingRunnable(-anglew));} else {post(mOuterFlingRunnable = new OuterFlingRunnable(mOneAngle - anglew));}return true;} else {// 内圈不匹配,回弹float anglen = (float) (nCircleAngle % mOneAngle);if (anglen < mOneAngle * 0.5) {post(mInnerFlingRunnable = new InnerFlingRunnable(-anglen));} else {post(mInnerFlingRunnable = new InnerFlingRunnable(mOneAngle - anglen));}return true;}// 如果当前旋转角度超过NOCLICK_VALUE屏蔽点击if (Math.abs(mInnerTmpAngle) > NOCLICK_VALUE) {return true;}break;}return super.dispatchTouchEvent(ev);
}/*
*设置MenuItem的点击事件
* */
public void setOnMenuItemClickListener(OnMenuItemClickListener mOnMenuItemClickListener) {this.mOnMenuItemClickListener = mOnMenuItemClickListener;
}public void setMenuItemIconsAndTexts(List<Integer> resIds) {this.resIds = resIds;if (resIds == null) {throw new IllegalArgumentException("items error");} else {mMenuItemCount = resIds.size();}if (mMenuItemCount != 0 && mMenuItemCount < ITEM_MAX_CONNT) {addMenuItems(mMenuItemCount);} else {throw new IllegalArgumentException("the number of items can't be more than eight");}
}private void addMenuItems(int number) {LayoutInflater mInflater = LayoutInflater.from(getContext());initItems();for (int i = 0; i < number; i++) {final int j = i;View view = mInflater.inflate(R.layout.item_circle_menu, this,false);ImageView iv = (ImageView) view.findViewById(R.id.id_circle_menu_item_image);TextView tv = (TextView) view.findViewById(R.id.id_circle_menu_item_text);if (iv != null) {iv.setVisibility(View.VISIBLE);iv.setImageResource(resIds.get(i));iv.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if (mOnMenuItemClickListener != null) {mOnMenuItemClickListener.itemClick(v, j);}}});}if (tv != null) {tv.setVisibility(View.VISIBLE);}// 添加view到容器中addView(view);}setMenuCentre(0);}/*
* 设置中心圆图片
* */
public void setMenuCentre(int imageNumber) {if (imageNumber >= mMenuItemCount) {return;}CircleImageView cv = (CircleImageView) findViewById(R.id.id_circle_menu_center_image);mOuterTmpAngle = 0;cv.setImageAngle(mOuterTmpAngle, false);cv.setImageResource(resIds.get(imageNumber));
}/*
* 设置中心圈转动角度
* */
public void setMenuCentreAngle(float imageAngle) {CircleImageView cv = (CircleImageView) findViewById(R.id.id_circle_menu_center_image);cv.setImageAngle(imageAngle, true);
}/*
* 处理item,避免重复添加
* */
private void initItems() {int childCount = getChildCount();if (childCount > 2) {for (int i = 2; i < childCount; i++) {removeViewAt(2);}}
}private void matchItem(double wCircleAngle, double nCircleAngle, boolean isBig) {if (mMenuItemCount == 0) {return;}//计算当前itemint w = (int) (Math.round(wCircleAngle / mOneAngle) % mMenuItemCount);if (w != 0) {w = getChildCount() - 2 - w;}int n = (int) (Math.round(nCircleAngle / mOneAngle) % mMenuItemCount);int item = w + n + mMenuItemCount;item = item % mMenuItemCount;// 当前对应item数setMenuCentre(item);
}/*
* 获得设备宽度
* */
private int getDefaultWidth() {WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);return Math.min(outMetrics.widthPixels, outMetrics.heightPixels);
}/*
* 计算象限
* */
private int getQuadrant(float x, float y) {int tmpX = (int) (x - mRadius * (259 / 1520d));int tmpY = (int) (y - mRadius / 2);if (tmpX >= 0) {return tmpY >= 0 ? 4 : 1;} else {return tmpY >= 0 ? 3 : 2;}}/*
*根据触摸的位置,计算角度
* */
private float getAngle(float xTouch, float yTouch) {double x = xTouch - (mRadius * (259 / 1520d));double y = yTouch - (mRadius / 2d);return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
}/** 判断旋转外圈还是内圈* */
private boolean isBigCircle(float xTouch, float yTouch) {double x = xTouch - (mRadius * (259 / 1520d));double y = yTouch - (mRadius / 2d);if (Math.hypot(x, y) > (mRadius * (994 / 1520f) / 2)) {return true;// 外圈}return false;// 内圈
}/*
*外圈自动滑动
* */
private class OuterFlingRunnable implements Runnable {private float angelPerSecond;private float angelSpeed;public OuterFlingRunnable(float velocity)//需移动角度{this.angelPerSecond = velocity;int i = 1;if (velocity < 0) {i = -1;}angelSpeed = i * FLINGABLE_SPEED;}public void run() {if (Math.abs(angelPerSecond) < 1) {double wCircleAngle = mOuterStartAngle;double nCircleAngle = mInnerStartAngle;wCircleAngle %= 360;wCircleAngle = wCircleAngle >= 0 ? wCircleAngle : (360 + wCircleAngle);nCircleAngle %= 360;nCircleAngle = nCircleAngle >= 0 ? nCircleAngle : (360 + nCircleAngle);matchItem(wCircleAngle, nCircleAngle, true);return;}if (Math.abs(angelPerSecond) < FLINGABLE_SPEED) {//需转动角度<速度mOuterStartAngle += angelPerSecond;angelPerSecond = 0;} else {mOuterStartAngle += angelSpeed;angelPerSecond -= angelSpeed;angelSpeed /= 1.0666F;}postDelayed(this, 30);// 重新布局requestLayout();}
}/*
*内圈自动滑动
* */
private class InnerFlingRunnable implements Runnable {private float angelPerSecond;private float angelSpeed;public InnerFlingRunnable(float velocity)//穿入需移动角度{this.angelPerSecond = velocity;int i = 1;if (velocity < 0) {i = -1;}angelSpeed = i * FLINGABLE_SPEED;}public void run() {if (Math.abs(angelPerSecond) < 1) {double wCircleAngle = mOuterStartAngle;double nCircleAngle = mInnerStartAngle;wCircleAngle %= 360;wCircleAngle = wCircleAngle >= 0 ? wCircleAngle : (360 + wCircleAngle);nCircleAngle %= 360;nCircleAngle = nCircleAngle >= 0 ? nCircleAngle : (360 + nCircleAngle);matchItem(wCircleAngle, nCircleAngle, false);return;}if (Math.abs(angelPerSecond) < FLINGABLE_SPEED) {// 需转动角度<速度mInnerStartAngle += angelPerSecond;mOuterTmpAngle += angelPerSecond;angelPerSecond = 0;} else {mInnerStartAngle += angelSpeed;mOuterTmpAngle += angelSpeed;angelPerSecond -= angelSpeed;angelSpeed /= 1.0666F;}postDelayed(this, 30);// 重新布局requestLayout();setMenuCentreAngle(mOuterTmpAngle);}
}

}

[Android自定义控件]双圆圈内外旋转菜单相关推荐

  1. Android 高仿优酷旋转菜单

    这是一个很早版本的优酷菜单,效果挺不错的,实现起来也挺简单的.废话不说,直接上代码: 首先是xml文件: <RelativeLayout xmlns:android="http://s ...

  2. my android tools优酷,Android自定义控件——仿优酷圆盘菜单

    最近学习的时候,看见一份资料上教怎么写自定义控件,上面的示例用的是优酷早期版本的客户端,该客户端的菜单就是一个自定义的组件(现在的版本就不清楚有没有了,没下载过了),好吧,废话不多说,先上优酷的原型图 ...

  3. android仿优酷菜单,Android自定义控件之仿优酷菜单

    去年的优酷HD版有过这样一种菜单,如下图: 应用打开之后,先是三个弧形的三级菜单,点击实体键menu之后,这三个菜单依次旋转退出,再点击实体键menu之后,一级菜单会旋转进入,点击一级菜单,二级菜单旋 ...

  4. android自定义控件之模仿优酷菜单

    去年的优酷HD版有过这样一种菜单,如下图: 应用打开之后,先是三个弧形的三级菜单,点击实体键menu之后,这三个菜单依次旋转退出,再点击实体键menu之后,一级菜单会旋转进入,点击一级菜单,二级菜单旋 ...

  5. android圆形旋转菜单,而对于移动转换功能支持

    LZ该公司最近接手一个项目,需要写一个圆形旋转菜单,和菜单之间的移动换位支持,我本来以为这样的demo如若互联网是非常.想想你妈妈也帮不了我,空旋转,但它不能改变位置,所以LZ我们只能靠自己摸索. 最 ...

  6. android汽车之家顶部滑动菜单,Android自定义控件之仿汽车之家下拉刷新

    关于下拉刷新的实现原理我在上篇文章Android自定义控件之仿美团下拉刷新中已经详细介绍过了,这篇文章主要介绍表盘的动画实现原理 汽车之家的下拉刷新分为三个状态: 第一个状态为下拉刷新状态(pull ...

  7. Android 自定义控件打造史上最简单的侧滑菜单

    侧滑菜单在很多应用中都会见到,最近QQ5.0侧滑还玩了点花样~~对于侧滑菜单,一般大家都会自定义ViewGroup,然后隐藏菜单栏,当手指滑动时,通过Scroller或者不断的改变leftMargin ...

  8. 自定义控件:旋转菜单

    效果图 项目概述 首先,我们学习如何自定义一个组合控件,其中,优酷菜单是一个典型的自定义组合控件,它的效果图如图1-1 所示: 图中由中间往外,分别是一级菜单.二级菜单.三级菜单.其基本用法是:点击一 ...

  9. android仿优酷菜单,Android编程实现仿优酷旋转菜单效果(附demo源码)

    本文实例讲述了Android编程实现仿优酷旋转菜单效果.分享给大家供大家参考,具体如下: 首先,看下效果: 不好意思,不会制作动态图片,只好上传静态的了,如果谁会,请教教我吧. 首先,看下xml文件: ...

最新文章

  1. 关于字符编码 转自廖雪峰的官方网站,至今看到最清晰的讲解
  2. Python之web开发(六):python使用django框架搭建网站之登陆页搭建不同页面之间跳转
  3. html文字超链接不让变色,css不让超链接变色怎么设置?
  4. Android Studio 开发安卓软件时下载的工程项目 Sync with gradle 失败
  5. 阿里资深技术专家:谁说程序员是吃“青春饭”的?
  6. 快速部署Enterprise Manager Cloud Control 12c(12.1) Agent
  7. LintCode 373: Partition Array
  8. 前向传播算法和反向传播算法
  9. 差速驱动机器人轮间距校准实验
  10. Python学习-20180105
  11. Matlab矩阵的运算
  12. 可以在搜索中突出显示网页上的多个单词_使用片段嵌入进行文档搜索
  13. vm15安装mac10.14提取ipa包
  14. type-c边玩边充电游戏手柄方案
  15. 神牛闪光灯TT865/V850II 860II Sony热靴口损坏,更换新热靴口
  16. 计算机的运算方法(中)测试
  17. 【CE入门教程】使用Cheat Engine(CE)修改游戏“植物大战僵尸”之植物篇
  18. 小米pro如何关闭安全启动_Apple的新安全功能不会让您维修MacBook Pro或iMac Pro [更新]...
  19. PHP与memcached实战
  20. 【ChatGPT的小妙招】结合Excel的vbs开发者工具达成对Excel文件的处理

热门文章

  1. 【LaTeX中英排版系列】LaTeX中英双标题、作者、机构、摘要文档首页排版指北
  2. APT组织最喜欢的工具 Cobalt Strike (CS) 实战
  3. 基于用户体验的设计思想和用户体验概述
  4. matlab添加文件夹语音_基于MATLAB的语音处理
  5. IE 调试工具 IETester+DebugBar
  6. 经济学的基础 —— 稀缺
  7. Squid缓存服务器和代理介绍
  8. PDPS软件:机器人外部柔性管线包模型导入与虚拟仿真操作方法
  9. 一位37岁被裁技术高管给你提个醒:在职场,这件事越早做越好
  10. intellij 取消svn 用户名 密码