Android,시 크 한 CheckBox 효과 구현
gif 의 효 과 는 좀 빠 를 수 있 으 며,실제 기기 에서 운행 하 는 효과 가 더욱 좋 을 것 입 니 다.우리 의 주요 사고방식 은 속성 애니메이션 을 이용 하여 선택 상태 와 체크 하 는 그리 기 과정 을 동적 으로 그 리 는 것 이다.위의 효과 도 를 보면 모두 가 잠시 도 지체 하지 않 고 해 보고 싶 어 할 것 이 라 고 믿 습 니 다.그럼 시작 합 시다.
사용자 정의 View 의 첫 번 째 단계:사용자 정의 속성.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SmoothCheckBox">
<!-- -->
<attr name="duration" format="integer"></attr>
<!-- -->
<attr name="strikeWidth" format="dimension|reference"></attr>
<!-- -->
<attr name="borderColor" format="color|reference"></attr>
<!-- -->
<attr name="trimColor" format="color|reference"></attr>
<!-- -->
<attr name="tickColor" format="color|reference"></attr>
<!-- -->
<attr name="tickWidth" format="dimension|reference"></attr>
</declare-styleable>
</resources>
우 리 는 CheckBox 를 SmoothCheckBox 라 고 이름 을 지어 서 몇 가지 등 필요 한 속성 을 정의 했다.이 단 계 는 매우 간단 해서 모두 가 숙련 되 었 다 고 믿는다.다음은 onMeasure(int width MeasureSpec,int height MeasureSpec)를 살 펴 보 겠 습 니 다.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;
} else {
mWidth = 40;
}
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
} else {
mHeight = 40;
}
setMeasuredDimension(mWidth, mHeight);
int size = Math.min(mWidth, mHeight);
center = size / 2;
mRadius = (int) ((size - mStrokeWidth) / 2 / 1.2f);
startPoint.set(center * 14 / 30, center * 28 / 30);
breakPoint.set(center * 26 / 30, center * 40 / 30);
endPoint.set(center * 44 / 30, center * 20 / 30);
downLength = (float) Math.sqrt(Math.pow(startPoint.x - breakPoint.x, 2f) + Math.pow(startPoint.y - breakPoint.y, 2f));
upLength = (float) Math.sqrt(Math.pow(endPoint.x - breakPoint.x, 2f) + Math.pow(endPoint.y - breakPoint.y, 2f));
totalLength = downLength + upLength;
}
처음에는 SmoothCheckBox 의 너비,높이 를 측정 하 였 으 며,기본 적 인 너비 높이 는 마음대로 하 나 를 정 의 했 습 니 다.물론 스스로 수정 하고 보완 할 수 있 습 니 다.그 다음 에 반경 을 설정 하 는 것 과 같은 것 입 니 다.마지막 startPoint,breakpoint,endPoint 는 선택 할 때 체크 하 는 세 가지 점 에 대응 합 니 다(왜 이 몇 개의 숫자 인지 에 대해 서 는 완전히 경험 치 입 니 다).downLength 는 startPoint 와 breakpoint 의 거리 이 고 이에 대응 하 는 upLength 는 breakpoint 와 endPoint 의 거리 입 니 다.즉 다음 그림 이다.onDraw(Canvas canvas)를 보기 전에 우 리 는 먼저 두 개의 애니메이션 을 보 았 습 니 다.그것 은 선택 한 상태의 애니메이션 과 선택 하지 않 은 상태의 애니메이션 입 니 다.
//
private void checkedAnimation() {
animatedValue = 0f;
tickValue = 0f;
//
mValueAnimator = ValueAnimator.ofFloat(0f, 1.2f, 1f).setDuration(2 * duration / 5);
mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
//
mTickValueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(3 * duration / 5);
mTickValueAnimator.setInterpolator(new LinearInterpolator());
mTickValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//
tickValue = (float) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//
animatedValue = (float) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//
mTickValueAnimator.start();
Log.i(TAG," mTickValueAnimator.start();");
}
});
mValueAnimator.start();
}
//
private void uncheckedAnimation() {
animatedValue = 0f;
mValueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(2 * duration / 5);
mValueAnimator.setInterpolator(new AccelerateInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
animatedValue = (float) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mValueAnimator.start();
}
이 두 애니메이션 은 SmoothCheckBox 를 클릭 할 때 호출 됩 니 다.비슷 한 것 은 애니메이션 실행 에서 애니메이션 이 실 행 된 진 도 를 얻 고 post Invaidate()를 호출 하 는 것 입 니 다.SmoothCheck Box 를 다시 그립 니 다.이것 을 보고 나 면 궁극 의 큰 방법 인 onDraw(Canvas canvas)입 니 다.
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
drawBorder(canvas);
drawTrim(canvas);
if (isChecked) {
drawTick(canvas);
}
canvas.restore();
}
//
private void drawTick(Canvas canvas) {
//
float temp = tickValue * totalLength;
Log.i(TAG, "temp:" + temp + "downlength :" + downLength);
// , startPoint
if (Float.compare(tickValue, 0f) == 0) {
Log.i(TAG, "startPoint : " + startPoint.x + ", " + startPoint.y);
path.reset();
path.moveTo(startPoint.x, startPoint.y);
}
// breakPoint , (breakPoint,endPoint]
if (temp > downLength) {
path.moveTo(startPoint.x, startPoint.y);
path.lineTo(breakPoint.x, breakPoint.y);
Log.i(TAG, "endPoint : " + endPoint.x + ", " + endPoint.y);
path.lineTo((endPoint.x - breakPoint.x) * (temp - downLength) / upLength + breakPoint.x, (endPoint.y - breakPoint.y) * (temp - downLength) / upLength + breakPoint.y);
} else {
// startPoinit breakPoint , (startPoint,breakPoint]
Log.i(TAG, "down x : " + (breakPoint.x - startPoint.x) * temp / downLength + ",down y: " + (breakPoint.y - startPoint.y) * temp / downLength);
path.lineTo((breakPoint.x - startPoint.x) * temp / downLength + startPoint.x, (breakPoint.y - startPoint.y) * temp / downLength + startPoint.y);
}
canvas.drawPath(path, tickPaint);
}
//
private void drawBorder(Canvas canvas) {
float temp;
// animatedValue “OverShooting”
if (animatedValue > 1f) {
temp = animatedValue * mRadius;
} else {
temp = mRadius;
}
canvas.drawCircle(center, center, temp, borderPaint);
}
// checkbox
private void drawTrim(Canvas canvas) {
canvas.drawCircle(center, center, (mRadius - mStrokeWidth) * animatedValue, trimPaint);
}
onDraw(Canvas canvas)코드 의 논 리 는 기본적으로 주석 을 달 았 는데 주로 원 리 를 이해 하면 비교적 간단 하 다.체크 를 그 릴 때 현재 체크 를 그 리 는 상태 에 있 는 상 태 를 구분 한 다음 에 처리 하고 선 을 그 려 야 합 니 다.나머지 는 간단 합 니 다.스 무 디 체크 박스 에 대한 설명 은 여기까지 입 니 다.다음은 SmoothCheckBox 의 전체 코드 를 붙 입 니 다.
public class SmoothCheckBox extends View implements View.OnClickListener {
//
private long duration;
//
private float mStrokeWidth;
//
private float mTickWidth;
//
private Paint trimPaint;
//
private Paint borderPaint;
//
private Paint tickPaint;
//
private float defaultStrikeWidth;
//
private float defaultTickWidth;
//
private int mWidth;
//
private int mHeight;
//
private int borderColor;
//
private int trimColor;
//
private int tickColor;
//
private int mRadius;
//
private int center;
//
private boolean isChecked;
//
private float downLength;
//
private float upLength;
//
private float totalLength;
//
private OnCheckedChangeListener listener;
private ValueAnimator mValueAnimator;
private ValueAnimator mTickValueAnimator;
private float animatedValue;
private float tickValue;
//
private Point startPoint = new Point();
//
private Point breakPoint = new Point();
//
private Point endPoint = new Point();
private static final String TAG = "SmoothCheckBox";
private static final String KEY_INSTANCE_STATE = "InstanceState";
private Path path = new Path();
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
this.listener = listener;
}
public SmoothCheckBox(Context context) {
this(context, null);
}
public SmoothCheckBox(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SmoothCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox);
duration = a.getInt(R.styleable.SmoothCheckBox_duration, 600);
defaultStrikeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
mStrokeWidth = a.getDimension(R.styleable.SmoothCheckBox_strikeWidth, defaultStrikeWidth);
defaultTickWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics());
mTickWidth = a.getDimension(R.styleable.SmoothCheckBox_tickWidth, defaultTickWidth);
borderColor = a.getColor(R.styleable.SmoothCheckBox_borderColor, getResources().getColor(android.R.color.darker_gray));
trimColor = a.getColor(R.styleable.SmoothCheckBox_trimColor, getResources().getColor(android.R.color.holo_green_light));
tickColor = a.getColor(R.styleable.SmoothCheckBox_tickColor, getResources().getColor(android.R.color.white));
a.recycle();
trimPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
trimPaint.setStyle(Paint.Style.FILL);
trimPaint.setColor(trimColor);
borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setStrokeWidth(mStrokeWidth);
borderPaint.setColor(borderColor);
borderPaint.setStyle(Paint.Style.STROKE);
tickPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
tickPaint.setColor(tickColor);
tickPaint.setStyle(Paint.Style.STROKE);
tickPaint.setStrokeCap(Paint.Cap.ROUND);
tickPaint.setStrokeWidth(mTickWidth);
setOnClickListener(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;
} else {
mWidth = 40;
}
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
} else {
mHeight = 40;
}
setMeasuredDimension(mWidth, mHeight);
int size = Math.min(mWidth, mHeight);
center = size / 2;
mRadius = (int) ((size - mStrokeWidth) / 2 / 1.2f);
startPoint.set(center * 14 / 30, center * 28 / 30);
breakPoint.set(center * 26 / 30, center * 40 / 30);
endPoint.set(center * 44 / 30, center * 20 / 30);
downLength = (float) Math.sqrt(Math.pow(startPoint.x - breakPoint.x, 2f) + Math.pow(startPoint.y - breakPoint.y, 2f));
upLength = (float) Math.sqrt(Math.pow(endPoint.x - breakPoint.x, 2f) + Math.pow(endPoint.y - breakPoint.y, 2f));
totalLength = downLength + upLength;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
drawBorder(canvas);
drawTrim(canvas);
if (isChecked) {
drawTick(canvas);
}
canvas.restore();
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_INSTANCE_STATE, super.onSaveInstanceState());
bundle.putBoolean(KEY_INSTANCE_STATE, isChecked);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
boolean isChecked = bundle.getBoolean(KEY_INSTANCE_STATE);
setChecked(isChecked);
super.onRestoreInstanceState(bundle.getParcelable(KEY_INSTANCE_STATE));
return;
}
super.onRestoreInstanceState(state);
}
//
private void toggle() {
isChecked = !isChecked;
if (listener != null) {
listener.onCheckedChanged(this, isChecked);
}
if (isChecked) {
checkedAnimation();
} else {
uncheckedAnimation();
}
}
//
private void checkedAnimation() {
animatedValue = 0f;
tickValue = 0f;
mValueAnimator = ValueAnimator.ofFloat(0f, 1.2f, 1f).setDuration(2 * duration / 5);
mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mTickValueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(3 * duration / 5);
mTickValueAnimator.setInterpolator(new LinearInterpolator());
mTickValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
tickValue = (float) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
animatedValue = (float) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTickValueAnimator.start();
Log.i(TAG," mTickValueAnimator.start();");
}
});
mValueAnimator.start();
}
//
private void uncheckedAnimation() {
animatedValue = 0f;
mValueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(2 * duration / 5);
mValueAnimator.setInterpolator(new AccelerateInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
animatedValue = (float) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mValueAnimator.start();
}
//
private void drawTick(Canvas canvas) {
float temp = tickValue * totalLength;
Log.i(TAG, "temp:" + temp + "downlength :" + downLength);
if (Float.compare(tickValue, 0f) == 0) {
Log.i(TAG, "startPoint : " + startPoint.x + ", " + startPoint.y);
path.reset();
path.moveTo(startPoint.x, startPoint.y);
}
if (temp > downLength) {
path.moveTo(startPoint.x, startPoint.y);
path.lineTo(breakPoint.x, breakPoint.y);
Log.i(TAG, "endPoint : " + endPoint.x + ", " + endPoint.y);
path.lineTo((endPoint.x - breakPoint.x) * (temp - downLength) / upLength + breakPoint.x, (endPoint.y - breakPoint.y) * (temp - downLength) / upLength + breakPoint.y);
} else {
Log.i(TAG, "down x : " + (breakPoint.x - startPoint.x) * temp / downLength + ",down y: " + (breakPoint.y - startPoint.y) * temp / downLength);
path.lineTo((breakPoint.x - startPoint.x) * temp / downLength + startPoint.x, (breakPoint.y - startPoint.y) * temp / downLength + startPoint.y);
}
canvas.drawPath(path, tickPaint);
}
//
private void drawBorder(Canvas canvas) {
float temp;
if (animatedValue > 1f) {
temp = animatedValue * mRadius;
} else {
temp = mRadius;
}
canvas.drawCircle(center, center, temp, borderPaint);
}
// checkbox
private void drawTrim(Canvas canvas) {
canvas.drawCircle(center, center, (mRadius - mStrokeWidth) * animatedValue, trimPaint);
}
@Override
public void onClick(View view) {
toggle();
}
/**
* checkbox
*
* @return
*/
public boolean isChecked() {
return isChecked;
}
/**
* checkbox
*
* @param isChecked
*/
public void setChecked(boolean isChecked) {
this.setChecked(isChecked, false);
}
/**
* checkbox
*
* @param isChecked
* @param isAnimation
*/
public void setChecked(boolean isChecked, boolean isAnimation) {
this.isChecked = isChecked;
if (isAnimation) {
if (isChecked) {
checkedAnimation();
} else {
uncheckedAnimation();
}
} else {
animatedValue = isChecked ? 1f : 0f;
tickValue = 1f;
invalidate();
}
if (listener != null) {
listener.onCheckedChanged(this, isChecked);
}
}
public interface OnCheckedChangeListener {
void onCheckedChanged(SmoothCheckBox smoothCheckBox, boolean isChecked);
}
}
총결산이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 면 댓 글 을 남 겨 주 십시오.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Kotlin의 기초 - 2부지난 글에서는 Kotlin이 무엇인지, Kotlin의 특징, Kotlin에서 변수 및 데이터 유형을 선언하는 방법과 같은 Kotlin의 기본 개념에 대해 배웠습니다. 유형 변환은 데이터 변수의 한 유형을 다른 데이터...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.