本文比較基礎,在閱讀本文前只須要掌握最基礎的自定義View知識和Android事件知識。java
有一天晚上,在Google Photos查看照片,用了一下它的圖片剪裁功能,因而我立刻就被其界面和操做吸引。git
次日我就想模仿作一個這樣的裁圖庫,固然,我作了。同時也作了一個和Google Photos裁圖頁面幾乎如出一轍的角度選擇器。那麼,來看一下最終的效果:github
仔細觀察這個效果,先分析構成結構,我把它分紅三部分:canvas
能夠看出,構成元素十分簡單,不涉及圖片,Drawable
,那麼只須要用Canvas
畫出來就行了。ide
接下來觀察手勢的操做,查看隨着手指滑動,控件作出的變化,這裏的變化有:函數
0°
的下方的點要大一些好了,咱們對這個控件已經分析的很透徹了,根據分析,首先咱們要在View
的onDraw()
方法中畫出構成元素,以後要讓它動起來,在onTouchEvent()
方法中監聽手勢,改變一些值並重繪咱們的View
,這裏很明顯,咱們要改變的確定是當前角度這個值。post
既然有了思路,那麼就要立刻動手,否則下次忘記掉了怎麼辦?spa
首先,先把點給畫出來,位置很簡單,確定是從視圖中心開始畫,往左往右分別畫點。3d
for (int i = 0; i < mPointCount; i++) {
canvas.getClipBounds(mCanvasClipBounds);
canvas.drawPoint(mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin,
mCanvasClipBounds.centerY(), mPointPaint);
}複製代碼
其中mPointCount
爲所要畫的點個數,這裏默認爲51個,mPointMargin
爲兩點間的邊距,長度爲View
的寬度除以點的個數。code
看看效果
嗯,成功的把點給畫出來了。
既然把點給畫出來了,根據咱們的思路,咱們要把數字給畫到正確的位置上,畫數字固然很簡單,這裏難的是找到正確的位置。
首先,咱們的數字是會移動的,隨着當前角度的不一樣,所展現的數字大小位置都不一樣。但毫無疑問的是,這個位置(x座標)確定是關於當前角度的一個函數。至於具體是一個什麼樣的函數,相信聰明的你很快就能夠分析出來,畢竟只是一個線性關係,這裏就直接貼代碼了。
private void drawDegreeText(int degrees, Canvas canvas, boolean canReach) {
canvas.drawText(degrees + "°",
getWidth() / 2 + mPointMargin * degrees / 2 - mTextWidths[0] / 2 * 3 - mCurrentDegrees / 2 * mPointMargin,
getHeight() / 2 - 10,
mTextPaint);
}
}複製代碼
按照咱們的效果,咱們須要畫出-90°~90°的刻度,其中-45°~45°是可到達角度,另外的角度不可到達。
drawDegreeText(0, canvas, true);
drawDegreeText(15, canvas, true);
drawDegreeText(30, canvas, true);
drawDegreeText(45, canvas, true);
drawDegreeText(-15, canvas, true);
drawDegreeText(-30, canvas, true);
drawDegreeText(-45, canvas, true);
drawDegreeText(60, canvas, false);
drawDegreeText(75, canvas, false);
drawDegreeText(90, canvas, false);
drawDegreeText(-60, canvas, false);
drawDegreeText(-75, canvas, false);
drawDegreeText(-90, canvas, false);複製代碼
好了,來看一下效果,能夠看到,數字被畫在了正確的位置。
這個超級簡單呢,位置也特別好肯定,上方三角形的Path也很是好知道,可是這裏要注意的是,噹噹前角度爲0°左右時,不該該把0°刻度顯示出來。
//畫當前角度
if (mCurrentDegrees >= 10) {
canvas.drawText(mCurrentDegrees + "°", getWidth() / 2 - mTextWidths[0], mBaseLine, mTextPaint);
} else if (mCurrentDegrees <= -10) {
canvas.drawText(mCurrentDegrees + "°", getWidth() / 2 - mTextWidths[0] / 2 * 3, mBaseLine, mTextPaint);
} else if (mCurrentDegrees < 0) {
canvas.drawText(mCurrentDegrees + "°", getWidth() / 2 - mTextWidths[0], mBaseLine, mTextPaint);
} else {
canvas.drawText(mCurrentDegrees + "°", getWidth() / 2 - mTextWidths[0] / 2, mBaseLine, mTextPaint);
}
//三角指示器的Path
mIndicatorPath.moveTo(w / 2, h / 2 + mFontMetrics.top / 2 - 18);
mIndicatorPath.rLineTo(-8, -8);
mIndicatorPath.rLineTo(16, 0);複製代碼
還有一個小細節,就是離中心越近,其透明度愈來愈低,也就是說,咱們要根據離中心的位置,來改變畫筆Paint
的alpha
值,再將點畫出。
for (int i = 0; i < mPointCount; i++) {
if (i > zeroIndex - 22 && i < zeroIndex + 22 && mScrollStarted) {
mPointPaint.setAlpha(255);
} else {
mPointPaint.setAlpha(100);
}
if (i > mPointCount / 2 - 8 && i < mPointCount / 2 + 8
&& i > zeroIndex - 22 && i < zeroIndex + 22) {
if (mScrollStarted)
mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 255 / 8);
else
mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 100 / 8);
}
……
}複製代碼
這時,已經很像了,只是還不能動。
該繪製的部分咱們已經都繪製起來了,是時候讓這個View動起來了,角度選擇器的觸摸事件不復雜,咱們只須要在咱們移動手指的時候根據滑動距離來改變當前角度值並重繪視圖就能夠看到移動效果了。爲何呢?由於咱們以前在畫數字的時候,位置都是由當前角度這個值決定的,因此它一發生變化,數字的位置也會發生改變。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
……
case MotionEvent.ACTION_MOVE:
float distance = event.getX() - mLastTouchedPosition;
……
if (distance != 0) {
onScrollEvent(event, distance);
}
break;
}
……
return true;
}
private void onScrollEvent(MotionEvent event, float distance) {
mTotalScrollDistance -= distance;
postInvalidate();
mLastTouchedPosition = event.getX();
mCurrentDegrees = (int) ((mTotalScrollDistance * mDragFactor) / mPointMargin);
if (mScrollingListener != null) {
mScrollingListener.onScroll(mCurrentDegrees);
}
}複製代碼
固然你還須要處理一些細節性的東西,好比在數字移動的時候,靠近中心必定範圍就會消失(透明度變爲0),這些都是很容易控制的,只要改變畫筆的透明度就行了,可是正是對細節的把控,才能作出一個效果讓用戶滿意的自定義View。最後,再來看一下效果。
到這裏,咱們作的角度選擇器已經和Google Photos的幾乎如出一轍了,可是,僅僅這樣就夠了。不,不夠,咱們還要繼續擴展,爲何只能達到正負45°,咱們要全部的範圍自由選擇,也就是-180°~180°,而後數字顏色啊,點的顏色啊都要讓人自由選擇。因此咱們要擴展咱們的角度選擇器,把一切能夠變化的接口暴露出來。
最後看一下咱們多種多樣的角度選擇器,仍是挺好看呀~
此次的自定義View相對於前兩次的來講,無疑是簡單了不少,只運用了最基礎的繪圖知識和事件機制,可是看起來效果也還不錯哦,嘿嘿,反正我特別喜歡這個角度選擇器,雖然我還不知道除了裁圖頁面能夠用到還有哪裏能夠用到。因此說運用最簡單的知識,也能夠組合出比較複雜的效果,但願你們多發揮本身的想象力,一塊兒自定義出更多好玩的,實用的,酷炫的控件吧。
但願這篇文章對你有幫助,哪怕只是一些啓發,我也很開心了。
最後附上源碼地址:github.com/wuapnjie/De…