浮动菜单
效果图:
1、菜单栏类
1 /** 2 * 1.该控件必须包含3个及以上子控件 3 * 2.第一个子控件表示浮动菜单的母菜单,剩余的作为浮动控件的子菜单 4 * 3.子菜单和母菜单之间的距离可以修改 RADIUS 的值 5 */ 6 public class FloatingMenu extends ViewGroup { 7 // 子菜单和母菜单图标距离 8 private final static int RADIUS = 400; 9 // 当前菜单状态 10 private MenuStatu currentStatu = MenuStatu.STATU_CLOSE; 11 12 // 菜单状态枚举 13 public enum MenuStatu { 14 STATU_OPEN, STATU_CLOSE 15 } 16 17 /** 18 * 子菜单被点击回调接口 19 */ 20 public interface OnItemMenuClickListener { 21 void onItemMenuClick(View view, int position); 22 } 23 // 子菜单被点击回调接口 24 private OnItemMenuClickListener onItemMenuClickListener; 25 public void setOnItemMenuClickListener(OnItemMenuClickListener onItemMenuClickListener) { 26 this.onItemMenuClickListener = onItemMenuClickListener; 27 } 28 public FloatingMenu(Context context) { 29 super(context); 30 } 31 public FloatingMenu(Context context, AttributeSet attrs) { 32 super(context, attrs); 33 } 34 public FloatingMenu(Context context, AttributeSet attrs, int defStyleAttr) { 35 super(context, attrs, defStyleAttr); 36 } 37 38 @Override 39 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 40 int childCount = getChildCount(); 41 42 if (childCount < 3) 43 throw new IllegalStateException("当前控件的孩子控件至少需要3个"); 44 45 measureChildren(widthMeasureSpec, heightMeasureSpec); 46 int tempWidth = getChildAt(1).getMeasuredWidth(); 47 int tempHeight = getChildAt(childCount - 1).getMeasuredHeight(); 48 setMeasuredDimension(RADIUS + tempWidth, RADIUS + tempHeight); 49 } 50 51 @Override 52 protected void onLayout(boolean changed, int l, int t, int r, int b) { 53 if (changed) { 54 int measuredWidth = getMeasuredWidth(); 55 int measuredHeight = getMeasuredHeight(); 56 int childCount = getChildCount(); 57 // 计算每2个子菜单之间的角度值 58 float averageAngle = 90 / (childCount - 1 - 1); 59 60 for (int i = 0; i < childCount; i++) { 61 final View childAt = getChildAt(i); 62 int childWidth = childAt.getMeasuredWidth(); 63 int childHeight = childAt.getMeasuredHeight(); 64 65 // 第一个子控件是母菜单 66 if (i == 0) { 67 int left = measuredWidth - childWidth; 68 int top = measuredHeight - childHeight; 69 childAt.layout(left, top, measuredWidth, measuredHeight); 70 71 childAt.setOnClickListener(new OnClickListener() { 72 @Override 73 public void onClick(View v) { 74 changeStatuAnim(300); 75 } 76 }); 77 } else { // 其余的为子菜单 78 final int temp = i; 79 // 计算每一个子菜单的位置 80 float calAngle = (i - 1) * averageAngle; 81 double centerX = RADIUS * Math.cos(Math.PI / 180 * calAngle); 82 double centerY = RADIUS * Math.sin(Math.PI / 180 * calAngle); 83 int left = (int) (measuredWidth - centerX - childWidth); 84 int top = (int) (measuredHeight - centerY - childHeight); 85 int right = (int) (measuredWidth - centerX); 86 int bottom = (int) (measuredHeight - centerY); 87 childAt.layout(left, top, right, bottom); 88 89 childAt.setVisibility(View.GONE); 90 91 childAt.setOnClickListener(new OnClickListener() { 92 @Override 93 public void onClick(View v) { 94 if (onItemMenuClickListener != null) { 95 onItemMenuClickListener.onItemMenuClick(childAt, temp); 96 } 97 clickItemAnim(temp); 98 } 99 }); 100 } 101 } 102 } 103 } 104 105 /** 106 * 点击子菜单时的动画效果 107 * 108 * @param position 109 */ 110 private void clickItemAnim(int position) { 111 for (int i = 1; i < getChildCount(); i++) { 112 View childAt = getChildAt(i); 113 if (i == position) { 114 childAt.startAnimation(toBig()); 115 } else { 116 childAt.startAnimation(toSmall()); 117 } 118 childAt.setVisibility(GONE); 119 } 120 changeStatu(); 121 } 122 123 /** 124 * 变小,变透明 125 */ 126 private Animation toSmall() { 127 AnimationSet animationSet = new AnimationSet(true); 128 AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0); 129 ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0, 1, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 130 animationSet.setDuration(200); 131 animationSet.addAnimation(alphaAnimation); 132 animationSet.addAnimation(scaleAnimation); 133 return animationSet; 134 } 135 136 /** 137 * 变大,变透明 138 * 139 * @return 140 */ 141 private Animation toBig() { 142 AnimationSet animationSet = new AnimationSet(true); 143 AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0); 144 ScaleAnimation scaleAnimation = new ScaleAnimation(1, 3, 1, 3, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 145 animationSet.setDuration(200); 146 animationSet.addAnimation(alphaAnimation); 147 animationSet.addAnimation(scaleAnimation); 148 return animationSet; 149 } 150 151 /** 152 * 子菜单状态改变动画效果 153 * 154 * @param durationMillis 动画执行时间 155 */ 156 private void changeStatuAnim(int durationMillis) { 157 int childCount = getChildCount(); 158 for (int i = 1; i < childCount; i++) { 159 final View view = getChildAt(i); 160 161 float jiao = 90 / (childCount - 1 - 1); 162 float jiJiao = (i - 1) * jiao; 163 float toX = (float) (RADIUS * Math.cos(Math.PI / 180 * jiJiao)); 164 float toY = (float) (RADIUS * Math.sin(Math.PI / 180 * jiJiao)); 165 166 AnimationSet animationSet = new AnimationSet(true); 167 RotateAnimation rotateAnim = new RotateAnimation( 168 0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 169 TranslateAnimation translateAnimation; 170 AlphaAnimation alphaAnimation; 171 // 根据子菜单状态实现不同的动画效果 172 if (!isOpen()) { 173 translateAnimation = new TranslateAnimation(toX, 0, toY, 0); 174 alphaAnimation = new AlphaAnimation(0, 1); 175 } else { 176 translateAnimation = new TranslateAnimation(0, toX, 0, toY); 177 alphaAnimation = new AlphaAnimation(1, 0); 178 } 179 180 // 注意先添加旋转动画,再添加平移动画 181 animationSet.addAnimation(rotateAnim); 182 animationSet.addAnimation(translateAnimation); 183 animationSet.addAnimation(alphaAnimation); 184 animationSet.setDuration(durationMillis); 185 animationSet.setAnimationListener(new Animation.AnimationListener() { 186 @Override 187 public void onAnimationStart(Animation animation) { 188 189 } 190 191 @Override 192 public void onAnimationEnd(Animation animation) { 193 if (currentStatu == MenuStatu.STATU_OPEN) { 194 view.setVisibility(View.VISIBLE); 195 } else { 196 view.setVisibility(View.GONE); 197 } 198 } 199 200 @Override 201 public void onAnimationRepeat(Animation animation) { 202 203 } 204 }); 205 view.startAnimation(animationSet); 206 } 207 changeStatu(); 208 } 209 210 /** 211 * 改变状态 212 */ 213 private void changeStatu() { 214 currentStatu = (currentStatu == MenuStatu.STATU_OPEN) ? MenuStatu.STATU_CLOSE : MenuStatu.STATU_OPEN; 215 } 216 217 /** 218 * 对外暴露的方法,关闭子菜单 219 */ 220 public void closeMenu() { 221 changeStatuAnim(300); 222 } 223 224 /** 225 * 判断是否打开子菜单 226 * 227 * @return 228 */ 229 public boolean isOpen() { 230 return currentStatu == MenuStatu.STATU_OPEN; 231 } 232 }
2、布局文件
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent"> 5 6 <com.example.dell.floatmenudemo.weight.FloatingMenu 7 android:id="@+id/floating" 8 android:layout_width="wrap_content" 9 android:layout_height="wrap_content" 10 android:layout_alignParentBottom="true" 11 android:layout_alignParentRight="true" 12 android:layout_marginBottom="50dp" 13 android:layout_marginRight="50dp"> 14 15 <!--第一个子控件表示母菜单--> 16 <ImageView 17 android:id="@+id/imageViewSwitch" 18 android:layout_width="wrap_content" 19 android:layout_height="wrap_content" 20 android:layout_alignParentLeft="true" 21 android:layout_alignParentStart="true" 22 android:layout_alignParentTop="true" 23 android:src="@mipmap/ic_launcher" /> 24 25 <!--其余的控件表示子菜单--> 26 <ImageView 27 android:layout_width="wrap_content" 28 android:layout_height="wrap_content" 29 android:src="@mipmap/ic_launcher_round" /> 30 31 <ImageView 32 android:layout_width="wrap_content" 33 android:layout_height="wrap_content" 34 android:src="@mipmap/ic_launcher_round" /> 35 36 <ImageView 37 android:layout_width="wrap_content" 38 android:layout_height="wrap_content" 39 android:src="@mipmap/ic_launcher_round" /> 40 41 <ImageView 42 android:layout_width="wrap_content" 43 android:layout_height="wrap_content" 44 android:src="@mipmap/ic_launcher_round" /> 45 46 <ImageView 47 android:layout_width="wrap_content" 48 android:layout_height="wrap_content" 49 android:src="@mipmap/ic_launcher_round" /> 50 51 <ImageView 52 android:layout_width="wrap_content" 53 android:layout_height="wrap_content" 54 android:src="@mipmap/ic_launcher_round" /> 55 </com.example.dell.floatmenudemo.weight.FloatingMenu> 56 57 </RelativeLayout>
3、 activity
1 package com.example.dell.floatmenudemo; 2 3 import android.os.Bundle; 4 import android.support.v7.app.AppCompatActivity; 5 import android.view.View; 6 import android.widget.Toast; 7 8 import com.example.dell.floatmenudemo.weight.FloatingMenu; 9 public class MainActivity extends AppCompatActivity { 10 private FloatingMenu floating; 11 @Override 12 protected void onCreate(Bundle savedInstanceState) { 13 super.onCreate(savedInstanceState); 14 setContentView(R.layout.activity_main); 15 floating = (FloatingMenu) findViewById(R.id.floating); 16 initFloatingMenu(); 17 } 18 /** 19 * 初始化浮动菜单控件 20 */ 21 private void initFloatingMenu() { 22 floating.setOnItemMenuClickListener(new FloatingMenu.OnItemMenuClickListener() { 23 @Override 24 public void onItemMenuClick(View view, int position) { 25 Toast.makeText(MainActivity.this, "子菜单 - " + position, Toast.LENGTH_SHORT).show(); 26 } 27 }); 28 } 29 }
4、 还可以编写 横竖的菜单栏 ,效果如下:
代码如下:
1 package com.example.dell.floatmenudemo.weight; 2 3 import android.content.Context; 4 import android.graphics.Camera; 5 import android.util.AttributeSet; 6 import android.util.Log; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.view.animation.AlphaAnimation; 10 import android.view.animation.Animation; 11 import android.view.animation.AnimationSet; 12 import android.view.animation.RotateAnimation; 13 import android.view.animation.ScaleAnimation; 14 import android.view.animation.TranslateAnimation; 15 16 public class FloatingActionsMenu extends ViewGroup{ 17 private static final String TAG = "FloatingActionsMenu"; 18 // 子控件之间的距离 19 public final static int DISTANCE = 20; 20 // 枚举 菜单栏的状态 21 public enum MenuStatus{STATUS_OPEN,STATUS_CLOSE} 22 // 当前状态 23 private MenuStatus currentStatus = MenuStatus.STATUS_CLOSE; 24 // 子菜单点击回调接口 25 public interface OnItemMenuClickListener{ 26 void onItemMenuClick(View view,int position);// 此方法内对点击作出响应,需实例化 27 } 28 // 调用接口封装到方法 29 private OnItemMenuClickListener onItemMenuClickListener; 30 public void setOnItemMenuClickListener(OnItemMenuClickListener onItemMenuClickListener){ 31 this.onItemMenuClickListener = onItemMenuClickListener; 32 } 33 public FloatingActionsMenu(Context context) { 34 super(context); 35 } 36 public FloatingActionsMenu(Context context, AttributeSet attrs) { 37 super(context, attrs); 38 } 39 40 //计算 menu 大小 41 @Override 42 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 43 int childCount = getChildCount(); 44 if (childCount<3)throw new IllegalStateException("当前控件至少需要三个子控件"); 45 measureChildren(widthMeasureSpec,heightMeasureSpec); 46 int tempWidth = getChildAt(0).getMeasuredWidth(); 47 int tempHeight = getChildAt(0).getMeasuredHeight(); 48 49 setMeasuredDimension(tempWidth,(tempHeight+DISTANCE)*childCount); 50 } 51 // 计算控件位置 52 // changed 是否可以改变 53 @Override 54 protected void onLayout(boolean changed, int l, int t, int r, int b) { 55 if(changed){ 56 int measureWidth = getMeasuredWidth(); 57 int measureHeidth = getMeasuredHeight(); 58 int childCount = getChildCount(); 59 for(int i = 0 ;i<childCount;i++){ 60 final View childAt = getChildAt(i); 61 int childWidth = childAt.getMeasuredWidth(); 62 int childHeight = childAt.getMeasuredHeight(); 63 // 第一个子控件是菜单栏开关 64 if (i==0){ 65 int left = 0; 66 int top = (childCount-1-i)*(DISTANCE+childHeight); 67 int right = left+childWidth; 68 int bottom = top+childHeight; 69 childAt.layout(left,top,right,bottom); 70 childAt.setOnClickListener(new OnClickListener() { 71 @Override 72 public void onClick(View view) { 73 changeStatuAnim(300); 74 } 75 }); 76 }else { 77 // 其余的为 子控件按钮 78 final int temp = i; 79 int left = 0; 80 int top = (childCount-1-i)*(DISTANCE+childHeight); 81 int right = left+childWidth; 82 int bottom = top+childHeight; 83 childAt.layout(left,top,right,bottom); 84 childAt.setVisibility(View.GONE); 85 childAt.setOnClickListener(new OnClickListener() { 86 @Override 87 public void onClick(View view) { 88 if (onItemMenuClickListener!=null){ 89 onItemMenuClickListener.onItemMenuClick(childAt,temp); 90 } 91 clickItemAnim(temp); 92 } 93 }); 94 } 95 } 96 } 97 } 98 99 /** 100 * 点击子菜单时的动画效果 101 */ 102 private void clickItemAnim(int position){ 103 for (int i=1;i<getChildCount();i++){ 104 View childAt = getChildAt(i); 105 if (i==position){ 106 // childAt.startAnimation(toBig()); 107 childAt.startAnimation(rotateAroundY());// Y轴旋转 108 } 109 childAt.setVisibility(GONE); 110 } 111 changeStatu(); 112 } 113 114 /** 115 * 变小,变透明 116 * @return 117 */ 118 private Animation toSmall(){ 119 AnimationSet animationSet = new AnimationSet(true); 120 AlphaAnimation alphaAnimation = new AlphaAnimation(1,0);// 透明 121 animationSet.setDuration(200);// 200 毫秒 122 animationSet.addAnimation(alphaAnimation); 123 return animationSet; 124 } 125 /** 126 * 变大,变透明 127 */ 128 private Animation toBig(){ 129 AnimationSet animationSet = new AnimationSet(true); 130 AlphaAnimation alphaAnimation = new AlphaAnimation(1,0);// 透明 131 animationSet.addAnimation(alphaAnimation); 132 ScaleAnimation scaleAnimation = new ScaleAnimation(1,3,1,3,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);// 变大 133 animationSet.setDuration(200);// 200 毫秒 134 return animationSet; 135 } 136 /** 137 * 绕Y 轴旋转 138 */ 139 private Animation rotateAroundY(){ 140 RotateToYAnimation rotateToYAnimation = new RotateToYAnimation(); 141 rotateToYAnimation.setRepeatCount(1); 142 return rotateToYAnimation; 143 } 144 /** 145 * 146 * 子菜单状态改变动画效果 147 */ 148 private void changeStatuAnim(int durationMillis){ 149 int childCount = getChildCount(); 150 for (int i = 0;i<childCount;i++){ 151 final View view = getChildAt(i); 152 int toX = 0; 153 int toY = (i-1)*view.getHeight()+DISTANCE*i; 154 if (i==0){ 155 // do something 156 }else { 157 158 AnimationSet animationSet = new AnimationSet(true); 159 animationSet.setDuration(300); 160 RotateAnimation rotateAnimation = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f); 161 TranslateAnimation translateAnimation; 162 AlphaAnimation alphaAnimation; 163 // 根据不同的子菜单状态实现不同的效果 164 if (!isopen()){ 165 translateAnimation = new TranslateAnimation(toX,0,toY,0); 166 alphaAnimation = new AlphaAnimation(0,1); 167 // currentStatus = MenuStatus.STATUS_OPEN; 168 }else { 169 translateAnimation = new TranslateAnimation(0,toX,0,toY); 170 alphaAnimation = new AlphaAnimation(1,0); 171 // currentStatus = MenuStatus.STATUS_CLOSE; 172 173 } 174 // 先添加旋转 在添加 平移 175 animationSet.addAnimation(rotateAnimation); 176 animationSet.addAnimation(translateAnimation); 177 animationSet.addAnimation(alphaAnimation); 178 animationSet.setAnimationListener(new Animation.AnimationListener() { 179 @Override 180 public void onAnimationStart(Animation animation) { 181 182 } 183 184 @Override 185 public void onAnimationEnd(Animation animation) { 186 if (currentStatus == MenuStatus.STATUS_OPEN){ 187 view.setVisibility(VISIBLE); 188 }else { 189 190 view.setVisibility(View.GONE); 191 } 192 } 193 @Override 194 public void onAnimationRepeat(Animation animation) { 195 196 } 197 }); 198 view.startAnimation(animationSet); 199 } 200 } 201 changeStatu(); 202 } 203 /** 204 * 改变状态 205 */ 206 private void changeStatu(){ 207 currentStatus = (currentStatus== MenuStatus.STATUS_OPEN)?MenuStatus.STATUS_CLOSE:MenuStatus.STATUS_OPEN; 208 } 209 210 /** 211 * 对外暴露的方法 关闭子菜单 212 */ 213 214 public void closeMenu(){changeStatuAnim(300);} 215 /** 216 * 判断是否打开 子菜单 217 */ 218 public boolean isopen(){return currentStatus==MenuStatus.STATUS_OPEN;} }
其中用到 沿Y轴为轴旋转的动画,代码如下:
1 package com.example.dell.floatmenudemo.weight; 2 3 import android.graphics.Camera; 4 import android.graphics.Matrix; 5 import android.view.animation.Animation; 6 import android.view.animation.DecelerateInterpolator; 7 import android.view.animation.Transformation; 8 9 /** 10 11 调用代码 12 RotateToYAnimation rotateToYAnimation = new RotateToYAnimation(); 13 rotateToYAnimation.setRepeatCount(Animation.INFINITE); //翻转无数次 14 view.startAnimation(rotateToYAnimation); 15 16 */ 17 18 public class RotateToYAnimation extends Animation{ 19 private Camera camera = new Camera(); 20 private int centerX ; 21 private int centerY; 22 23 /** 24 * 获取坐标 定义动画时间 25 * @param width 26 * @param height 27 * @param parentWidth 28 * @param parentHeight 29 */ 30 @Override 31 public void initialize(int width, int height, int parentWidth, int parentHeight) { 32 super.initialize(width, height, parentWidth, parentHeight); 33 // 获取中心点坐标 34 centerX = width/2; 35 centerY = width/2; 36 // 动画执行时间 37 setDuration(200); 38 // 内插器 39 setInterpolator(new DecelerateInterpolator()); 40 } 41 42 /** 43 * 44 * 旋转角度 45 * @param interpolatedTime 46 * @param t 47 */ 48 @Override 49 protected void applyTransformation(float interpolatedTime, Transformation t) { 50 final Matrix matrix = t.getMatrix(); 51 camera.save(); 52 // 旋转中心是Y轴 可自行设置 53 camera.rotateY(360*interpolatedTime); 54 // 把 摄像头 加在变换 矩阵上 55 camera.getMatrix(matrix); 56 // 设置翻转 中心点 57 matrix.preTranslate(-centerX,centerY); 58 matrix.postTranslate(centerX,centerY); 59 camera.restore(); 60 } 61 }