안 드 로 이 드 는 샤 오미 시 계 를 모방 합 니 다(카메라 와 Matrix 를 사용 하여 3D 효 과 를 실현 합 니 다)

14600 단어 android좁쌀 시계
사용자 정의 View 를 계속 연습 합 니 다.익 어야 익 는 법 이 니까.샤 오미 의 시계 가 아름 답 다 는 생각 이 들 었 습 니 다.이번 에는 사용자 정의 뷰 를 연습 하 는 것 외 에 카메라 와 Matrix 를 사용 하여 3D 효 과 를 실현 하 는 것 도 포함 되 어 있 습 니 다.
这里写图片描述
이러한 효 과 는 그 릴 때 한 방향 으로 한 걸음 한 걸음 그 리 는 것 이 좋 습 니 다.여기 서 저 는 밖에서 안 으로,깊이 에서 얕 은 방향 으로 그 리 는 것 을 선택 하 겠 습 니 다.코드 절 차 는 다음 과 같 습 니 다.
1.먼저 구식~새 attrs.xml 파일 을 만 들 고 시계 배경 색,밝 은 색(분침,초침,그 라 데 이 션 종료 색 에 사용),어두 운 색(원호,눈금 선,시침,그 라 데 이 션 시작 색),새 MiClockView 계승 View,구조 재 작성 방법,사용자 정의 속성 값 을 얻 고 Paint,Path 와 원,호 를 그 리 는 데 필요 한 RectF 등 을 초기 화 합 니 다.다시 쓰기 onMeasure 컴 퓨 팅 폭 이 높 습 니 다.여 기 는 더 이상 잔소리 하지 않 습 니 다.처음에 사용자 정의 View 를 배 웠 던 학생 들 은 제 앞의 블 로 그 를 보 는 것 을 권장 합 니 다.
2.onSize Changed 방법 은 구조 방법,onMeasure 에 이 어 onDraw 에 이 르 기 전에 전역 변수 초기 화 를 완 료 했 고 컨트롤 의 너비 도 높 았 기 때문에 이 방법 에서 너비 와 관련 된 수 치 를 확인 할 수 있 습 니 다.예 를 들 어 이 View 의 반지름,padding 값 등 은 그리 기 편 할 때 크기 와 위 치 를 계산 할 수 있 습 니 다.

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh);
  //       padding , min         
  mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(),
      h - getPaddingTop() - getPaddingBottom()) / 2;
  //      padding ,     camera           view  
  mDefaultPadding = 0.12f * mRadius;//        padding  
  //        match_parent、wrap_content、      padding  
  mPaddingLeft = mDefaultPadding + w / 2 - mRadius + getPaddingLeft();
  mPaddingTop = mDefaultPadding + h / 2 - mRadius + getPaddingTop();
  mPaddingRight = mPaddingLeft;
  mPaddingBottom = mPaddingTop;
  mScaleLength = 0.12f * mRadius;//           
  mScaleArcPaint.setStrokeWidth(mScaleLength);//      
  mScaleLinePaint.setStrokeWidth(0.012f * mRadius);//      
  //      , (w/2,h/2)    ,          
  //float    ,[0,0.75)         ,[0.75,1}           
  mSweepGradient = new SweepGradient(w / 2, h / 2,
      new int[]{mDarkColor, mLightColor}, new float[]{0.75f, 1});
}

