您好,登錄后才能下訂單哦!
Android中怎么自定義組件衛星菜單,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
首先如果要想自定義組件
1.那么第一件事就是賦予自定義組件的屬性,從效果圖上看出,該組件可以存在屏幕的各個角落點,那么位置是其屬性之一。
2.既然是衛星菜單,那么主按鈕和其附屬的小按鈕之間的圍繞半徑也應該作為其參數之一。
3.右圖得出,該組件包含很多按鈕,主按鈕和附屬按鈕,那么這個組件應該繼承 ViewGroup。
一、定義衛星菜單的屬性在 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可能會造成屏幕適配問題--> <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,那么一個衛星菜單包括一個大按鈕和其他的子按鈕群. */ public class ArcMenu extends ViewGroup implements View.OnClickListener { //設置常量,標識成枚舉 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個成員變量是所需要的. //聲明兩個屬性 位置 還有半徑 private Position mPosition = Position.RIGHT_BOTTOM; private int mRadius; /** * 菜單的狀態 */ private Status mCurrentStatus = Status.CLOSE; /** * 菜單的主按鈕 */ private View mCButton; //子菜單的回調按鈕 private OnMenuItemClickListener mMenuItemClickListener; /** * 菜單的位置枚舉類,4個位置 */ public enum Position { LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM } public enum Status { OPEN, CLOSE } /** * 點擊子菜單項,順便把位置傳遞過去 */ public interface OnMenuItemClickListener { void onClick(View view, int pos); } //3個構造方法,相互傳遞. //注意別寫錯誤. 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是轉變標準尺寸的方法 參數一:單位 參數二:默認值 參數三:可以獲取當前屏幕的分辨率信息. mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP , 100, getResources().getDisplayMetrics()); //獲取自定義屬性的值 //參數1:attrs AttributeSet是節點的屬性集合 //參數2:attrs的一個數組集 //參數3:指向當前theme 某個item 描述的style 該style指定了一些默認值為這個TypedArray //參數4;當defStyleAttr 找不到或者為0, 可以直接指定某個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的各個屬性. 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(); //獲得容器內組件的個數,并且包括這個主的組件(大按鈕) int count = getChildCount(); for (int i = 0; i < count - 1; i++) { //這里直接獲取第一個,是因為getChildAt(0)是紅色的按鈕. View child = getChildAt(i + 1); //正常來說,如果設置按鈕動畫,移動出去后,是不能點擊的,這里給按鈕設置一個隱藏的屬性.等衛星菜單飛過去,在讓它們顯示出來. child.setVisibility(View.GONE); /** * 根據畫圖分析,得出每個子衛星按鈕的夾角 a = 90°/(菜單的個數-1) * 假設menu總數為4,那么從左側數menu1的坐標為(0,R); * menu2的坐標為(R*sin(a),R*cos(a)); * menu3的坐標為(R*sin(2a),R*cos(2a)); * ... * menuN的坐標為(R,0); * 另:PI為π * */ //測量每個子衛星組件的在屏幕上面的坐標距離 //這里count-2,是因為count包含了主按鈕 //每個組件的坐標為(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(); //如果衛星菜單存在于底部,那么坐標位置的計算方法,就完全相反. /** * 如果菜單位置在底部 左下 ,右下.坐標會發生變化 * */ 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; } //子布局的測量坐標; child.layout(cl, ct, cl + cWidth, ct + cHeight); } } } /** * 定位主菜單按鈕 */ private void layoutCButton() { // 給主按鈕設置監聽 mCButton = getChildAt(0); mCButton.setOnClickListener(this); //分別代表控件所處離左側和上側得距離 int l = 0; int t = 0; int width = mCButton.getMeasuredWidth(); int height = mCButton.getMeasuredHeight(); /** * getMeasuredHeight()如果前面沒有對象調用,那么這個控件繼承ViewGroup,就意味著這是獲取容器的總高度. * getMeasuredWidth()也是同理. * 那么就可以判斷出控件在四個位置(根據坐標系判斷.) * */ 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的四個屬性.分別代表主按鈕在不同位置距離屏幕左側和上側 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); } //旋轉動畫 rotateCButton(v, 0f, 360f, 300); //判斷菜單是否關閉,如果菜單關閉需要給菜單展開,如果菜單是展開的需要給菜單關閉. toggleMenu(500); } /** * 切換菜單 * 參數:切換菜單的時間是可控的. */ public void toggleMenu(int duration) { //為所有子菜單添加動畫. :平移動畫丶旋轉動畫 int count = getChildCount(); for (int i = 0; i < count - 1; i++) { /** * 默認位置左上的話,子菜單起始坐標點為(-cl,-ct); * 位置右上的話,子菜單起始坐標點為(+cl,-ct); * 位置左下的話,子菜單起始坐標點為(-cl,+ct); * 位置右下的話,子菜單起始坐標點為(+cl,+ct);** * */ final View childView = getChildAt(i + 1); //不管按鈕是開還是關,子菜單必須顯示才能出現動畫效果. childView.setVisibility(View.VISIBLE); //平移 結束為止 0,0(以子菜單按鈕當前位置,為坐標系.) 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 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; } //多個動畫同時使用使用,用到AnimationSet AnimationSet animset = new AnimationSet(true); Animation tranAnim = null; //to open 打開的情況下 if (mCurrentStatus == Status.CLOSE) { tranAnim = new TranslateAnimation(xflag * cl, 0, yflag * ct, 0); //當衛星菜單打開的時候,按鈕就可以進行點擊. childView.setClickable(true); childView.setFocusable(true); } else {//to close tranAnim = new TranslateAnimation(0, xflag * cl, 0, yflag * ct); //當衛星菜單關閉的時候,按鈕也不能隨之點擊. childView.setClickable(false); childView.setFocusable(false); } tranAnim.setFillAfter(true); tranAnim.setDuration(duration); //設置彈出速度. tranAnim.setStartOffset((i * 100) / count); //為動畫設置監聽 如果需要關閉的話,在動畫結束的同時,需要將子菜單的按鈕全部隱藏. tranAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } //在動畫結束時,進行設置. @Override public void onAnimationEnd(Animation animation) { if (mCurrentStatus == Status.CLOSE) { // Log.d("動畫結束狀態",mCurrentStatus +""); childView.setVisibility(View.GONE); } } @Override public void onAnimationRepeat(Animation animation) { } }); //設置旋轉動畫(轉兩圈) RotateAnimation rotateAnim = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotateAnim.setDuration(duration); rotateAnim.setFillAfter(true); //把兩個動畫放到動畫集里面 //注意動畫順序.先增加旋轉/在增加移動./ animset.addAnimation(rotateAnim); animset.addAnimation(tranAnim); childView.startAnimation(animset); final int pos = i + 1; //設置子菜單的點擊事件 childView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mMenuItemClickListener != null) { mMenuItemClickListener.onClick(childView, pos); } menuItemAnim(pos - 1); //切換菜單狀態 changeStatus(); } }); } /** * 當所有子菜單切換完成后,那么菜單的狀態也發生了改變. * 所以changeStatus()必須放在循環外, * */ //切換菜單狀態 changeStatus(); } /** * 切換菜單狀態 */ private void changeStatus() { //在執行一個操作之后,如果按鈕是打開的在次點擊就會切換狀態. mCurrentStatus = (mCurrentStatus == Status.CLOSE ? Status.OPEN : Status.CLOSE); Log.d("動畫結束狀態", mCurrentStatus + ""); } public boolean isOpen(){ return mCurrentStatus ==Status.OPEN; } //設置旋轉動畫繞自身旋轉一圈 然后持續時間為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); //保持動畫旋轉后的狀態. anim.setFillAfter(true); v.startAnimation(anim); } /** * 添加menuItem的點擊動畫 * */ private void menuItemAnim(int pos) { for (int i = 0; i < getChildCount() - 1; i++) { View childView = getChildAt(i + 1); //在判斷條件下,寫入動畫 //當其中一個子菜單被點擊后,自身變大并且消失 //其他子菜單則變小消失. if (i == pos) { childView.startAnimation(scaleBigAnim(300)); } else { childView.startAnimation(scaleSmallAnim(300)); } //當子菜單被點擊之后,其他子菜單就要變成不可被點擊和獲得焦點的狀態, childView.setClickable(false); childView.setFocusable(false); } } /** * 為當前點擊的Item設置變大和透明度降低的動畫 * * @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; } }
以上就是 衛星菜單的編寫,上面的注釋量比較大。
這里需要注意的一點。衛星菜單在屏幕不同位置,他的動畫平移值是不一樣的。
如果實在不理解可以畫圖試試。
三、使用時注意賦予命名空間
<?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中怎么自定義組件衛星菜單問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。