概述
双圆圈菜单是之前做的一个项目的需求,写完以后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 views
final 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() {
@Override
public 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;