간단 한 Android 원호 새로 고침 애니메이션

이전에 스티커 를 칠 할 때 본 스티커 의 새로 고침 애니메이션 은 바로 원호 가 회전 하 는 애니메이션 으로 보기 좋 은 것 같 아서 시간 을 내 서 이 루어 졌 습 니 다.
최종 결 과 는 이렇다.

위의 그림 에서 보 듯 이 애니메이션 의 효 과 는 3 단 원호 가 회전 하 는 동시에 호도 도 점점 커지 고 축소 되 는 것 이다.여기 서 사용 하 는 것 은 onDraw 에서 3 단 원 호 를 그 리 는 것 이다.

//     
mPaint.setColor(mTopColor);
canvas.drawArc(left, top, right, bottom, startAngle, sweepAngle, false, mPaint);
mPaint.setColor(mLeftColor);
canvas.drawArc(left, top, right, bottom, startAngle - 120, sweepAngle, false, mPaint);
mPaint.setColor(mRightColor);
canvas.drawArc(left, top, right, bottom, startAngle + 120, sweepAngle, false, mPaint);
애니메이션 의 기 초 는 onDraw 에서 세 가지 서로 다른 색 의 원 호 를 차례대로 그 리 는 것 입 니 다.세 단락 의 원 호 는 각각 120 도 간격 을 두 면 원 전 체 를 똑 같이 나 눌 수 있어 서 비교적 아름답다.
여기 서 startAngle 의 초기 값 은-90 이 고 원 의 맨 위 에 있 습 니 다.여기 서 주의해 야 할 것 은 canvas 의 draw Arc 방법 에서 앞의 네 개의 매개 변 수 는 원호 의 위 치 를 결정 하 는 사각형 의 좌 표를 말 합 니 다.startAngle 은 원호 가 시작 하 는 각 도 를 말 합 니 다.0 도 는 원 의 가장 오른쪽 점 이 고 시계 방향 을 바 꾸 고 시계 반대 방향 을 마이너스 로 합 니 다.그래서-90 도 는 원 의 맨 위 에 있 는 점 입 니 다.
sweepAngle 은 원호 가 스 캔 한 각 도 를 말 하 는데 똑 같이 시계 방향 을 바 꾸 고 시계 반대 방향 을 마이너스 로 한다.여기 sweepAngle 의 크기 초기 값 은-1 입 니 다.애니메이션 이 시작 되 기 전에 도 원점 을 그 릴 수 있 습 니 다.
다음 매개 변 수 는 useCenter 입 니 다.원심 을 사용 할 지,true 일 때 원호 의 두 점 을 원심 으로 연결 하여 하나의 부채 형 을 구성 하고 false 일 때 원심 을 연결 하지 않 습 니 다.
또한 paint 의 style 을 stroke 로 설정 해 야 합 니 다.기본 적 인 상황 에서 fill 모드,즉 직접 채 울 수 있 습 니 다.이곳 의 원호 에 대해 서 는 원호 의 두 점 을 직접 연결 하여 닫 힌 도형 을 구성 한 후 채 웁 니 다.

이렇게 하면 애니메이션 의 초기 상 태 를 그립 니 다.세 개의 원점(실제 각도 가 1 인 원호)입 니 다.
위 에서 도 알 수 있 듯 이 원 호 를 그 리 려 면 반드시 네 개의 좌표 가 있어 야 한다.이곳 의 좌 표 는 이런 방식 으로 얻 은 것 이다.View 의 길이 와 너비 중 가장 짧 은 한 쪽 을 원 을 구성 하 는 정사각형 의 길이 로 한 다음 에 가운데 에 나타난다.

int width = getMeasuredWidth();
int height = getMeasuredHeight();
int side = Math.min(width - getPaddingStart() - getPaddingEnd(), height - getPaddingTop() - getPaddingBottom()) - (int) (mStrokeWidth + 0.5F);

