27
2017
09

Android圆环选择View

Android圆环选择View

无奈产品喜欢在APP中加入各种动画,再加上UI小姐姐的奇思妙想,然后就设计出了一大堆动画,前两周才把动画写完,故有了此篇博客来记录一下当时所遇到的坑,效果图镇楼,如下:

注:点击哪个那个旋转到最下面,旋转到最下面的为选中状态。

哇,当时看到这效果,真的是有辞职的冲动,但是转眼一想,哎,反正动画也不太熟悉,那就把这个做出来吧,我们先不加动画,就先实现静态的,如下图:

后面的圆环是怎么画出来的?

先实现简单的吧,我们可以看到后面有个圆环,我们都知道圆环嘛,大圆套小圆,就可以实现,但是现在的圆环颜色是不一样的,我们若还是用普通的圆来实现的话,就有点儿困难了。既然直接画圆不行,那就用圆弧把这个圆拼出来吧,代码如下:

    //画大圆
    private void drawBackRound(Canvas canvas) {
        initPaint();
        roundWidth = Math.min(width, height) / 2 * 0.15f;
        rectF = new RectF(
                (width > height ? Math.abs(width - height) / 2 : 0) + roundWidth / 2,
                (height > width ? Math.abs(height - width) / 2 : 0) + roundWidth / 2,
                width - (width > height ? Math.abs(width - height) / 2 : 0) - roundWidth / 2,
                height - (height > width ? Math.abs(height - width) / 2 : 0) - roundWidth / 2);
        paint.setStrokeWidth(roundWidth * 0.8f);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(getResources().getColor(R.color.white_0));
        paint.setAntiAlias(true);
        canvas.drawArc(rectF, 0, 360, false, paint);
    }

额……若是RectF不知道是什么玩意儿的话…简单总结RectF、Rect 和Matrix ,还有Paint的使用方法这篇博客里面有讲。同理啦,圆环是大圆套小圆的,那么画小圆的方法和这个类似,只是改变了paint的颜色罢了,这就不贴代码啦,继续解决下一个问题~

圆环上的圆位置是怎么确定的?

在看完成的整体效果时,想必大家若是看到过鸿洋大神的Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单那这个位置解决的就soeasy啦~,其实我就是借鉴的鸿洋大神的这个例子,主要的代码如下:


    /** * 设置menu item的位置 */
    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        int layoutRadius = mRadius;
        float angleDelay;
        final int childCount = getChildCount();
        int left, top;
        // menu item 的尺寸
        int cWidth = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION);
        // 找到中心的view
        View cView = findViewById(R.id.id_circle_menu_item_center);
        // 根据menu item的个数,计算角度
        if (cView != null) {
            //如果中心View存在item个数减1
            angleDelay = 360 / (getChildCount() - 1);
            // 设置center item位置
            //居中
            int cl = layoutRadius / 2 - cView.getMeasuredWidth() / 2;
            int cr = cl + cView.getMeasuredWidth();
            cView.layout(cl, cl, cr, cr);
        } else {
            angleDelay = 360 / (getChildCount());
        }
        // 遍历去设置menuitem的位置
        for (int j = 0; j < childCount; j++) {
            final View child = getChildAt(j);
            if (child.getId() == R.id.id_circle_menu_item_center)
                continue;
            if (child.getVisibility() == GONE) {
                continue;
            }
            mStartAngle %= 360;
            // 计算,中心点到menu item中心的距离
            float tmp = layoutRadius / 2f - cWidth / 2;
            // tmp cosa 即menu item中心点的横坐标
            left = layoutRadius
                    / 2
                    + (int) Math.round(tmp
                    * Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f
                    * cWidth);
            // tmp sina 即menu item的纵坐标
            top = layoutRadius
                    / 2
                    + (int) Math.round(tmp
                    * Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f
                    * cWidth);

            child.layout(left, top, left + cWidth, top + cWidth);
            // 叠加尺寸
            mStartAngle += angleDelay;
        }
    }

没错,就是重写onLayout来确定圆环上圆的位置,每当要旋转时就调用requestLayout()重新确定圆的位置~

