Android 自定义ViewGroup手把手教你实现ArcMenu

逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这么个开源项目~~~~当然本篇不是教你如何使用这个开源项目,而是教你如何自己通过自定义ViewGroup写这样的效果,自定义ViewGroup也是我的痛楚,嘿嘿,希望以此可以抛砖引玉~~

效果图:



1、实现思路

通过效果图,会有几个问题:

a、动画效果如何实现

可以看出动画是从顶点外外发射的,可能有人说,那还不简单,默认元素都在定点位置,然后TraslateAnimation就好了;这样忽略了一点,就是TraslateAnimation虽然有动画效果,但是本质是不会改变按钮的位置,我们的按钮动画结束是要点击的;有人可能会说那使用属性动画,或者改变leftMagin,rightMagin;这样可能比较麻烦,其实我们可以默认让子菜单就已经在目标位置,然后GONE,当点击时还是用TraslateAnimation,把起始位置设为定点,终点位置就是我们隐藏的区域,动画结束VISIBLE.

b、如何确定位置呢?

这可能需要一点数学上的知识,我画了一张草图(冰天雪地,跪玻璃碴子求画下面这些图的工具):


每次会根据子菜单数量,算出a这个角度,然后通过sin , cos 分别算出每个子菜单的left , top ;

当然这是在左上的情况,如果在右上,则top还是和左上一致的,left则为 (屏幕宽度-左上算出的left) ;其他两个方位同理~


整体我通过自定义一个ViewGroup,这个ViewGroup中第一个子元素为点击的按钮(你可以随便布局,随便用什么控件),接下来的子元素我认为是菜单项。根据效果图,决定展开半径和显示的位置,让用户自己去定制。下面看具体实现:

2、自定义View的属性:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <attr name="position">  
  4.         <enum name="left_top" value="0" />  
  5.         <enum name="right_top" value="1" />  
  6.         <enum name="right_bottom" value="2" />  
  7.         <enum name="left_bottom" value="3" />  
  8.     </attr>  
  9.     <attr name="radius" format="dimension"></attr>  
  10.   
  11.     <declare-styleable name="ArcMenu">  
  12.         <attr name="position" />  
  13.         <attr name="radius"/>  
  14.     </declare-styleable>  
  15.   
  16. </resources>  

3、在自定义的ViewGroup中获取这些属性

