Android自定義View實戰之角度選擇器

本文比較基礎,在閱讀本文前只需要掌握最基礎的自定義View知識和Android事件知識。

起步

有一天晚上,在Google Photos查看照片,用了一下它的圖片剪裁功能,于是我馬上就被其界面和操作吸引。

第二天我就想模仿做一個這樣的裁圖庫,當然,我做了。同時也做了一個和Google Photos裁圖頁面幾乎一模一樣的角度選擇器。那么,來看一下最終的效果:

思路

仔細觀察這個效果,先分析構成結構,我把它分成三部分:

  1. 表示刻度的點
  2. 相應點上方的數字
  3. 控件中央的當前刻度與三角

可以看出,構成元素十分簡單,不涉及圖片,Drawable,那么只需要用Canvas畫出來就好了。

接下來觀察手勢的操作,查看隨著手指滑動,控件做出的變化,這里的變化有:

  1. 手指按上去時,部分區域變亮(部分區域即為可見區域)
  2. 隨著手指滑動,相應的數字發生移動,當前角度值也發生改變
  3. 離中心越近,透明度越低,且的下方的點要大一些

好了,我們對這個控件已經分析的很透徹了,根據分析,首先我們要在ViewonDraw()方法中畫出構成元素,之后要讓它動起來,在onTouchEvent()方法中監聽手勢,改變一些值并重繪我們的View,這里很明顯,我們要改變的肯定是當前角度這個值。

動手

既然有了思路,那么就要馬上動手,不然下次忘記掉了怎么辦?

畫點

首先,先把點給畫出來,位置很簡單,肯定是從視圖中心開始畫,往左往右分別畫點。

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的寬度除以點的個數。

看看效果

嗯,成功的把點給畫出來了。

畫刻度上方的數字

既然把點給畫出來了,根據我們的思路,我們要把數字給畫到正確的位置上,畫數字當然很簡單,這里難的是找到正確的位置。

首先,我們的數字是會移動的,隨著當前角度的不同,所展示的數字大小位置都不同。但毫無疑問的是,這個位置(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);

還有一個小細節,就是離中心越近,其透明度越來越低,也就是說,我們要根據離中心的位置,來改變畫筆Paintalpha值,再將點畫出。

 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相對于前兩次的來說,無疑是簡單了很多,只運用了最基礎的繪圖知識和事件機制,但是看起來效果也還不錯哦,嘿嘿,反正我特別喜歡這個角度選擇器,雖然我還不知道除了裁圖頁面可以用到還有哪里可以用到。所以說運用最簡單的知識,也可以組合出比較復雜的效果,希望大家多發揮自己的想象力,一起自定義出更多好玩的,實用的,酷炫的控件吧。

希望這篇文章對你有幫助,哪怕只是一些啟發,我也很開心了。

最后附上源碼地址:https://github.com/wuapnjie/DegreeSeekBar

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,597評論 25 707
  • 聲明:原創作品,轉載請注明出處 http://www.lxweimin.com/p/3b789490fc04 最近,...
    蛇發女妖閱讀 10,851評論 31 122
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,147評論 4 61
  • 《如何閱讀一本書》的閱讀層次方法中,第二個層次是檢視閱讀,如何快速閱讀完一本書。 有系統的略讀或粗讀;粗淺的閱讀。...
    陳sir閱讀 834評論 0 4
  • 出售時間之前你要牢記的三條鐵律下 昨天讀了上,說的是成長才是根本的關鍵 今天讀了鐵律二,重視價值忽略估值,在一個大...
    沈青青閱讀 186評論 0 1