自定义View-画出你的菜单

效果图:

效果图

这个菜单是我在materialup中看到的一个效果,觉得很不错,就用代码撸了一下,基本还原了UI的效果,如果大家有更好的意见或者优化随时邮件我(认真脸)
设计地址:http://www.material.uplabs.com/posts/alignment-fab-bar

思路:
1.画圆角矩形
2.画圆形
3.画中间的X形状
4.点击圆的事件处理
5.点击动画(菜单关闭和打开,圆中间的X形状和V形状之间的转换)

 private Paint mBluePaint;

    private Paint mPurplePaint;

    private Paint mWPaint;

    private int mWidth;//宽

    private int mHeight;//高

    private int mRadius;//半径

    private int mXCenter;//x圆心

    private int mYCenter;//y圆心

    private int mMenuRectTop = 20;//菜单矩形的高度

    private int mMenuRectLeftFinal = 80;//50

    private int mMenuRectLeft ;

    private int mMenuRound = 40;//菜单矩形圆角  40

    private int mLen = 15;

    private int mWradio = 8;

    private final int STATE_OPEN = 0;

    private final int STATE_CLOSE = 1;

    private int STATE = STATE_OPEN;
    private int x1;
    private int y1;
    private int x3;
    private int y3;
    private int x2;
    private int y2;
    private int x4;
    private int y4;
    private int finalX1;
    private int finalX4;

    private int mMenuColor=Color.parseColor("#AB47BC");//默认紫色
    private int mCircleColor = Color.parseColor("#288AFF");//默认蓝色
    private int mIconColor = Color.parseColor("#ffffff");//默认白色

    private OnMenuOnClickListener mOnClickListener;


    public FabBarView(Context context) {
        this(context, null);

    }

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


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

    private void init() {

        mBluePaint = new Paint();
        mBluePaint.setAntiAlias(true);
        mBluePaint.setStyle(Paint.Style.FILL);
        mBluePaint.setStrokeWidth(10);
        mBluePaint.setColor(mCircleColor);

        mPurplePaint = new Paint();
        mPurplePaint.setAntiAlias(true);
        mPurplePaint.setStyle(Paint.Style.FILL);
        mPurplePaint.setStrokeWidth(10);
        mPurplePaint.setColor(mMenuColor);

        mWPaint = new Paint();
        mWPaint.setAntiAlias(true);
        mWPaint.setStyle(Paint.Style.FILL);
        mWPaint.setStrokeWidth(17);
        mWPaint.setColor(mIconColor);


    }

上面的代码主要是一些常量和Paint的初始化

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec) - 5;
        mXCenter = mWidth / 2;
        mYCenter = mHeight / 2;//圆心按照高来计算
        mRadius = mHeight / 2;

        mMenuRectLeft=mMenuRectLeftFinal;//菜单矩形的框度,可变

        finalX1 = (int) (mXCenter + mLen * Math.sqrt(2));//固定的x1
        x1 = finalX1;//可变的s1
        y1 = (int) (mYCenter - mLen * Math.sqrt(2));
        x3 = (int) (mXCenter - mLen * Math.sqrt(2));
        y3 = (int) (mYCenter + mLen * Math.sqrt(2));
        x2 = (int) (mXCenter + mLen * Math.sqrt(2));
        y2 = (int) (mYCenter + mLen * Math.sqrt(2));
        finalX4 = (int) (mXCenter - mLen * Math.sqrt(2));//固定的x4
        x4 = finalX4;
        y4 = (int) (mYCenter - mLen * Math.sqrt(2));
    }

给我们上面初始化的常量赋值,mMenuRectLeft是矩形的left,也就是距离左变的距离,把mMenuRectLeftFinal赋值给mMenuRectLeft是因为mMenuRectLeftFinal我们会在和面做相应的加减处理,需要记住最初的left值,x1,finalX1 ,x4,finalX4同理;x1,y1-x4,y4 是圆中四个点的坐标,因为我们要在圆内画一个X的形状,可以看下面的草图,我们知道圆心AO、BO、CO、DO这四条线段的长度,也知道每个角的角度(角度为90°),根据这3个条件我们就可以求出 ABCD这四个坐标点,拿(x1,y1)的坐标点的具体算法就是:
(x0 -z cos 90°,y0-zsin 90°) //x0 是圆心x

图1
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        RectF menuRect = new RectF(mMenuRectLeft, mMenuRectTop, mWidth - mMenuRectLeft, mHeight - mMenuRectTop);
        canvas.drawRoundRect(menuRect, mMenuRound, mMenuRound, mPurplePaint);

        canvas.drawCircle(mXCenter, mYCenter, mRadius, mBluePaint);

        canvas.drawCircle(x1, y1, mWradio, mWPaint);
        canvas.drawCircle(x3, y3, mWradio, mWPaint);
        canvas.drawCircle(x2, y2, mWradio, mWPaint);
        canvas.drawCircle(x4, y4, mWradio, mWPaint);
        canvas.drawLine(x1, y1, x3, y3, mWPaint);
        canvas.drawLine(x2, y2, x4, y4, mWPaint);

    }