圆环上的圆旋转的角度是怎么确定的?

在效果图上可以看到,点击哪个哪个就旋转到最下面,这中间有个问题,就是:我们人眼是可以看到那个在最上面,那个在左,在右,但是,在代码中实现的时候,我们并不知道它们的位置是在哪里,这就有个问题了,我们不知道位置,所以就没办法确定到底要旋转多少度,不知道要旋转的角度,那么这个效果就无法实现了。
后来想了半天,其实我们可以这样想,如下图:

首先我们可以获取的资源如下:当前选中的ImageView,要选中的ImageView,当前ImageView的在数组中的下标和要选中的ImageView在数组中的下标。这些我们是可以知道的,假如,现在选中了下标为1的ImageView,那么我不管是要选中下标为2还是0的都旋转90度,那么这个角度就可以用要选中的ImageView的下标来减去当前选中 X 90就好了,然后顺时针转还是逆时针转就看需求啦~但是还有一个要解决的,就是,当前选中的下标为0,那么左右的下标就是3和1了,这样(3-0) X 90要旋转的角度就不是我们想要的角度了,所以,在0和3的时候要有个判断,分析结束,主要的代码如下:


 cl_group.setOnMenuItemClickListener(new CircleMenuLayout.OnMenuItemClickListener() {
            @Override
            public void itemClick(View iv, int pos, View tv) {
                //旋转动画
                groupRotating(pos);
                ...
                oldElect = pos;
            }
        });

    //旋转
    private void groupRotating(int pos) {
        ValueAnimator anim = new ValueAnimator();
        int newElect = pos;
        int angle = 0;
        if (oldElect == 0 && newElect == 3) {
            //当前选中的是0,未来选中的是3,则顺时针旋转180
            angle = ROTATION_ANGLE;
        } else if (oldElect == 3 && newElect == 0) {
            //当前选中的是3,未来选中的是0,则逆时针旋转180
            angle = -ROTATION_ANGLE;
        } else {
            //若不属于以上两种情况,按普通的处理
            angle = (oldElect - newElect) * ROTATION_ANGLE;
        }
        //范围,基准角度----基准角度+要旋转的角度
        anim = ValueAnimator.ofInt(tag, tag + angle);
        anim.setDuration(SUCCESSFUL_ANIM_TIME).start();
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int angle = (int) valueAnimator.getAnimatedValue();
                cl_group.setmStartAngle(angle);
            }
        });
        tag = tag + angle;
    }

ok,这里用了ValueAnimator属性动画,主要就是通过返回的数值,达到旋转的效果~

实现背景圆环同步旋转

我们在效果图上可以看到后面的圆环也是可以同步旋转的哈,其实要实现同步旋转很简单,在RingView加属性动画,然后外部调用就好啦!如下:

 public void setRingRotating(int nowAngle, int futureAngle) {
        ObjectAnimator.ofFloat(this, "rotation", nowAngle, futureAngle).setDuration(animTime).start();
    }

外部调用:

//旋转
    private void groupRotating(int pos) {
        ...
        //背景圆环同步旋转
        rl_view.setRingRotating(tag, tag + angle);
        tag = tag + angle;
    }

圆环颜色美化

在效果图上,可以看到当圆环在旋转的时候之前选中的颜色渐渐显示,要选中的渐渐隐藏,这个其实很简单,还是借助ValueAnimator属性动画就可以实现啦,同样的我们在外部调用它,在RingView里面添加如下代码:


    //设置颜色要显示还是隐藏的参数
    public void setElect(int oldElect, int newElect, int oldValue, int newValue) {
        this.oldElect = oldElect;
        this.newElect = newElect;
        //当前的
        int currentValue = 0;
        //将来的
        int futureValue = 0;
        currentValue = (oldElect % 2 == 0) ? oldValue : newValue;
        futureValue = (newElect % 2 == 0) ? oldValue : newValue;
        //动画
        ValueAnimator oldAnim = ValueAnimator.ofInt(0, currentValue);
        ValueAnimator newAnim = ValueAnimator.ofInt(futureValue, 0);
        oldAnim.setDuration(animTime).start();
        newAnim.setDuration(animTime).start();
        //显示
        oldAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                oldAlpha = (int) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        //隐藏
        newAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                newAlpha = (int) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
    }

