基于Android的卫星菜单效果实现

备注说明:本文主要是笔者通过之前“卫星菜单实现”的学习,特意将实现的思路给理出来、记录下来,作为自己的学习总结。文中存在不对之处,请大家踊跃指出,转载请标明出处。

卫星菜单
实现思路:
1、自定义ViewGroup
(1)自定义属性:a、创建attr.xml文件 b、在布局中使用 c、在自定义控件中解析使用
(2)重写onMeasure与onLayout方法,设置菜单按钮的布局位置
2、定位Item(设置每一个菜单项的位置)
3、展开Item(动画效果实现,包括菜单按钮旋转动画、菜单项平移、旋转动画、菜单项缩放、透明度变换动画)
4、监听Item点击事件实现
效果展示:
这里写图片描述

一、自定义ViewGroup
(1)自定义属性:a、创建attr.xml文件 b、在布局中使用 c、在自定义控件中解析使用

a、创建attr.xml文件

<resources>
    <attr name="position">
        <enum name="left_top" value="0"/>
        <enum name="left_bottom" value="1"/>
        <enum name="center" value="2"/>
        <enum name="right_top" value="3"/>
        <enum name="right_bottom" value="4"/>
    </attr>
    <attr name="radius" format="dimension"></attr>
    <declare-styleable name="ArcMenu">
        <attr name="position"/>
        <attr name="radius"/>
    </declare-styleable>
</resources>

b、在布局中使用

注意:在ADT中引入自定义属性,创建命名空间是 xmlns:wusw=”http://schemas.android.com/apk/res/+应用程序包名;而在Androis Studio中则直接是: xmlns:wusw=”http://schemas.android.com/apk/res-auto” ,并不需要指明包名。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:wusw="http://schemas.android.com/apk/res/com.wusw.arcmenu"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.wusw.arcmenu.widget.ArcMenu
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
       wusw:position="left_top"
       wusw:radius="150dp">    
</com.wusw.arcmenu.widget.ArcMenu>
</RelativeLayout>

c、在自定义控件中解析使用

    TypedArray typedArray=context.getResources().obtainAttributes(attrs, R.styleable.ArcMenu);
         mRadius=(int) typedArray.getDimension(R.styleable.ArcMenu_radius, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics()));
         int pos=typedArray.getInt(R.styleable.ArcMenu_position, 4);
         switch (pos) {
        case LEFT_TOP:
            mPosition=Position.LEFT_TOP;
            break;
        case LEFT_BOTTOM:
            mPosition=Position.LEFT_BOTTOM;
            break;
        case CENTER:
            mPosition=Position.CENTER;
            break;
        case RIGHT_TOP:
            mPosition=Position.RIGHT_TOP;
            break;
        case RIGHT_BOTTOM:
            mPosition=Position.RIGHT_BOTTOM;
            break;
        }
        typedArray.recycle();
(2)重写onMeasure与onLayout方法,设置菜单按钮的布局位置。

