Android衛(wèi)星菜單效果的實(shí)現(xiàn)方法
Android小白第一次寫博客,心情無(wú)比激動(dòng)。下面給大家展示一下衛(wèi)星菜單的實(shí)現(xiàn)。
1.簡(jiǎn)單介紹衛(wèi)星菜單
在應(yīng)用程序中,有很多展示菜單的方式,但其功能都是大同小異,這樣一來(lái),菜單的美觀以及展示方式就顯的尤為重要,衛(wèi)星菜單就是很不錯(cuò)的一種。下面是本案例的gif圖:
2.學(xué)習(xí)本案例需要的知識(shí)點(diǎn)
(1)動(dòng)畫
(2)自定義ViewGroup
(3)自定義屬性
a、attr.xml
b、在布局中使用自定義屬性
c、在代碼中獲取自定義屬性值
3.首先分析我們的衛(wèi)星菜單需要那些自定義屬性并書寫代碼
首先,菜單可以顯示在屏幕的四個(gè)角,所以我們需要一個(gè)屬性來(lái)確定它的位置,菜單在屏幕的四個(gè)角比較美觀,在這里用到枚舉。
其次,我們還需要一個(gè)展開半徑,因此還需要自定義半徑。
下面是attr.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="position"> <enum name="left_top" value="0" /> <enum name="left_bottom" value="1" /> <enum name="right_top" value="2" /> <enum name="right_bottom" value="3" /> </attr> <attr name="radius" format="dimension"/> <declare-styleable name="SateMenu"> <attr name="radius" /> <attr name="position" /> </declare-styleable> </resources>
4.自定義ViewGroup
–繼承ViewGroup 以相關(guān)屬性
public class SateMenu extends ViewGroup implements View.OnClickListener { private int animationTime; //動(dòng)畫時(shí)間 private int radius; //展開半徑 private int pos; //從自定義屬性中獲取的菜單位置 private State state; //菜單狀態(tài) private int l = 0, t = 0; //左上值 private View centerBtn = null; //展開按鈕 private MenuItemListener menuItemListener; //菜單項(xiàng)點(diǎn)擊監(jiān)聽 private Position position; //枚舉型菜單位置 private enum Position { //位置枚舉 LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM } private enum State { //菜單狀態(tài)枚舉 OPEN, COLSE }
–構(gòu)造方法
public SateMenu(Context context) { //一個(gè)參數(shù)構(gòu)造方法調(diào)用兩個(gè)參數(shù)構(gòu)造方法 this(context, null); } public SateMenu(Context context, AttributeSet attrs) { //兩個(gè)參數(shù)構(gòu)造方法調(diào)用三個(gè)個(gè)參數(shù)構(gòu)造方法 this(context, attrs, 0); } public SateMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); animationTime = 500; //設(shè)置動(dòng)畫展開時(shí)間 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SateMenu, defStyleAttr, 0); //獲取自定義屬性值集合 radius = (int) a.getDimension(R.styleable.SateMenu_radius, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics())); //獲取半徑并轉(zhuǎn)化為像素值 state = State.COLSE; //設(shè)置菜單默認(rèn)關(guān)閉 pos = a.getInt(R.styleable.SateMenu_position, 0); //獲取位置 //將位置轉(zhuǎn)化為枚舉值 (這樣就把無(wú)意義的int轉(zhuǎn)化為有意義的枚舉值) switch (pos) { case 0: position = Position.LEFT_TOP; break; case 1: position = Position.LEFT_BOTTOM; break; case 2: position = Position.RIGHT_TOP; break; case 3: position = Position.RIGHT_BOTTOM; break; } }
–重寫onMeasure方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); //測(cè)量子view for (int i = 0; i < count; i++) { measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); } }
–重寫onLayout方法
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) btnLayout(); } private void btnLayout() { centerBtn = getChildAt(0); if (position == Position.RIGHT_BOTTOM || position == Position.RIGHT_TOP) { //如果菜單設(shè)置在屏幕的右側(cè),那么展開按鈕的l值=ViewGroup寬度-按鈕寬度 l = getMeasuredWidth() - centerBtn.getMeasuredWidth(); } if (position == Position.LEFT_BOTTOM || position == Position.RIGHT_BOTTOM) { //如果菜單設(shè)置在屏幕的下邊,那么展開按鈕的t值=ViewGroup高度-按鈕高度 t = getMeasuredHeight() - centerBtn.getMeasuredHeight(); } //設(shè)置展開按鈕位置 centerBtn.layout(l, t, l + centerBtn.getMeasuredWidth(), t + centerBtn.getMeasuredHeight()); childBtnlayout(); //設(shè)置子按鈕位置 centerBtn.setOnClickListener(this); }
–設(shè)置子按鈕位置需要一點(diǎn)點(diǎn)數(shù)學(xué)知識(shí),下面我以主菜單在右下角為例,畫一個(gè)簡(jiǎn)圖,圖片對(duì)應(yīng)右側(cè)第一個(gè)公式
private void childBtnlayout() { int childMuneCount = getChildCount() - 1; //角度等于90度/子按鈕個(gè)數(shù)-1 float a = (float) (Math.PI / 2 / (childMuneCount - 1)); int cl, ct; //分別是子按鈕的 左 上 for (int i = 0; i < childMuneCount; i++) { if (position == Position.RIGHT_BOTTOM || position == Position.RIGHT_TOP) { cl = (int) (l - radius * Math.cos(i * a)); } else { cl = (int) (l + radius * Math.cos(i * a)); } if (position == Position.LEFT_TOP || position == Position.RIGHT_TOP) { ct = (int) (t + radius * Math.sin(i * a)); } else { ct = (int) (t - radius * Math.sin(i * a)); } View childView = getChildAt(i + 1); childView.layout(cl, ct, cl + childView.getMeasuredWidth(), ct + childView.getMeasuredHeight()); childView.setOnClickListener(this); childView.setTag(i); childView.setVisibility(View.GONE); } }
–動(dòng)畫的展開與關(guān)閉,這里沒有用屬性動(dòng)畫,原理是:當(dāng)用戶關(guān)閉菜單的時(shí)候,將子按鈕隱藏,打開才打的時(shí)候在把子按鈕顯示出來(lái)
private void changeState() { int childMuneCount = getChildCount() - 1; //設(shè)置展開按鈕旋轉(zhuǎn)動(dòng)畫 Animation animation = new RotateAnimation(0, 360, centerBtn.getMeasuredWidth() / 2, centerBtn.getMeasuredHeight() / 2); animation.setDuration(animationTime); centerBtn.setAnimation(animation); animation.start(); View childView; //子按鈕有兩個(gè)動(dòng)畫(位移、旋轉(zhuǎn)),所以這里用到動(dòng)畫集,這里也涉及到一些數(shù)學(xué)知識(shí),和之前設(shè)置子按鈕位置差不多 AnimationSet animationSet; Animation translateAnimation; Animation rotateAnimation; int cl, ct; float a = (float) (Math.PI / 2 / (childMuneCount - 1)); if (state == State.OPEN) { state = State.COLSE; for (int i = 0; i < childMuneCount; i++) { if (position == Position.RIGHT_BOTTOM || position == Position.RIGHT_TOP) cl = (int) (radius * Math.cos(i * a)); else cl = (int) (-radius * Math.cos(i * a)); if (position == Position.LEFT_TOP || position == Position.RIGHT_TOP) ct = (int) (-radius * Math.sin(i * a)); else ct = (int) (radius * Math.sin(i * a)); childView = getChildAt(i + 1); childView.setVisibility(View.GONE); translateAnimation = new TranslateAnimation(0, cl, 0, ct); translateAnimation.setDuration(animationTime); rotateAnimation = new RotateAnimation(0, 360, childView.getMeasuredHeight() / 2, childView.getMeasuredHeight() / 2); rotateAnimation.setDuration(animationTime); animationSet = new AnimationSet(true); animationSet.addAnimation(rotateAnimation); animationSet.addAnimation(translateAnimation); childView.setAnimation(animationSet); animationSet.start(); childView.setVisibility(View.GONE); } } else { state = State.OPEN; for (int i = 0; i < childMuneCount; i++) { if (position == Position.RIGHT_BOTTOM || position == Position.RIGHT_TOP) cl = (int) (radius * Math.cos(i * a)); else cl = (int) (-radius * Math.cos(i * a)); if (position == Position.LEFT_TOP || position == Position.RIGHT_TOP) ct = (int) (-radius * Math.sin(i * a)); else ct = (int) (radius * Math.sin(i * a)); childView = getChildAt(i + 1); childView.setVisibility(View.GONE); translateAnimation = new TranslateAnimation(cl, 0, ct, 0); translateAnimation.setDuration(animationTime); rotateAnimation = new RotateAnimation(360, 0, childView.getMeasuredHeight() / 2, childView.getMeasuredHeight() / 2); rotateAnimation.setDuration(animationTime); animationSet = new AnimationSet(true); animationSet.addAnimation(rotateAnimation); animationSet.addAnimation(translateAnimation); childView.setAnimation(animationSet); animationSet.start(); childView.setVisibility(View.VISIBLE); } } }
–寫到這里我們的衛(wèi)星菜單已經(jīng)可以展現(xiàn)出來(lái)的,運(yùn)行一下,效果還是不錯(cuò)的。美中不足的是,子按鈕還沒有點(diǎn)擊事件,下面我們就將這個(gè)小小的不足補(bǔ)充一下。我們可以通過給子按鈕添加點(diǎn)擊事件來(lái)監(jiān)聽它,但點(diǎn)擊之后要做的事情不可能寫在ViewGroup中,這就需要用接口進(jìn)行回調(diào)。大家看一下在設(shè)置子按鈕位置的時(shí)候有這樣一句代碼 childView.setTag(i); 它的目的就是給子按鈕添加索引,接下來(lái)看一下具體怎樣實(shí)現(xiàn)的。
@Override public void onClick(View v) { if (v.getId() == centerBtn.getId()) { changeState(); } else { if (menuItemListener != null) { menuItemListener.onclick((Integer) v.getTag()); } } } public interface MenuItemListener { void onclick(int position); } public void setMenuItemListener(MenuItemListener menuItemListener) { this.menuItemListener = menuItemListener; }
–到這里我們已經(jīng)完全實(shí)現(xiàn)了衛(wèi)星菜單的所有功能,但大家有沒有發(fā)現(xiàn),一些菜單在展開之后,我們點(diǎn)擊其他區(qū)域,菜單會(huì)自動(dòng)收起來(lái),所以我們還要給我們的ViewGroup添加onTouchEvent事件,在菜單展開的時(shí)候,他把菜單收起來(lái),并將此次點(diǎn)擊攔截。
@Override public boolean onTouchEvent(MotionEvent event) { if (state == State.OPEN) { changeState(); return true; //攔截 } return super.onTouchEvent(event); }
5.下面試用一下我們編寫的衛(wèi)星菜單,看一下成果。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:wzw="http://schemas.android.com/apk/res/com.satemenudemo" android:layout_width="match_parent" android:layout_height="match_parent"> <com.satemenudemo.SateMenu android:id="@+id/menu_id" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="3dp" wzw:position="right_bottom" wzw:radius="150dp"> <ImageButton android:id="@+id/center_btn" android:layout_width="40dp" android:layout_height="40dp" android:background="@drawable/add" /> <ImageButton android:id="@+id/menu1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/find" /> <ImageButton android:id="@+id/menu2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/shop" /> <ImageButton android:id="@+id/menu3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/people" /> <ImageButton android:id="@+id/menu4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/love" /> </com.satemenudemo.SateMenu> </RelativeLayout>
MainActivity.java
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SateMenu sateMenu = (SateMenu) findViewById(R.id.menu_id); sateMenu.setMenuItemListener(new SateMenu.MenuItemListener() { @Override public void onclick(int position) { Toast.makeText(MainActivity.this, "-- "+position, Toast.LENGTH_SHORT).show(); } }); } }
以上所述是小編給大家介紹的Android衛(wèi)星菜單效果的實(shí)現(xiàn)方法,希望對(duì)大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的!
- Android 自定義組件衛(wèi)星菜單的實(shí)現(xiàn)
- Android自定義VIew實(shí)現(xiàn)衛(wèi)星菜單效果淺析
- Android實(shí)現(xiàn)自定義的衛(wèi)星式菜單(弧形菜單)詳解
- Android編程實(shí)現(xiàn)仿優(yōu)酷圓盤旋轉(zhuǎn)菜單效果的方法詳解【附demo源碼下載】
- Android學(xué)習(xí)教程之圓形Menu菜單制作方法(1)
- Android自定義view實(shí)現(xiàn)圓形與半圓形菜單
- Android圓形旋轉(zhuǎn)菜單開發(fā)實(shí)例
- Android自定義ViewGroup實(shí)現(xiàn)帶箭頭的圓角矩形菜單
- Android仿優(yōu)酷圓形菜單學(xué)習(xí)筆記分享
- Adapter模式實(shí)戰(zhàn)之重構(gòu)鴻洋集團(tuán)的Android圓形菜單建行
- Android實(shí)現(xiàn)衛(wèi)星菜單效果
相關(guān)文章
Android GZip的使用-開發(fā)中網(wǎng)絡(luò)請(qǐng)求的壓縮實(shí)例詳解
這篇文章主要介紹了Android GZip的使用-開發(fā)中網(wǎng)絡(luò)請(qǐng)求的壓縮實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2016-11-11android通過Location API顯示地址信息的實(shí)現(xiàn)方法
這篇文章主要介紹了android通過Location API顯示地址信息的方法,涉及Android操作Geocoder類獲取地址信息的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07淺談onTouch先執(zhí)行,還是onClick執(zhí)行(詳解)
onTouch先執(zhí)行,還是onClick執(zhí)行?下面小編就為大家?guī)?lái)一篇淺談onTouch先執(zhí)行,還是onClick執(zhí)行(詳解)。希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2017-03-03Android自定義Toast樣式實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Android自定義Toast樣式,Toast是一種很方便的消息提示框,會(huì)在 屏幕中顯示一個(gè)消息提示框,沒任何按鈕,也不會(huì)獲得焦點(diǎn)一段時(shí)間過后自動(dòng)消失!非常常用!本文就來(lái)通過一個(gè)例子把Toast的使用講透2023-01-01淺談Android為RecyclerView增加監(jiān)聽以及數(shù)據(jù)混亂的小坑
下面小編就為大家?guī)?lái)一篇淺談Android為RecyclerView增加監(jiān)聽以及數(shù)據(jù)混亂的小坑。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-04-04Android自定義Chronometer實(shí)現(xiàn)短信驗(yàn)證碼秒表倒計(jì)時(shí)功能
這篇文章主要介紹了Android自定義ChronometerView實(shí)現(xiàn)類似秒表倒計(jì)時(shí),短信驗(yàn)證碼倒計(jì)時(shí)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11Android提高之BLE開發(fā)Android手機(jī)搜索iBeacon基站
這篇文章主要介紹了BLE開發(fā)Android手機(jī)搜索iBeacon基站,需要的朋友可以參考下2014-08-08Android SpannableString設(shè)置超鏈接、顏色、字體等屬性
這篇文章主要介紹了Android SpannableString設(shè)置超鏈接、顏色、字體等屬性的相關(guān)資料,需要的朋友可以參考下2017-01-01Android?ASM插樁探索實(shí)戰(zhàn)詳情
這篇文章主要介紹了Android?ASM插樁探索實(shí)戰(zhàn)詳情,文章圍繞主題展開詳細(xì)的內(nèi)容戒殺,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09