Android 自定義組件衛(wèi)星菜單的實(shí)現(xiàn)
衛(wèi)星菜單 ArcMenu
相信大家接觸安卓,從新手到入門的過渡,就應(yīng)該會(huì)了解到衛(wèi)星菜單、抽屜、Xutils、Coolmenu、一些大神封裝好的一些組件。這些組件在 Github 上面很容易搜得到,但是有時(shí)候打開會(huì)發(fā)現(xiàn)看不懂里面的代碼,包括一些方法和函數(shù) 。。。。。
首先先上效果圖:
實(shí)現(xiàn)效果
首先如果要想自定義組件
1.那么第一件事就是賦予自定義組件的屬性,從效果圖上看出,該組件可以存在屏幕的各個(gè)角落點(diǎn),那么位置是其屬性之一。
2.既然是衛(wèi)星菜單,那么主按鈕和其附屬的小按鈕之間的圍繞半徑也應(yīng)該作為其參數(shù)之一。
3.右圖得出,該組件包含很多按鈕,主按鈕和附屬按鈕,那么這個(gè)組件應(yīng)該繼承 ViewGroup。
一、定義衛(wèi)星菜單的屬性在 values 包下建立 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> <!-- 尺寸屬性dp如果使用px可能會(huì)造成屏幕適配問題--> <attr name="radius" format="dimension" /> <!-- 自定義屬性--> <declare-styleable name="ArcMenu"> <attr name="position" /> <attr name="radius" /> </declare-styleable> </resources>
二、編寫自定義組件
package com.lanou.dllo.arcmenudemo.arcmenu; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.RotateAnimation; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; import com.lanou.dllo.arcmenudemo.R; /** * Created by dllo on 16/3/25. * 1.首先ArcMenu是繼承ViewGroup,那么一個(gè)衛(wèi)星菜單包括一個(gè)大按鈕和其他的子按鈕群. */ public class ArcMenu extends ViewGroup implements View.OnClickListener { //設(shè)置常量,標(biāo)識(shí)成枚舉 private static final int POS_LEFT_TOP = 0; private static final int POS_LEFT_BOTTOM = 1; private static final int POS_RIGHT_TOP = 2; private static final int POS_RIGHT_BOTTOM = 3; //以下5個(gè)成員變量是所需要的. //聲明兩個(gè)屬性 位置 還有半徑 private Position mPosition = Position.RIGHT_BOTTOM; private int mRadius; /** * 菜單的狀態(tài) */ private Status mCurrentStatus = Status.CLOSE; /** * 菜單的主按鈕 */ private View mCButton; //子菜單的回調(diào)按鈕 private OnMenuItemClickListener mMenuItemClickListener; /** * 菜單的位置枚舉類,4個(gè)位置 */ public enum Position { LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM } public enum Status { OPEN, CLOSE } /** * 點(diǎn)擊子菜單項(xiàng),順便把位置傳遞過去 */ public interface OnMenuItemClickListener { void onClick(View view, int pos); } //3個(gè)構(gòu)造方法,相互傳遞. //注意別寫錯(cuò)誤. public ArcMenu(Context context) { this(context, null); } public ArcMenu(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ArcMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //TypedValue.applyDimension是轉(zhuǎn)變標(biāo)準(zhǔn)尺寸的方法 參數(shù)一:單位 參數(shù)二:默認(rèn)值 參數(shù)三:可以獲取當(dāng)前屏幕的分辨率信息. mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP , 100, getResources().getDisplayMetrics()); //獲取自定義屬性的值 //參數(shù)1:attrs AttributeSet是節(jié)點(diǎn)的屬性集合 //參數(shù)2:attrs的一個(gè)數(shù)組集 //參數(shù)3:指向當(dāng)前theme 某個(gè)item 描述的style 該style指定了一些默認(rèn)值為這個(gè)TypedArray //參數(shù)4;當(dāng)defStyleAttr 找不到或者為0, 可以直接指定某個(gè)style TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArcMenu, defStyleAttr, 0); int pos = a.getInt(R.styleable.ArcMenu_position, POS_RIGHT_BOTTOM); switch (pos) { case POS_LEFT_TOP: mPosition = Position.LEFT_TOP; break; case POS_LEFT_BOTTOM: mPosition = Position.LEFT_BOTTOM; break; case POS_RIGHT_TOP: mPosition = Position.RIGHT_TOP; break; case POS_RIGHT_BOTTOM: mPosition = Position.RIGHT_BOTTOM; break; } mRadius = (int) a.getDimension(R.styleable.ArcMenu_radius, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP , 100, getResources().getDisplayMetrics())); Log.d("TAG", "Position = " + mPosition + ", radius" + mRadius); //使用完必須回收. a.recycle(); } public void setOnMenuItemClickListener(OnMenuItemClickListener mMenuItemClickListener) { this.mMenuItemClickListener = mMenuItemClickListener; } /** * 測量方法 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); for (int i = 0; i < count; i++) { //測量child的各個(gè)屬性. measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { layoutCButton(); //獲得容器內(nèi)組件的個(gè)數(shù),并且包括這個(gè)主的組件(大按鈕) int count = getChildCount(); for (int i = 0; i < count - 1; i++) { //這里直接獲取第一個(gè),是因?yàn)間etChildAt(0)是紅色的按鈕. View child = getChildAt(i + 1); //正常來說,如果設(shè)置按鈕動(dòng)畫,移動(dòng)出去后,是不能點(diǎn)擊的,這里給按鈕設(shè)置一個(gè)隱藏的屬性.等衛(wèi)星菜單飛過去,在讓它們顯示出來. child.setVisibility(View.GONE); /** * 根據(jù)畫圖分析,得出每個(gè)子衛(wèi)星按鈕的夾角 a = 90°/(菜單的個(gè)數(shù)-1) * 假設(shè)menu總數(shù)為4,那么從左側(cè)數(shù)menu1的坐標(biāo)為(0,R); * menu2的坐標(biāo)為(R*sin(a),R*cos(a)); * menu3的坐標(biāo)為(R*sin(2a),R*cos(2a)); * ... * menuN的坐標(biāo)為(R,0); * 另:PI為π * */ //測量每個(gè)子衛(wèi)星組件的在屏幕上面的坐標(biāo)距離 //這里count-2,是因?yàn)閏ount包含了主按鈕 //每個(gè)組件的坐標(biāo)為(cl,ct); int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i)); int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i)); int cWidth = child.getMeasuredWidth(); int cHeight = child.getMeasuredHeight(); //如果衛(wèi)星菜單存在于底部,那么坐標(biāo)位置的計(jì)算方法,就完全相反. /** * 如果菜單位置在底部 左下 ,右下.坐標(biāo)會(huì)發(fā)生變化 * */ if (mPosition == Position.LEFT_BOTTOM || mPosition == Position.RIGHT_BOTTOM) { ct = getMeasuredHeight() - cHeight - ct; } /** * 右上,右下 * */ if (mPosition == Position.RIGHT_TOP || mPosition == Position.RIGHT_BOTTOM) { cl = getMeasuredWidth() - cWidth - cl; } //子布局的測量坐標(biāo); child.layout(cl, ct, cl + cWidth, ct + cHeight); } } } /** * 定位主菜單按鈕 */ private void layoutCButton() { // 給主按鈕設(shè)置監(jiān)聽 mCButton = getChildAt(0); mCButton.setOnClickListener(this); //分別代表控件所處離左側(cè)和上側(cè)得距離 int l = 0; int t = 0; int width = mCButton.getMeasuredWidth(); int height = mCButton.getMeasuredHeight(); /** * getMeasuredHeight()如果前面沒有對象調(diào)用,那么這個(gè)控件繼承ViewGroup,就意味著這是獲取容器的總高度. * getMeasuredWidth()也是同理. * 那么就可以判斷出控件在四個(gè)位置(根據(jù)坐標(biāo)系判斷.) * */ switch (mPosition) { case LEFT_TOP: l = 0; t = 0; break; case LEFT_BOTTOM: l = 0; t = getMeasuredHeight() - height; break; case RIGHT_TOP: l = getMeasuredWidth() - width; t = 0; break; case RIGHT_BOTTOM: l = getMeasuredWidth() - width; t = getMeasuredHeight() - height; break; } //layout的四個(gè)屬性.分別代表主按鈕在不同位置距離屏幕左側(cè)和上側(cè) mCButton.layout(l, t, l + width, t + height); } @Override public void onClick(View v) { //主要確定mCButton的值 mCButton = findViewById(R.id.id_button); if (mCButton == null) { mCButton = getChildAt(0); } //旋轉(zhuǎn)動(dòng)畫 rotateCButton(v, 0f, 360f, 300); //判斷菜單是否關(guān)閉,如果菜單關(guān)閉需要給菜單展開,如果菜單是展開的需要給菜單關(guān)閉. toggleMenu(500); } /** * 切換菜單 * 參數(shù):切換菜單的時(shí)間是可控的. */ public void toggleMenu(int duration) { //為所有子菜單添加動(dòng)畫. :平移動(dòng)畫丶旋轉(zhuǎn)動(dòng)畫 int count = getChildCount(); for (int i = 0; i < count - 1; i++) { /** * 默認(rèn)位置左上的話,子菜單起始坐標(biāo)點(diǎn)為(-cl,-ct); * 位置右上的話,子菜單起始坐標(biāo)點(diǎn)為(+cl,-ct); * 位置左下的話,子菜單起始坐標(biāo)點(diǎn)為(-cl,+ct); * 位置右下的話,子菜單起始坐標(biāo)點(diǎn)為(+cl,+ct);** * */ final View childView = getChildAt(i + 1); //不管按鈕是開還是關(guān),子菜單必須顯示才能出現(xiàn)動(dòng)畫效果. childView.setVisibility(View.VISIBLE); //平移 結(jié)束為止 0,0(以子菜單按鈕當(dāng)前位置,為坐標(biāo)系.) int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i)); int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i)); //創(chuàng)建兩個(gè)判斷變量,判別起始位置. int xflag = 1; int yflag = 1; if (mPosition == Position.LEFT_TOP || mPosition == Position.LEFT_BOTTOM) { xflag = -1; } if (mPosition == Position.LEFT_TOP || mPosition == Position.RIGHT_TOP) { yflag = -1; } //多個(gè)動(dòng)畫同時(shí)使用使用,用到AnimationSet AnimationSet animset = new AnimationSet(true); Animation tranAnim = null; //to open 打開的情況下 if (mCurrentStatus == Status.CLOSE) { tranAnim = new TranslateAnimation(xflag * cl, 0, yflag * ct, 0); //當(dāng)衛(wèi)星菜單打開的時(shí)候,按鈕就可以進(jìn)行點(diǎn)擊. childView.setClickable(true); childView.setFocusable(true); } else {//to close tranAnim = new TranslateAnimation(0, xflag * cl, 0, yflag * ct); //當(dāng)衛(wèi)星菜單關(guān)閉的時(shí)候,按鈕也不能隨之點(diǎn)擊. childView.setClickable(false); childView.setFocusable(false); } tranAnim.setFillAfter(true); tranAnim.setDuration(duration); //設(shè)置彈出速度. tranAnim.setStartOffset((i * 100) / count); //為動(dòng)畫設(shè)置監(jiān)聽 如果需要關(guān)閉的話,在動(dòng)畫結(jié)束的同時(shí),需要將子菜單的按鈕全部隱藏. tranAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } //在動(dòng)畫結(jié)束時(shí),進(jìn)行設(shè)置. @Override public void onAnimationEnd(Animation animation) { if (mCurrentStatus == Status.CLOSE) { // Log.d("動(dòng)畫結(jié)束狀態(tài)",mCurrentStatus +""); childView.setVisibility(View.GONE); } } @Override public void onAnimationRepeat(Animation animation) { } }); //設(shè)置旋轉(zhuǎn)動(dòng)畫(轉(zhuǎn)兩圈) RotateAnimation rotateAnim = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotateAnim.setDuration(duration); rotateAnim.setFillAfter(true); //把兩個(gè)動(dòng)畫放到動(dòng)畫集里面 //注意動(dòng)畫順序.先增加旋轉(zhuǎn)/在增加移動(dòng)./ animset.addAnimation(rotateAnim); animset.addAnimation(tranAnim); childView.startAnimation(animset); final int pos = i + 1; //設(shè)置子菜單的點(diǎn)擊事件 childView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mMenuItemClickListener != null) { mMenuItemClickListener.onClick(childView, pos); } menuItemAnim(pos - 1); //切換菜單狀態(tài) changeStatus(); } }); } /** * 當(dāng)所有子菜單切換完成后,那么菜單的狀態(tài)也發(fā)生了改變. * 所以changeStatus()必須放在循環(huán)外, * */ //切換菜單狀態(tài) changeStatus(); } /** * 切換菜單狀態(tài) */ private void changeStatus() { //在執(zhí)行一個(gè)操作之后,如果按鈕是打開的在次點(diǎn)擊就會(huì)切換狀態(tài). mCurrentStatus = (mCurrentStatus == Status.CLOSE ? Status.OPEN : Status.CLOSE); Log.d("動(dòng)畫結(jié)束狀態(tài)", mCurrentStatus + ""); } public boolean isOpen(){ return mCurrentStatus ==Status.OPEN; } //設(shè)置旋轉(zhuǎn)動(dòng)畫繞自身旋轉(zhuǎn)一圈 然后持續(xù)時(shí)間為300 private void rotateCButton(View v, float start, float end, int duration) { RotateAnimation anim = new RotateAnimation(start, end, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); anim.setDuration(duration); //保持動(dòng)畫旋轉(zhuǎn)后的狀態(tài). anim.setFillAfter(true); v.startAnimation(anim); } /** * 添加menuItem的點(diǎn)擊動(dòng)畫 * */ private void menuItemAnim(int pos) { for (int i = 0; i < getChildCount() - 1; i++) { View childView = getChildAt(i + 1); //在判斷條件下,寫入動(dòng)畫 //當(dāng)其中一個(gè)子菜單被點(diǎn)擊后,自身變大并且消失 //其他子菜單則變小消失. if (i == pos) { childView.startAnimation(scaleBigAnim(300)); } else { childView.startAnimation(scaleSmallAnim(300)); } //當(dāng)子菜單被點(diǎn)擊之后,其他子菜單就要變成不可被點(diǎn)擊和獲得焦點(diǎn)的狀態(tài), childView.setClickable(false); childView.setFocusable(false); } } /** * 為當(dāng)前點(diǎn)擊的Item設(shè)置變大和透明度降低的動(dòng)畫 * * @param duration * @return */ private Animation scaleBigAnim(int duration) { AnimationSet animationSet = new AnimationSet(true); ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); AlphaAnimation alphaAnimation=new AlphaAnimation(1f,0.0f); animationSet.addAnimation(scaleAnimation); animationSet.addAnimation(alphaAnimation); animationSet.setDuration(duration); animationSet.setFillAfter(true); return animationSet; } private Animation scaleSmallAnim(int duration) { AnimationSet animationSet = new AnimationSet(true); ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); AlphaAnimation alphaAnimation=new AlphaAnimation(1f,0.0f); animationSet.addAnimation(scaleAnimation); animationSet.addAnimation(alphaAnimation); animationSet.setDuration(duration); animationSet.setFillAfter(true); return animationSet; } }
以上就是 衛(wèi)星菜單的編寫,上面的注釋量比較大。
這里需要注意的一點(diǎn)。衛(wèi)星菜單在屏幕不同位置,他的動(dòng)畫平移值是不一樣的。
如果實(shí)在不理解可以畫圖試試。
三、使用時(shí)注意賦予命名空間
<?xml version="1.0" encoding="utf-8"?> <com.lanou.dllo.arcmenudemo.arcmenu.ArcMenu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:arcmenu="http://schemas.android.com/apk/res/com.lanou.dllo.arcmenudemo" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/id_menu" android:layout_width="match_parent" android:layout_height="match_parent" arcmenu:position="left_top" arcmenu:radius="140dp" > <!-- 主按鈕--> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@mipmap/composer_button"> <ImageView android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@mipmap/composer_icn_plus" /> </RelativeLayout> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/composer_camera" android:tag="Camera"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/composer_music" android:tag="Music"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/composer_place" android:tag="Place"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/composer_sleep" android:tag="Sleep"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/composer_thought" android:tag="Sun"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/composer_with" android:tag="People"/> </com.lanou.dllo.arcmenudemo.arcmenu.ArcMenu>
其他的大家可以自行探索研究。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
- 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短信備份及數(shù)據(jù)插入實(shí)現(xiàn)代碼解析
這篇文章主要介紹了Android短信備份及數(shù)據(jù)插入實(shí)現(xiàn)代碼解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Android使用注解進(jìn)行代碼檢查的實(shí)現(xiàn)方法
這篇文章主要介紹了Android如何使用注解進(jìn)行代碼檢查,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Android自定義View實(shí)現(xiàn)游戲搖桿鍵盤的方法示例
Android進(jìn)階過程中有一個(gè)繞不開的話題——自定義View。最近在做項(xiàng)目中又遇到了,所以下面這篇文章主要給大家介紹了利用Android自定義View實(shí)現(xiàn)游戲搖桿鍵盤的相關(guān)資料,操作方式類似王者榮耀的搖桿操作,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面來一起看看吧。2017-07-07android開發(fā)教程之獲取使用當(dāng)前api的應(yīng)用程序名稱
開發(fā)手機(jī)安全管家的時(shí)候,比如要打電話,或者照相需要知道是哪個(gè)應(yīng)用程序在調(diào)用,就可以在API接口中調(diào)用下面的代碼2014-02-02Android雙擊返回鍵退出程序的實(shí)現(xiàn)方法
這篇文章主要介紹了Android雙擊返回鍵退出程序的實(shí)現(xiàn)方法,是Android程序開發(fā)中非常具有實(shí)用價(jià)值的重要技巧,需要的朋友可以參考下2014-09-09android studio節(jié)省C盤空間的配置方法
這篇文章主要介紹了android studio節(jié)省C盤空間的配置方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-07-07Android開發(fā)之獲取LayoutInflater對象的方法總結(jié)
這篇文章主要介紹了Android開發(fā)之獲取LayoutInflater對象的方法,結(jié)合實(shí)例形式總結(jié)分析了Android獲取LayoutInflater對象的常用技巧,需要的朋友可以參考下2016-02-02