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

逛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的属性:
  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

  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、计算子元素的大小:
  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、确定子元素的位置:
  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中对按钮位置就行设置,以及初始化点击事件;然后从第二个子元素开始为菜单项,分别设置其位置,计算的原理就是上面我画的草图,可以再去仔细看看,动手在纸上画一画。

  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、设置按钮点击事件
  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. }
  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(0, 720,
  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、设置子菜单的点击事件
  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(1, 0);
  51. animationset.addAnimation(anim);
  52. animationset.addAnimation(alphaAnimation);
  53. animationset.setDuration(durationMillis);
  54. animationset.setFillAfter(true);
  55. return animationset;
  56. }

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

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

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

8、布局文件:
  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
  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. mArcMenuLeftTop
  30. .setOnMenuItemClickListener(new OnMenuItemClickListener()
  31. {
  32. @Override
  33. public void onClick(View view, int pos)
  34. {
  35. Toast.makeText(MainActivity.this,
  36. pos + ":" + view.getTag(), Toast.LENGTH_SHORT)
  37. .show();
  38. }
  39. });
  40. }
  41.  
  42. }

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

源码点击下载

版权声明:本文为博主原创文章,未经博主允许不得转载。

Android 自定义ViewGroup手把手教你实现ArcMenu的更多相关文章

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

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这种UI效果,感觉非常不错,后来知道github上有这 ...

  2. android自定义viewgroup之我也玩瀑布流

    先看效果图吧, 继上一篇<android自定义viewgroup实现等分格子布局>中实现的布局效果,这里稍微有些区别,每个格子的高度不规则,就是传说的瀑布流布局,一般实现这种效果,要么用第 ...

  3. Android开发之手把手教你写ButterKnife框架(三)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52672188 本文出自:[余志强的博客] 一.概述 上一篇博客讲了, ...

  4. Android开发之手把手教你写ButterKnife框架(二)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开 ...

  5. Android 自定义ViewGroup 实战篇 -> 实现FlowLayout

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38352503 ,本文出自[张鸿洋的博客] 1.概述 上一篇已经基本给大家介绍了如 ...

  6. android自定义viewgroup初步之一----抽屉菜单

    转载请注明出处 http://blog.csdn.net/wingichoy/article/details/47832151 几天前在慕课网上看到鸿洋老师的 自定义卫星菜单,感觉很有意思,于是看完视 ...

  7. Android自定义ViewGroup

    视图分类就两类,View和ViewGroup.ViewGroup是View的子类,ViewGroup可以包含所有的View(包括ViewGroup),View只能自我描绘,不能包含其他View. 然而 ...

  8. Android自定义ViewGroup,实现自动换行

    学习<Android开发艺术探索>中自定义ViewGroup章节 自定义ViewGroup总结的知识点 一.自定义ViewGroup中,onMeasure理解 onMeasure(int ...

  9. android自定义viewgroup实现等分格子布局

    先上效果图: 实现这样的效果: 一般的思路就是,直接写布局文件,用LinearLayout 嵌套多层子LinearLayout,然后根据权重layout_weight可以达到上面的效果 还有就是利用g ...

随机推荐

  1. java--加强之 jdk1.5简单新特性,枚举,注解

    转载请申明出处:http://blog.csdn.net/xmxkf/article/details/9944041 Jdk1.51新特性(静态导入,可变参数,加强for循环,自动拆装箱) 08.ja ...

  2. LeetCode(47)-Reverse Bits

    题目: Reverse bits of a given 32 bits unsigned integer. For example, given input 43261596 (represented ...

  3. AngularJs 指令directive之require

    controller的用法分为两种情形,一种是require自定义的controller,由于自定义controller中的属性方法都由自己编 写,使用起来比较简单:另一种方法则是require An ...

  4. ORACLE复杂查询之连接查询

    一.传统的连接查询 1.交叉连接:返回笛卡尔积 WHERE中限定查询条件,可以预先过滤掉掉不符合条件的记录,返回的只是两个表中剩余记录(符合条件的记录)的笛卡尔积. 2.内连接:参与连接的表地位平等, ...

  5. Gradle初探

    (一):创建一个Gradle项目 1. 环境准备 1.1. 先上Gradle官网下载最新版的程序,地址:https://gradle.org/gradle-download/. 1.2. 配置环境变量 ...

  6. Partitions - Partition Storage Modes and Processing-MOLAP、ROLAP、HOLAP

    https://docs.microsoft.com/en-us/sql/analysis-services/multidimensional-models-olap-logical-cube-obj ...

  7. AdminIII连接linux Postgresql过程中的几个小问题

    1.postgresql.conf主配置文件中要配置postgresql绑定的IP,如果不设置,可能只绑定本地闭环地址:127.0.0.1,可以设定为0.0.0.0:就包括了一切IPv4地址 2.pg ...

  8. Django1.6版本的PG数据库定义手动升级

    Django1.7以后添加了migration功能,数据库定义的升级完全实现自动化,之前是通过一个叫south的app来做的.这篇文章谈一下1.6下的手动更新升级. 1.table create和ta ...

  9. Java杂记9—NIO

    前言 非阻塞IO,也被称之为新IO,它重新定义了一些概念. 缓冲buffer 通道 channel 通道选择器 BIO 阻塞IO,几乎所有的java程序员都会的字节流,字符流,输入流,输出流等分类就是 ...

  10. Java 8 基础教程 - Predicate

    在Java 8中,Predicate是一个函数式接口,可以被应用于lambda表达式和方法引用.其抽象方法非常简单: /** * Evaluates this predicate on the giv ...