Arcmenu.java

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * @author zhy 
  3.  */  
  4. public class ArcMenu extends ViewGroup implements OnClickListener  
  5. {  
  6.   
  7.     private static final String TAG = "ArcMenu";  
  8.     /** 
  9.      * 菜单的显示位置 
  10.      */  
  11.     private Position mPosition = Position.LEFT_TOP;  
  12.   
  13.     /** 
  14.      * 菜单显示的半径,默认100dp 
  15.      */  
  16.     private int mRadius = 100;  
  17.     /** 
  18.      * 用户点击的按钮 
  19.      */  
  20.     private View mButton;  
  21.     /** 
  22.      * 当前ArcMenu的状态 
  23.      */  
  24.     private Status mCurrentStatus = Status.CLOSE;  
  25.     /** 
  26.      * 回调接口 
  27.      */  
  28.     private OnMenuItemClickListener onMenuItemClickListener;  
  29.   
  30.     /** 
  31.      * 状态的枚举类 
  32.      *  
  33.      * @author zhy 
  34.      *  
  35.      */  
  36.     public enum Status  
  37.     {  
  38.         OPEN, CLOSE  
  39.     }  
  40.   
  41.     /** 
  42.      * 设置菜单现实的位置,四选1,默认右下 
  43.      *  
  44.      * @author zhy 
  45.      */  
  46.     public enum Position  
  47.     {  
  48.         LEFT_TOP, RIGHT_TOP, RIGHT_BOTTOM, LEFT_BOTTOM;  
  49.     }  
  50.   
  51.     public interface OnMenuItemClickListener  
  52.     {  
  53.         void onClick(View view, int pos);  
  54.     }  
  55.   
  56.     public ArcMenu(Context context)  
  57.     {  
  58.         this(context, null);  
  59.     }  
  60.   
  61.     public ArcMenu(Context context, AttributeSet attrs)  
  62.     {  
  63.         this(context, attrs, 0);  
  64.   
  65.     }  
  66.   
  67.     /** 
  68.      * 初始化属性 
  69.      *  
  70.      * @param context 
  71.      * @param attrs 
  72.      * @param defStyle 
  73.      */  
  74.     public ArcMenu(Context context, AttributeSet attrs, int defStyle)  
  75.     {  
  76.   
  77.         super(context, attrs, defStyle);  
  78.         // dp convert to px  
  79.         mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  80.                 mRadius, getResources().getDisplayMetrics());  
  81.         TypedArray a = context.getTheme().obtainStyledAttributes(attrs,  
  82.                 R.styleable.ArcMenu, defStyle, 0);  
  83.   
  84.         int n = a.getIndexCount();  
  85.         for (int i = 0; i < n; i++)  
  86.         {  
  87.             int attr = a.getIndex(i);  
  88.             switch (attr)  
  89.             {  
  90.             case R.styleable.ArcMenu_position:  
  91.                 int val = a.getInt(attr, 0);  
  92.                 switch (val)  
  93.                 {  
  94.                 case 0:  
  95.                     mPosition = Position.LEFT_TOP;  
  96.                     break;  
  97.                 case 1:  
  98.                     mPosition = Position.RIGHT_TOP;  
  99.                     break;  
  100.                 case 2:  
  101.                     mPosition = Position.RIGHT_BOTTOM;  
  102.                     break;  
  103.                 case 3:  
  104.                     mPosition = Position.LEFT_BOTTOM;  
  105.                     break;  
  106.                 }  
  107.                 break;  
  108.             case R.styleable.ArcMenu_radius:  
  109.                 // dp convert to px  
  110.                 mRadius = a.getDimensionPixelSize(attr, (int) TypedValue  
  111.                         .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100f,  
  112.                                 getResources().getDisplayMetrics()));  
  113.                 break;  
  114.   
  115.             }  
  116.         }  
  117.         a.recycle();  
  118.     }  

4、计算子元素的大小:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
  3.     {  
  4.         int count = getChildCount();  
  5.         for (int i = 0; i < count; i++)  
  6.         {  
  7.             // mesure child  
  8.             getChildAt(i).measure(MeasureSpec.UNSPECIFIED,  
  9.                     MeasureSpec.UNSPECIFIED);  
  10.         }  
  11.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  12.     }  

5、确定子元素的位置:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2.     protected void onLayout(boolean changed, int l, int t, int r, int b)  
  3.     {  
  4.         if (changed)  
  5.         {  
  6.   
  7.             layoutButton();  
  8.             int count = getChildCount();  
  9.             /** 
  10.              * 设置所有孩子的位置 例如(第一个为按钮): 左上时,从左到右 ] 第2个:mRadius(sin0 , cos0) 
  11.              * 第3个:mRadius(sina ,cosa) 注:[a = Math.PI / 2 * (cCount - 1)] 
  12.              * 第4个:mRadius(sin2a ,cos2a) 第5个:mRadius(sin3a , cos3a) ... 
  13.              */  
  14.             for (int i = 0; i < count - 1; i++)  
  15.             {  
  16.                 View child = getChildAt(i + 1);  
  17.                 child.setVisibility(View.GONE);  
  18.   
  19.                 int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2)  
  20.                         * i));  
  21.                 int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2)  
  22.                         * i));  
  23.                 // childview width  
  24.                 int cWidth = child.getMeasuredWidth();  
  25.                 // childview height  
  26.                 int cHeight = child.getMeasuredHeight();  
  27.   
  28.                 // 右上,右下  
  29.                 if (mPosition == Position.LEFT_BOTTOM  
  30.                         || mPosition == Position.RIGHT_BOTTOM)  
  31.                 {  
  32.                     ct = getMeasuredHeight() - cHeight - ct;  
  33.                 }  
  34.                 // 右上,右下  
  35.                 if (mPosition == Position.RIGHT_TOP  
  36.                         || mPosition == Position.RIGHT_BOTTOM)  
  37.                 {  
  38.                     cl = getMeasuredWidth() - cWidth - cl;  
  39.                 }  
  40.   
  41.                 Log.e(TAG, cl + " , " + ct);  
  42.                 child.layout(cl, ct, cl + cWidth, ct + cHeight);  
  43.   
  44.             }  
  45.         }  
  46.     }  