a、重写onMeasure()方法。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        for(int i=0;i<getChildCount();i++){
            View child=getChildAt(i);
            measureChild(child, widthMeasureSpec, widthMeasureSpec);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

b、重写onLayout方法。
这里写图片描述
如图所示,放置不同位置时,left和top的位置情况。

    @Override
    protected void onLayout(boolean changed, int arg1, int arg2, int arg3, int arg4) {
        // TODO Auto-generated method stub
        if(changed){
            layoutButton();//为菜单项布局
        }
    }
        //为菜单按钮布局
    private void layoutButton() {
        // TODO Auto-generated method stub
        if(getChildCount()>0){
            mCButton=getChildAt(0);
            int left=0;
            int top=0;
            int width=mCButton.getMeasuredWidth();
            int height=mCButton.getMeasuredHeight();
            switch (mPosition) {
            case LEFT_TOP:
                left=0;
                top=0;
                break;
            case LEFT_BOTTOM:
                left=0;
                top=getMeasuredHeight()-height;
                break;
            case CENTER:
                left=(getMeasuredWidth()-width)/2;
                top=(getMeasuredHeight()-height)/2;
                break;
            case RIGHT_TOP:
                left=getMeasuredWidth()-width;
                top=0;
                break;
            case RIGHT_BOTTOM:
                left=getMeasuredWidth()-width;
                top=getMeasuredHeight()-height;
                break;
            }
            mCButton.layout(left, top, left+width, top+height);
        }
    }

效果实现:
这里写图片描述

二、定位Item(设置每一个菜单项的位置)

这里写图片描述
如果所示,每个菜单项的位置与角度的关系。

//菜单项布局
    private void layoutItem() {
        // TODO Auto-generated method stub
        int childCount=getChildCount()-1;
        for(int i=0;i<childCount;i++){
            View child=getChildAt(i+1);//第一个view为菜单按钮项
            int left=0;
            int top=0;
            int width=child.getMeasuredWidth();
            int height=child.getMeasuredHeight();
            if(mPosition==Position.CENTER){
                left=(int) (mRadius*Math.sin(Math.PI*2/(childCount)*i));
                top=(int) (mRadius*Math.cos(Math.PI*2/(childCount)*i));
            }else{
                left=(int) (mRadius*Math.cos(Math.PI/2/(childCount-1)*i));
                top=(int) (mRadius*Math.sin(Math.PI/2/(childCount-1)*i));
            }
             if(mPosition==Position.LEFT_BOTTOM||mPosition==Position.RIGHT_BOTTOM){
                 top=getMeasuredHeight()-top-height;
             }
             if(mPosition==Position.RIGHT_TOP||mPosition==Position.RIGHT_BOTTOM){
                 left=getMeasuredWidth()-left-width;
             }
             if(mPosition==Position.CENTER){
                 left=mButtonX+left;
                    top=mButtonY+top;
             }
             child.layout(left, top, left+width, top+height);
        }   
    }

其中mButtonX,mButtonY是居中显示菜单按钮时,菜单按钮的中心的位置:

case CENTER:
                left=(getMeasuredWidth()-width)/2;
                top=(getMeasuredHeight()-height)/2;
                mButtonX=left+width/10;
                mButtonY=top+height/10;
                break;

效果显示:
这里写图片描述

三、展开Item(动画效果实现,包括菜单按钮旋转动画、菜单项平移、旋转动画、菜单项缩放、透明度变换动画)

(1)菜单按钮旋转动画实现:

    /**菜单按钮旋转*/
    private void rotateButton(View v, float start, float end, int duration) {
        RotateAnimation roteAnimation=new RotateAnimation(start,end, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);//自我为中心
        roteAnimation.setDuration(duration);
        roteAnimation.setFillAfter(true);
        v.startAnimation(roteAnimation);
    }

(2)菜单项平移:

final View child=getChildAt(i+1);
            child.setVisibility(View.VISIBLE);//菜单项显示出来
            int left=0;//平移时,起点的位置
            int top=0;
            if(mPosition==Position.CENTER){
                left= (int) (mRadius*Math.cos(Math.PI*2 / (childCount) * i));
                top= (int) (mRadius*Math.sin(Math.PI*2 / (childCount) * i));
            }else{
                left= (int) (mRadius*Math.sin(Math.PI/2/(childCount - 1) * i))-(mCButton.getMeasuredWidth()-child.getMeasuredWidth())/2;
                top= (int) (mRadius*Math.cos(Math.PI / 2 / (childCount - 1) * i))-(mCButton.getMeasuredHeight()-child.getMeasuredHeight())/2;
            }
            int xFlag=1;//平移标识 1:正向 -1:负向
            int yFlag=1;
            if(mPosition==Position.LEFT_BOTTOM||mPosition==Position.LEFT_TOP){
                xFlag=-1;
            }
            if(mPosition==Position.LEFT_TOP||mPosition==Position.RIGHT_TOP){
                yFlag=-1;
            }
            int startX=left*xFlag;//平移开始位置
            int startY=top*yFlag;
            int endX=0;//平移结束位置
            int endY=0;
            //居中
            if(mPosition==Position.CENTER){
                startX=-left;
                startY=-top;
                endX=0;
                endY=0;
            }
            //为item设置平移和旋转动画
            AnimationSet animationSet=new AnimationSet(true);
            TranslateAnimation transAnimation;//平移动画
            if(mState==State.CLOSE){//需要打开时
                transAnimation=new TranslateAnimation(startX,endX,startY,endY);
                child.setClickable(true);
                child.setFocusable(true);

            }else{//需要关闭
                transAnimation=new TranslateAnimation(endX,startX,endY,startY);
                child.setClickable(false);
                child.setFocusable(false);

(3)菜单项旋转动画+加平移动画:

   //旋转动画
            RotateAnimation   roateAnimation=new RotateAnimation(0,720,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
            roateAnimation.setDuration(duration);
            roateAnimation.setFillAfter(true);
            transAnimation.setDuration(duration);
            transAnimation.setFillAfter(true);
            animationSet.addAnimation(roateAnimation);//动画的点击顺序对实现的效果存在影响
            animationSet.addAnimation(transAnimation);
            animationSet.setStartOffset((i * 100) / childCount);//设置每个菜单项不同的时间
            child.startAnimation(animationSet);

(4)菜单项透明度变换动画:

  /**菜单项点击时,缩小隐藏,透明度变换动画*/
    private AnimationSet childSmallAnimation(int duration,float scale) {
        AnimationSet animationSet=new AnimationSet(true);
        ScaleAnimation scaleAnimation=new ScaleAnimation(scale,0.0f,scale,0.0f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        AlphaAnimation alphaAnimation=new AlphaAnimation(1.0f,0.0f);
        animationSet.setFillAfter(true);
        animationSet.setDuration(duration);
        animationSet.addAnimation(scaleAnimation);
        animationSet.addAnimation(alphaAnimation);
      return  animationSet;
    }
    /**菜单项点击时,放大显示,透明度变换动画*/
    private AnimationSet childBigAnimation(int duration,float scale) {
        AnimationSet animationSet=new AnimationSet(true);
        //缩放动画
        ScaleAnimation scaleAnimation=new ScaleAnimation(1.0f,scale,1.0f,scale,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        AlphaAnimation alphaAnimation=new AlphaAnimation(1.0f,0.0f);
        animationSet.setFillAfter(true);
        animationSet.setDuration(duration);
        animationSet.addAnimation(scaleAnimation);
        animationSet.addAnimation(alphaAnimation);
        return  animationSet;
    }

(5)菜单按钮自动放大缩小动画:

/**菜单按钮放大缩小动画*/
    private void menuAnimation(final View v,int duration){
        if(mMenuAnimation==null){
        mMenuAnimation=childBigAnimation(duration, 2.0f);

        mMenuAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }
            @Override
            public void onAnimationEnd(Animation animation) {
                v.startAnimation(mMenuAnimation);
            }
            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
    }
    }

四、监听Item点击事件实现

(1)设置回调接口:


    /**设置菜单回调接口*/
    public void setOnMenuItemClickListner(onMenuItemClickListner mOnMenuItemClickListner) {
        this.mOnMenuItemClickListner = mOnMenuItemClickListner;
    }

    /**菜单项回调接口*/
    public interface  onMenuItemClickListner{
        public void onClick(View childView,int position);
    }

(2)在菜单项点击时,调用回调接口方法。

 final int poisition=i;//第几个菜单项
            child.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(mOnMenuItemClickListner!=null){
                        mOnMenuItemClickListner.onClick(child,poisition);
                    }
                    childClickAnimation(poisition, duration);
                    changeMenuState();
                    startMenuAnimation(mCButton);
                }
            });

五、源码下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值