国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > Android > 正文

android高仿小米時(shí)鐘(使用Camera和Matrix實(shí)現(xiàn)3D效果)

2019-10-23 19:50:56
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

繼續(xù)練習(xí)自定義View。。畢竟熟才能生巧。一直覺(jué)得小米的時(shí)鐘很精美,那這次就搞它~這次除了練習(xí)自定義View,還涉及到使用Camera和Matrix實(shí)現(xiàn)3D效果。

android,小米時(shí)鐘,camera,matrix時(shí)鐘,仿小米時(shí)鐘

一個(gè)這樣的效果,在繪制的時(shí)候最好選擇一個(gè)方向一步一步的繪制,這里我選擇由外到內(nèi)、由深到淺的方向來(lái)繪制,代碼步驟如下:

1、首先老一套~新建attrs.xml文件,編寫自定義屬性如時(shí)鐘背景色、亮色(用于分針、秒針、漸變終止色)、暗色(圓弧、刻度線、時(shí)針、漸變起始色),新建MiClockView繼承View,重寫構(gòu)造方法,獲取自定義屬性值,初始化Paint、Path以及畫圓、弧需要的RectF等東東,重寫onMeasure計(jì)算寬高,這里不再啰嗦~剛開始學(xué)自定義View的同學(xué)建議從我的前幾篇博客看起

2、由于onSizeChanged方法在構(gòu)造方法、onMeasure之后,又在onDraw之前,此時(shí)已經(jīng)完成全局變量初始化,也得到了控件的寬高,所以可以在這個(gè)方法中確定一些與寬高有關(guān)的數(shù)值,比如這個(gè)View的半徑啊、padding值等,方便繪制的時(shí)候計(jì)算大小和位置:

@Overrideprotected 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;  //加一個(gè)默認(rèn)的padding值,為了防止用android/140789.html">camera旋轉(zhuǎn)時(shí)鐘時(shí)造成四周超出view大小  mDefaultPadding = 0.12f * mRadius;//根據(jù)比例確定默認(rèn)padding大小  //為了適配控件大小match_parent、wrap_content、精確數(shù)值以及padding屬性  mPaddingLeft = mDefaultPadding + w / 2 - mRadius + getPaddingLeft();  mPaddingTop = mDefaultPadding + h / 2 - mRadius + getPaddingTop();  mPaddingRight = mPaddingLeft;  mPaddingBottom = mPaddingTop;  mScaleLength = 0.12f * mRadius;//根據(jù)比例確定刻度線長(zhǎng)度  mScaleArcPaint.setStrokeWidth(mScaleLength);//刻度盤的弧寬  mScaleLinePaint.setStrokeWidth(0.012f * mRadius);//刻度線的寬度  //梯度掃描漸變,以(w/2,h/2)為中心點(diǎn),兩種起止顏色梯度漸變  //float數(shù)組表示,[0,0.75)為起始顏色所占比例,[0.75,1}為起止顏色漸變所占比例  mSweepGradient = new SweepGradient(w / 2, h / 2,      new int[]{mDarkColor, mLightColor}, new float[]{0.75f, 1});}

3、準(zhǔn)備工作做的差不多了,那就開始繪制,根據(jù)方向我先確定最外層的小時(shí)時(shí)間文本的位置及其旁邊的四個(gè)弧:

android,小米時(shí)鐘,camera,matrix時(shí)鐘,仿小米時(shí)鐘

注意兩位數(shù)字的寬度和一位數(shù)的寬度是不一樣的,在計(jì)算的時(shí)候一定要注意

 

  String timeText = "12";  mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);  int textLargeWidth = mTextRect.width();//兩位數(shù)字的寬  mCanvas.drawText("12", getWidth() / 2 - textLargeWidth / 2, mPaddingTop + mTextRect.height(), mTextPaint);  timeText = "3";  mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);  int textSmallWidth = mTextRect.width();//一位數(shù)字的寬  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);

我計(jì)算文本的寬高一般采用的方法是,new一個(gè)Rect,然后再繪制時(shí)調(diào)用

mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);

將這個(gè)文本的范圍賦值給這個(gè)mTextRect,此時(shí)mTextRect.width()就是這段文本的寬,mTextRect.height()就是這段文本的高。