首先在layoutButton中对按钮位置就行设置,以及初始化点击事件;然后从第二个子元素开始为菜单项,分别设置其位置,计算的原理就是上面我画的草图,可以再去仔细看看,动手在纸上画一画。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 第一个子元素为按钮,为按钮布局且初始化点击事件 
  3.  */  
  4. private void layoutButton()  
  5. {  
  6.     View cButton = getChildAt(0);  
  7.   
  8.     cButton.setOnClickListener(this);  
  9.   
  10.     int l = 0;  
  11.     int t = 0;  
  12.     int width = cButton.getMeasuredWidth();  
  13.     int height = cButton.getMeasuredHeight();  
  14.     switch (mPosition)  
  15.     {  
  16.     case LEFT_TOP:  
  17.         l = 0;  
  18.         t = 0;  
  19.         break;  
  20.     case LEFT_BOTTOM:  
  21.         l = 0;  
  22.         t = getMeasuredHeight() - height;  
  23.         break;  
  24.     case RIGHT_TOP:  
  25.         l = getMeasuredWidth() - width;  
  26.         t = 0;  
  27.         break;  
  28.     case RIGHT_BOTTOM:  
  29.         l = getMeasuredWidth() - width;  
  30.         t = getMeasuredHeight() - height;  
  31.         break;  
  32.   
  33.     }  
  34.     Log.e(TAG, l + " , " + t + " , " + (l + width) + " , " + (t + height));  
  35.     cButton.layout(l, t, l + width, t + height);  
  36.   
  37. }  

这是定位Button的代码,此时的代码已经实现了定位,如果你把onLayout中childView.setVisibility(VISIBLE)。ArcMenu的整个控件的样子已经实现了,接下来就是点击事件,已经效果动画的实现了。

6、设置按钮点击事件

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * 为按钮添加点击事件 
  3.      */  
  4.     @Override  
  5.     public void onClick(View v)  
  6.     {  
  7.         mButton = findViewById(R.id.id_button);  
  8.         if (mButton == null)  
  9.         {  
  10.             mButton = getChildAt(0);  
  11.         }  
  12.         rotateView(mButton, 0f, 270f, 300);  
  13.         toggleMenu(300);  
  14.     }  

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * 按钮的旋转动画 
  3.      *  
  4.      * @param view 
  5.      * @param fromDegrees 
  6.      * @param toDegrees 
  7.      * @param durationMillis 
  8.      */  
  9.     public static void rotateView(View view, float fromDegrees,  
  10.             float toDegrees, int durationMillis)  
  11.     {  
  12.         RotateAnimation rotate = new RotateAnimation(fromDegrees, toDegrees,  
  13.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
  14.                 0.5f);  
  15.         rotate.setDuration(durationMillis);  
  16.         rotate.setFillAfter(true);  
  17.         view.startAnimation(rotate);  
  18.     }  
  19.   
  20.     public void toggleMenu(int durationMillis)  
  21.     {  
  22.         int count = getChildCount();  
  23.         for (int i = 0; i < count - 1; i++)  
  24.         {  
  25.             final View childView = getChildAt(i + 1);  
  26.             childView.setVisibility(View.VISIBLE);  
  27.   
  28.             int xflag = 1;  
  29.             int yflag = 1;  
  30.   
  31.             if (mPosition == Position.LEFT_TOP  
  32.                     || mPosition == Position.LEFT_BOTTOM)  
  33.                 xflag = -1;  
  34.             if (mPosition == Position.LEFT_TOP  
  35.                     || mPosition == Position.RIGHT_TOP)  
  36.                 yflag = -1;  
  37.   
  38.             // child left  
  39.             int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));  
  40.             // child top  
  41.             int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));  
  42.   
  43.             AnimationSet animset = new AnimationSet(true);  
  44.             Animation animation = null;  
  45.             if (mCurrentStatus == Status.CLOSE)  
  46.             {// to open  
  47.                 animset.setInterpolator(new OvershootInterpolator(2F));  
  48.                 animation = new TranslateAnimation(xflag * cl, 0, yflag * ct, 0);  
  49.                 childView.setClickable(true);  
  50.                 childView.setFocusable(true);  
  51.             } else  
  52.             {// to close  
  53.                 animation = new TranslateAnimation(0f, xflag * cl, 0f, yflag  
  54.                         * ct);  
  55.                 childView.setClickable(false);  
  56.                 childView.setFocusable(false);  
  57.             }  
  58.             animation.setAnimationListener(new AnimationListener()  
  59.             {  
  60.                 public void onAnimationStart(Animation animation)  
  61.                 {  
  62.                 }  
  63.   
  64.                 public void onAnimationRepeat(Animation animation)  
  65.                 {  
  66.                 }  
  67.   
  68.                 public void onAnimationEnd(Animation animation)  
  69.                 {  
  70.                     if (mCurrentStatus == Status.CLOSE)  
  71.                         childView.setVisibility(View.GONE);  
  72.   
  73.                 }  
  74.             });  
  75.   
  76.             animation.setFillAfter(true);  
  77.             animation.setDuration(durationMillis);  
  78.             // 为动画设置一个开始延迟时间,纯属好看,可以不设  
  79.             animation.setStartOffset((i * 100) / (count - 1));  
  80.             RotateAnimation rotate = new RotateAnimation(0720,  
  81.                     Animation.RELATIVE_TO_SELF, 0.5f,  
  82.                     Animation.RELATIVE_TO_SELF, 0.5f);  
  83.             rotate.setDuration(durationMillis);  
  84.             rotate.setFillAfter(true);  
  85.             animset.addAnimation(rotate);  
  86.             animset.addAnimation(animation);  
  87.             childView.startAnimation(animset);  
  88.             final int index = i + 1;  
  89.             childView.setOnClickListener(new View.OnClickListener()  
  90.             {  
  91.                 @Override  
  92.                 public void onClick(View v)  
  93.                 {  
  94.                     if (onMenuItemClickListener != null)  
  95.                         onMenuItemClickListener.onClick(childView, index - 1);  
  96.                     menuItemAnin(index - 1);  
  97.                     changeStatus();  
  98.                       
  99.                 }  
  100.             });  
  101.   
  102.         }  
  103.         changeStatus();  
  104.         Log.e(TAG, mCurrentStatus.name() +"");  
  105.     }  