3.준비 작업 의 차이 가 많 지 않 으 면 그리 기 시작 합 니 다.방향 에 따라 저 는 먼저 가장 바깥쪽 시간 텍스트 의 위치 와 옆 에 있 는 네 개의 호 를 확인 합 니 다.
这里写图片描述
두 자리 숫자의 너비 와 한 자리 수의 너비 가 다 르 므 로 계산 할 때 반드시 주의해 야 한다.
 

  String timeText = "12";
  mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);
  int textLargeWidth = mTextRect.width();//      
  mCanvas.drawText("12", getWidth() / 2 - textLargeWidth / 2, mPaddingTop + mTextRect.height(), mTextPaint);
  timeText = "3";
  mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);
  int textSmallWidth = mTextRect.width();//      
  mCanvas.drawText("3", getWidth() - mPaddingRight - mTextRect.height() / 2 - textSmallWidth / 2,
      getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
  mCanvas.drawText("6", getWidth() / 2 - textSmallWidth / 2, getHeight() - mPaddingBottom, mTextPaint);
  mCanvas.drawText("9", mPaddingLeft + mTextRect.height() / 2 - textSmallWidth / 2,
      getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
내 가 텍스트 의 너비 와 높이 를 계산 할 때 일반적으로 사용 하 는 방법 은 new Rect 를 사용 한 다음 에 그 릴 때 호출 하 는 것 이다.

mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);
이 텍스트 의 범 위 를 이 mTextRect 에 할당 합 니 다.이때 mTextRect.width()는 이 텍스트 의 너비 이 고 mTextRect.height()는 이 텍스트 의 높이 입 니 다.
这里写图片描述
텍스트 옆 에 있 는 네 개의 호 를 그립 니 다:

mCircleRectF.set(mPaddingLeft + mTextRect.height() / 2 + mCircleStrokeWidth / 2,
    mPaddingTop + mTextRect.height() / 2 + mCircleStrokeWidth / 2,
    getWidth() - mPaddingRight - mTextRect.height() / 2 + mCircleStrokeWidth / 2,
    getHeight() - mPaddingBottom - mTextRect.height() / 2 + mCircleStrokeWidth / 2);
for (int i = 0; i < 4; i++) {
  mCanvas.drawArc(mCircleRectF, 5 + 90 * i, 80, false, mCirclePaint);
}
원호 외 접 사각형 의 범 위 를 계산 할 때 원호 선 너비 의 반 을 더 하 는 것 을 잊 지 마 세 요.
4.다시 안 으로 들 어가 면 눈금 판 입 니 다.이 눈금 판 을 그 리 는 방향 은 현재 밑 에 mScale Length 너비 의 원 을 그리고 SweetGradient 그 라 데 이 션 을 설정 하 는 것 입 니 다.그 위 에 배경 색 의 눈금 선 을 한 바퀴 더 그 리 는 것 입 니 다.SweetGradient 의 Matrix 대상 을 획득 하고 mGradientMatrix 의 각 도 를 계속 회전 시 켜 눈금 판 의 회전 효 과 를 실현 합 니 다.

/**
 *                ,       ,            
 */
private void drawScaleLine() {
  mScaleArcRectF.set(mPaddingLeft + 1.5f * mScaleLength + mTextRect.height() / 2,
      mPaddingTop + 1.5f * mScaleLength + mTextRect.height() / 2,
      getWidth() - mPaddingRight - mTextRect.height() / 2 - 1.5f * mScaleLength,
      getHeight() - mPaddingBottom - mTextRect.height() / 2 - 1.5f * mScaleLength);

  //matrix                ,    
  //              ,          90 
  mGradientMatrix.setRotate(mSecondDegree - 90, getWidth() / 2, getHeight() / 2);
  mSweepGradient.setLocalMatrix(mGradientMatrix);
  mScaleArcPaint.setShader(mSweepGradient);
  mCanvas.drawArc(mScaleArcRectF, 0, 360, false, mScaleArcPaint);
  //       
  mCanvas.save();
  for (int i = 0; i < 200; i++) {
    mCanvas.drawLine(getWidth() / 2, mPaddingTop + mScaleLength + mTextRect.height() / 2,
        getWidth() / 2, mPaddingTop + 2 * mScaleLength + mTextRect.height() / 2, mScaleLinePaint);
    mCanvas.rotate(1.8f, getWidth() / 2, getHeight() / 2);
  }
  mCanvas.restore();
}

여기에 전역 변수 mSecondDegree,즉 초침 이 회전 하 는 각도 가 있 습 니 다.현재 시간 에 따라 동적 으로 가 져 와 야 합 니 다.

/**
 *                
 *                  ,       
 */
private void getTimeDegree() {
  Calendar calendar = Calendar.getInstance();
  float milliSecond = calendar.get(Calendar.MILLISECOND);
  float second = calendar.get(Calendar.SECOND) + milliSecond / 1000;
  float minute = calendar.get(Calendar.MINUTE) + second / 60;
  float hour = calendar.get(Calendar.HOUR) + minute / 60;
  mSecondDegree = second / 60 * 360;
  mMinuteDegree = minute / 60 * 360;
  mHourDegree = hour / 12 * 360;
}

5.그 다음 에 초침 을 그리고 Path 로 12 시 를 가리 키 는 삼각형 을 그립 니 다.캔버스 를 계속 회전 시 켜 초침 의 회전 을 실현 합 니 다.

/**
 *    ,               
 */
private void drawSecondHand() {
  mCanvas.save();
  mCanvas.rotate(mSecondDegree, getWidth() / 2, getHeight() / 2);
  mSecondHandPath.reset();
  float offset = mPaddingTop + mTextRect.height() / 2;
  mSecondHandPath.moveTo(getWidth() / 2, offset + 0.27f * mRadius);
  mSecondHandPath.lineTo(getWidth() / 2 - 0.05f * mRadius, offset + 0.35f * mRadius);
  mSecondHandPath.lineTo(getWidth() / 2 + 0.05f * mRadius, offset + 0.35f * mRadius);
  mSecondHandPath.close();
  mSecondHandPaint.setColor(mLightColor);
  mCanvas.drawPath(mSecondHandPath, mSecondHandPaint);
  mCanvas.restore();
}

这里写图片描述
6.실현 도 를 보면 시침 이 분침 아래 에 있 고 분침 보다 색깔 이 옅 습 니 다.그러면 저 는 먼저 시침 을 그 려 서 Path 이 고 바늘 이 원호 모양 입 니 다.그러면 2 단계 베 셀 곡선 을 사용 하고 경 로 는 moveto(A),lineto(B),quadTo(C,D),lineto(E),close 입 니 다.
这里写图片描述

/**
 *    ,               
 *       ,         
 */
private void drawHourHand() {
  mCanvas.save();
  mCanvas.rotate(mHourDegree, getWidth() / 2, getHeight() / 2);
  mHourHandPath.reset();
  float offset = mPaddingTop + mTextRect.height() / 2;
  mHourHandPath.moveTo(getWidth() / 2 - 0.02f * mRadius, getHeight() / 2);
  mHourHandPath.lineTo(getWidth() / 2 - 0.01f * mRadius, offset + 0.5f * mRadius);
  mHourHandPath.quadTo(getWidth() / 2, offset + 0.48f * mRadius,
      getWidth() / 2 + 0.01f * mRadius, offset + 0.5f * mRadius);
  mHourHandPath.lineTo(getWidth() / 2 + 0.02f * mRadius, getHeight() / 2);
  mHourHandPath.close();
  mCanvas.drawPath(mHourHandPath, mHourHandPaint);
  mCanvas.restore();
}

7.그 다음 에 분침 을 하고 시계 방향 에 따라
这里写图片描述

/**
 *    ,               
 */
private void drawMinuteHand() {
  mCanvas.save();
  mCanvas.rotate(mMinuteDegree, getWidth() / 2, getHeight() / 2);
  mMinuteHandPath.reset();
  float offset = mPaddingTop + mTextRect.height() / 2;
  mMinuteHandPath.moveTo(getWidth() / 2 - 0.01f * mRadius, getHeight() / 2);
  mMinuteHandPath.lineTo(getWidth() / 2 - 0.008f * mRadius, offset + 0.38f * mRadius);
  mMinuteHandPath.quadTo(getWidth() / 2, offset + 0.36f * mRadius,
      getWidth() / 2 + 0.008f * mRadius, offset + 0.38f * mRadius);
  mMinuteHandPath.lineTo(getWidth() / 2 + 0.01f * mRadius, getHeight() / 2);
  mMinuteHandPath.close();
  mCanvas.drawPath(mMinuteHandPath, mMinuteHandPaint);
  mCanvas.restore();
}

8.마지막 으로 path 는 close 이기 때문에 아예 두 개의 원 을 그 려 서 덮 습 니 다.
这里写图片描述

/**
 *         ,    path       
 */
private void drawCoverCircle() {
  mCanvas.drawCircle(getWidth() / 2, getHeight() / 2, 0.05f * mRadius, mSecondHandPaint);
  mSecondHandPaint.setColor(mBackgroundColor);
  mCanvas.drawCircle(getWidth() / 2, getHeight() / 2, 0.025f * mRadius, mSecondHandPaint);
}
9.드디어 다 그 렸 습 니 다.onDraw 부분 은 이 렇 습 니 다.

@Override
protected void onDraw(Canvas canvas) {
  mCanvas = canvas;
  getTimeDegree();
  drawTimeText();
  drawScaleLine();
  drawSecondHand();
  drawHourHand();
  drawMinuteHand();
  drawCoverCircle();
  invalidate();
}
그 릴 때,특히 이렇게 원형 view 를 유연 하 게 활용 합 니 다.

canvas.save();
canvas.rotate(mDegree, mCenterX, mCenterY);
<!-- draw something -->
canvas.restore();
이 조합 권 은 많은 삼각함수,각도 호도 와 관련 된 계산 을 줄 일 수 있다.
10.매 운 지 다음은 터치 로 시 계 를 3D 로 회전 시 키 는 방법 입 니 다.
Camera 류 와 Matrix 류 를 빌려 구조 방법 에서:

Matrix mCameraMatrix = new Matrix();
Camera mCamera = new Camera();

/**
 *   3D    ,         、        
 *          ,    
 *
 * @param rotateX  X      
 * @param rotateY  Y      
 */
private void setCameraRotate(float rotateX, float rotateY) {
  mCameraMatrix.reset();
  mCamera.save();
  mCamera.rotateX(mCameraRotateX);// x     
  mCamera.rotateY(mCameraRotateY);// y     
  mCamera.getMatrix(mCameraMatrix);//       matrix 
  mCamera.restore();
  //camera view      ,               
  //      pre matrix    getWidth()/2  ,    getHeight()/2  
  mCameraMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2);
  //     post     
  mCameraMatrix.postTranslate(getWidth() / 2, getHeight() / 2);
  mCanvas.concat(mCameraMatrix);//matrix canvas   
}

