内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

SectorMenuView底部导航扇形菜单

2018-02-13 15:48 出处:清屏网 人气: 评论(0

这次分析一个扇形菜单展开的自定义View, 也是我实习期间做的一个印象比较深刻的自定义View, 前后切换了很多种实现思路, 先看看效果展示

效果展示

扇形菜单效果展示图.gif

效果分析

  1. 点击圆形的FloatActionBar, 自身旋转一定的角度
  2. 菜单像波纹一样扩散开来
  3. 显示我们添加的item

实现分析

  1. 使用adapter适配器去设置View, 用户可自定义性强, 不过每次使用需要去设置Adapter, 较为繁琐
  2. 直接调用ItemView, 将ImageView和TextView写死, 用户操作简单, 但是缺乏可定制性(利他)
    本次功能实现采用了方案 2

实现步骤

  1. 与气泡拖拽类似, 新开启一个Window进行自定义View的绘制
  2. 初始化时调用setWillNotDraw(false)方法, 强行启动ViewGroup的绘制
  3. onMeasure中将宽高写死
  4. 绘制背景
    • 锚点为View的底部中心点
    • 半径为屏幕宽度一半的平方和的开方(注意这里不是屏幕的一半)
  5. 添加itemView, 在onLayout中去确定其位置
  6. 添加动画效果
  7. 将相关接口暴露给外界

使用方式

BottomSectorMenuView.Converter(mFab)
                .setToggleDuration(500, 800)
                .setAnchorRotationAngle(135f)
                .addMenuItem(R.drawable.icon_camera, "拍照") { Toast.makeText(this@MainActivity, "拍照", Toast.LENGTH_SHORT).show() }
                .addMenuItem(R.drawable.icon_photo, "图片") { Toast.makeText(this@MainActivity, "图片", Toast.LENGTH_SHORT).show() }
                .addMenuItem(R.drawable.icon_text, "文字") { Toast.makeText(this@MainActivity, "文字", Toast.LENGTH_SHORT).show() }
                .addMenuItem(R.drawable.icon_video, "视频") { Toast.makeText(this@MainActivity, "视频", Toast.LENGTH_SHORT).show() }
                .addMenuItem(R.drawable.icon_camera_shooting, "摄像") { Toast.makeText(this@MainActivity, "摄像", Toast.LENGTH_SHORT).show() }
                .apply()

源码实现

/**
 * Email: frankchoochina@gmail.com
 * Created by FrankChoo on 2017/10/9.
 * Description: 底部扇形菜单, 通过Adapter添加Item
 *              1. 调用openMenu打开菜单
 *              2. 调用closeMenu关闭菜单
 */
public class SectorMenuView extends FrameLayout {
    // 每个ItemView之间的角度差
    private double mAngle;
    // 圆心坐标
    private Point mCenterPoint;
    // ItemView到圆心的半径
    private float mMaxItemRadius;
    private float mCurItemRadius;
    // 背景圆的半径
    private float mMaxBkgRadius;
    private float mCurBkgRadius;
    private Paint mPaint;

    private SectorMenuAdapter mAdapter;
    private OnMenuOpenedListener mMenuOpenedListener;
    private OnMenuClosedListener mMenuClosedListener;

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

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

    public SectorMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        // 初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.WHITE);
        // 设置背景圆绘制的半径
        int displayWidth = getResources().getDisplayMetrics().widthPixels;
        mMaxBkgRadius = (int) Math.sqrt(Math.pow(displayWidth/2, 2.0) + Math.pow(displayWidth/2, 2.0));
        // 开启ViewGroup的绘制
        setWillNotDraw(false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 这里直接将宽高写死, 不支持Margin
        int width = getResources().getDisplayMetrics().widthPixels;
        int height = (int) Math.sqrt(Math.pow(width / 2, 2.0) + Math.pow(width / 2, 2.0));
        setMeasuredDimension(width, height);
        // 计算半径
        int realWidth = width - getPaddingRight() - getPaddingLeft();
        int realHeight = height - getPaddingTop() - getPaddingBottom();
        mMaxItemRadius = realWidth / 2;
        // 计算圆心
        int centerX = getPaddingLeft() + realWidth / 2;
        int centerY = getPaddingTop() + realHeight;
        mCenterPoint = new Point(centerX, centerY);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            double curAngle = Math.PI - mAngle * (i + 1);
            int childCenterX = (int) (mCenterPoint.x + mCurItemRadius * Math.cos(curAngle));
            int childCenterY = (int) (mCenterPoint.y - mCurItemRadius * Math.sin(curAngle));
            child.layout(
                    childCenterX - child.getMeasuredWidth() / 2,
                    childCenterY - child.getMeasuredHeight() / 2,
                    childCenterX + child.getMeasuredWidth() / 2,
                    childCenterY + child.getMeasuredHeight() / 2
            );
            // 这里动态的去设置子View的透明度
            child.setAlpha(mCurItemRadius / mMaxItemRadius);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mCurBkgRadius, mPaint);
        super.onDraw(canvas);
    }

    public void setAdapter(SectorMenuAdapter adapter) {
        mAdapter = adapter;
        for (int i = 0; i < mAdapter.getCount(); i++) {
            View child = mAdapter.getView(i, null, this);
            addView(child);
        }
        mAngle = Math.PI / (mAdapter.getCount() + 1);
    }

    public void setBackgroudColor(@ColorInt int color) {
        mPaint.setColor(color);
    }

    public void setBackgroundResource(@ColorRes int colorResId) {
        mPaint.setColor(ContextCompat.getColor(getContext(), colorResId));
    }

    /**
     * 打开菜单
     */
    public void openMenu() {
        if (mMaxItemRadius == 0) {
            mMaxItemRadius = getResources().getDisplayMetrics().widthPixels / 2
                    - getPaddingRight() - getPaddingLeft();
        }
        // 背景动画
        ValueAnimator bkgAnim = ValueAnimator.ofFloat(0f, mMaxBkgRadius).setDuration(300);
        bkgAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurBkgRadius = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        // item的位置动画
        ValueAnimator itemTranslationAnim = ValueAnimator.ofFloat(0f, mMaxItemRadius).setDuration(300);
        itemTranslationAnim.setInterpolator(new OvershootInterpolator(2f));
        itemTranslationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurItemRadius = (float) animation.getAnimatedValue();
                requestLayout();
            }
        });
        // 动画集合
        final AnimatorSet set = new AnimatorSet();
        set.playSequentially(bkgAnim, itemTranslationAnim);
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                setAlpha(1f);
                setVisibility(View.VISIBLE);
            }
            @Override
            public void onAnimationEnd(Animator animation) {
                if (mMenuOpenedListener != null) {
                    mMenuOpenedListener.opened();
                }
            }
        });
        set.start();
    }

    /**
     * 关闭菜单
     */
    public void closeMenu() {
        // Item动画
        ValueAnimator itemViewAnim = ValueAnimator.ofFloat(mMaxItemRadius, 0f).setDuration(300);
        itemViewAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurItemRadius = (float) animation.getAnimatedValue();
                requestLayout();
            }
        });
        itemViewAnim.setInterpolator(new AnticipateInterpolator(2f));

        // 背景动画
        ValueAnimator backgroundAnim = ValueAnimator.ofFloat(mMaxBkgRadius, 0f).setDuration(300);
        backgroundAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurBkgRadius = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        // 这里设置了该View整体透明度的变化, 防止消失的背景不在锚点处, 显示效果突兀
        ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f).setDuration(250);

        // 动画集合
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(itemViewAnim).before(backgroundAnim);
        animatorSet.play(backgroundAnim).with(alphaAnim);
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (mMenuClosedListener != null) {
                    mMenuClosedListener.closed();
                }
                setVisibility(View.INVISIBLE);
            }
        });
        animatorSet.start();
    }

    public void setOnMenuOpenedListener(OnMenuOpenedListener listener) {
        mMenuOpenedListener = listener;
    }

    public void setOnMenuClosedListener(OnMenuClosedListener listener) {
        mMenuClosedListener = listener;
    }


    /**
     * 供外界调用的Adapter
     */
    public abstract static class SectorMenuAdapter extends BaseAdapter {

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return createView(position, parent);
        }

        protected abstract View createView(int position, ViewGroup parent);

        @Override
        public abstract int getCount();
    }

    public interface OnMenuOpenedListener {
        void opened();
    }

    public interface OnMenuClosedListener {
        void closed();
    }

}
分享给小伙伴们:
本文标签: SectorMenuVi底部导航

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号