自定义卫星菜单CustomArcMenu

现在这种效果的开源项目,确实蛮多的。SatelliteMenu,ARCMenu都是。

下面是我自己仿照他们写的:

先交代一下具体步骤:

自定义卫星菜单:

1,自定义属性 

    a,自定义attr属性 

    b,xml布局文件中引用

    c,CustomArcMenu中获取自定义属性值

2,onMesure测量自控件的大小 

3,onLayout 布置自控件。

4,添加点击事件,添加主按钮与子菜单的按钮动画效果 

    a,主按钮的旋转动画

    b,子菜单的旋转和平移动画 

 5,添加子菜单的点击事件

展示效果图:点击主按钮,子按钮弹射出去并且呈弧形展示,再次点击主按钮,子菜单按钮返回主按钮位置并消失,点击子菜单按钮,被点击子菜单按钮放大并消失

其他子菜单按钮缩小消失。下图是点击展开后的效果。



下面是具体的代码实现:

1,自定义属性 

    a,自定义attr属性 

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <attr name="position">
        <enum name="left_top" value="0"></enum>
        <enum name="left_bottom" value="1"></enum>
        <enum name="right_top" value="2"></enum>
        <enum name="right_bottom" value="3"></enum>
    </attr>
    
    <attr name="radius" format="dimension"></attr>
    
    <declare-styleable name="CustomArcMenu">
        <attr name="position"/>
        <attr name="radius"/>
    </declare-styleable>
    
</resources>

    b,xml布局文件中引用

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:wang="http://schemas.android.com/apk/res/com.wang.demo_android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <ListView 
        android:id="@+id/lv_content"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"></ListView>
    
    <com.wang.demo_android.view.CustomArcMenu
        android:id="@+id/custom_arcmenu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        wang:radius="140dp"
        wang:position="left_bottom">
        
        <ImageView 
            android:id="@+id/arc_main"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:padding="5dp"
            android:src="@drawable/main"/>
        
        <ImageView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="search"
            android:src="@drawable/ic_search"/>
                
        <ImageView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="camera"
            android:src="@drawable/ic_camera"/>
                        
        <ImageView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="notifaction"
            android:src="@drawable/ic_notifaction"/>
                                
        <ImageView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="music"
            android:src="@drawable/ic_music"/>
    </com.wang.demo_android.view.CustomArcMenu> 
    
</FrameLayout>

    c,CustomArcMenu中获取自定义属性值

    public CustomArcMenu(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        TypedArray array = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.CustomArcMenu, 0, defStyleRes);
        int position = array.getInt(R.styleable.CustomArcMenu_position, POS_RIGHTT_BOTTOM);
        switch (position) {
            case POS_LEFT_TOP:
                mPosition = POSITION.LEFT_TOP;
                break;
            case POS_LEFT_BOTTOM:
                mPosition = POSITION.LEFT_BOTTOM;
                break;
            case POS_RIGHT_TOP:
                mPosition = POSITION.RIGHT_TOP;
                break;
            case POS_RIGHTT_BOTTOM:
                mPosition = POSITION.RIGHT_BOTTOM;
                break;
        }
        // 设置半径的默认值:100dp
        mRadius = (int) array.getDimension(R.styleable.CustomArcMenu_radius, (int) TypedValue
                .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources()
                        .getDisplayMetrics()));

        Log.i(tag, "positon:" + mPosition + " radius:" + mRadius);

        array.recycle();// 回收

    }

2,onMesure测量自控件的大小 

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            // 测量child
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

