自定义viewgroup实现ArcMenu
最終效果如下
實現(xiàn)思路
通過效果圖,會有幾個問題:
a、動畫效果如何實現(xiàn)
可以看出動畫是從頂點外外發(fā)射的,可能有人說,那還不簡單,默認(rèn)元素都在定點位置,然后TraslateAnimation就好了;這樣忽略了一點,就是TraslateAnimation雖然有動畫效果,但是本質(zhì)是不會改變按鈕的位置,我們的按鈕動畫結(jié)束是要點擊的;有人可能會說那使用屬性動畫,或者改變leftMagin,rightMagin;這樣可能比較麻煩,其實我們可以默認(rèn)讓子菜單就已經(jīng)在目標(biāo)位置,然后GONE,當(dāng)點擊時還是用TraslateAnimation,把起始位置設(shè)為定點,終點位置就是我們隱藏的區(qū)域,動畫結(jié)束VISIBLE.
b、如何確定位置呢?
每次會根據(jù)子菜單數(shù)量,算出a這個角度,然后通過sin , cos 分別算出每個子菜單的left , top ;
當(dāng)然這是在左上的情況,如果在右上,則top還是和左上一致的,left則為 (屏幕寬度-左上算出的left) ;其他兩個方位同理~
整體我通過自定義一個ViewGroup,這個ViewGroup中第一個子元素為點擊的按鈕(你可以隨便布局,隨便用什么控件),接下來的子元素我認(rèn)為是菜單項。根據(jù)效果圖,決定展開半徑和顯示的位置,讓用戶自己去定制。下面看具體實現(xiàn):
自定義View的屬性
<?xml version="1.0" encoding="utf-8"?> <resources><attr name="position"><enum name="left_top" value="0" /><enum name="right_top" value="1" /><enum name="right_bottom" value="2" /><enum name="left_bottom" value="3" /></attr><attr name="radius" format="dimension"></attr><declare-styleable name="ArcMenu"><attr name="position" /><attr name="radius"/></declare-styleable></resources>在自定義的ViewGroup中獲取這些屬性
/** * @author zhy */ public class ArcMenu extends ViewGroup implements OnClickListener { private static final String TAG = "ArcMenu"; /** * 菜單的顯示位置 */ private Position mPosition = Position.LEFT_TOP; /** * 菜單顯示的半徑,默認(rèn)100dp */ private int mRadius = 100; /** * 用戶點擊的按鈕 */ private View mButton; /** * 當(dāng)前ArcMenu的狀態(tài) */ private Status mCurrentStatus = Status.CLOSE; /** * 回調(diào)接口 */ private OnMenuItemClickListener onMenuItemClickListener; /** * 狀態(tài)的枚舉類 * * @author zhy * */ public enum Status { OPEN, CLOSE } /** * 設(shè)置菜單現(xiàn)實的位置,四選1,默認(rèn)右下 * * @author zhy */ public enum Position { LEFT_TOP, RIGHT_TOP, RIGHT_BOTTOM, LEFT_BOTTOM; } public interface OnMenuItemClickListener { void onClick(View view, int pos); } public ArcMenu(Context context) { this(context, null); } public ArcMenu(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 初始化屬性 * * @param context * @param attrs * @param defStyle */ public ArcMenu(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // dp convert to px mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mRadius, getResources().getDisplayMetrics()); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArcMenu, defStyle, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.ArcMenu_position: int val = a.getInt(attr, 0); switch (val) { case 0: mPosition = Position.LEFT_TOP; break; case 1: mPosition = Position.RIGHT_TOP; break; case 2: mPosition = Position.RIGHT_BOTTOM; break; case 3: mPosition = Position.LEFT_BOTTOM; break; } break; case R.styleable.ArcMenu_radius: // dp convert to px mRadius = a.getDimensionPixelSize(attr, (int) TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100f, getResources().getDisplayMetrics())); break; } } a.recycle(); }計算子元素的大小
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); for (int i = 0; i < count; i++) { // mesure child getChildAt(i).measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); }確定子元素的位置
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { layoutButton(); int count = getChildCount(); /** * 設(shè)置所有孩子的位置 例如(第一個為按鈕): 左上時,從左到右 ] 第2個:mRadius(sin0 , cos0) * 第3個:mRadius(sina ,cosa) 注:[a = Math.PI / 2 * (cCount - 1)] * 第4個:mRadius(sin2a ,cos2a) 第5個:mRadius(sin3a , cos3a) ... */ for (int i = 0; i < count - 1; i++) { View child = getChildAt(i + 1); child.setVisibility(View.GONE); int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i)); int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i)); // childview width int cWidth = child.getMeasuredWidth(); // childview height 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; } Log.e(TAG, cl + " , " + ct); child.layout(cl, ct, cl + cWidth, ct + cHeight); } } }首先在layoutButton中對按鈕位置就行設(shè)置,以及初始化點擊事件;然后從第二個子元素開始為菜單項,分別設(shè)置其位置,計算的原理就是上面我畫的草圖,可以再去仔細(xì)看看,動手在紙上畫一畫
/** * 第一個子元素為按鈕,為按鈕布局且初始化點擊事件 */ private void layoutButton() { View cButton = getChildAt(0); cButton.setOnClickListener(this); int l = 0; int t = 0; int width = cButton.getMeasuredWidth(); int height = cButton.getMeasuredHeight(); 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; } Log.e(TAG, l + " , " + t + " , " + (l + width) + " , " + (t + height)); cButton.layout(l, t, l + width, t + height); }這是定位Button的代碼,此時的代碼已經(jīng)實現(xiàn)了定位,如果你把onLayout中childView.setVisibility(VISIBLE)。ArcMenu的整個控件的樣子已經(jīng)實現(xiàn)了,接下來就是點擊事件,已經(jīng)效果動畫的實現(xiàn)了。
設(shè)置按鈕點擊事件
/** * 為按鈕添加點擊事件 */ @Override public void onClick(View v) { mButton = findViewById(R.id.id_button); if (mButton == null) { mButton = getChildAt(0); } rotateView(mButton, 0f, 270f, 300); toggleMenu(300); } /** * 按鈕的旋轉(zhuǎn)動畫 * * @param view * @param fromDegrees * @param toDegrees * @param durationMillis */ public static void rotateView(View view, float fromDegrees, float toDegrees, int durationMillis) { RotateAnimation rotate = new RotateAnimation(fromDegrees, toDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotate.setDuration(durationMillis); rotate.setFillAfter(true); view.startAnimation(rotate); } public void toggleMenu(int durationMillis) { int count = getChildCount(); for (int i = 0; i < count - 1; i++) { final View childView = getChildAt(i + 1); childView.setVisibility(View.VISIBLE); 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; // child left int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i)); // child top int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i)); AnimationSet animset = new AnimationSet(true); Animation animation = null; if (mCurrentStatus == Status.CLOSE) {// to open animset.setInterpolator(new OvershootInterpolator(2F)); animation = new TranslateAnimation(xflag * cl, 0, yflag * ct, 0); childView.setClickable(true); childView.setFocusable(true); } else {// to close animation = new TranslateAnimation(0f, xflag * cl, 0f, yflag * ct); childView.setClickable(false); childView.setFocusable(false); } animation.setAnimationListener(new AnimationListener() { public void onAnimationStart(Animation animation) { } public void onAnimationRepeat(Animation animation) { } public void onAnimationEnd(Animation animation) { if (mCurrentStatus == Status.CLOSE) childView.setVisibility(View.GONE); } }); animation.setFillAfter(true); animation.setDuration(durationMillis); // 為動畫設(shè)置一個開始延遲時間,純屬好看,可以不設(shè) animation.setStartOffset((i * 100) / (count - 1)); RotateAnimation rotate = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotate.setDuration(durationMillis); rotate.setFillAfter(true); animset.addAnimation(rotate); animset.addAnimation(animation); childView.startAnimation(animset); final int index = i + 1; childView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (onMenuItemClickListener != null) onMenuItemClickListener.onClick(childView, index - 1); menuItemAnin(index - 1); changeStatus(); } }); } changeStatus(); Log.e(TAG, mCurrentStatus.name() +""); }點擊時,觸發(fā)TanslateAnimation動畫,從定點向外擴展,也給點擊按鈕添加了一個旋轉(zhuǎn)動畫,每個子菜單項同樣添加了旋轉(zhuǎn)動畫,且如果用戶設(shè)置回調(diào),調(diào)用回調(diào)接口;設(shè)置子菜單的點擊事件。整體就是點擊然后動畫效果~~
設(shè)置子菜單的點擊事件
/** * 開始菜單動畫,點擊的MenuItem放大消失,其他的縮小消失 * @param item */ private void menuItemAnin(int item) { for (int i = 0; i < getChildCount() - 1; i++) { View childView = getChildAt(i + 1); if (i == item) { childView.startAnimation(scaleBigAnim(300)); } else { childView.startAnimation(scaleSmallAnim(300)); } childView.setClickable(false); childView.setFocusable(false); } } /** * 縮小消失 * @param durationMillis * @return */ private Animation scaleSmallAnim(int durationMillis) { Animation anim = new ScaleAnimation(1.0f, 0f, 1.0f, 0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); anim.setDuration(durationMillis); anim.setFillAfter(true); return anim; } /** * 放大,透明度降低 * @param durationMillis * @return */ private Animation scaleBigAnim(int durationMillis) { AnimationSet animationset = new AnimationSet(true); Animation anim = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); Animation alphaAnimation = new AlphaAnimation(1, 0); animationset.addAnimation(anim); animationset.addAnimation(alphaAnimation); animationset.setDuration(durationMillis); animationset.setFillAfter(true); return animationset; }點擊的菜單項變大且慢慢透明消失,未點擊的菜單項縮小消失~有興趣的可以改成自己喜歡的動畫~
注:動畫效果很多借鑒了eoe上那位仁兄的代碼,這類動畫也比較簡單,就不多說了~
好了,剩下就是些getter,setter了~
布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:zhy="http://schemas.android.com/apk/res/com.example.zhy_arcmenu"android:layout_width="match_parent"android:layout_height="match_parent" ><com.example.zhy_arcmenu.ArcMenu android:id="@+id/id_arcmenu1"android:layout_width="fill_parent"android:layout_height="fill_parent"zhy:position="left_top"zhy:radius="130dp" ><RelativeLayout android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/composer_button" ><ImageView android:id="@+id/id_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_icn_plus" /></RelativeLayout><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_camera"android:tag="Camera" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_sun"android:tag="Sun" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_place"android:tag="Place" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_sleep"android:tag="Sleep" /></com.example.zhy_arcmenu.ArcMenu><com.example.zhy_arcmenu.ArcMenu android:layout_width="fill_parent"android:layout_height="fill_parent"zhy:position="right_bottom"zhy:radius="130dp" ><RelativeLayout android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/composer_button" ><ImageView android:id="@+id/id_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_icn_plus" /></RelativeLayout><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_camera"android:tag="Camera" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_sun"android:tag="Sun" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_place"android:tag="Place" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_sleep"android:tag="Sleep" /></com.example.zhy_arcmenu.ArcMenu><com.example.zhy_arcmenu.ArcMenu android:layout_width="fill_parent"android:layout_height="fill_parent"zhy:position="left_bottom"zhy:radius="130dp" ><RelativeLayout android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/composer_button" ><ImageView android:id="@+id/id_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_icn_plus" /></RelativeLayout><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_sun"android:tag="Sun" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_place"android:tag="Place" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_sleep"android:tag="Sleep" /></com.example.zhy_arcmenu.ArcMenu><com.example.zhy_arcmenu.ArcMenu android:layout_width="fill_parent"android:layout_height="fill_parent"zhy:position="right_top"zhy:radius="130dp" ><RelativeLayout android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/composer_button" ><ImageView android:id="@+id/id_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_icn_plus" /></RelativeLayout><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_camera"android:tag="Camera" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_sun"android:tag="Sun" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_place"android:tag="Place" /><ImageView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:src="@drawable/composer_sleep"android:tag="Sleep" /></com.example.zhy_arcmenu.ArcMenu></RelativeLayout>MainActivity
package com.example.zhy_arcmenu;import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.Window; import android.widget.ImageView; import android.widget.Toast;import com.example.zhy_arcmenu.ArcMenu.OnMenuItemClickListener;public class MainActivity extends Activity {private ArcMenu mArcMenuLeftTop;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mArcMenuLeftTop = (ArcMenu) findViewById(R.id.id_arcmenu1);//動態(tài)添加一個MenuItemImageView people = new ImageView(this);people.setImageResource(R.drawable.composer_with);people.setTag("People");mArcMenuLeftTop.addView(people);mArcMenuLeftTop.setOnMenuItemClickListener(new OnMenuItemClickListener(){@Overridepublic void onClick(View view, int pos){Toast.makeText(MainActivity.this,pos + ":" + view.getTag(), Toast.LENGTH_SHORT).show();}});}}注意
本文中用到了android TypedValue.applyDimension,這個方法是轉(zhuǎn)變?yōu)闃?biāo)準(zhǔn)尺寸的一個函數(shù)
參考鏈接
android TypedValue.applyDimension()的作用 - BuleRiver的專欄 - 博客頻道 - CSDN.NET
Animation.setFillAfter and Animation.setFillBefore的作用
動畫終止時停留在最后一幀~不然會回到?jīng)]有執(zhí)行之前的狀態(tài)
參考鏈接
Animation.setFillAfter and Animation.setFillBefore的作用 - PuerTea - 博客園
關(guān)于animation的詳細(xì)介紹
詳情參見
【TweenedAnimation】四種動畫效果參數(shù)詳解(自測所得) - 邪天殤 - 博客園
Animation & Property Animation 使用 - Ajian_studio - 博客頻道 - CSDN.NET
本文主要參考
Android 自定義ViewGroup手把手教你實現(xiàn)ArcMenu - Hongyang - 博客頻道 - CSDN.NET
源代碼下載
源代碼
總結(jié)
以上是生活随笔為你收集整理的自定义viewgroup实现ArcMenu的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 彩色图像处理
- 下一篇: C语言实现大数据除法