Android 自定义卫星扇形菜单栏 可自行修改参数

        最近手头有一个需求,要实现一个悬浮的按钮位于界面的右下角,但是距边框还有点距离,点击这个悬浮按钮可以打开扇形菜单,并且悬浮按钮也要更换成另一个图片,先看一下效果图吧

最后实现的效果大概就是这个样子的,下面不再解说这个界面的整体实现,只说一下这个点击悬浮按钮弹出来这个卫星扇形菜单按钮的实现,下面说几个思路:

          1、看到这样的效果,手动入手肯定是不划算的,时间划不来,首先就是上www.baidu.com、github找类似效果的源码

          2、找到这种类似的效果图以后可能不是自己需要的最终效果,可能还需要修改其中的某些动画等等,这些都需要自己去手动实现更改,满足当前项目的需求

那么下面步入正题,当我看到这个效果的时候,在脑海里分为几步走:

          1、主菜单按钮需要悬浮

          2、主菜单按钮需要触发点击事件,并且需要旋转切换成另一张图片

          3、三个子按钮需要同时向外弹出、并且附加点击事件

          4、再次点击主按钮、子按钮需要同时收回

          5、子菜单弹出来以后整个界面的背景需要半透明,且禁止操作

          6、主按钮和子按钮在一个group组内,主按钮永远显示,子按钮在弹出的时候显示,收回的时候隐藏

好了,需求也就这么多,开始撸代码了,先来瞄一下xml布局,上面这个RelativiLayout是一个自定义的layout,为的是实现弹出子菜单后整个节面只能子菜单和主菜单能操作,禁止触摸这个layout以下的控件,默认是gone状态,主菜单和子菜单是在下面那个SrcMenu布局,这个是核心,自定义的卫星菜单栏是主要部分,下面可以看下代码,继承了ViewGroup

<com.android.infantschool.ui.view.ForbidRelativeLayout
        android:id="@+id/rl_shan"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/title"
        android:background="@drawable/bg_rl_shan_open"//自定义的shape,下面给出
        android:visibility="gone" />

    <com.android.infantschool.ui.view.SrcMenu xmlns:srcmenu="http://schemas.android.com/apk/res-auto"
        android:id="@+id/src_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        srcmenu:position="right_bottom"
        srcmenu:radius="150dp">

        <ImageView
            android:layout_width="55dp"
            android:layout_height="55dp"
            android:src="@mipmap/icon_dynamic_video"//子菜单1图片
            android:tag="Video" />

        <ImageView
            android:layout_width="55dp"
            android:layout_height="55dp"
            android:src="@mipmap/icon_dynamic_camera"//子菜单2图片
            android:tag="Camera" />

        <ImageView
            android:layout_width="55dp"
            android:layout_height="55dp"
            android:src="@mipmap/icon_dynamic_text"//子菜单3图片
            android:tag="Text" />

        <ImageView
            android:id="@+id/btn_plus"
            android:layout_width="55dp"
            android:layout_height="55dp"
            android:src="@mipmap/icon_dynamic_edit"//主菜单图片
            android:stateListAnimator="@null" />
    </com.android.infantschool.ui.view.SrcMenu>

自定义RelativiLayout

public class ForbidRelativeLayout extends RelativeLayout {

    public ForbidRelativeLayout(Context context) {
        super(context);
    }

    public ForbidRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

    /**
     * 添加事件拦截,以免用户在banner广告中随意切换广告
     *
     * @param event
     * @return true:拦截,false:不拦截
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        super.onInterceptTouchEvent(event);
        return true;
    }

    /**
     * 添加事件拦截,以免用户在banner广告中随意切换广告
     *
     * @param event
     * @return true:拦截,false:不拦截
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        return true;
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
    }
}

自定义菜单栏

public class SrcMenu extends ViewGroup implements View.OnClickListener {
    private static final int POS_LEFT_TOP = 0;
    private static final int POS_LEFT_BOTTOM = 1;
    private static final int POS_RIGHT_TOP = 2;
    private static final int POS_RIGHT_BOTTOM = 3;

    private Position mPosition = Position.RIGHT_BOTTOM;//菜单位置
    private int mRadius;//菜单半径

    public Status mStatus = Status.CLOSE;//菜单开闭状态

    public View mCButton;//菜单主按钮

    private OnMenuItemClickListener mOnMenuItemClickListener;//菜单点击事件的成员变量

    private OnMainClickListener mainClickListener;//主菜单事件

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

    /**
     * 菜单位置枚举类
     */
    private enum Position {
        LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM
    }

    /**
     * 菜单按钮点击事件回调接口
     */
    public interface OnMenuItemClickListener {
        void onClick(View view, int position);
    }

