Android 를 이용 하여 멋 진 사용자 정의 View 구현

배경
1.1 컨트롤 효과
구현 할 사용자 정의 컨트롤 효 과 는 크게 다음 과 같 습 니 다.구현 과정 에서 사용자 정의 View API 를 많이 사 용 했 습 니 다.대표 적 이 라 고 생각 되면 공유 하 는 것 도 학습 총화 로 삼 습 니 다.
프로젝트 코드 가 github 에 업로드 되 었 습 니 다.
github.com/DaLeiGe/And


1.2 기능 적 으로 이 컨트롤 을 분석 하면 대체적으로 다음 과 같은 특징 이 있다.
4.567917.랜 덤 운동 입 자 는 원주 에서 원심 으로 운동 하고 접선 방향 과 플러스 마이너스 30°의 각도 차이 가 있 으 며 입자 투명도,반지름,운동 속 도 는 랜 덤 으로 운동 이 일정한 거 리 를 초과 하거나 시간 이 사라 집 니 다4.567917.배경 원 은 안에서 밖으로 그 라 데 이 션 색 이 있다4
  • 시간 계산 모드 에서 원 링 은 색 이 점점 변 하 는 시계 방향 rotate 애니메이션 이 있 습 니 다
  • 전체 배경 원 색 은 부채 형 각도 에 따라 변화 한다포인터 색상 변화
    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
    총결산
    안 드 로 이 드 를 이용 한 멋 진 사용자 정의 뷰 구현 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 안 드 로 이 드 사용자 정의 뷰 내용 은 이전 글 을 검색 하거나 아래 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!

    좋은 웹페이지 즐겨찾기