接下来就是onDraw ,这里面比较简单,先创建一个RectF用来存储矩形的左上右下,分别对应下图中的ABCD,然后是画圆,最后就是把在onMeasure中求出圆内4个点的坐标链接起来,然后在每个点上画圆,这样我们的X形状就出来了;

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                int clickX = (int) event.getRawX();
                int clickY = (int) event.getRawY();

                int[] location = new int[2];
                this.getLocationInWindow(location);
                //XY是控件在屏幕的XY,而不是定义的圆心
                int cenY = location[1] + mYCenter;

                if (Math.pow(mXCenter - clickX, 2) + Math.pow(cenY - clickY, 2) <= Math.pow(mRadius, 2)) {
                    doAnim();
                    if (mOnClickListener != null) {
                        mOnClickListener.onClick(this);
                    }
                    return true;
                }
                break;

            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        return true;
    }

    /**
     * 设置点击事件,用于处理打开或关闭菜单的其他事件
     *
     * @param listener
     */
    public void setOnMenuOnClickListener(OnMenuOnClickListener listener) {
        this.mOnClickListener = listener;
    }
    /**
     * 菜单点击事件
     */
    public static interface OnMenuOnClickListener {
        public void onClick(View v);
    }

这个View是个菜单,和用户是有交互的,所以我们就要处理它的点击事件,千万不要直接给这个View设置点击事件,如果你设置了整个View不管你点哪里都会接收这个事件,这样交互就很恶心了,我们要让用户点击中间的圆形接收事件,点击View的其他位置不做处理,算法是:
(圆心x-点击x)平方 + (圆心y - 点击y)平方<圆半径的平方
注:圆心x和圆心y都是圆在整个屏幕上的坐标,可以获取当前控件的位置,然后加上原坐标,获取出圆在屏幕上的坐标,还要留出一个监听,方便做一些其他逻辑的处理


 private void doAnim() {
        switch (STATE) {
            case STATE_OPEN:
                //当前是打开,关闭操作
                //  (View 的宽 - 圆半径)/2 - left
                performAnimate(mMenuRectLeft, (mWidth - mRadius) / 2 - (0) );
                performAnimateX1(x1, mXCenter);
                performAnimateX4(x4, mXCenter);

                STATE = STATE_CLOSE;
                break;
            case STATE_CLOSE:
                //当前是关闭,打开操作
                performAnimate(mMenuRectLeft, mMenuRectLeftFinal);
                performAnimateX1(mXCenter, finalX1);
                performAnimateX4(mXCenter, finalX4);

                STATE = STATE_OPEN;
                break;
        }
    }


 private void performAnimate(final int start, final int end) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //持有一个IntEvaluator对象,方便下面估值的时候使用
            private IntEvaluator mEvaluator = new IntEvaluator();

            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                //获得当前动画的进度值,整型,1-100之间
                int currentValue = (Integer) animator.getAnimatedValue();

                //计算当前进度占整个动画过程的比例,浮点型,0-1之间
                float fraction = currentValue / 100f;

                mMenuRectLeft = mEvaluator.evaluate(fraction, start, end);
                invalidate();
            }
        });
        valueAnimator.setInterpolator(new AnticipateOvershootInterpolator());
        valueAnimator.setDuration(1000).start();
    }

    private void performAnimateX1(final int start, final int end) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            private IntEvaluator mEvaluator = new IntEvaluator();

            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                int currentValue = (Integer) animator.getAnimatedValue();
                float fraction = currentValue / 100f;
                x1 = mEvaluator.evaluate(fraction, start, end);
                invalidate();
            }
        });
        valueAnimator.setInterpolator(new AnticipateOvershootInterpolator());
        valueAnimator.setDuration(1000).start();
    }

    private void performAnimateX4(final int start, final int end) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            private IntEvaluator mEvaluator = new IntEvaluator();

            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                int currentValue = (Integer) animator.getAnimatedValue();
                float fraction = currentValue / 100f;
                x4 = mEvaluator.evaluate(fraction, start, end);
                invalidate();
            }
        });
        valueAnimator.setInterpolator(new AnticipateOvershootInterpolator());
        valueAnimator.setDuration(1000).start();
    }

最后一步就是动画效果,这里用的是valueAnimator;第一个动画是菜单的闭动画,具体的操作方法就是操作矩形的left值,也就是图2中的A线段,开始值是 left,结束值是图2中的E值,在1000毫秒中增大left,打开动画传入的是 E值和固定的left值,每次递减left的值,因为关闭动画我们是加大left,所以打开也就是减少left的值,通俗一些就是根据left的值让View显示和隐藏;X形状和V形状之前的动画是控件图1中的AD点,A和D点都在一定的时间中移动到圆心的X点,也就是mXCenter;

图2

我的Github博客

传送门

**炫酷的菜单基本就搞定了!源码已经上传到Github。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 一:canvas简介 1.1什么是canvas? ①:canvas是HTML5提供的一种新标签 ②:HTML5 ...
    GreenHand1阅读 4,674评论 2 32
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,857评论 25 707
  • 转载请声明 原文链接 关注公众号获取更多资讯 这篇文章主要总结H5的一些新增的功能以及一些基础归纳,这里只是一个提...
    程序员poetry阅读 9,069评论 22 225
  • 系列文章之 Android中自定义View(一)系列文章之 Android中自定义View(二)系列文章之 And...
    YoungerDev阅读 4,387评论 3 11
  • 感恩早上的一场大雨,洗刷了整座城市,带来了凉爽的空气。 感恩孩子们睡了大半个上午的懒觉,也让我可以偷得半日闲。 感...
    rainlove2011阅读 215评论 0 0