    /**
     * 菜单按钮点击事件回调接口
     */
    public interface OnMainClickListener {
        void onClick();
    }

    public SrcMenu(Context context) {
        this(context, null);
    }

    public SrcMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

        mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_FRACTION, 60,
                getResources().getDisplayMetrics());//设置菜单半径默认值

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

        //获取位置值
        int pos = array.getInt(R.styleable.SrcMenu_position, POS_RIGHT_BOTTOM);
        switch (pos) {
            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_RIGHT_BOTTOM:
                mPosition = Position.RIGHT_BOTTOM;
                break;
            default:
                break;
        }

        //获取菜单半径值
        mRadius = (int) array.getDimension(R.styleable.SrcMenu_radius, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_FRACTION, 60, getResources().getDisplayMetrics())) / 5 * 4;
        array.recycle();
    }

    /**
     * 设置菜单点击事件
     */
    public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
        mOnMenuItemClickListener = onMenuItemClickListener;
    }

    private int type;

    public void setMainOnClickLister(OnMainClickListener mainOnClickLister) {
        this.mainClickListener = mainOnClickLister;
    }

    /**
     * 测量模式+测量值
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @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);
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        if (b) {
            layoutCButton();
            int count = getChildCount();
            for (int j = 0; j < count - 1; j++) {
                View child = getChildAt(j);
                //开始时设置子菜单不可见
                child.setVisibility(GONE);
                //默认按钮位于左上时
                int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * j));
                int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * j));
                int cWidth = child.getMeasuredWidth();
                int cHeight = child.getMeasuredHeight();

                //按钮位于左下、右下时
                if (mPosition == Position.LEFT_BOTTOM || mPosition == Position.RIGHT_BOTTOM) {
                    ct = getMeasuredHeight() - cHeight - ct;
                }

                //按钮位于右上、右下时
                if (mPosition == Position.RIGHT_TOP || mPosition == Position.RIGHT_BOTTOM) {
                    cl = getMeasuredWidth() - cWidth - cl;
                }
                child.layout(cl - 30, ct - 50, cl + cWidth - 30, ct + cHeight - 50);
            }
        }
    }

    /**
     * 定位主菜单按钮
     */
    private void layoutCButton() {
        mCButton = getChildAt(3);
        /*mCButton.bringToFront();*/
        mCButton.setOnClickListener(this);

        int l = 0;
        int t = 0;
        int width = mCButton.getMeasuredWidth();
        int height = mCButton.getMeasuredHeight();

        //设置按钮显示的位置
        switch (mPosition) {
            case LEFT_BOTTOM:
                l = 0;
                t = getMeasuredHeight() - height;
                break;
            case LEFT_TOP:
                l = 0;
                t = 0;
                break;
            case RIGHT_TOP:
                l = getMeasuredWidth() - width;
                t = 0;
                break;
            case RIGHT_BOTTOM:
                l = getMeasuredWidth() - width;
                t = getMeasuredHeight() - height;
                break;
            default:
                break;
        }
        mCButton.layout(l - 30, t - 50, l + width - 30, t + height - 50);
    }

    @Override
    public void onClick(View view) {//主按钮点击事件
        if (isOpen()) {//打开
            rotateCButton(view, -90, 0, 200);
        } else {
            rotateCButton(view, 0, -90, 200);
        }
        toggleMenu();
        if (mainClickListener != null) {
            mainClickListener.onClick();
        }
    }

    /**
     * 切换菜单
     */
    public void toggleMenu() {
        int count = getChildCount();
        for (int i = 0; i < count - 1; i++) {
            final View childView = getChildAt(i);
            childView.setVisibility(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 animationSet = new AnimationSet(true);
            Animation animation;

            if (mStatus == Status.CLOSE) {
                //打开按钮的动画
                animation = new TranslateAnimation(xFlag * cl - 00, -00, yFlag * ct - 00, -00);
                childView.setFocusable(true);
                childView.setClickable(true);
                animation.setDuration(300);
            } else {
                //关闭按钮的动画   xFlag * cl    yFlag * ct
                animation = new TranslateAnimation(-00, xFlag * cl - 00, -00, yFlag * ct - 00);
                childView.setFocusable(false);
                childView.setClickable(false);
                animation.setDuration(300);
            }

            animation.setFillAfter(true);
            animation.setStartOffset((i * 100) / count);

            //监听动画状态
            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    if (mStatus == Status.CLOSE) {
                        childView.setVisibility(GONE);
                    }
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });

            //旋转动画
            RotateAnimation rotateAnimation = new RotateAnimation(360, 0,//控制菜单收回的时候选择角度
                    Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            rotateAnimation.setFillAfter(true);
            rotateAnimation.setDuration(200);

            //添加动画
            animationSet.addAnimation(rotateAnimation);
            animationSet.addAnimation(animation);
            childView.startAnimation(animationSet);

            //为子菜单项添加点击事件
            final int pos = i + 1;
            childView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (mOnMenuItemClickListener != null) {
                        mOnMenuItemClickListener.onClick(childView, pos);
                        menuItemAnim(pos - 1);
                        changeStatus();
                        rotateCButton(mCButton, -90, 0, 200);
                    }
                }
            });
        }
        changeStatus();
    }

    /**
     * 点击子菜单项的动画
     */
    private void menuItemAnim(int pos) {
        for (int i = 0; i < getChildCount() - 1; i++) {
            View childView = getChildAt(i);

            if (i == pos) {
                childView.startAnimation(scaleBigAnim(200));
            } else {
                childView.startAnimation(scaleSmallAnim(200));
            }

            //设置子菜单隐藏
            childView.setClickable(false);
            childView.setFocusable(false);
        }
    }

    /**
     * 使菜单项变小并消失的动画
     */
    private Animation scaleSmallAnim(int duration) {
        AnimationSet animationSet = new AnimationSet(true);
        ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        animationSet.addAnimation(scaleAnimation);
        animationSet.addAnimation(alphaAnimation);
        animationSet.setDuration(duration);
        animationSet.setFillAfter(true);
        return animationSet;
    }

    /**
     * 使菜单项变大,透明度降低的动画
     */
    private Animation scaleBigAnim(int duration) {
        AnimationSet animationSet = new AnimationSet(true);
        ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        animationSet.addAnimation(scaleAnimation);
        animationSet.addAnimation(alphaAnimation);
        animationSet.setDuration(duration);
        animationSet.setFillAfter(true);
        return animationSet;
    }

    /**
     * 改变菜单状态值
     */
    private void changeStatus() {
        mStatus = (mStatus == Status.CLOSE ? Status.OPEN : Status.CLOSE);
    }

    /**
     * 判断菜单是否打开
     */
    public boolean isOpen() {
        return mStatus == Status.OPEN;
    }

    /**
     * 主按钮旋转动画
     */
    public void rotateCButton(View view, float start, float end, int duration) {
        RotateAnimation animation = new RotateAnimation(start, end, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setDuration(duration);
        animation.setFillAfter(true);
        view.startAnimation(animation);
    }
}

