Android 를 이용 하여 멋 진 사용자 정의 View 구현
1.1 컨트롤 효과
구현 할 사용자 정의 컨트롤 효 과 는 크게 다음 과 같 습 니 다.구현 과정 에서 사용자 정의 View API 를 많이 사 용 했 습 니 다.대표 적 이 라 고 생각 되면 공유 하 는 것 도 학습 총화 로 삼 습 니 다.
프로젝트 코드 가 github 에 업로드 되 었 습 니 다.
github.com/DaLeiGe/And …
1.2 기능 적 으로 이 컨트롤 을 분석 하면 대체적으로 다음 과 같은 특징 이 있다.
4.567917.랜 덤 운동 입 자 는 원주 에서 원심 으로 운동 하고 접선 방향 과 플러스 마이너스 30°의 각도 차이 가 있 으 며 입자 투명도,반지름,운동 속 도 는 랜 덤 으로 운동 이 일정한 거 리 를 초과 하거나 시간 이 사라 집 니 다4.567917.배경 원 은 안에서 밖으로 그 라 데 이 션 색 이 있다4
4.567917.디지털 변 화 는 상하 전환 애니메이션 이다1.3 구조 적 분석
이 컨트롤 은 두 부분 으로 나 눌 수 있 습 니 다.배경 원+디지털 컨트롤 두 부분 으로 구 성 된 조합 컨트롤 입 니 다.디지털 컨트롤 을 따로 나 누 는 이 유 는 디지털 위아래 로 뛰 는 애니메이션 을 만 드 는 데 편리 하기 때 문 입 니 다.drawText 의 위 치 를 제어 하여 애니메이션 을 만 드 는 것 이 불편 하기 때문에 View 의 속성 애니메이션 을 통 해 이 루어 집 니 다.
2.배경 원 실현
2.1 입자 운동 실현
AnimPoint.java 를 사용 하여 운동 입 자 를 표시 합 니 다.x,y 좌표,반경,각도,운동 속도,투명도 등 속성 을 가지 고 있 습 니 다.이 속성 을 통 해 정적 입 자 를 그 릴 수 있 습 니 다.
public class AnimPoint implements Cloneable {
/**
* x
*/
private float mX;
/**
* y
*/
private float mY;
/**
*
*/
private float radius;
/**
*
*/
private double anger;
/**
*
*/
private float velocity;
/**
*
*/
private int num = 0;
/**
* 0~255
*/
private int alpha = 153;
/**
*
*/
private double randomAnger = 0;
}
입자 의 초기 위 치 는 무 작위 각도 의 원주 에 위치 하고 한 입자 가 무 작위 반경,투명도,속도 등 을 가지 고 있 으 며 init()방법 을 통 해 입 자 를 다음 과 같이 초기 화 합 니 다.
public void init(Random random, float viewRadius) {
anger = Math.toRadians(random.nextInt(360));
velocity = random.nextFloat() * 2F;
radius = random.nextInt(6) + 5;
mX = (float) (viewRadius * Math.cos(anger));
mY = (float) (viewRadius * Math.sin(anger));
// -30°~30°
randomAnger = Math.toRadians(30 - random.nextInt(60));
alpha = 153 + random.nextInt(102);
}
입자 가 움 직 이려 면 update 를 사용 하여 입 자 를 업데이트 하 는 이러한 좌표 속성 을 실현 할 수 있 습 니 다.예 를 들 어 입자 가 현재 좌표(5,5)에 있 습 니 다.update()를 통 해 입자 의 좌 표를(6,6)로 바 꾸 고 속성 애니메이션 과 결합 하여 update()를 계속 호출 하면 x,y 의 좌 표를 끊임없이 바 꾸 고 입자 운동 을 실현 한 다음 에 입자 가 일정한 거 리 를 초과 하거나 update 를 일정 횟수 이상 호출 할 수 있 습 니 다.init()를 다시 호출 하여 입자 가 원주 에서 다음 생명주기 운동 을 시작 하도록 합 니 다.
public void updatePoint(Random random, float viewRadius) {
//
float distance = 1F;
double moveAnger = anger + randomAnger;
mX = (float) (mX - distance * Math.cos(moveAnger) * velocity);
mY = (float) (mY - distance * Math.sin(moveAnger) * velocity);
//
radius = radius - 0.02F * velocity;
num++;
//
int maxDistance = 180;
int maxNum = 400;
if (velocity * num > maxDistance || num > maxNum) {
num = 0;
init(random, viewRadius);
}
}
View 에서 대체적으로 다음 과 같다.
/**
*
*/
private void initAnim() {
//
AnimPoint mAnimPoint = new AnimPoint();
for (int i = 0; i < pointCount; i++) {
// clone ,
AnimPoint cloneAnimPoint = mAnimPoint.clone();
//
cloneAnimPoint.init(mRandom, mRadius - mOutCircleStrokeWidth / 2F);
mPointList.add(cloneAnimPoint);
}
//
mPointsAnimator = ValueAnimator.ofFloat(0F, 1F);
mPointsAnimator.setDuration(Integer.MAX_VALUE);
mPointsAnimator.setRepeatMode(ValueAnimator.RESTART);
mPointsAnimator.setRepeatCount(ValueAnimator.INFINITE);
mPointsAnimator.addUpdateListener(animation -> {
for (AnimPoint point : mPointList) {
//
point.updatePoint(mRandom, mRadius);
}
invalidate();
});
mPointsAnimator.start();
}
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mCenterX, mCenterY);
//
for (AnimPoint animPoint : mPointList) {
mPointPaint.setAlpha(animPoint.getAlpha());
canvas.drawCircle(animPoint.getmX(), animPoint.getmY(),
animPoint.getRadius(), mPointPaint);
}
}
2.2 그 라 데 이 션 원 실현원 의 내부 에서 외부 로 그 라 데 이 션 을 실현 하여 Radial Gradient 를 사용 합 니 다.
대체로 실현 방식 은 다음 과 같다.
float[] mRadialGradientStops = {0F, 0.69F, 0.86F, 0.94F, 0.98F, 1F};
mRadialGradientColors[0] = transparentColor;
mRadialGradientColors[1] = transparentColor;
mRadialGradientColors[2] = parameter.getInsideColor();
mRadialGradientColors[3] = parameter.getOutsizeColor();
mRadialGradientColors[4] = transparentColor;
mRadialGradientColors[5] = transparentColor;
mRadialGradient = new RadialGradient(
0,
0,
mCenterX,
mRadialGradientColors,
mRadialGradientStops,
Shader.TileMode.CLAMP);
mSweptPaint.setShader(mRadialGradient);
...
//onDraw()
canvas.drawCircle(0, 0, mCenterX, mSweptPaint);
2.3 배경 원 을 보 여 주 는 부채 형 영역DrawArc 을 통 해 이 효 과 를 구현 하려 고 했 으 나 DrawArc 은 원심 의 영역 에 도달 할 수 없 었 습 니 다.
그러면 어떻게 이런 불규칙 한 모양 을 실현 할 수 있 습 니까?canvas.clipPath()를 사용 하여 불규칙 한 모양 을 재단 할 수 있 기 때문에 부채 형의 Path 만 얻 으 면 실현 할 수 있 습 니 다.원점+원호 형 재 폐쇄 path 를 통 해 실현 할 수 있 습 니 다.
/**
* path
*
* @param r
* @param startAngle
* @param sweepAngle
*/
private void getSectorClip(float r, float startAngle, float sweepAngle) {
mArcPath.reset();
mArcPath.addArc(-r, -r, r, r, startAngle, sweepAngle);
mArcPath.lineTo(0, 0);
mArcPath.close();
}
// onDraw() ,
canvas.clipPath(mArcPath);
2.4 포인터 변색 실현포인터 가 불규칙 한 모양 으로 기하학 적 도형 을 그 려 서 구현 할 수 없 기 때문에 drawBitmap 를 사용 하여 구현 합 니 다.
bitmap 포인터 그림 의 색상 변 화 를 어떻게 실현 하 는 지 에 대해 서 는 AvoidXfermode 를 사용 하여 지정 한 픽 셀 채널 범위 내의 색상 을 바 꾸 는 것 이 었 으 나 AvoidXfermode 는 API 24 에서 제거 되 었 기 때문에 이 방안 은 유효 하지 않 습 니 다.
최종 적 으로 그래 픽 혼합 모드 를 이용 하여 포인터 그림 의 변색 을 실현 한다.
Porter Duff.Mode.MULTIPLY 모드 를 통 해 bitmap 색상 을 구현 할 수 있 습 니 다.원본 그림 은 수정 할 포인터 색상 이 고 대상 그림 은 흰색 포인터 이 며 두 이미지 의 중첩 부분 을 가 져 와 변색 을 실현 합 니 다.
대체로 다음 과 같다.
/**
* Bitmap
*/
private void initBitmap() {
float f = 130F / 656F;
mBitmapDST = BitmapFactory.decodeResource(getResources(), R.drawable.indicator);
float mBitmapDstHeight = width * f;
float mBitmapDstWidth = mBitmapDstHeight * mBitmapDST.getWidth() / mBitmapDST.getHeight();
//
mXfermode = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY);
mPointerRectF = new RectF(0, 0, mBitmapDstWidth, mBitmapDstHeight);
mBitmapSRT = Bitmap.createBitmap((int) mBitmapDstWidth, (int) mBitmapDstHeight, Bitmap.Config.ARGB_8888);
mBitmapSRT.eraseColor(mIndicatorColor);
}
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
//
canvas.translate(mCenterX, mCenterY);
canvas.rotate(mCurrentAngle / 10F);
canvas.translate(-mPointerRectF.width() / 2, -mCenterY);
mPointerLayoutId = canvas.saveLayer(mPointerRectF, mBmpPaint);
mBitmapSRT.eraseColor(mIndicatorColor);
canvas.drawBitmap(mBitmapDST, null, mPointerRectF, mBmpPaint);
mBmpPaint.setXfermode(mXfermode);
canvas.drawBitmap(mBitmapSRT, null, mPointerRectF, mBmpPaint);
mBmpPaint.setXfermode(null);
canvas.restoreToCount(mPointerLayoutId);
}
2.5.배경 원 색 이 부채 형 각도 에 따라 변화 하 는 것 을 실현 한다.원형 컨트롤 을 3600°로 뜯 고 각 각도 마다 컨트롤 의 구체 적 인 색상 값 에 대응 합 니 다.그러면 특정한 각도 에서 그의 구체 적 인 색상 값 을 어떻게 계산 합 니까?
속성 애니메이션 의 변색 애니메이션 android.animation.argbEvaluator 실현 방식 을 참고 하여 두 색상 중 구체 적 인 한 점 의 색상 값 을 계산 하 는 방식 은 다음 과 같 습 니 다.
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = ( startInt & 0xff) / 255.0f;
int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = ( endInt & 0xff) / 255.0f;
// convert from sRGB to linear
startR = (float) Math.pow(startR, 2.2);
startG = (float) Math.pow(startG, 2.2);
startB = (float) Math.pow(startB, 2.2);
endR = (float) Math.pow(endR, 2.2);
endG = (float) Math.pow(endG, 2.2);
endB = (float) Math.pow(endB, 2.2);
// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA);
float r = startR + fraction * (endR - startR);
float g = startG + fraction * (endG - startG);
float b = startB + fraction * (endB - startB);
// convert back to sRGB in the [0..255] range
a = a * 255.0f;
r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
}
컨트롤 에는 모두 네 개의 색상 세그먼트 가 있 습 니 다.3600/4=900 이 므 로 fraction=progressValue%900/900;그리고 현재 각도 가 몇 번 째 색상 값 에 있 는 지 판단 합 니 다.android.animation.ArgbEvaluator.evaluator.evaluate(float fraction,Object startValue,Object endValue)를 통 해 구체 적 인 색상 값 으로 돌아 갈 수 있 습 니 다.
대체로 실현 과정 은 다음 과 같다.
private ProgressParameter getProgressParameter(float progressValue) {
float fraction = progressValue % 900 / 900;
if (progressValue < 900) {
//
mParameter.setInsideColor(evaluate(fraction, insideColor1, insideColor2));
mParameter.setOutsizeColor(evaluate(fraction, outsizeColor1, outsizeColor2));
mParameter.setProgressColor(evaluate(fraction, progressColor1, progressColor2));
mParameter.setPointColor(evaluate(fraction, pointColor1, pointColor2));
mParameter.setBgCircleColor(evaluate(fraction, bgCircleColor1, bgCircleColor2));
mParameter.setIndicatorColor(evaluate(fraction, indicatorColor1, indicatorColor2));
} else if (progressValue < 1800) {
//
mParameter.setInsideColor(evaluate(fraction, insideColor2, insideColor3));
mParameter.setOutsizeColor(evaluate(fraction, outsizeColor2, outsizeColor3));
mParameter.setProgressColor(evaluate(fraction, progressColor2, progressColor3));
mParameter.setPointColor(evaluate(fraction, pointColor2, pointColor3));
mParameter.setBgCircleColor(evaluate(fraction, bgCircleColor2, bgCircleColor3));
mParameter.setIndicatorColor(evaluate(fraction, indicatorColor2, indicatorColor3));
} else if (progressValue < 2700) {
//
mParameter.setInsideColor(evaluate(fraction, insideColor3, insideColor4));
mParameter.setOutsizeColor(evaluate(fraction, outsizeColor3, outsizeColor4));
mParameter.setProgressColor(evaluate(fraction, progressColor3, progressColor4));
mParameter.setPointColor(evaluate(fraction, pointColor3, pointColor4));
mParameter.setBgCircleColor(evaluate(fraction, bgCircleColor3, bgCircleColor4));
mParameter.setIndicatorColor(evaluate(fraction, indicatorColor3, indicatorColor4));
} else {
//
mParameter.setInsideColor(evaluate(fraction, insideColor4, insideColor5));
mParameter.setOutsizeColor(evaluate(fraction, outsizeColor4, outsizeColor5));
mParameter.setProgressColor(evaluate(fraction, progressColor4, progressColor5));
mParameter.setPointColor(evaluate(fraction, pointColor4, pointColor5));
mParameter.setBgCircleColor(evaluate(fraction, bgCircleColor4, bgCircleColor5));
mParameter.setIndicatorColor(evaluate(fraction, indicatorColor4, indicatorColor5));
}
return mParameter;
}
3.디지털 애니메이션 의 실현3.1 속성 애니메이션+2 개의 TextView 로 디지털 상하 전환 애니메이션 실현
디지털 전환 애니메이션 을 실현 하기 위해 Recycle View 로 이 루어 지 려 고 했 으 나 효과 적 인 측면 에서 앞으로 UI 언니 의 다양한 동작 에 직면 할 수 있 음 을 고려 하여 최종 적 으로 두 개의 TextView 로 상하 번역 애니메이션 을 만 들 기로 결정 했다.이렇게 하면 통제 가능성 이 높 고 View 에 대한 속성 애니메이션 도 간단 하 다.
NumberView 는 FrameLayout 로 TextView 두 개 를 감 싸 고 widgetprogress_number_item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_number_one"
style="@style/progress_text_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="0dp"
android:text="0"
android:textColor="@android:color/white" />
<TextView
style="@style/progress_text_font"
android:id="@+id/tv_number_tow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="1"
android:textColor="@android:color/white" />
</FrameLayout>
그리고 속성 애니메이션 을 통 해 두 개의 TextView 상하 전환 을 제어 합 니 다.
mNumberAnim = ValueAnimator.ofFloat(0F, 1F);
mNumberAnim.setDuration(400);
mNumberAnim.setInterpolator(new OvershootInterpolator());
mNumberAnim.setRepeatCount(0);
mNumberAnim.setRepeatMode(ValueAnimator.RESTART);
mNumberAnim.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
if (UP_OR_DOWN_MODE == UP_ANIMATOR_MODE) {
// ,
mTvFirst.setTranslationY(-mHeight * value);
mTvSecond.setTranslationY(-mHeight * value);
} else {
// ,
mTvFirst.setTranslationY(mHeight * value);
mTvSecond.setTranslationY(-2 * mHeight + mHeight * value);
}
});
이렇게 하면 NumberView 는 한 숫자의 변 화 를 실현 할 수 있 습 니 다.상하 로 애니메이션 을 전환 하 는 것 입 니 다.100 비트 와 시계 콜론 이 있 는 용기 레이아웃 을 통 해AnimNumberView 조합 레이아웃 방식 은 시간 과 100 자리 수 를 나타 낸다.
4.프로젝트 소스 코드
블 로 그 는 단지 실현 방향 을 대충 말 했 을 뿐 이 니,구체 적 으로 실현 하려 면 원본 코드 를 읽 으 세 요.
github.com/DaLeiGe/And …
총결산
안 드 로 이 드 를 이용 한 멋 진 사용자 정의 뷰 구현 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 안 드 로 이 드 사용자 정의 뷰 내용 은 이전 글 을 검색 하거나 아래 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Kotlin의 기초 - 2부지난 글에서는 Kotlin이 무엇인지, Kotlin의 특징, Kotlin에서 변수 및 데이터 유형을 선언하는 방법과 같은 Kotlin의 기본 개념에 대해 배웠습니다. 유형 변환은 데이터 변수의 한 유형을 다른 데이터...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.