3,onLayout 布置自控件。

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed) {
            // 定位主菜单按钮
            layoutMainButton();
            // 定位子菜单按钮
            layoutMenuItem();

        }

    }

    /**
     * 定位子菜单按钮
     */
    private void layoutMenuItem() {
        int count = getChildCount();
        for (int i = 0; i < count - 1; i++) {
            View child = getChildAt(i + 1);
            int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
            int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));
            // 右上和右下位置
            if (mPosition == POSITION.RIGHT_TOP || mPosition == POSITION.RIGHT_BOTTOM) {
                cl = getMeasuredWidth() - cl - child.getMeasuredWidth();
            }
            // 左下和右下位置
            if (mPosition == POSITION.LEFT_BOTTOM || mPosition == POSITION.RIGHT_BOTTOM) {
                ct = getMeasuredHeight() - ct - child.getMeasuredHeight();
            }

            child.layout(cl, ct, cl + child.getMeasuredWidth(), ct + child.getMeasuredHeight());
            child.setVisibility(View.GONE);
        }

    }

    /**
     * 定位主菜单按钮
     * 
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    private void layoutMainButton() {
        mMainButton = getChildAt(0);
        if (mMainButton == null) {
            return;
        }
        mMainButton.setOnClickListener(this);
        int l = 0;
        int t = 0;
        int width = mMainButton.getMeasuredWidth();
        int height = mMainButton.getMeasuredHeight();
        if (mPosition == POSITION.LEFT_BOTTOM || mPosition == POSITION.RIGHT_BOTTOM) {
            t = getMeasuredHeight() - height;
        }
        if (mPosition == POSITION.RIGHT_TOP || mPosition == POSITION.RIGHT_BOTTOM) {
            l = getMeasuredWidth() - width;
        }
        mMainButton.layout(l, t, l + width, t + height);
    }

4,添加点击事件,添加主按钮与子菜单的按钮动画效果 

    a,主按钮的旋转动画

    @Override
    public void onClick(View v) {
        rotateMainButton(v, 0f, 360f, 300);
        toggleMenu(300);
    }

    /**
     * 启动主按钮的动画效果
     */
    private void rotateMainButton(View v, float start, float end, int duration) {
        RotateAnimation rotateAnim = new RotateAnimation(start, end, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        rotateAnim.setDuration(duration);
        rotateAnim.setFillAfter(true);
        v.startAnimation(rotateAnim);
    }

    b,子菜单的旋转和平移动画 

    /**
     * 启动子菜单的动画
     */
    public void toggleMenu(int duration) {
        int count = getChildCount();
        for (int i = 0; i < count - 1; i++) {
            final View child = getChildAt(i + 1);
            child.setVisibility(View.VISIBLE);

            int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
            int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));
            int xFlag = 1;
            int yFlag = 1;

            if (mPosition == POSITION.LEFT_TOP || mPosition == POSITION.LEFT_BOTTOM) {
                xFlag = -1;
            }
            if (mPosition == POSITION.LEFT_TOP || mPosition == POSITION.RIGHT_TOP) {
                yFlag = -1;
            }
            AnimationSet animSet = new AnimationSet(true);
            // 平移动画
            TranslateAnimation translateAnim = null;
            if (mCurrentStatus == STATUS.CLOSE) {
                translateAnim = new TranslateAnimation(xFlag * cl, 0, yFlag * ct, 0);
                child.setClickable(true);
                child.setFocusable(true);
            } else {
                translateAnim = new TranslateAnimation(0, xFlag * cl, 0, yFlag * ct);
                child.setClickable(false);
                child.setFocusable(false);
            }
            translateAnim.setDuration(duration);
            translateAnim.setFillAfter(true);
            // 使得动画具有较明显的先后循序
            translateAnim.setStartOffset(i * 100 / count);
            translateAnim.setAnimationListener(new Animation.AnimationListener() {

                @Override
                public void onAnimationStart(Animation animation) {
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    if (mCurrentStatus == STATUS.CLOSE) {
                        child.setVisibility(View.GONE);
                    }
                }
            });
            // 旋转动画
            RotateAnimation rotateAnim = new RotateAnimation(0, 720f, Animation.RELATIVE_TO_SELF,
                    0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            rotateAnim.setDuration(duration);
            rotateAnim.setFillAfter(true);

            animSet.addAnimation(rotateAnim);
            animSet.addAnimation(translateAnim);
            // 开始动画
            child.startAnimation(animSet);

            final int pos = i + 1;
            child.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    if (mOnMenuItemClickListener != null) {
                        mOnMenuItemClickListener.OnClick(v, pos);
                    }
                    // 子菜单按钮的点击事件
                    menuItemAnim(pos);
                    // 改变状态
                    changeStatus();
                }
            });
        }
        changeStatus();
    }

 5,添加子菜单的点击事件

    /**
     * 添加item的点击动画
     * 
     * @param pos
     */
    private void menuItemAnim(int pos) {
        int count = getChildCount();
        for (int i = 0; i < count-1; i++) {
            View child = getChildAt(i + 1);
            if ((i + 1) == pos) {
                child.startAnimation(getScaleBig(300));
            } else {
                child.startAnimation(getScaleSmall(300));
            }
            
            child.setClickable(false);
            child.setFocusable(false);
        }
    }

    /**
     * 返回一个缩小的动画
     * 
     * @return
     */
    private Animation getScaleSmall(int duration) {
        AnimationSet animSet = new AnimationSet(true);
        ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 0f, 1.0f, 0, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        scaleAnim.setDuration(duration);
        scaleAnim.setFillAfter(true);
        
        AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0f);
        alphaAnim.setDuration(duration);
        alphaAnim.setFillAfter(true);
        
        animSet.addAnimation(alphaAnim);
        animSet.addAnimation(scaleAnim);
        animSet.setFillAfter(true);
        return animSet;
    }

    private Animation getScaleBig(int duration) {
        AnimationSet animSet = new AnimationSet(true);
        ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        scaleAnim.setDuration(duration);
        //scaleAnim.setFillAfter(true);
        
        AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0f);
        alphaAnim.setDuration(duration);
        //alphaAnim.setFillAfter(true);
        
        animSet.addAnimation(alphaAnim);
        animSet.addAnimation(scaleAnim);
        animSet.setFillAfter(true);
        return animSet;
    }

    /**
     * 改变当前的状态
     */
    private void changeStatus() {
        mCurrentStatus = mCurrentStatus == STATUS.CLOSE ? STATUS.OPEN : STATUS.CLOSE;
    }
注意item个数一定要大于等于三个,否则会有异常

类的下载






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值