이 코드 는 camera 의 회전,이동,확대 와 같은 조작 을 제외 하고 나머지 코드 는 일반적으로 고정 되 어 있다.
전역 변수 mCameraRotateX 와 mCameraRotateY 는 이때 손가락 터치 좌표 와 관련 된 동적 가 져 오기:

@Override
public boolean onTouchEvent(MotionEvent event) {
  switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
      getCameraRotate(event);
      break;
    case MotionEvent.ACTION_MOVE:
      //        camera       
      getCameraRotate(event);
      break;
  }
  return true;
}
카메라 의 좌표계 와 View 의 좌표계 가 다 릅 니 다.
View 좌표 계 는 2 차원 이 고 원점 은 화면 왼쪽 상단 에 있 으 며 오른쪽 은 x 축 정방 향 이 고 아래 는 y 축 정방 향 이다.한편,Camera 좌 표 는 3 차원 이 고 원점 은 화면 왼쪽 상단 에 있 으 며 오른쪽 은 x 축 정방 향 이 고 위 는 y 축 정방 향 이 며 화면 방향 은 안 으로 z 축 정방 향 이다.

/**
 *   camera     
 *   view   camera       
 */
private void getCameraRotate(MotionEvent event) {
  float rotateX = -(event.getY() - getHeight() / 2);
  float rotateY = (event.getX() - getWidth() / 2);
  //              
  float percentX = rotateX / mRadius;
  float percentY = rotateY / mRadius;
  if (percentX > 1) {
    percentX = 1;
  } else if (percentX < -1) {
    percentX = -1;
  }
  if (percentY > 1) {
    percentY = 1;
  } else if (percentY < -1) {
    percentY = -1;
  }
  //              
  mCameraRotateX = percentX * mMaxCameraRotate;
  mCameraRotateY = percentY * mMaxCameraRotate;
}