没错,代码就这么多,自定义的view里面注释还是写的还是比较清楚的,哪里不知道的可以给我留言,也可以根据自己的需求更改动画之类的,下面再来看一下在activity或者fragment里面如何使用:

srcMenu.setOnMenuItemClickListener(new SrcMenu.OnMenuItemClickListener() {
            @Override
            public void onClick(View view, int position) {//子菜单点击事件
                imgPlus.setImageResource(R.mipmap.icon_dynamic_edit);
                fadeOut(rlShan);
                if (position == 1) {
                    startVideo();
                }
                if (position == 2) {
                    startCameta();
                }
                if (position == 3) {
                    startActivity(ReleaseDynamicActivity.newIntent(activity, list));
                }
            }
        });

        srcMenu.setMainOnClickLister(new SrcMenu.OnMainClickListener() {
            @Override
            public void onClick() {//主菜单点击事件
                if (srcMenu.isOpen()) {//打开
                    imgPlus.setImageResource(R.mipmap.icon_dynamic_close);
                    fadeIn(rlShan);
                } else {//关闭
                    imgPlus.setImageResource(R.mipmap.icon_dynamic_edit);
                    fadeOut(rlShan);
                }
            }
        });

 

到这里这个卫星扇形弹出式菜单就完成了里面有用到某些文件,下面附加粘贴上

1、子菜单弹出后背景需要从显示渐变到阴影,收回同理

public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
        if (view.getVisibility() == View.VISIBLE) return;
        view.setVisibility(View.VISIBLE);
        Animation animation = new AlphaAnimation(startAlpha, endAlpha);
        animation.setDuration(duration);
        view.startAnimation(animation);
    }

    /**
     * 隐藏到显示渐变
     *
     * @param view
     */
    public static void fadeIn(View view) {
        fadeIn(view, 0F, 1F, 500);
        view.setEnabled(true);
    }

    /**
     * 显示到隐藏渐变
     *
     * @param view
     */
    public static void fadeOut(View view) {
        if (view.getVisibility() != View.VISIBLE) return;
        // Since the button is still clickable before fade-out animation
        // ends, we disable the button first to block click.
        view.setEnabled(false);
        Animation animation = new AlphaAnimation(1F, 0F);
        animation.setDuration(500);
        view.startAnimation(animation);
        view.setVisibility(View.GONE);
    }

2、drawable下的自定义半透明背景 bg_rl_shan_open.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#80000000" />
</shape>

致辞结束!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落魄的Android开发

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值