//       
float left = (width - side) / 2F;
float top = (height - side) / 2F;
float right = left + side;
float bottom = top + side;
위의 코드 는 원호 의 정사각형 좌 표를 포 지 셔 닝 하 는 것 입 니 다.여기 서 사 이 드 를 계산 할 때 view 의 padding 과 mStroken Width 를 제거 한 것 을 볼 수 있 습 니 다.그 중에서 mStroken Width 는 원호 의 포물선 의 너비 로 원호 의 선 이 비교적 넓 을 때(이때 링 에 해당)내외 로 고 르 게 연장 된다.즉,내 변 거리 와 바깥쪽 거리의 중간 에서 원심 까지 의 거리 가 반지름 이다.따라서 원호 의 위 치 를 정할 때 는 선 폭 을 제거 하여 접경 에서 원호 가 완전히 그 려 지지 않도록 해 야 한다.
또한,우리 가 View 를 사용자 정의 할 때 기본 wrapcontent 모드 에서 matchparent 의 효과 가 같 기 때문에 onMeasure 에서 처리 해 야 합 니 다.여 기 는 간단하게 wrap 을 설정 합 니 다.content 모드 에서 20dp 입 니 다.

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  int heightMode = MeasureSpec.getMode(heightMeasureSpec);

  int width = MeasureSpec.getSize(widthMeasureSpec);
  int height = MeasureSpec.getSize(heightMeasureSpec);

  //   wrap_content ,    20dp。     wrap_content match_parent      
  if (widthMode == MeasureSpec.AT_MOST) {
   width = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getContext().getResources().getDisplayMetrics()) + 0.5F);
  }
  if (heightMode == MeasureSpec.AT_MOST) {
   height = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getContext().getResources().getDisplayMetrics()) + 0.5F);
  }
  setMeasuredDimension(width, height);
 }
이상 의 조작 은 애니메이션 의 전체 기초 이 고 View 를 움 직 이게 하 는 조작 은 원호 의 startAngle 과 sweepAngle 을 계속 수정 한 다음 에 View 의 재 그림 을 터치 하 는 것 입 니 다.이 과정 은 ValueAnimator 를 사용 하여 일련의 숫자 를 만 든 다음 에 이것 에 따라 원호 의 시작 각도 와 스캐닝 각 도 를 계산한다.

//      1 ,        
sweepAngle = -1;
startAngle = -90;
curStartAngle = startAngle;

//     
mValueAnimator = ValueAnimator.ofFloat(0, 1).setDuration(mDuration);
mValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
float value = (float) animation.getAnimatedValue();
   if (mReverse)
    fraction = 1 - fraction;
   startAngle = curStartAngle + fraction * 120;
   sweepAngle = -1 - mMaxSweepAngle * value;
   postInvalidate();
  });
  mValueAnimator.addListener(new AnimatorListenerAdapter() {
   @Override
   public void onAnimationRepeat(Animator animation) {
    curStartAngle = startAngle;
    mReverse = !mReverse;
   }
  });