点击时,触发TanslateAnimation动画,从定点向外扩展,也给点击按钮添加了一个旋转动画,每个子菜单项同样添加了旋转动画,且如果用户设置回调,调用回调接口;设置子菜单的点击事件。整体就是点击然后动画效果~~

7、设置子菜单的点击事件

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * 开始菜单动画,点击的MenuItem放大消失,其他的缩小消失 
  3.      * @param item 
  4.      */  
  5.     private void menuItemAnin(int item)  
  6.     {  
  7.         for (int i = 0; i < getChildCount() - 1; i++)  
  8.         {  
  9.             View childView = getChildAt(i + 1);  
  10.             if (i == item)  
  11.             {  
  12.                 childView.startAnimation(scaleBigAnim(300));  
  13.             } else  
  14.             {  
  15.                 childView.startAnimation(scaleSmallAnim(300));  
  16.             }  
  17.             childView.setClickable(false);  
  18.             childView.setFocusable(false);  
  19.   
  20.         }  
  21.   
  22.     }  
  23.       
  24.     /** 
  25.      * 缩小消失 
  26.      * @param durationMillis 
  27.      * @return 
  28.      */  
  29.     private Animation scaleSmallAnim(int durationMillis)  
  30.     {  
  31.         Animation anim = new ScaleAnimation(1.0f, 0f, 1.0f, 0f,  
  32.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
  33.                 0.5f);  
  34.         anim.setDuration(durationMillis);  
  35.         anim.setFillAfter(true);  
  36.         return anim;  
  37.     }  
  38.     /** 
  39.      * 放大,透明度降低 
  40.      * @param durationMillis 
  41.      * @return 
  42.      */  
  43.     private Animation scaleBigAnim(int durationMillis)  
  44.     {  
  45.         AnimationSet animationset = new AnimationSet(true);  
  46.   
  47.         Animation anim = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f,  
  48.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
  49.                 0.5f);  
  50.         Animation alphaAnimation = new AlphaAnimation(10);  
  51.         animationset.addAnimation(anim);  
  52.         animationset.addAnimation(alphaAnimation);  
  53.         animationset.setDuration(durationMillis);  
  54.         animationset.setFillAfter(true);  
  55.         return animationset;  
  56.     }  