11.마지막 으로 onTouchEvent 에서 손가락 을 풀 때 복원 하고 흔 들 리 는 애니메이션 을 추가 합 니 다.

case MotionEvent.ACTION_UP:
  //    ,           
  ValueAnimator animX = getShakeAnim(mCameraRotateX, 0);
  animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
      mCameraRotateX = (float) valueAnimator.getAnimatedValue();
    }
  });
  ValueAnimator animY = getShakeAnim(mCameraRotateY, 0);
  animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
      mCameraRotateY = (float) valueAnimator.getAnimatedValue();
    }
  });
  break;


/**
 *   OvershootInterpolator        
 */
private ValueAnimator getShakeAnim(float start, float end) {
  ValueAnimator anim = ValueAnimator.ofFloat(start, end);
  anim.setInterpolator(new OvershootInterpolator(10));
  anim.setDuration(500);
  anim.start();
  return anim;
}

드디어 다 썼 습 니 다.이 MiClock View 는 어 울 리 는 것 도 별로 차이 가 나 지 않 습 니 다.시간 도 동기 화 된 핸드폰 시간 입 니 다.보통 가 져 와 서 사용 할 수 있 습 니 다.
demo 다운로드 주소:http://xiazai.jb51.net/201701/yuanma/MiClockView_jb51.rar
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기