由于每段圆弧的颜色透明度不一样,所以要判断下当前的圆环和要选择的圆环的透明度。而它们的条件就是是否可以被2整除!
外部调用如下:

        cl_group.setOnMenuItemClickListener(new CircleMenuLayout.OnMenuItemClickListener() {
            @Override
            public void itemClick(View iv, int pos, View tv) {
               ...
                //背景圆弧换颜色动画
                rl_view.setElect(oldElect, pos, 80, 50);
               ...
                oldElect = pos;
            }
        });

圆弧上的ImageView美化

效果图上,可以看到圆弧上的ImageView是带有动画的,其实加动画也不难,主要的就是在点击后,有个缩放动画,主要代码如下:


    private void zoomImageViewAnim(Bean oldBean, Bean newBean, final int width, final int height) {
        //当前选中的缩小,换背景,字体换颜色
        //将来选中的缩小,换背景,字体换颜色
        final TextView oldTv = oldBean.getTv();
        final ImageView oldIv = oldBean.getIv();

        final TextView newTv = newBean.getTv();
        final ImageView newIv = newBean.getIv();

        ValueAnimator shrinkImageAnimWidth = new ValueAnimator();
        ValueAnimator shrinkImageAnimHeight = new ValueAnimator();
        shrinkImageAnimWidth = ValueAnimator.ofInt(width, 0);
        shrinkImageAnimHeight = ValueAnimator.ofInt(height, 0);
        //缩小
        AnimatorSet set = new AnimatorSet();
        set.playTogether(shrinkImageAnimWidth, shrinkImageAnimHeight);
        set.setDuration(500).start();
        shrinkImageAnimWidth.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int w = (int) animation.getAnimatedValue();
                FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) oldIv.getLayoutParams();
                params.width = w;
                //改变宽度
                oldIv.setLayoutParams(params);
                newIv.setLayoutParams(params);
            }
        });
        shrinkImageAnimHeight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int h = (int) animation.getAnimatedValue();
                FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) oldIv.getLayoutParams();
                params.height = h;
                //改变高度
                oldIv.setLayoutParams(params);
                newIv.setLayoutParams(params);
            }
        });
        shrinkImageAnimWidth.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                //缩小动画,完成,开启放大动画
                //文字换颜色
                oldTv.setTextColor(Color.BLACK);
                newTv.setTextColor(Color.WHITE);
                //ImageView换背景
                oldIv.setBackgroundResource(R.drawable.shape_circle_yellow_50);
                newIv.setBackgroundResource(R.drawable.shape_circle_ring_yellow);

                AnimatorSet set1 = new AnimatorSet();
                ValueAnimator bigImageAnimWidth = new ValueAnimator();
                ValueAnimator bigImageAnimHeight = new ValueAnimator();
                bigImageAnimWidth = ValueAnimator.ofInt(0, width);
                bigImageAnimHeight = ValueAnimator.ofInt(0, height);

                set1.playTogether(bigImageAnimWidth, bigImageAnimHeight);
                set1.setDuration(500).start();

                bigImageAnimWidth.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int w = (int) animation.getAnimatedValue();
                        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) oldIv.getLayoutParams();
                        params.width = w;
                        //改变宽度
                        oldIv.setLayoutParams(params);
                        newIv.setLayoutParams(params);
                    }
                });

                bigImageAnimHeight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int h = (int) animation.getAnimatedValue();
                        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) oldIv.getLayoutParams();
                        params.height = h;
                        //改变高度
                        oldIv.setLayoutParams(params);
                        newIv.setLayoutParams(params);
                    }
                });
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

就不多做解释啦,里面都注释了。

END

其实最主要的是借鉴了,鸿洋大神的Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单,写之前感觉好难,写之后感觉,也就是一层窗户纸的事儿,思路最重要,哈哈……
源码链接

上一篇:stm32f103系列芯片P15端口当做普通IO使用 下一篇:C#泛型方法的反射