天天看點

Android圓環選擇ViewAndroid圓環選擇View

Android圓環選擇View

無奈産品喜歡在APP中加入各種動畫,再加上UI小姐姐的奇思妙想,然後就設計出了一大堆動畫,前兩周才把動畫寫完,故有了此篇部落格來記錄一下當時所遇到的坑,效果圖鎮樓,如下:

Android圓環選擇ViewAndroid圓環選擇View

注:點選哪個那個旋轉到最下面,旋轉到最下面的為選中狀态。

哇,當時看到這效果,真的是有辭職的沖動,但是轉眼一想,哎,反正動畫也不太熟悉,那就把這個做出來吧,我們先不加動畫,就先實作靜态的,如下圖:

Android圓環選擇ViewAndroid圓環選擇View

後面的圓環是怎麼畫出來的?

先實作簡單的吧,我們可以看到後面有個圓環,我們都知道圓環嘛,大圓套小圓,就可以實作,但是現在的圓環顔色是不一樣的,我們若還是用普通的圓來實作的話,就有點兒困難了。既然直接畫圓不行,那就用圓弧把這個圓拼出來吧,代碼如下:

//畫大圓
    private void drawBackRound(Canvas canvas) {
        initPaint();
        roundWidth = Math.min(width, height) /  * f;
        rectF = new RectF(
                (width > height ? Math.abs(width - height) /  : ) + roundWidth / ,
                (height > width ? Math.abs(height - width) /  : ) + roundWidth / ,
                width - (width > height ? Math.abs(width - height) /  : ) - roundWidth / ,
                height - (height > width ? Math.abs(height - width) /  : ) - roundWidth / );
        paint.setStrokeWidth(roundWidth * f);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(getResources().getColor(R.color.white_0));
        paint.setAntiAlias(true);
        canvas.drawArc(rectF, , , 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 =  / (getChildCount() - );
            // 設定center item位置
            //居中
            int cl = layoutRadius /  - cView.getMeasuredWidth() / ;
            int cr = cl + cView.getMeasuredWidth();
            cView.layout(cl, cl, cr, cr);
        } else {
            angleDelay =  / (getChildCount());
        }
        // 周遊去設定menuitem的位置
        for (int j = ; 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 %= ;
            // 計算,中心點到menu item中心的距離
            float tmp = layoutRadius / f - cWidth / ;
            // tmp cosa 即menu item中心點的橫坐标
            left = layoutRadius
                    / 
                    + (int) Math.round(tmp
                    * Math.cos(Math.toRadians(mStartAngle)) -  / f
                    * cWidth);
            // tmp sina 即menu item的縱坐标
            top = layoutRadius
                    / 
                    + (int) Math.round(tmp
                    * Math.sin(Math.toRadians(mStartAngle)) -  / f
                    * cWidth);

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

沒錯,就是重寫onLayout來确定圓環上圓的位置,每當要旋轉時就調用requestLayout()重新确定圓的位置~

圓環上的圓旋轉的角度是怎麼确定的?

在效果圖上可以看到,點選哪個哪個就旋轉到最下面,這中間有個問題,就是:我們人眼是可以看到那個在最上面,那個在左,在右,但是,在代碼中實作的時候,我們并不知道它們的位置是在哪裡,這就有個問題了,我們不知道位置,是以就沒辦法确定到底要旋轉多少度,不知道要旋轉的角度,那麼這個效果就無法實作了。

後來想了半天,其實我們可以這樣想,如下圖:

Android圓環選擇ViewAndroid圓環選擇View

首先我們可以擷取的資源如下:目前選中的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 = ;
        if (oldElect ==  && newElect == ) {
            //目前選中的是0,未來選中的是3,則順時針旋轉180
            angle = ROTATION_ANGLE;
        } else if (oldElect ==  && newElect == ) {
            //目前選中的是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 = ;
        //将來的
        int futureValue = ;
        currentValue = (oldElect %  == ) ? oldValue : newValue;
        futureValue = (newElect %  == ) ? oldValue : newValue;
        //動畫
        ValueAnimator oldAnim = ValueAnimator.ofInt(, currentValue);
        ValueAnimator newAnim = ValueAnimator.ofInt(futureValue, );
        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, , );
               ...
                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, );
        shrinkImageAnimHeight = ValueAnimator.ofInt(height, );
        //縮小
        AnimatorSet set = new AnimatorSet();
        set.playTogether(shrinkImageAnimWidth, shrinkImageAnimHeight);
        set.setDuration().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(, width);
                bigImageAnimHeight = ValueAnimator.ofInt(, height);

                set1.playTogether(bigImageAnimWidth, bigImageAnimHeight);
                set1.setDuration().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 打造炫目的圓形菜單 秒秒鐘高仿建行圓形菜單,寫之前感覺好難,寫之後感覺,也就是一層窗戶紙的事兒,思路最重要,哈哈……

源碼連結