Android 自定义卫星式弧形菜单

来源:互联网 发布:苏州慧博网络是坑吗 编辑:程序博客网 时间:2024/05/12 02:40

学自鸿洋(hyman)的imooc视频



[java] view plain copy
 print?
  1. import android.content.Context;  
  2. import android.content.res.TypedArray;  
  3. import android.util.AttributeSet;  
  4. import android.util.TypedValue;  
  5. import android.view.View;  
  6. import android.view.ViewGroup;  
  7. import android.view.animation.AlphaAnimation;  
  8. import android.view.animation.Animation;  
  9. import android.view.animation.AnimationSet;  
  10. import android.view.animation.RotateAnimation;  
  11. import android.view.animation.ScaleAnimation;  
  12. import android.view.animation.TranslateAnimation;  
  13.   
  14. import com.stone.satellitemenu.R;  
  15.   
  16.   
  17. /** 
  18.  * 卫星式菜单 
  19.  * author : stone 
  20.  * email  : aa86799@163.com 
  21.  * time   : 15/5/7 14 17 
  22.  */  
  23.   
  24. public class SateliteMenu extends ViewGroup implements View.OnClickListener {  
  25.   
  26.     public enum Position {  
  27.         POS_LEFT_TOP, POS_RIGHT_TOP, POS_LEFT_BOTTOM, POS_RIGHT_BOTTOM  
  28.     }  
  29.   
  30.     private final int LEFT_TOP = 1;  
  31.     private final int RIGHT_TOP = 2;  
  32.     private final int LEFT_BOTTOM = 4;  
  33.     private final int RIGHT_BOTTOM = 8;  
  34.     private final int STATUS_OPEN = 0//菜单的状态 打开  
  35.     private final int STATUS_CLOSE = 1//菜单的状态 关闭  
  36.   
  37.     private Position mPosition;  
  38.     private int mRadius;  
  39.   
  40.     private int mStatus;  
  41.     private onMenuItemClickListener mMenuItemClickListener;  
  42.     private View mMenuButton;  
  43.   
  44.     public interface onMenuItemClickListener {  
  45.         /** 
  46.          * @param view  item-view 
  47.          * @param position  item-position 
  48.          */  
  49.         void onItemClick(View view, int position);  
  50.     }  
  51.   
  52.   
  53.     public SateliteMenu(Context context) {  
  54.         this(context, null);  
  55.     }  
  56.   
  57.     public SateliteMenu(Context context, AttributeSet attrs) {  
  58.         this(context, attrs, 0);  
  59.     }  
  60.   
  61.     public SateliteMenu(Context context, AttributeSet attrs, int defStyleAttr) {  
  62.         super(context, attrs, defStyleAttr);  
  63.   
  64.         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SateliteMenu);  
  65.         int position = typedArray.getInt(R.styleable.SateliteMenu_position, LEFT_TOP);  
  66.         //定义半径默认值  
  67.         float defRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics());  
  68.         switch (position) {  
  69.             case LEFT_TOP:  
  70.                 mPosition = Position.POS_LEFT_TOP;  
  71.                 break;  
  72.             case RIGHT_TOP:  
  73.                 mPosition = Position.POS_RIGHT_TOP;  
  74.                 break;  
  75.             case LEFT_BOTTOM:  
  76.                 mPosition = Position.POS_LEFT_BOTTOM;  
  77.                 break;  
  78.             case RIGHT_BOTTOM:  
  79.                 mPosition = Position.POS_RIGHT_BOTTOM;  
  80.                 break;  
  81.         }  
  82.         mRadius = (int) typedArray.getDimension(R.styleable.SateliteMenu_radius, defRadius);  
  83.   
  84.         typedArray.recycle(); //回收  
  85.   
  86.         mStatus = STATUS_CLOSE; //默认关闭状态  
  87.   
  88.     }  
  89.   
  90.     public void setOnMenuItemClickListener(onMenuItemClickListener menuItemClickListener) {  
  91.         this.mMenuItemClickListener = menuItemClickListener;  
  92.     }  
  93.   
  94.     public void setPosition(Position position) {  
  95.         if (mPosition == position) {  
  96.             return;  
  97.         }  
  98.         this.mPosition = position;  
  99.   
  100.         View child;  
  101.         int count = getChildCount();  
  102.         for (int i = 0; i < count; i++) {  
  103.             child = getChildAt(i);  
  104.             child.clearAnimation();  
  105.         }  
  106. //        invalidate(); //会触发 测量、布局和绘制  
  107.         requestLayout(); //这里只要请求布局  
  108.     }  
  109.   
  110.     @Override  
  111.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  112.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  113.         //测量子view  
  114.         for (int i = 0, count = getChildCount(); i < count; i++) {  
  115.             //需要传入父view的spec  
  116.             measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);  
  117.         }  
  118.   
  119.     }  
  120.   
  121.     @Override //lt 左上点  rb 右下点  如果 r<l 或 b<t 则无法显示了  
  122.     protected void onLayout(boolean changed, int l, int t, int r, int b) {//l=0, t=0  因为是相对于父view的位置  
  123.         layoutMenuButton();  
  124.         /* 
  125.         分析: 
  126.             menuButton距离每个item为radius。 
  127.             到item作直线,其夹角,应为90度均分。90/(item-1)=每个夹角的度数。 
  128.             有角度,就能求出正弦值sina。 
  129.             根据正弦公式:sina=a/c,且已知c=radius,求出a边长,即x坐标。 
  130.             有角度,就能求出正弦值cosa。 
  131.             余弦公式:cosa=b/c,且已知radius(斜边),求出b边长,即y坐标 
  132.          */  
  133.         int count = getChildCount();  
  134.         double angle = 90.0f / (count - 2);//这里-2,是多减去了一个menuButton  
  135.         View child;  
  136.         int w,h;  
  137.         for (int i = 1; i < count; i++) {  
  138.             child = getChildAt(i);  
  139.             child.setVisibility(View.GONE);  
  140.             w = child.getMeasuredWidth();  
  141.             h = child.getMeasuredHeight();  
  142.             double sin = 0, cos = 0;  
  143.             //Math.toRadians:math.pi/180 * angle = 弧度   angel/180*pi<==>angel*pi/180  
  144.             sin = Math.sin(Math.toRadians(angle * (i - 1))); //第i个角度的 sin(0)=0   i-1即从0开始,会有与屏幕直角边平行的 math.sin需要传弧度值  
  145.             cos = Math.cos(Math.toRadians(angle * (i - 1)));// 邻直角边/斜边   cos(0)=1  
  146.             l = (int) (mRadius * sin); //对横边长  
  147.             t = (int) (mRadius * cos); //邻纵边长  
  148.   
  149.             //左上,左下 left值 就是上面的l l递增    符合默认变化规则  
  150.             //左上,右上 top值 就是上面的t  t递减    符合默认变化规则  
  151.   
  152.             //右上、右下 left值一样: 从右向左 递减  
  153.             if (mPosition == Position.POS_RIGHT_TOP || mPosition == Position.POS_RIGHT_BOTTOM) {  
  154.                 l = getMeasuredWidth() - w - l;  
  155.             }  
  156.             //左下、右下 top值一样: 从上向下 递增  
  157.             if (mPosition == Position.POS_LEFT_BOTTOM || mPosition == Position.POS_RIGHT_BOTTOM) {  
  158.                 t = getMeasuredHeight() - h - t;  
  159.             }  
  160.   
  161.             child.layout(l, t, l + w, t + h);  
  162.   
  163.             final int pos = i;  
  164.             child.setOnClickListener(new OnClickListener() {  
  165.                 @Override  
  166.                 public void onClick(View v) {  
  167.                     if (mMenuItemClickListener != null) {  
  168.                         mMenuItemClickListener.onItemClick(v, pos);  
  169.                         itemAnim(pos);  
  170.                     }  
  171.                     mStatus = STATUS_CLOSE; //关闭状态  
  172.                 }  
  173.             });  
  174.   
  175.         }  
  176.   
  177.     }  
  178.   
  179.     /** 
  180.      * 菜单按钮设置layout 
  181.      */  
  182.     private void layoutMenuButton() {  
  183.         mMenuButton = getChildAt(0);  
  184.         int l = 0, t = 0;  
  185.         int w = mMenuButton.getMeasuredWidth();  
  186.         int h = mMenuButton.getMeasuredHeight();  
  187.         switch (mPosition) {  
  188.             case POS_LEFT_TOP:  
  189.                 l = t = 0;  
  190.                 break;  
  191.             case POS_RIGHT_TOP:  
  192.                 l = getMeasuredWidth() - w;  
  193.                 t = 0;  
  194.                 break;  
  195.             case POS_LEFT_BOTTOM:  
  196.                 l = 0;  
  197.                 t = getMeasuredHeight() - h;  
  198.                 break;  
  199.             case POS_RIGHT_BOTTOM:  
  200.                 l = getMeasuredWidth() - w;  
  201.                 t = getMeasuredHeight() - h;  
  202.                 break;  
  203.         }  
  204.         mMenuButton.layout(l, t, w + l, h + t);  
  205.   
  206.         mMenuButton.setOnClickListener(this);  
  207.     }  
  208.   
  209.     @Override  
  210.     public void onClick(View v) {  
  211.         rotateMenuBotton(mMenuButton, 360500);  
  212.         toggleMenu(500);  
  213.   
  214.     }  
  215.   
  216.     private void rotateMenuBotton(View view, int angle, int duration) {  
  217.         RotateAnimation anim = new RotateAnimation(  
  218.                 0, angle, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);  
  219.         anim.setDuration(duration);  
  220.         anim.setFillAfter(true); //view保持在动画结束位置  
  221.         view.startAnimation(anim);  
  222.     }  
  223.   
  224.     /** 
  225.      * 展开/隐藏子菜单 
  226.      * 子菜单动画 平移 
  227.      */  
  228.     private void toggleMenu(int duration) {  
  229.   
  230.         int count = getChildCount();  
  231.         for (int i = 1; i < count; i++) {  
  232.             final View child = getChildAt(i);  
  233.             /* 
  234.                平移动画 以layout中计算的长度 再乘以1或-1 
  235.                close: 
  236.                    左上   r->l b->t 
  237.                    右上   l->r b->t 
  238.                    左下   r->l t->b 
  239.                    右下   l->r t->b 
  240.                open: 
  241.                    左上 
  242.                    右上 
  243.                    左下 
  244.                    右下 
  245.  
  246.                 */  
  247.             int xflag = 1, yflag = 1;  
  248.             //  
  249.             if (mPosition == Position.POS_LEFT_TOP || mPosition == Position.POS_LEFT_BOTTOM) {  
  250.                 xflag = -1;  
  251.             }  
  252.             //  
  253.             if (mPosition == Position.POS_LEFT_TOP || mPosition == Position.POS_RIGHT_TOP) {  
  254.                 yflag = -1;  
  255.             }  
  256.   
  257.             double angle = 90 / (count - 2);  
  258.             /* 
  259.              一个圆的弧度是2π,角度是360°   π/2即90度的弧度 
  260.              */  
  261.             int oppositeLen = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * (i - 1))); //对边 横向  
  262.             int adjacentLen = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * (i - 1))); //邻边 纵向  
  263. /* 
  264.  一个圆的弧度是2π,角度是360°  π/180,每角度对应的弧度   然后乘以角度数=其所对应的弧度 
  265.  */  
  266. //            int oppositeLen = (int) (mRadius * Math.sin(angle * Math.PI / 180 * (i-1))); //对边 横向  
  267. //            int adjacentLen = (int) (mRadius * Math.cos(angle * Math.PI / 180 * (i-1))); //邻边 纵向  
  268.   
  269.   
  270.             int stopx = xflag * oppositeLen;  
  271.             int stopy = yflag * adjacentLen;  
  272.             AnimationSet set = new AnimationSet(true);  
  273.             if (mStatus == STATUS_OPEN) {//如是打开,则要关闭  
  274.                 //4个值是起始点和结束点,相对于自身x、y的距离  
  275.                 TranslateAnimation tranAnim = new TranslateAnimation(0, stopx, 0, stopy);  
  276.                 tranAnim.setStartOffset(mRadius/6);//偏移  
  277.                 set.addAnimation(tranAnim);  
  278.                 AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);  
  279.                 set.addAnimation(alphaAnim);  
  280.                 set.setAnimationListener(new Animation.AnimationListener() {  
  281.                     @Override  
  282.                     public void onAnimationStart(Animation animation) {  
  283.                         setItemClickable(child, false);  
  284.                     }  
  285.   
  286.                     @Override  
  287.                     public void onAnimationEnd(Animation animation) {  
  288.   
  289.                     }  
  290.   
  291.                     @Override  
  292.                     public void onAnimationRepeat(Animation animation) {  
  293.   
  294.                     }  
  295.                 });  
  296.   
  297.             } else { //要打开  
  298.                 TranslateAnimation tranAnim = new TranslateAnimation(stopx, 0, stopy, 0);  
  299.                 set.addAnimation(tranAnim);  
  300.                 AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);  
  301.                 set.addAnimation(alphaAnim);  
  302.                 set.setAnimationListener(new Animation.AnimationListener() {  
  303.                     @Override  
  304.                     public void onAnimationStart(Animation animation) {  
  305.                         setItemClickable(child, false);  
  306.                     }  
  307.   
  308.                     @Override  
  309.                     public void onAnimationEnd(Animation animation) {  
  310.                         setItemClickable(child, true);  
  311.                     }  
  312.   
  313.                     @Override  
  314.                     public void onAnimationRepeat(Animation animation) {  
  315.   
  316.                     }  
  317.                 });  
  318.             }  
  319.   
  320.             set.setDuration(duration);  
  321.             set.setFillAfter(true);  
  322.             child.startAnimation(set);  
  323.   
  324.         }  
  325.   
  326.         if (mStatus == STATUS_OPEN) {  
  327.             mStatus = STATUS_CLOSE;  
  328.         } else {  
  329.             mStatus = STATUS_OPEN;  
  330.         }  
  331.     }  
  332.   
  333.     /** 
  334.      * item点击动画 
  335.      * @param position 
  336.      */  
  337.     private void itemAnim(int position) {  
  338.         View child;  
  339.         int count = getChildCount();  
  340.         for (int i = 1; i < count; i++) {  
  341.             child = getChildAt(i);  
  342.             if (position == i) {  
  343.                 scaleBigAnim(child);  
  344.             } else {  
  345.                 scaleSmallAnim(child);  
  346.             }  
  347.             setItemClickable(child, false);  
  348.         }  
  349.     }  
  350.   
  351.     private void scaleBigAnim(View view) {  
  352.         ScaleAnimation scaleAnim = new ScaleAnimation(  
  353.                 1.0f, 3f, 1.0f, 3f,  
  354.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);  
  355.         AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);  
  356.         AnimationSet set = new AnimationSet(true);  
  357.         set.addAnimation(alphaAnim);  
  358.         set.addAnimation(scaleAnim);  
  359.         set.setDuration(800);  
  360.         set.setFillAfter(true);  
  361.         view.startAnimation(set);  
  362.     }  
  363.   
  364.     private void scaleSmallAnim(View view) {  
  365.         ScaleAnimation scaleAnim = new ScaleAnimation(  
  366.                 1.0f, 01.0f, 0,  
  367.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);  
  368.         AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);  
  369.         AnimationSet set = new AnimationSet(true);  
  370.         set.addAnimation(alphaAnim);  
  371.         set.addAnimation(scaleAnim);  
  372.         set.setFillAfter(true);  
  373.         set.setDuration(500);  
  374.         view.startAnimation(set);  
  375.     }  
  376.   
  377.     private void setItemClickable(View view, boolean flag) {  
  378.         view.setClickable(flag);  
  379.         view.setFocusable(flag);  
  380.     }  
  381.   
  382.   
  383. }  

我的自定义View项目地址: https://github.com/aa86799/MyCustomView (欢迎start&fork)

本文地址:https://github.com/aa86799/MyCustomView/tree/master/satellitemenu2


0 0
原创粉丝点击