간단 한 Android 원호 새로 고침 애니메이션
14116 단어 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();
}
}
}
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.