Android--可收放旋转菜单

这篇博客介绍了如何在Android中创建一个可收放旋转菜单。通过设置属性和自定义View,详细阐述了实现这一功能的具体步骤和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果:


实现:

1.设置属性:在values文件夹下创建arr.xml,内容:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!--半径属性-->
    <attr name="radius" format="dimension" />
    <!--自定义控件属性-->
    <declare-styleable name="RotateMenu">
        <!--<attr name="position" />-->
        <attr name="radius" />
    </declare-styleable>

</resources>

自定义View:

public class RotateMenuView extends ViewGroup {

    /**定义屏幕的宽度*/
    private int pm_width;

    /**定义屏幕的高度*/
    private int pm_height;

    /**定义菜单半径*/
    private int mRadius;

    /**
     * 枚举类 菜单状态
     */
    public enum Status
    {
        OPEN, CLOSE
    }

    /**默认为关闭状态*/
    private Status mCurrentStatus = Status.CLOSE;

    /**触发菜单的按钮*/
    private View mButton;

    /**
     * 单击子菜单的回调接口
     */
    public interface OnRotateMenuItemClickListener
    {
        void onClick(View view, int pos);
    }

    /**子菜单单击事件*/
    private OnRotateMenuItemClickListener mMenuItemClickListener;


    //构造方法
    public RotateMenuView(Context context) {
        this(context,null);
    }

    //构造方法
    public RotateMenuView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    //构造方法
    public RotateMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取屏幕宽高
        WindowManager windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        pm_width=windowManager.getDefaultDisplay().getWidth();
        pm_height=windowManager.getDefaultDisplay().getHeight();

