Android 사용자 정의 View: 체크 작은 애니메이션에 대한 생각 재구성

14771 단어 모바일 개발
Github 주소: TickView, 정교한 갈고리 작은 애니메이션github.com/ChengangFen…
먼저 효과도를 올려라, 그렇지 않으면 읽을 수 없다,right?
그림
그림을 그리다.gif
정적 그림

1. 회고


[안드로이드 사용자 정의 View: 정교한 갈고리 애니메이션] 지난 글에서 우리는 기본적으로 컨트롤의 효과를 실현했지만...그런데...사나흘이 지난 후에 자신이 쓴 코드를 자세히 보았는데 생각은 아직 있지만 일부 코드는 단번에 알아볼 수 없었다.
오 마이 갓, 이거 바로 재구성해야죠~ 마침 어떤 네티즌 ChangQin 패러디가 이 컨트롤을 썼는데 저도 보고 이렇게 실현할 수 있을 것 같아요.

2.깊이 생각하다


컨트롤 그리기에 대한 사고방식은 지난 글을 보면 더 이상 분석하지 않을 수 있다.여기에 먼저 지난 글에서 컨트롤 안의 일부 완고한 부분, 어떤 부분을 개선해야 하는지를 분석해 봅시다.
동그라미 진도를 그리는 걸로 볼게요.
//   
private int ringCounter = 0;

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (!isChecked) {
        ...
        return;
    }
    //     ,       12   ,         12 
    //   12      ,                 
    ringCounter += 12;
    if (ringCounter >= 360) {
        ringCounter = 360;
    }
    canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing);
    ...
    //    
    postInvalidate();
}