위 는 계산 하 는 과정 입 니 다.이 애니메이션 은 value 값 을 0 에서 1 에서 0 으로 사용 하고 그 중의 원호 가 원점 에서 최대 로 스 트 레 칭 하고 원점 으로 축소 하 는 것 에 대응 합 니 다.그 중에서 sweepAngle 의 계산 은 sweepAngle=-1-mMaxSweetAngle*value 이다.즉,전체 과정 에서 원호 의 각 도 는 maxSweetAngle 로 점점 커진다.여기 서 마이너스,즉 startAngle 에서 시계 반대 방향 으로 그립 니 다.-1 은 기본 값 으로 최 시간 으로 축소 되 는 것 을 방지 하고 원점 을 나 타 낼 수 있다.
startAngle 의 계산 은 애니메이션 과정의 fraction 에 따라 애니메이션 값 이 아니 라 0 에서 1 까지 전체 애니메이션 과정 에서 120 도 증가 합 니 다.전체 View 는 3 단 이 같은 원호 로 이 루어 져 있 기 때문에 각 단락 의 원호 가 최대 120 도 만 차지 할 수 있 고 그렇지 않 으 면 중첩 된다 는 것 이다.그러면 0 에서 1 까지 이 과정 에서 라디안 이 120 도로 커지 고 startAngle 은 120 도 를 이동 하여 원호 에 위 치 를 비 워 야 한다.이것 이 바로 120 도의 유래 이다.또한 Reverse 상 태 를 감청 합 니 다.Reverse 상태 에서 fraction 은 1 에서 0 까지 이 고 우리 에 게 필요 한 것 은 startAngle 이 점점 커지 기 때문에 Reverse 에서 1-fraction 을 통 해 원래 애니메이션 과 일치 하도록 합 니 다.또한 매번 reverse 감청 하에 startAngle 을 새로운 현재 위치 로 기록 하고 reverse 상 태 를 기록 합 니 다.
이상 은 전체 원호 애니메이션 의 실현 디 테 일 입 니 다.전체적으로 간단 합 니 다.바로 라디안 의 startAngle 과 sweepAngle 을 바 꾼 다음 에 View 에 다시 그 리 는 것 을 알 리 는 것 입 니 다.다음은 실 현 된 전체 코드 입 니 다.여기 서 기본 변 수 를 추출 하여 속성 에 넣 고 애니메이션 의 디 스 플레이 를 간편 하 게 제어 할 수 있 습 니 다.
values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="RefreshView">
  <attr name="top_color" format="color"/>
  <attr name="left_color" format="color"/>
  <attr name="right_color" format="color"/>
  <!--       -->
  <attr name="border_width" format="dimension"/>
  <!--        ,           ,ms -->
  <attr name="duration" format="integer"/>
  <!--           -->
  <attr name="max_sweep_angle" format="integer"/>
  <!--          -->
  <attr name="auto_start" format="boolean"/>
 </declare-styleable>

</resources>
RefreshView.java

package com.pgaofeng.mytest.other;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import com.pgaofeng.mytest.R;

/**
 * @author gaofengpeng
 * @date 2019/9/16
 * @description :
 */
public class RefreshView extends View {

 /**
  *        
  */
 private int mTopColor;
 private int mLeftColor;
 private int mRightColor;

 private Paint mPaint;

 /**
  *     ,         
  */
 private float sweepAngle;
 /**
  *     ,           
  */
 private float startAngle;
 /**
  *     ,         
  */
 private float curStartAngle;

 /**
  *          
  */
 private ValueAnimator mValueAnimator;

 /**
  *        
  */
 private int mDuration;
 /**
  *     
  */
 private float mStrokeWidth;

 /**
  *             
  */
 private int mMaxSweepAngle;

 /**
  *         
  */
 private boolean mAutoStart;

 /**
  *             Reverse  
  */
 private boolean mReverse = false;


 public RefreshView(Context context) {
  this(context, null);
 }

 public RefreshView(Context context, @Nullable AttributeSet attrs) {
  this(context, attrs, 0);
 }