android,小米時(shí)鐘,camera,matrix時(shí)鐘,仿小米時(shí)鐘

畫文本旁邊的四個(gè)弧:

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);}

計(jì)算圓弧外接矩形的范圍別忘了加上圓弧線寬的一半

4、再往里是刻度盤,畫這個(gè)刻度盤的思路是現(xiàn)在底層畫一個(gè)mScaleLength寬度的圓,并設(shè)置SweepGradient漸變,上面再畫一圈背景色的刻度線。獲得SweepGradient的Matrix對(duì)象,通過(guò)不斷旋轉(zhuǎn)mGradientMatrix的角度實(shí)現(xiàn)刻度盤的旋轉(zhuǎn)效果:

/** * 畫一圈梯度渲染的亮暗色漸變圓弧,重繪時(shí)不斷旋轉(zhuǎn),上面蓋一圈背景色的刻度線 */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默認(rèn)會(huì)在三點(diǎn)鐘方向開始顏色的漸變,為了吻合  //鐘表十二點(diǎn)鐘順時(shí)針旋轉(zhuǎn)的方向,把秒針旋轉(zhuǎn)的角度減去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();}

這里有一個(gè)全局變量mSecondDegree,即秒針旋轉(zhuǎn)的角度,需要根據(jù)當(dāng)前時(shí)間動(dòng)態(tài)獲取:

/** * 獲取當(dāng)前 時(shí)分秒 所對(duì)應(yīng)的角度 * 為了不讓秒針走得像老式掛鐘一樣僵硬,需要精確到毫秒 */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繪制一個(gè)指向12點(diǎn)鐘的三角形,通過(guò)不斷旋轉(zhuǎn)畫布實(shí)現(xiàn)秒針的旋轉(zhuǎn):

/** * 畫秒針,根據(jù)不斷變化的秒針角度旋轉(zhuǎn)畫布 */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();}

android,小米時(shí)鐘,camera,matrix時(shí)鐘,仿小米時(shí)鐘

6、看實(shí)現(xiàn)圖,時(shí)針在分針之下并且比分針顏色淺,那我就先畫時(shí)針,仍然是Path,并且針頭為圓弧狀,那么就用二階貝賽爾曲線,路徑為moveTo( A),lineTo(B),quadTo(C,D),lineTo(E),close.

android,小米時(shí)鐘,camera,matrix時(shí)鐘,仿小米時(shí)鐘

/** * 畫時(shí)針,根據(jù)不斷變化的時(shí)針角度旋轉(zhuǎn)畫布 * 針頭為圓弧狀,使用二階貝塞爾曲線 */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、然后是分針,按照時(shí)針的思路:

android,小米時(shí)鐘,camera,matrix時(shí)鐘,仿小米時(shí)鐘

/** * 畫分針,根據(jù)不斷變化的分針角度旋轉(zhuǎn)畫布 */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的,所以干脆畫兩個(gè)圓蓋在上面:

android,小米時(shí)鐘,camera,matrix時(shí)鐘,仿小米時(shí)鐘

