美文网首页Android 学习资源自定义ViewAndroid开发
Android初级进阶之自定义酷炫菜单

Android初级进阶之自定义酷炫菜单

作者: AndroidRookie | 来源:发表于2016-08-27 15:36 被阅读682次

    效果图

    image

    前言

    最近一直在学习自定义控件,发现自己依然是那么的渣渣。上面的UI效果来自这个网站,果然,交互设计的效果与实际做出来的效果还是相差很大啊。

    分析

    整个效果实现起来还是十分的简单,分析一下:

    1. 中间是一个圆用drawCircle()就可以画出来。
    2. 圆中心是两条线:drawLine()
    3. 后边的背景是一个圆角矩形:drawRoundRect()
    4. 动画效果使用ValueAnimator发散1--0之间的数值,不断的更改长度和坐标即可完成。
    5. 不想吐槽那四个小正方形。

    开始编码

    Step 1

    继承自原生的View,重写构造方法,在构造方法中初始化画笔等操作

     /**
     * 初始化画笔等
     */
    private void initSomeThing() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(1);
    }
    

    Step 2 Measure(测量)

    测量View的宽高,支持Padding,wrap_content等

     @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measureWidth = measureSize(widthMeasureSpec, getWindowMsg(getContext())[0] - getPaddingLeft() - getPaddingRight() - mCenterMenuOffset);
        int measureHeight = measureSize(heightMeasureSpec, dp2px(getContext(), 90)) + getPaddingBottom() + getPaddingTop();
        setMeasuredDimension(measureWidth, measureHeight);
    }
    

    测量封装方法

    /**
     * 测量宽高 模式
     *
     * @param measureSpec
     * @return
     */
    private  int measureSize(int measureSpec,int defaultSize) {
        int result;
        int size = MeasureSpec.getSize(measureSpec);
        int mode = MeasureSpec.getMode(measureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = defaultSize;
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }
    

    获取屏幕宽高

     /**
     * 获取屏幕宽高
     *
     * @return wh[1] 屏幕高度
     */
    private   int[] getWindowMsg(Context context) {
        int[] wh = new int[2];
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        wh[0] = wm.getDefaultDisplay().getWidth();
        wh[1] = wm.getDefaultDisplay().getHeight();
        //Log.d("vivi", "屏幕宽 =" + wh[0]);
        //Log.d("vivi", "屏幕高 =" + wh[1]);
        return wh;
    }
    

    dp2px

    /**
     * dp转px
     *
     * @param context
     * @param dpVal
     * @return
     */
    private  int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }
    

    Step 3 在onSizeChanged中初始化宽高的一些属性

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;//宽度
        mHeight = h;//高度
        mCenterMenuOffset = (int) (mHeight * 0.3);//中间 圆形菜单的上下偏移量
        mMenuBackgroundOffset = (int) ((mHeight * 0.5) / 2); //紫色圆角矩形的上下偏移量
        mCenterMenuRadius = (mHeight - mCenterMenuOffset) / 2;//中间圆形的半径
        mForkLenght = (int) (mCenterMenuRadius * 0.4);//中间两个线的长度
    
        //得到中心菜单的矩阵 start
        int l = mWidth / 2 - mCenterMenuRadius;
        int t = mCenterMenuOffset / 2;
        int b = mHeight - mCenterMenuOffset / 2;
        int r = mWidth / 2 + mCenterMenuRadius;
        mCenterMenuRect = new Rect(l, t, r, b);//中心的圆的矩阵,用来响应点击事件的
        //得到中心菜单的矩阵 end
    }
    

    Step 4 画中心的圆

    画个圆,没什么难度,偏移量,半径都在onSizeChanged中初始化完成了。这里要给圆加上阴影

      //画中心的圆 start
        mPaint.setShadowLayer(7, 4, 5, 0xff999999);//设置阴影图层
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);
        mPaint.setColor(mCenterMenuColor);
        canvas.drawCircle(mWidth / 2, mHeight / 2, mCenterMenuRadius, mPaint);
    

    Step 5 圆中心的两根线

    仔细看效果图,两个线上边的点是相同的,只是在动画的时候更改X轴的坐标就可以从 ^ 变成X了。首先要将画布的坐标点移到中心,这样更方便绘制这两条线。

    //圆形菜单中的 叉叉
        canvas.translate(mWidth / 2, mHeight / 2);//移动坐标点
        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(10);
        canvas.drawLine(0, -mForkLenght, -mForkLenght, mForkLenght, mPaint);
        canvas.drawLine(0, -mForkLenght, mForkLenght, mForkLenght, mPaint);
    

    效果就出来啦:

    image

    只要将左边的线的坐标更改为负数,右边更改为正数,就变成了X了

     canvas.drawLine(mForkLenght, -mForkLenght, -mForkLenght, mForkLenght, mPaint);
     canvas.drawLine(1mForkLenght, -mForkLenght, mForkLenght, mForkLenght, mPaint);
    
    image

    Step 6 绘制背景矩形

    绘制静态的矩形,也就是整个展开的效果

     // 菜单背景 start
    int left = mWidth / 2;//100
    float nowWidth = (left - mMenuBackgroundOffset / 2);
    RectF mMenuRectF = new RectF(left - nowWidth, mMenuBackgroundOffset, left + nowWidth, mHeight - mMenuBackgroundOffset);
    mPaint.setColor(mMenuBackgroundColor);
    canvas.drawRoundRect(mMenuRectF, mCenterMenuRadius, mCenterMenuRadius, mPaint);
    

    效果出来了有没有?

    展开初始状态

    Step 7 为中心的按钮响应点击事件

    自定义原生View应该如何响应点击事件呢?其实非常的简单,在onSizeChanged里面计算出了中心圆的矩形区域mCenterMenuRect。而Rect提供了一个方法contains(x, y)来判断xy坐标是不是在这个矩形当中,于是这就简单了,只需要重写onTouch方法判断xy坐标就好。

      @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();//获得点击的坐标
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_UP:
                if (mCenterMenuRect.contains(x, y)) {//包含 打开菜单获关闭
                    openOrClose();
                }
                break;
        }
        return true;
    }
    
    /**
     * 打开或关闭
     */
    public void openOrClose() {
        if (isOpen) {
            startAnimator(1, 0);
        } else {
            startAnimator(0, 1);
        }
        isOpen = !isOpen;
    }
    

    Step 8 增加动画

    使用ValueAnimator来发散1--0之间的数值,相当于百分比,然后不断的postInvalidate达到效果

    /**
     * 开启启动动画
     *
     * @param from
     * @param end
     */
    private void startAnimator(final int from, int end) {
        if (mAnimatorBackground != null && mAnimatorBackground.isRunning()) {
            mAnimatorBackground.cancel();
            mAnimatorBackground = null;
        }
        mAnimatorBackground = ValueAnimator.ofFloat(from, end).setDuration(500);
        mAnimatorBackground.setInterpolator(new OvershootInterpolator());
        mAnimatorBackground.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPreValue = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        mAnimatorBackground.start();
    }
    

    onDraw方法代码

     @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 菜单背景 start
        int left = mWidth / 2;//100
        float nowWidth = (left - mMenuBackgroundOffset / 2) * mPreValue;
        RectF mMenuRectF = new RectF(left - nowWidth, mMenuBackgroundOffset, left + nowWidth, mHeight - mMenuBackgroundOffset);
        mPaint.setColor(mMenuBackgroundColor);
        canvas.drawRoundRect(mMenuRectF, mCenterMenuRadius, mCenterMenuRadius, mPaint);
        // 菜单背景 end
        //画中心的圆 start
        mPaint.setShadowLayer(7, 4, 5, 0xff999999);//设置阴影图层
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);
        mPaint.setColor(mCenterMenuColor);
        canvas.drawCircle(mWidth / 2, mHeight / 2, mCenterMenuRadius, mPaint);
        mPaint.setShadowLayer(0, 0, 0, Color.TRANSPARENT);
        // canvas.rotate(360 * mPreValue, mWidth / 2, mHeight / 2);
    
        //画中心的圆 end
        //圆形菜单中的 叉叉
        canvas.translate(mWidth / 2, mHeight / 2);//移动坐标点
        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(10);
        canvas.drawLine(mForkLenght * mPreValue, -mForkLenght, -mForkLenght, mForkLenght, mPaint);
        canvas.drawLine(-mForkLenght * mPreValue, -mForkLenght, mForkLenght, mForkLenght, mPaint);
    }
    

    效果如下:

    酷炫菜单2
     // canvas.rotate(360 * mPreValue, mWidth / 2, 
    

    这句注释打开后就会有一个旋转的效果,如下:

    酷炫菜单3

    最后

    OK,这个效果基本上到这里也就完成了,不要问我为什么没有画矩形,我只是想偷懒,最后会附上源码的地址,自己研究去,也就是计算矩形的位置加上动画而已。

    自己造轮子,才是最快的进步方式。

    源码

    相关文章

      网友评论

      本文标题:Android初级进阶之自定义酷炫菜单

      本文链接:https://www.haomeiwen.com/subject/brctettx.html