여기에 우리는 계수기ringCounter를 정의했는데 그릴 때 12개 단위에 따라 360까지 증가하여 진도의 변화를 시뮬레이션한다.
곰곰이 생각하다
  • 증가하는 단위를 바꾸어 애니메이션 속도의 변화를 제어하면 만족스럽게 조정하기 어렵다. 이때 우리는 애니메이션 속도를 느리게 하는 것이 바로 시간을 제어하는 것이라고 생각할 수 있다. 만약에 시간으로 애니메이션 속도를 제어할 수 있다면 훨씬 편리할 것이다
  • 애니메이션은 4단계로 나뉘어 실행된다. 만약에 모든 애니메이션이 핸드폰 카운터로 이루어진다면 4개의 구성원 변수 또는 더 많은 구성원 변수를 정의해야 한다. 너무 많은 구성원 변수는 코드를 더욱 혼란스럽게 할 뿐이다
  • 애니메이션에 플러그인을 추가하려면 손으로 쓴 계수기를 만족시킬 수 없다
  • 위의 분석을 보고 받아들일 수 없습니다
  • 3. 고치다


    그렇다면 위에서 말한 문제를 어떻게 개선할 것인가. 답은 사용자 정의 속성 애니메이션으로 해결한 것이다. 그래서 이 글에서 주로 다루는 부분은 속성 애니메이션으로 손으로 쓴 계수기를 교체하고 가능한 한 코드 논리의 명확성을 확보하는 것이다. 특히onDraw() 방법의 코드.
    속성 애니메이션을 사용하는 장점 중 하나는 수치의 범위를 정하면 당신이 원하는 수치를 만들 수 있고 플러그인에 맞추면 예상치 못한 효과를 얻을 수 있다는 것이다. 다음 부분은 애니메이션이 실행하는 부분을 한 걸음 한 걸음 재구성한다.

    3.1 링 진행률 막대 그리기


    먼저 사용자 정의ObjectAnimator를 사용하여 진행 상황을 시뮬레이션합니다.
    //ringProgress         ,        0 - 360,        
    ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
    //         ,                        
    mRingAnimator.setDuration(mRingAnimatorDuration);
    //        
    mRingAnimator.setInterpolator(null);

    사용자 정의 속성 애니메이션은 상응하는 settergetter를 설정해야 한다. 왜냐하면 애니메이션을 실행할 때 상응하는 setter를 찾아 상응하는 값을 바꾸기 때문이다.
    private int getRingProgress() {
        return ringProgress;
    }
    
    private void setRingProgress(int ringProgress) {
        //       ,   setter
        //                  ,      , ondraw    
        this.ringProgress = ringProgress;
        //    
        postInvalidate();
    }

    마지막으로 onDraw()에서 그림을 그립니다.
    //     
    canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);

    3.2 중심점으로 축소하는 애니메이션 그리기


    같은 이치로 속성 애니메이션을 만들기도 한다
    //               
    ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
    //         
    mCircleAnimator.setInterpolator(new DecelerateInterpolator());
    mCircleAnimator.setDuration(mCircleAnimatorDuration);

    setter/getter도 비슷해서 말 안 할게요.
    마지막onDraw()에서 그리기
    //   
    mPaintCircle.setColor(checkBaseColor);
    canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
    //         ,      
    if (ringProgress == 360) {
        mPaintCircle.setColor(checkTickColor);
        canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
    }

    3.3 갈고리 그리기와 확대 리턴 효과


    이것은 두 개의 독립된 효과인데, 여기서 동시에 집행하면, 나는 함께 말하겠다
    우선 속성 애니메이션을 정의합니다.
    //        
    ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
    mAlphaAnimator.setDuration(200);
    //           ,          
    //      ,        
    //          ,        n ,           
    ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
    mScaleAnimator.setInterpolator(null);
    mScaleAnimator.setDuration(mScaleAnimatorDuration);
    
    //              
    AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
    mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);

    getter/setter
    private int getTickAlpha() {
        return 0;
    }
    
    private void setTickAlpha(int tickAlpha) {
        //     ,          
        //                 
        mPaintTick.setAlpha(tickAlpha);
        postInvalidate();
    }
    
    private float getRingStrokeWidth() {
        return mPaintRing.getStrokeWidth();
    }
    
    private void setRingStrokeWidth(float strokeWidth) {
        //      ,          
        //                
        mPaintRing.setStrokeWidth(strokeWidth);
        postInvalidate();
    }

    마지막으로 onDraw()에서 그리면 됩니다.
    if (circleRadius == 0) {
        canvas.drawLines(mPoints, mPaintTick);
        canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
    }

    3.4 순서대로 애니메이션 실행


    여러 개의 애니메이션을 실행하면 AnimatorSet에 사용할 수 있습니다. 그 중에서 playTogether()는 함께 실행하고 playSequentially()는 하나씩 step by step에서 실행합니다.
    mFinalAnimatorSet = new AnimatorSet();
    mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);

    마지막으로 onDraw()에서 애니메이션을 실행합니다.
    //          ,      ,          
    if (!isAnimationRunning) {
        isAnimationRunning = true;
        //    
        mFinalAnimatorSet.start();
    }

    3.5 각 방법마다 단일한 직책이 있는 것이 가장 좋다


    만약에 속성 애니메이션을 정의하는 방법을 onDraw()에 두면 저는 개인적으로 매우 혼란스러워요. 그리고 다시 자세히 보면 이 몇 개의 속성 애니메이션은 동적 변화가 필요 없어요. 왜 처음부터 초기화하지 않았어요?
    so, 우리는 속성 애니메이션을 정의하는 코드를 추출하여 구조 함수에 초기화할 것이다
    public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ...
        initAnimatorCounter();
    }
    /**
     *  ObjectAnimator        
     */
    private void initAnimatorCounter() {
        //    
        ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
        ...
        //    
        ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
        ...
        //        
        ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
        ...
        //           ,          
        ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
        ...
    
        //              
        AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
        mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
    
        mFinalAnimatorSet = new AnimatorSet();
        mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
    }

    마지막으로 onDraw() 방법에서는 간단한 그림만 책임지고 아무것도 상관하지 않는다
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!isChecked) {
            canvas.drawArc(mRectF, 90, 360, false, mPaintRing);
            canvas.drawLines(mPoints, mPaintTick);
            return;
        }
        //     
        canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
        //      
        mPaintCircle.setColor(checkBaseColor);
        canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
        //       
        if (ringProgress == 360) {
            mPaintCircle.setColor(checkTickColor);
            canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
        }
        //  ,         
        if (circleRadius == 0) {
            canvas.drawLines(mPoints, mPaintTick);
            canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
        }
        //ObjectAnimator       
        if (!isAnimationRunning) {
            isAnimationRunning = true;
            mFinalAnimatorSet.start();
        }
    }

    최종 효과는 똑같다. 코드 논리가 한눈에 최종 효과를 알 수 있다.gif
    그래서 개인적으로 개발 과정에서 정해진 시간에 리뷰를 해서 자신의 코드를 보는 것이 자신에게나 향후 유지보수에 도움이 된다고 생각합니다.
    That's all~ 읽어주셔서 감사합니다. 마지막으로 프로젝트의 github 주소를 틀어드릴게요.
    Github 주소: TickView, 정교한 갈고리 작은 애니메이션github.com/ChengangFen…
    전재 대상:https://juejin.im/post/59f5609851882534af2538c0

    좋은 웹페이지 즐겨찾기