/** * 畫指針的連接圓圈,蓋住指針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部分就是這樣

@Overrideprotected void onDraw(Canvas canvas) {  mCanvas = canvas;  getTimeDegree();  drawTimeText();  drawScaleLine();  drawSecondHand();  drawHourHand();  drawMinuteHand();  drawCoverCircle();  invalidate();}

繪制的時(shí)候,尤其是像這樣圓形view,靈活運(yùn)用

canvas.save();canvas.rotate(mDegree, mCenterX, mCenterY);<!-- draw something -->canvas.restore();

這一套組合拳可以減少不少三角函數(shù)、角度弧度相關(guān)的計(jì)算。

10、辣么接下來(lái)就是如何實(shí)現(xiàn)觸摸使鐘表3D旋轉(zhuǎn)

借助Camera類和Matrix類,在構(gòu)造方法中:

Matrix mCameraMatrix = new Matrix();Camera mCamera = new Camera();
/** * 設(shè)置3D時(shí)鐘效果,觸摸矩陣的相關(guān)設(shè)置、照相機(jī)的旋轉(zhuǎn)大小 * 應(yīng)用在繪制圖形之前,否則無(wú)效 * * @param rotateX 繞X軸旋轉(zhuǎn)的大小 * @param rotateY 繞Y軸旋轉(zhuǎn)的大小 */private void setCameraRotate(float rotateX, float rotateY) {  mCameraMatrix.reset();  mCamera.save();  mCamera.rotateX(mCameraRotateX);//繞x軸旋轉(zhuǎn)角度  mCamera.rotateY(mCameraRotateY);//繞y軸旋轉(zhuǎn)角度  mCamera.getMatrix(mCameraMatrix);//相關(guān)屬性設(shè)置到matrix中  mCamera.restore();  //camera在view左上角那個(gè)點(diǎn),故旋轉(zhuǎn)默認(rèn)是以左上角為中心旋轉(zhuǎn)  //故在動(dòng)作之前pre將matrix向左移動(dòng)getWidth()/2長(zhǎng)度,向上移動(dòng)getHeight()/2長(zhǎng)度  mCameraMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2);  //在動(dòng)作之后post再回到原位  mCameraMatrix.postTranslate(getWidth() / 2, getHeight() / 2);  mCanvas.concat(mCameraMatrix);//matrix與canvas相關(guān)聯(lián)}

這段代碼除了camera的旋轉(zhuǎn)、平移、縮放之類的操作之外,剩下的代碼一般是固定的

全局變量mCameraRotateX和mCameraRotateY應(yīng)該與此時(shí)手指觸摸坐標(biāo)相關(guān)聯(lián)動(dòng)態(tài)獲取:

@Overridepublic boolean onTouchEvent(MotionEvent event) {  switch (event.getAction()) {    case MotionEvent.ACTION_DOWN:      getCameraRotate(event);      break;    case MotionEvent.ACTION_MOVE:      //根據(jù)手指坐標(biāo)計(jì)算camera應(yīng)該旋轉(zhuǎn)的大小      getCameraRotate(event);      break;  }  return true;}

Camera的坐標(biāo)系和View的坐標(biāo)系是不一樣的

View坐標(biāo)系是二維的,原點(diǎn)在屏幕左上角,右為x軸正方向,下為y軸正方向;而Camera坐標(biāo)系是三維的,原點(diǎn)在屏幕左上角,右為x軸正方向,上為y軸正方向,屏幕向里為z軸正方向

/** * 獲取camera旋轉(zhuǎn)的大小 * 注意view坐標(biāo)與camera坐標(biāo)方向的轉(zhuǎn)換 */private void getCameraRotate(MotionEvent event) {  float rotateX = -(event.getY() - getHeight() / 2);  float rotateY = (event.getX() - getWidth() / 2);  //求出此時(shí)旋轉(zhuǎn)的大小與半徑之比  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;  }  //最終旋轉(zhuǎn)的大小按比例勻稱改變  mCameraRotateX = percentX * mMaxCameraRotate;  mCameraRotateY = percentY * mMaxCameraRotate;}

11、最后在onTouchEvent中松開手指時(shí)加一個(gè)復(fù)原并晃動(dòng)的動(dòng)畫

case MotionEvent.ACTION_UP:  //松開手指,時(shí)鐘復(fù)原并伴隨晃動(dòng)動(dòng)畫  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完成時(shí)鐘晃動(dòng)動(dòng)畫 */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;}

終于寫完了,這個(gè)MiClockView適配也做的差不多了,時(shí)間也是同步的手機(jī)時(shí)間,一般可以拿來(lái)就用了~

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到Android開發(fā)頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 海安县| 沐川县| 临澧县| 哈尔滨市| 壤塘县| 黔江区| 开原市| 克拉玛依市| 金乡县| 台北县| 延津县| 靖江市| 南郑县| 岗巴县| 安塞县| 乳源| 都兰县| 舟曲县| 托克逊县| 衡阳县| 昌江| 怀安县| 渭南市| 寻甸| 苗栗县| 尼玛县| 景宁| 靖边县| 万宁市| 晋中市| 通道| 潼南县| 江都市| 新竹市| 芜湖县| 桐城市| 泊头市| 江门市| 乌鲁木齐县| 凤山市| 宜兰市|