 public RefreshView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  initAttr(context, attrs);
  init();
 }

 private void initAttr(Context context, AttributeSet attrs) {
  TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RefreshView);
  mTopColor = array.getColor(R.styleable.RefreshView_top_color, Color.BLUE);
  mLeftColor = array.getColor(R.styleable.RefreshView_left_color, Color.YELLOW);
  mRightColor = array.getColor(R.styleable.RefreshView_right_color, Color.RED);
  mDuration = array.getInt(R.styleable.RefreshView_duration, 600);
  if (mDuration <= 0) {
   mDuration = 600;
  }
  mStrokeWidth = array.getDimension(R.styleable.RefreshView_border_width, 8F);
  mMaxSweepAngle = array.getInt(R.styleable.RefreshView_max_sweep_angle, 90);
  if (mMaxSweepAngle <= 0 || mMaxSweepAngle > 120) {
   //              
   mMaxSweepAngle = 90;
  }
  mAutoStart = array.getBoolean(R.styleable.RefreshView_auto_start, true);

  array.recycle();
 }

 private void init() {

  mPaint = new Paint();
  mPaint.setAntiAlias(true);
  mPaint.setStyle(Paint.Style.STROKE);
  mPaint.setStrokeWidth(mStrokeWidth);
  mPaint.setStrokeCap(Paint.Cap.ROUND);

  //      1 ,        
  sweepAngle = -1;
  startAngle = -90;
  curStartAngle = startAngle;

  //     
  mValueAnimator = ValueAnimator.ofFloat(0, 1).setDuration(mDuration);
  mValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
  mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
  mValueAnimator.addUpdateListener(animation -> {
   float fraction = animation.getAnimatedFraction();
   float value = (float) animation.getAnimatedValue();

   if (mReverse)
    fraction = 1 - fraction;

   startAngle = curStartAngle + fraction * 120;
   sweepAngle = -1 - mMaxSweepAngle * value;
   postInvalidate();
  });
  mValueAnimator.addListener(new AnimatorListenerAdapter() {
   @Override
   public void onAnimationRepeat(Animator animation) {
    curStartAngle = startAngle;
    mReverse = !mReverse;
   }
  });

 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  int heightMode = MeasureSpec.getMode(heightMeasureSpec);

  int width = MeasureSpec.getSize(widthMeasureSpec);
  int height = MeasureSpec.getSize(heightMeasureSpec);

  //   wrap_content ,    20dp。     wrap_content match_parent      
  if (widthMode == MeasureSpec.AT_MOST) {
   width = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getContext().getResources().getDisplayMetrics()) + 0.5F);
  }
  if (heightMode == MeasureSpec.AT_MOST) {
   height = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getContext().getResources().getDisplayMetrics()) + 0.5F);
  }
  setMeasuredDimension(width, height);
 }

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

  int width = getMeasuredWidth();
  int height = getMeasuredHeight();

  int side = Math.min(width - getPaddingStart() - getPaddingEnd(), height - getPaddingTop() - getPaddingBottom()) - (int) (mStrokeWidth + 0.5F);

  //       
  float left = (width - side) / 2F;
  float top = (height - side) / 2F;
  float right = left + side;
  float bottom = top + side;

  //     
  mPaint.setColor(mTopColor);
  canvas.drawArc(left, top, right, bottom, startAngle, sweepAngle, false, mPaint);
  mPaint.setColor(mLeftColor);
  canvas.drawArc(left, top, right, bottom, startAngle - 120, sweepAngle, false, mPaint);
  mPaint.setColor(mRightColor);
  canvas.drawArc(left, top, right, bottom, startAngle + 120, sweepAngle, false, mPaint);
 }


 @Override
 protected void onDetachedFromWindow() {
  if (mAutoStart && mValueAnimator.isRunning()) {
   mValueAnimator.cancel();
  }
  super.onDetachedFromWindow();
 }

 @Override
 protected void onAttachedToWindow() {
  if (mAutoStart && !mValueAnimator.isRunning()) {
   mValueAnimator.start();
  }
  super.onAttachedToWindow();
 }

 /**
  *     
  */
 public void start() {
  if (!mValueAnimator.isStarted()) {
   mValueAnimator.start();
  }
 }

 /**
  *     
  */
 public void pause() {
  if (mValueAnimator.isRunning()) {
   mValueAnimator.pause();
  }
 }

 /**
  *     
  */
 public void resume() {
  if (mValueAnimator.isPaused()) {
   mValueAnimator.resume();
  }
 }

 /**
  *     
  */
 public void stop() {
  if (mValueAnimator.isStarted()) {
   mReverse = false;
   mValueAnimator.end();
  }
 }

}
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기