点击的菜单项变大且慢慢透明消失,未点击的菜单项缩小消失~有兴趣的可以改成自己喜欢的动画~

注:动画效果很多借鉴了eoe上那位仁兄的代码,这类动画也比较简单,就不多说了~


好了,剩下就是些getter,setter了~

8、布局文件:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     xmlns:zhy="http://schemas.android.com/apk/res/com.example.zhy_arcmenu"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent" >  
  6.   
  7.     <com.example.zhy_arcmenu.ArcMenu  
  8.         android:id="@+id/id_arcmenu1"  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="fill_parent"  
  11.         zhy:position="left_top"  
  12.         zhy:radius="130dp" >  
  13.   
  14.         <RelativeLayout  
  15.             android:layout_width="wrap_content"  
  16.             android:layout_height="wrap_content"  
  17.             android:background="@drawable/composer_button" >  
  18.   
  19.             <ImageView  
  20.                 android:id="@+id/id_button"  
  21.                 android:layout_width="wrap_content"  
  22.                 android:layout_height="wrap_content"  
  23.                 android:layout_centerInParent="true"  
  24.                 android:src="@drawable/composer_icn_plus" />  
  25.         </RelativeLayout>  
  26.   
  27.         <ImageView  
  28.             android:layout_width="wrap_content"  
  29.             android:layout_height="wrap_content"  
  30.             android:layout_centerInParent="true"  
  31.             android:src="@drawable/composer_camera"  
  32.             android:tag="Camera" />  
  33.   
  34.         <ImageView  
  35.             android:layout_width="wrap_content"  
  36.             android:layout_height="wrap_content"  
  37.             android:layout_centerInParent="true"  
  38.             android:src="@drawable/composer_sun"  
  39.             android:tag="Sun" />  
  40.   
  41.         <ImageView  
  42.             android:layout_width="wrap_content"  
  43.             android:layout_height="wrap_content"  
  44.             android:layout_centerInParent="true"  
  45.             android:src="@drawable/composer_place"  
  46.             android:tag="Place" />  
  47.           
  48.         <ImageView  
  49.             android:layout_width="wrap_content"  
  50.             android:layout_height="wrap_content"  
  51.             android:layout_centerInParent="true"  
  52.             android:src="@drawable/composer_sleep"  
  53.             android:tag="Sleep" />  
  54.   
  55.     </com.example.zhy_arcmenu.ArcMenu>  

嗯,第一个元素为按钮,其他的都是菜单项了~~喜欢用代码的,也可以代码生成,我是比较喜欢布局文件中写这些玩意~

9、MainActivity

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.zhy_arcmenu;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6. import android.view.Window;  
  7. import android.widget.ImageView;  
  8. import android.widget.Toast;  
  9.   
  10. import com.example.zhy_arcmenu.ArcMenu.OnMenuItemClickListener;  
  11.   
  12. public class MainActivity extends Activity  
  13. {  
  14.   
  15.     private ArcMenu mArcMenuLeftTop;  
  16.   
  17.     @Override  
  18.     protected void onCreate(Bundle savedInstanceState)  
  19.     {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.         mArcMenuLeftTop = (ArcMenu) findViewById(R.id.id_arcmenu1);  
  23.         //动态添加一个MenuItem  
  24.         ImageView people = new ImageView(this);  
  25.         people.setImageResource(R.drawable.composer_with);  
  26.         people.setTag("People");  
  27.         mArcMenuLeftTop.addView(people);  
  28.   
  29.       
  30.         mArcMenuLeftTop  
  31.                 .setOnMenuItemClickListener(new OnMenuItemClickListener()  
  32.                 {  
  33.                     @Override  
  34.                     public void onClick(View view, int pos)  
  35.                     {  
  36.                         Toast.makeText(MainActivity.this,  
  37.                                 pos + ":" + view.getTag(), Toast.LENGTH_SHORT)  
  38.                                 .show();  
  39.                     }  
  40.                 });  
  41.     }  
  42.   
  43. }  



结束~~有任何意见欢迎指出~~

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907

源码点击下载


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值