圓形可滑動選單(可以動態新增選單項)
首先想要說明一下,這個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
相關文章
- 圓形、扇形選單,支援移動、桌面
- CollectionView 單個選項卡的滑動View
- CSS 動態導航選單CSS
- (十)如果實現滑動展示選單效果
- 透明層滑動跟隨導航選單
- React Router、And、Redux動態選單和動態路由ReactRedux路由
- .net core3.1 abp動態選單和動態許可權(思路) (二)
- 手動新增git到 右鍵選單Git
- elementUI 多級選單動態渲染UI
- JavaScript動態操作select下拉選單JavaScript
- 【Flutter 專題】130 圖解 DraggableScrollableSheet 可手勢滑動的選單欄Flutter圖解
- css3實現側邊滑動選單CSSS3
- 函式節流實現滑動下拉選單函式
- 使用Vue實現下拉選單框批量新增選項Vue
- Vue的動態選單無法登入(無法載入選單)Vue
- .Net Core Razor動態選單實現
- CSS 可伸縮圓角導航選單CSS
- 圓角select下拉選單
- (系列十四)Vue3+WebApi 搭建動態選單VueWebAPI
- 用Vue擼一個『A-Z字母滑動檢索選單』Vue
- JavaScript 動態圓形鐘錶JavaScript
- vue+element-ui實現動態的許可權管理和選單渲染VueUI
- Tkinter (13) 選項選單部件 OptionMenu
- 選單元素溢位時,自動滾動到可視區域
- vue 動態選單以及動態路由載入、重新整理採的坑Vue路由
- Win10系統怎麼在開始選單新增休眠選項Win10
- Gnome新增Open with Code選單
- 基於jquery-treeview的動態選單實現jQueryView
- vue 動態載入路由,渲染左側選單欄Vue路由
- ABP後臺管理頁面AdminLTE框架,實現選單項點選後,選單展開當前選單項高亮框架
- JavaScript左右滑動切換的選項卡詳解JavaScript
- 關於自動化平臺的動態選單設計(二)
- 導航選單(動畫)--- jQuery動畫jQuery
- SVG點選實現動態放大的圓效果SVG
- win10系統怎麼在右鍵選單新增BitLocker加密選項Win10加密
- vue+elementUI實現動態增加表單項並新增驗證VueUI
- 2.4.15 Step 14: (可選) 開啟自動例項啟動
- 原生Android 側滑選單實踐(部分)Android
- 下拉選單框和滾動條