您好,登錄后才能下訂單哦!
Android中怎么自定義組件衛(wèi)星菜單,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎ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),順便把位置傳遞過(guò)去 */ 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; } /** * 測(cè)量方法 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); for (int i = 0; i < count; i++) { //測(cè)量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); //正常來(lái)說(shuō),如果設(shè)置按鈕動(dòng)畫,移動(dòng)出去后,是不能點(diǎn)擊的,這里給按鈕設(shè)置一個(gè)隱藏的屬性.等衛(wèi)星菜單飛過(guò)去,在讓它們顯示出來(lá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為π * */ //測(cè)量每個(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; } //子布局的測(cè)量坐標(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()如果前面沒有對(duì)象調(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>
關(guān)于Android中怎么自定義組件衛(wèi)星菜單問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。