自定义环形菜单

来源:互联网 发布:mac俄罗斯红色号 编辑:程序博客网 时间:2024/05/29 07:23

本文参考自:这里


package com.example.resource;



import com.example.studyapp.R;


import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;


public class CircleMenu extends ViewGroup {
private String[] mItems;
private int[] mImages;
private ImageClickListner clickListner;
/**
* 当每秒移动角度达到该值时,认为是快速移动
*/
private static final int FLINGABLE_VALUE = 300;
//
/**
* 如果移动角度达到该值,则屏蔽点击
*/
private static final int NOCLICK_VALUE = 3;
//
/**
* 当每秒移动角度达到该值时,认为是快速移动
*/
private int mFlingableValue = FLINGABLE_VALUE;
/**
* 布局时的开始角度
*/
private double mStartAngle = 0;
/**
* 该容器的内边距,无视padding属性,如需边距请用该变量
*/
private float mPadding=20;


/**
* 检测按下到抬起时旋转的角度
*/
private float mTmpAngle;
/**
* 检测按下到抬起时使用的时间
*/
private long mDownTime;
//
// /**
// * 判断是否正在自动滚动
// */
private boolean isFling;


public CircleMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}


// 添加标签项
public void setMenuItemIconsAndTexts(int[] images, String[] items) {
if (items == null && images == null) {
throw new IllegalArgumentException("必须设置item或者images");
}
this.mImages = images;
this.mItems = items;
for (int i = 0; i < mImages.length; i++) {
final int j = i;
// 添加View
final View view = View.inflate(getContext(), R.layout.menue_item,
null);
ImageView img = (ImageView) view
.findViewById(R.id.id_circle_menu_item_image);
TextView tv = (TextView) view
.findViewById(R.id.id_circle_menu_item_text);


img.setBackgroundResource(mImages[i]);
tv.setText(mItems[i]);


img.setOnClickListener(new OnClickListener() {


@Override
public void onClick(View arg0) {
if (clickListner != null) {
clickListner.onclick(j);
}
}
});
addView(view);
}
}


public void setImageClickListner(ImageClickListner Listner) {
this.clickListner = Listner;
}


public interface ImageClickListner {
void onclick(int position);
}


/**
* 设置布局的宽高,并策略menu item宽高
*/
@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 = resHeight = 300;
} else {
// 如果都设置为精确值,则直接取小值;
resWidth = resHeight = Math.min(width, height);
}


setMeasuredDimension(resWidth, resHeight);


// 获得半径
int mRadius = Math.min(getMeasuredWidth(), getMeasuredHeight());


// menu item数量
final int count = getChildCount();
// menu item尺寸
int childSize = (int) (mRadius * 0.25f);
// 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 makeMeasureSpec1 = -1;
makeMeasureSpec1 = MeasureSpec.makeMeasureSpec(childSize,
childMode);
child.measure(makeMeasureSpec1, makeMeasureSpec1);
}


}


@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
// 实现环状布局
int count = getChildCount();
int sweepAngle = 360 / (count);
int radius = getMeasuredHeight() / 2;
int child_Width = getChildAt(0).getMeasuredWidth();
int child_height = getChildAt(0).getMeasuredHeight();
View view;
int left = 0, top = 0;
int cWidth = child_Width;
for (int i = 0; i < count; i++) {
view = getChildAt(i);
float tmp = radius - cWidth / 2-mPadding;
mStartAngle %= 360;
// tmp cosa 即menu item中心点的横坐标
left = radius
+ (int) Math.round(tmp
* Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f
* cWidth);
// tmp sina 即menu item的纵坐标
top = radius
+ (int) Math.round(tmp
* Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f
* cWidth);


view.layout(left, top, left + child_Width, top + child_height);
mStartAngle += sweepAngle;


}
}


// 一下是滚动相关的代码


/**
* 记录上一次的x,y坐标
*/
private float mLastX;
private float mLastY;


/**
* 自动滚动的Runnable
*/
private AutoFlingRunnable mFlingRunnable;


@Override
public boolean dispatchTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();


Log.e("TAG", "x = " + x + " , y = " + y);
super.dispatchTouchEvent(event);//先把这个事件分配给子View,再在下面做二次处理
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
mDownTime = System.currentTimeMillis();
mTmpAngle = 0;


// 如果当前已经在快速滚动
if (isFling) {
// 移除快速滚动的回调
removeCallbacks(mFlingRunnable);
isFling = false;
}
return true;//只有DOWN事件返回TRUE才能接收到后续的MOVE和UP事件
case MotionEvent.ACTION_MOVE:
/**
* 获得开始的角度
*/
float start = getAngle(mLastX, mLastY);
/**
* 获得当前的角度
*/
float end = getAngle(x, y);


// Log.e("TAG", "start = " + start + " , end =" + end);
// 如果是一、四象限,则直接end-start,角度值都是正值
if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4) {
mStartAngle += end - start;
mTmpAngle += end - start;
} else
// 二、三象限,色角度值是付值
{
mStartAngle += start - end;
mTmpAngle += start - end;
}
// 重新布局
System.out.println("请求布局");
requestLayout();


mLastX = x;
mLastY = y;


break;
case MotionEvent.ACTION_UP:
// 计算,每秒移动的角度
float anglePerSecond = mTmpAngle * 1000
/ (System.currentTimeMillis() - mDownTime);


// Log.e("TAG", anglePrMillionSecond + " , mTmpAngel = " +
// mTmpAngle);


// 如果达到该值认为是快速移动
if (Math.abs(anglePerSecond) > mFlingableValue && !isFling) {
// post一个任务,去自动滚动
post(mFlingRunnable = new AutoFlingRunnable(anglePerSecond));


return true;
}


// 如果当前旋转角度超过NOCLICK_VALUE屏蔽点击
if (Math.abs(mTmpAngle) > NOCLICK_VALUE) {
return true;
}


break;
}
return false;
}


/**
* 根据触摸的位置,计算角度

* @param xTouch
* @param yTouch
* @return
*/
private float getAngle(float xTouch, float yTouch) {
double x = xTouch - (getMeasuredWidth() / 2d);
double y = yTouch - (getMeasuredWidth() / 2d);
return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
}


/**
* 根据当前位置计算象限

* @param x
* @param y
* @return
*/
private int getQuadrant(float x, float y) {
int tmpX = (int) (x - getMeasuredWidth() / 2);
int tmpY = (int) (y - getMeasuredWidth() / 2);
if (tmpX >= 0) {
return tmpY >= 0 ? 4 : 1;
} else {
return tmpY >= 0 ? 3 : 2;
}


}


/**
* 自动滚动的任务

* @author zhy

*/
private class AutoFlingRunnable implements Runnable {


private float angelPerSecond;


public AutoFlingRunnable(float velocity) {
this.angelPerSecond = velocity;
}


public void run() {
// 如果小于20,则停止
if ((int) Math.abs(angelPerSecond) < 20) {
isFling = false;
return;
}
isFling = true;
// 不断改变mStartAngle,让其滚动,/30为了避免滚动太快
mStartAngle += (angelPerSecond / 30);
// 逐渐减小这个值
angelPerSecond /= 1.0666F;
postDelayed(this, 30);
// 重新布局
requestLayout();
}
}
}
0 0
原创粉丝点击