首先想要说明一下,这个Demo例子是从eoeAndroid上面Download下来的,本文里只是解析,学习一下实现原理。从昨天开始就想分析下,一直拖到今天,不到5点,睡不着了(当然不是因为这个技术问题),就趁着早晨把他写下来吧,多有不足,请多多原谅。
下面开始正题 ,先看下程序运行是图片:
然后你可以拖动圆形菜单外面项到圆形菜单中:
开始正式的代码解析:
主Activity的onCreate:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
mNewView = new NewView(getApplicationContext(), 300, 200, 150);
setContentView(mNewView);
new Thread(new myThread()).start();
}
里面自定义了一个NewView类,继承自View,就是我们第一张图显示的内容,然后又起了一个线程:
class myThread implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
Message message = new Message();
switch (mNewView.getReturn()) {
case 1:
message.what = 0x101;
break;
case 2:
message.what = 0x102;
break;
case 3:
message.what = 0x103;
break;
case 4:
message.what = 0x104;
break;
case 5:
message.what = 0x105;
break;
case 6:
message.what = 0x106;
break;
case 7:
message.what = 0x107;
break;
case 8:
message.what = 0x108;
break;
case 9:
message.what = 0x109;
break;
}
QSA.this.myHandler.sendMessage(message);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
这个线程就是监听图片有滑动的时候,通过Handler发送一个消息到UI线程,进行相对应的操作:
Handler myHandler = new Handler() {
public void handleMessage(Message msg) {
// newView.getReturn = -1;
switch (msg.what) {
case 0x101:
break;
case 0x102:
break;
case 0x103:
mIntent = new Intent(getApplicationContext(), ShowPic.class);
startActivity(mIntent);
break;
case 0x104:
break;
case 0x105:
break;
case 0x106:
break;
case 0x107:
mIntent = new Intent(getApplicationContext(), RunLed.class);
startActivity(mIntent);
break;
case 0x108:
break;
case 0x109:
break;
}
super.handleMessage(msg);
}
};
其主要内容在与NewView类,下面我们看下这个类:
public class NewView extends View {
//返回值,用于调用者获取newView状态
protected static int mGetReturn = -1;
private Paint mPaint = new Paint();
// stone列表
private BigStone[] mStones;
int mode = NONE;
static final int NONE = 0;
//拖动
static final int DRAG = 1;
//用于双指视图缩放
static final int ZOOM = 2;
//存放数据的数组
private BigStone[] mMenus;
private BigStone[] mAddMenus = new BigStone[MENUS];
// 数目
private static int STONE_COUNT = 5;
private static int MENUS = 4;
// 圆心坐标
private float mPointX = 0, mPointY = 0;
private int flagwai = 0;
private int flag = 0;
// 半径
private int mRadius = 0;
// 每两个点间隔的角度
private int mDegreeDelta;
private float maxX, maxY, minX, minY;
上面是一些全局变量,都加上了注释,比较好理解。
看它的构造方法:
public NewView(Context context, int px, int py, int radius) {
super(context);
//为圆心和半径赋值
mPointX = px;
mPointY = py;
mRadius = radius;
setBackgroundResource(R.drawable.menubkground);
//设置菜单项(为两个菜单项赋值)
setupStones();
//计算坐标
computeCoordinates();
}
先看一下setupStone方法:
private void setupStones() {
mStones = new BigStone[STONE_COUNT];
mMenus = new BigStone[MENUS];
BigStone stone;
BigStone menus;
//初始角度
int angle = 0;
//每项之间相隔角度
mDegreeDelta = 360 / STONE_COUNT;
//初始圆形菜单外的项
if (flagwai == 0) {
for (int i = 0; i < MENUS; i++) {
menus = new BigStone();
menus.bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.menu6 + i);
menus.text = String.valueOf(1 + i);
mMenus[i] = menus;
}
}
//初始化圆形菜单项
for (int index = 0; index < STONE_COUNT; index++) {
stone = new BigStone();
stone.angle = angle;
stone.bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.menu1 + index);
stone.text = String.valueOf(1 + index);
//每一项的角度
angle += mDegreeDelta;
mStones[index] = stone;
}
}
里面该加注释的地方,我都加了注释,比较简单,不多说了,里面用到了一个BigStone的实体类:
class BigStone {
// 图片
Bitmap bitmap;
// 角度
int angle;
// x坐标
float x;
// y坐标
float y;
String text;
// 是否可见
boolean isVisible = true;
}
用于存储每一项的各个数据。
computeCoordinates,计算每一项的坐标:
private void computeCoordinates() {
BigStone stone;
BigStone menus;
for (int index = 0; index < STONE_COUNT; index++) {
stone = mStones[index];
stone.x = mPointX
+ (float) (mRadius * Math.cos(stone.angle * Math.PI / 180));
stone.y = mPointY
+ (float) (mRadius * Math.sin(stone.angle * Math.PI / 180));
}
if (flag == 0) {
for (int i = 0; i < MENUS; i++) {
menus = mMenus[i];
switch (i) {
case 0:
menus.x = 300 * 1.8f;
menus.y = 50;
break;
case 1:
menus.x = 300 * 1.8f + 100;
menus.y = 50;
break;
case 2:
menus.x = 300 * 1.8f + 200;
menus.y = 50;
break;
case 3:
menus.x = 300 * 1.8f;
menus.y = 150;
break;
//MENUS设置为4,下面这个应该是多余的
case 4:
menus.x = 300 * 1.8f + 10 + 100;
menus.y = 250;
break;
}
}
}
}
设置好各项参数,就应该绘制:
@Override
public void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
paint.setAlpha(0x30);
if (change == 0) {
//绘制中心小圆
canvas.drawCircle(mPointX, mPointY, mRadius - 80, paint);
}
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
paint.setColor(Color.BLUE);
paint.setAlpha(0x30);
if (change == 0) {
canvas.drawCircle(mPointX, mPointY, mRadius + 41, paint); // 大圆
}
if (change == 1) {
//满足一个条件不绘制圆盘菜单,绘制矩形菜单。
canvas.drawRect(800, 240, 0, 140, paint);
}
//绘制每一个外部菜单项
for (int i = 0; i < MENUS; i++) {
if (!mMenus[i].isVisible)
continue;
drawMenus(canvas, mMenus[i].bitmap, mMenus[i].x, mMenus[i].y);
}
//绘制每一个园内菜单
for (int index = 0; index < STONE_COUNT; index++) {
if (!mStones[index].isVisible)
continue;
drawInCenter(canvas, mStones[index].bitmap, mStones[index].x,
mStones[index].y, mStones[index].text);
}
}
先绘制小圆,然后是大圆:
绘制菜单项时,调用了drawMenus和drawInCenter方法:
void drawMenus(Canvas canvas, Bitmap b, float x, float y) {
canvas.drawBitmap(b, x - b.getWidth() / 2, y - b.getHeight() / 2, null); // 图标
}
void drawInCenter(Canvas canvas, Bitmap bitmap, float left, float top,
String text) {
canvas.drawText(text, left, top, mPaint);
canvas.drawBitmap(bitmap, left - bitmap.getWidth() / 2,
top - bitmap.getHeight() / 2, null);
}
下面就是触发事件的处理:
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
dumpEvent(e);
switch (e.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//判断选中的是哪个点
for (int i = 0; i < STONE_COUNT; i++) {
if (e.getX() >= mStones[i].x - 20
&& e.getX() <= mStones[i].x + 40
&& e.getY() >= mStones[i].y
&& e.getY() <= mStones[i].y + 40) {
if (i < 5) {
mGetReturn = Integer.valueOf(mStones[i].text);
} else {
mGetReturn = Integer.valueOf(mStones[i].text) + 5;
}
Toast.makeText(getContext(), String.valueOf(mGetReturn),
Toast.LENGTH_SHORT).show();
}
}
//把mode设为拖动
mode = DRAG;
return true;
case MotionEvent.ACTION_POINTER_DOWN:
//获取两点间的距离
oldDist = spacing(e);
if (oldDist > 100f) {
mode = ZOOM;
}
return true;
case MotionEvent.ACTION_MOVE:
//获取最大最小坐标
getMaxMin(e);
if (mode == DRAG) {
} else if (mode == ZOOM) {
//获取两点间的距离
// float newDist = spacing(e);
// if (newDist > 100f) {
// /*
// * if (change == 0) { change = 1;
// *
// * resetStonesAngle(e.getX(), e.getY());
// * computeCoordinates(); invalidate(); }
// */
// }
}
int a = 0;
for (int i = 0; i < MENUS; i++) {
if (e.getX() > mMenus[i].x - 40 && e.getX() < mMenus[i].x + 40
&& e.getY() > mMenus[i].y - 40
&& e.getY() < mMenus[i].y + 40) {
mMenus[i].x = e.getX();
mMenus[i].y = e.getY();
flag = 1;
computeCoordinates();
//重新绘制
postInvalidate();
//从外面添加到圆形菜单中
if (e.getX() < maxX && e.getX() > minX && e.getY() < maxY
&& e.getY() > minY) {
if (mMenus[i].isVisible) {
for (int j = 0; j < MENUS; j++) {
if (mAddMenus[j] == null && a == 0) {
mAddMenus[j] = mMenus[i];
a = 1;
}
}
STONE_COUNT++;
mDegreeDelta = 360 / STONE_COUNT;
mStones = new BigStone[STONE_COUNT];
flagwai = 1;
int angle = 0;
BigStone stone;
for (int index = 0; index < STONE_COUNT; index++) {
stone = new BigStone();
if (index < 5) {
stone.bitmap = BitmapFactory
.decodeResource(getResources(),
R.drawable.menu1 + index);
stone.text = String.valueOf(1 + index);
} else {
stone.bitmap = mAddMenus[index - 5].bitmap;
stone.text = mAddMenus[index - 5].text;
}
stone.angle = angle;
angle += mDegreeDelta;
mStones[index] = stone;
}
//把添加到圆内菜单项中的外部Item设为不可见,是不是可以考虑移除?
mMenus[i].isVisible = false;
}
}
break;
}
}
if (e.getX() < maxX && e.getX() > minX && e.getY() < maxY
&& e.getY() > minY) {
if (e.getX() < maxX - 80 && e.getX() > minX + 81
&& e.getY() < maxY - 80 && e.getY() > minY + 81) {
mPointX = e.getX();
mPointY = e.getY();
}
//重新设置每个item的角度
resetStonesAngle(e.getX(), e.getY());
//重新设置每一个item的坐标
computeCoordinates();
//重绘
invalidate();
}
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
return super.dispatchTouchEvent(e);
}
先看以下事件:
- MotionEvent.ACTION_DOWN:在第一个点被按下时触发
- MotionEvent.ACTION_UP:当屏幕上唯一的点被放开时触发
- MotionEvent.ACTION_POINTER_DOWN:当屏幕上已经有一个点被按住,此时再按下其他点时触发。
- MotionEvent.ACTION_POINTER_UP:当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)。
- MotionEvent.ACTION_MOVE:当有点在屏幕上移动时触发。值得注意的是,由于它的灵敏度很高,而我们的手指又不可能完全静止(即使我们感觉不到移动,但其实我们的手指也在不停地抖动),所以实际的情况是,基本上只要有点在屏幕上,此事件就会一直不停地被触发。
举例子来说:当我们放一个食指到屏幕上时,触发ACTION_DOWN事件;再放一个中指到屏幕上,触发ACTION_POINTER_DOWN事件;此时再把食指或中指放开,都会触发ACTION_POINTER_UP事件;再放开最后一个手指,触发ACTION_UP事件;而同时在整个过程中,ACTION_MOVE事件会一直不停地被触发。
逐个事件分析一下:
case MotionEvent.ACTION_DOWN:
//判断选中的是哪个点
for (int i = 0; i < STONE_COUNT; i++) {
if (e.getX() >= mStones[i].x - 20
&& e.getX() <= mStones[i].x + 40
&& e.getY() >= mStones[i].y
&& e.getY() <= mStones[i].y + 40) {
if (i < 5) {
mGetReturn = Integer.valueOf(mStones[i].text);
} else {
mGetReturn = Integer.valueOf(mStones[i].text) + 5;
}
Toast.makeText(getContext(), String.valueOf(mGetReturn),
Toast.LENGTH_SHORT).show();
}
}
//把mode设为拖动
mode = DRAG;
return true;
判断点击的是哪一项,然后弹出一个Toast提示。
case MotionEvent.ACTION_POINTER_DOWN:
//获取两点间的距离
oldDist = spacing(e);
if (oldDist > 100f) {
mode = ZOOM;
}
return true;
多点触发,这里只是算了一下距离,没做什么处理,如有需要,可以添加一些功能代码。里面用到了spacing方法:
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
该方法用于获取两点间的距离。
move:
case MotionEvent.ACTION_MOVE:
//获取最大最小坐标
getMaxMin(e);
if (mode == DRAG) {
} else if (mode == ZOOM) {
//获取两点间的距离
// float newDist = spacing(e);
// if (newDist > 100f) {
// /*
// * if (change == 0) { change = 1;
// *
// * resetStonesAngle(e.getX(), e.getY());
// * computeCoordinates(); invalidate(); }
// */
// }
}
int a = 0;
for (int i = 0; i < MENUS; i++) {
if (e.getX() > mMenus[i].x - 40 && e.getX() < mMenus[i].x + 40
&& e.getY() > mMenus[i].y - 40
&& e.getY() < mMenus[i].y + 40) {
mMenus[i].x = e.getX();
mMenus[i].y = e.getY();
flag = 1;
computeCoordinates();
//重新绘制
postInvalidate();
//从外面添加到圆形菜单中
if (e.getX() < maxX && e.getX() > minX && e.getY() < maxY
&& e.getY() > minY) {
if (mMenus[i].isVisible) {
for (int j = 0; j < MENUS; j++) {
if (mAddMenus[j] == null && a == 0) {
mAddMenus[j] = mMenus[i];
a = 1;
}
}
STONE_COUNT++;
mDegreeDelta = 360 / STONE_COUNT;
mStones = new BigStone[STONE_COUNT];
flagwai = 1;
int angle = 0;
BigStone stone;
for (int index = 0; index < STONE_COUNT; index++) {
stone = new BigStone();
if (index < 5) {
stone.bitmap = BitmapFactory
.decodeResource(getResources(),
R.drawable.menu1 + index);
stone.text = String.valueOf(1 + index);
} else {
stone.bitmap = mAddMenus[index - 5].bitmap;
stone.text = mAddMenus[index - 5].text;
}
stone.angle = angle;
angle += mDegreeDelta;
mStones[index] = stone;
}
//把添加到圆内菜单项中的外部Item设为不可见,是不是可以考虑移除?
mMenus[i].isVisible = false;
}
}
break;
}
}
if (e.getX() < maxX && e.getX() > minX && e.getY() < maxY
&& e.getY() > minY) {
if (e.getX() < maxX - 80 && e.getX() > minX + 81
&& e.getY() < maxY - 80 && e.getY() > minY + 81) {
mPointX = e.getX();
mPointY = e.getY();
}
//重新设置每个item的角度
resetStonesAngle(e.getX(), e.getY());
//重新设置每一个item的坐标
computeCoordinates();
//重绘
invalidate();
}
break;
里面就是各种判断,然后重新计算角度、坐标等,最后重新绘制。
里面有getMaxMin方法:
private void getMaxMin(MotionEvent e) {
float tempx;
float tempy;
for (int i = 0; i < STONE_COUNT; i++) {
for (int j = 0; j < STONE_COUNT; j++) {
if (mStones[i].x < mStones[j].x) {
tempx = mStones[i].x;
mStones[i].x = mStones[j].x;
mStones[j].x = tempx;
}
if (mStones[i].y < mStones[j].y) {
tempy = mStones[i].y;
mStones[i].y = mStones[j].y;
mStones[j].y = tempy;
}
}
}
maxX = mStones[STONE_COUNT - 1].x;
minX = mStones[0].x;
maxY = mStones[STONE_COUNT - 1].y;
minY = mStones[0].y;
}
通过重新排序,获取最大最小坐标点。
里面的计算角度方法:
private void resetStonesAngle(float x, float y) {
int angle = computeCurrentAngle(x, y);
for (int index = 0; index < STONE_COUNT; index++) {
mStones[index].angle = angle;
angle += mDegreeDelta;
}
}
private int computeCurrentAngle(float x, float y) {
float distance = (float) Math
.sqrt(((x - mPointX) * (x - mPointX) + (y - mPointY)
* (y - mPointY)));
int degree = (int) (Math.acos((x - mPointX) / distance) * 180 / Math.PI);
if (y < mPointY) {
degree = -degree;
}
return degree;
}
好吧,就先写到这吧,饿了,先吃点东西。一会把代码上传上去,先吃点东西。
最后源代码下载地址:http://download.csdn.net/detail/aomandeshangxiao/4857216