        // 获取自定义属性的值
        TypedArray typedArray=context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.RotateMenu,defStyleAttr,0);

        //获取按钮半径属性值
        mRadius= (int) typedArray.getDimension(R.styleable.RotateMenu_radius,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                        100,getResources().getDisplayMetrics()));

        //回收资源
        typedArray.recycle();
    }

    /**
     *测量自定义控件中所有子控件的尺寸
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取所有的按钮
        int count = getChildCount();

        //遍历所有的按钮
        for (int i = 0; i < count; i++)
        {
            // 测量所有按钮,宽度与高度
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     *设置按钮的位置
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //如果按钮布局发生改变
        if (changed) {
            //调用主按钮布局位置的方法
            buttonLayout();
            childLayout();

        }
    }

    /**
     * 设置主按钮在布局中的位置
     */
    private void buttonLayout() {
        mButton = getChildAt(0);       //获取菜单主按钮
        //设置按钮的单击事件
        mButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //调用主按钮旋转的方法
                rotatemButton(v, 0f, 360f, 500);
                //调用收缩子按钮动画方法
                shrinkMenu(500);
            }
        });
        //初始化按钮左边距与上边距
        int l = 0;
        int t = 0;
        //获取主按钮的宽度与高度
        int width = mButton.getMeasuredWidth();
        int height = mButton.getMeasuredHeight();
        //判断主按钮中间按钮的位置
        l = pm_width/2-width/2;
        t = getMeasuredHeight() - height;
        //设置主按钮在父容器的位置
        mButton.layout(l, t, l + width, t + width);
    }

    private void childLayout() {
        //获取所有的子按钮
        int count = getChildCount();
        //遍历所有按钮,第一个是主按钮,所以其他子按钮的总数是count - 1
        for (int i = 0; i < count - 1; i++) {
            //获取子控件下标,因为子按钮是从1开始的,所以i + 1
            View child = getChildAt(i + 1);
            //隐藏子按钮
            child.setVisibility(View.GONE);

            //获取子按钮的宽高尺寸
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            //中心坐标(pm_width/2-childWidth/2,getMeasuredHeight() - height)
            int c_x = pm_width / 2 - childWidth / 2;
            int c_y = getMeasuredHeight() - childHeight/2;

            //子按钮与父容器左边距
            int childLeft = (int) (mRadius * Math.sin(Math.PI / (count - 2)
                    * i));
            int childTop = (int) (mRadius * Math.sin(Math.PI / (count - 2)
                    * i));
            childTop = getMeasuredHeight() - childHeight - childTop;
            if (i == 0) {
                childLeft = c_x - mRadius;
                childTop = getMeasuredHeight() - childHeight;
            } else if (i == 1) {
                childLeft = c_x  - childLeft;
            } else if (i == 2) {
                childLeft = c_x;
                childTop=getMeasuredHeight() - childHeight-mRadius;
            } else if (i == 3) {
                childLeft = c_x  + childLeft;

            } else if (i == 4) {
                childLeft = c_x + mRadius;
                childTop = getMeasuredHeight() - childHeight;
            }
            //设置子按钮的位置
            child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
        }
    }


    /**
     *收缩子按钮动画
     */
    public void shrinkMenu(int time) {
        //获取所有的子按钮
        int count = getChildCount();
        //遍历所有按钮
        for (int i = 0; i < count - 1; i++){
            //获取子按钮下标
            final View childView = getChildAt(i + 1);
            //显示子按钮
            childView.setVisibility(View.VISIBLE);

            //动画开始与结束的位置x,y
            int clx = (int) (mRadius * Math.sin(Math.PI / (count - 2) * i));
            int cty = (int) (mRadius * Math.cos(Math.PI  / (count - 2) * i));
            if (i == 0) {       //第一个子按钮
                clx = mRadius;
                cty = 0;
            } else if (i == 1) {    //第二个子按钮

            } else if (i == 2) {    //第三个子按钮
                clx = 0;
                cty = mRadius;
            } else if (i == 3) {    //第四个子按钮
                clx=(int) (mRadius * Math.cos(Math.PI  / (count - 2) * i));
                cty=-cty;
            } else if (i == 4) {    //第五个子按钮
                clx = - mRadius;
                cty = 0;
            }

            //设置动画集合
            AnimationSet animset = new AnimationSet(true);
            Animation tranAnim = null;

            //如果当前按钮菜单为关闭状态
            if (mCurrentStatus == Status.CLOSE){
                //设置打开按钮菜单动画
                tranAnim = new TranslateAnimation(clx,0 , cty, 0);
                //子按钮可以单击
                childView.setClickable(true);
                //子按钮可调焦
                childView.setFocusable(true);
            } else{
                //如果当前按钮菜单为开启状态,设置关闭按钮菜单动画
                tranAnim = new TranslateAnimation(0, clx, 0, cty);
                //子按钮不可点击
                childView.setClickable(false);
                //子按钮不可调焦
                childView.setFocusable(false);
            }
            //  动画显示的时间
            tranAnim.setDuration(time);
            //停止最后一帧的位置
            tranAnim.setFillAfter(true);
            tranAnim.setStartOffset((i * 100) / count);
            //设置动画监听器
            tranAnim.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)
                    {
                        childView.setVisibility(View.GONE);
                    }
                }
            });
            // 旋转动画
            RotateAnimation rotateAnim = new RotateAnimation(0, 720,
                    Animation.RELATIVE_TO_SELF, 0.5f,
                    Animation.RELATIVE_TO_SELF, 0.5f);
            //设置旋转的时间
            rotateAnim.setDuration(time);
            //停止最后一帧的位置
            rotateAnim.setFillAfter(true);
            //添加旋转动画
            animset.addAnimation(rotateAnim);
            //添加平移动画
            animset.addAnimation(tranAnim);
            //启动动画
            childView.startAnimation(animset);
            final int pos = i + 1;
            //子按钮单击事件
            childView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mMenuItemClickListener != null)
                        mMenuItemClickListener.onClick(childView, pos);
                    //调用字按钮单击动画方法
                    childButtonClickAnim(pos - 1);
                    //调用菜单状态方法
                    menuStatus();
                }
            });
        }
        // 切换菜单状态
        menuStatus();

    }

    /**
     * 子按钮单击动画的方法
     */
    private void childButtonClickAnim(int pos) {
        for (int i = 0; i < getChildCount() - 1; i++)
        {

            View childView = getChildAt(i + 1);
            //当前单击的子按钮
            if (i == pos)
            {   //启动子按钮变大并消失动画
                childView.startAnimation(scaleBigAnim(200));
            } else
            {
                //启动其它没有被单击的按钮变小并消失动画
                childView.startAnimation(scaleSmallAnim(200));
            }
            //子按钮不可点击
            childView.setClickable(false);
            //子按钮不可调焦
            childView.setFocusable(false);

        }
    }

    /**
     *没有被单击的子按钮,变小并消失的动画方法
     */
    private Animation scaleSmallAnim(int time)
    {   //设置动画集合
        AnimationSet animationSet = new AnimationSet(true);
        //缩放动画
        ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        //透明度动画
        AlphaAnimation alphaAnim = new AlphaAnimation(1f, 0.0f);
        //添加缩放动画
        animationSet.addAnimation(scaleAnim);
        //添加透明度动画
        animationSet.addAnimation(alphaAnim);
        //设置动画时间
        animationSet.setDuration(time);
        //停止最后一帧的位置
        animationSet.setFillAfter(true);
        return animationSet;

    }

    /**
     * 单击子按钮,当前子按钮变大并消失动画方法
     */
    private Animation scaleBigAnim(int time)
    {   //设置动画集合
        AnimationSet animationSet = 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);
        //透明度动画
        AlphaAnimation alphaAnim = new AlphaAnimation(1f, 0.0f);
        //添加缩放动画
        animationSet.addAnimation(scaleAnim);
        //添加透明度动画
        animationSet.addAnimation(alphaAnim);
        //设置动画时间
        animationSet.setDuration(time);
        //停止最后一帧的位置
        animationSet.setFillAfter(true);
        return animationSet;

    }

    /**
     * 切换菜单状态
     */
    private void menuStatus()
    {
        mCurrentStatus = (mCurrentStatus == Status.CLOSE ? Status.OPEN
                : Status.CLOSE);
    }
    /**
     *设置主按钮旋转动画
     */
    private void rotatemButton(View v, float start, float end, int time) {
        //中心旋转动画
        RotateAnimation anim = new RotateAnimation(start, end,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        //设置旋转的时间
        anim.setDuration(time);
        //停止最后一帧的位置
        anim.setFillAfter(true);
        //开始动画
        v.startAnimation(anim);
    }
}
 

3.main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:menu="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary"
    tools:context=".MainActivity">
    <com.example.shanshan.statemenu.RotateMenuView
        android:id="@+id/satelliteMenu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        menu:radius="100dp">
        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@mipmap/button_bg" >

            <ImageView
                android:id="@+id/id_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:src="@mipmap/button_in" />

        </RelativeLayout>

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/img5"
            android:tag="@string/text_btn1" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/img4"
            android:tag="@string/text_btn2" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/img3"
            android:tag="@string/text_btn3" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/img2"
            android:tag="@string/text_btn4" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/img1"
            android:tag="@string/text_btn5" />
    </com.example.shanshan.statemenu.RotateMenuView>
</RelativeLayout>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值