canvas 手勢控制

基本介紹

關(guān)于 canvas 的基本使用,可以參考以下兩個網(wǎng)站:

Android Canvas繪圖詳解(圖文) - 泡在網(wǎng)上的日子

Android中Canvas繪圖基礎(chǔ)詳解(附源碼下載) - CSDN博客

這里主要講解如何將 canvas 實際運用到我們的項目中云头。

手勢控制

canvas 沒有提供有關(guān)手勢縮放的功能绍赛,但我們可以利用 onTouchListener 來監(jiān)測手勢,并根據(jù)手勢的不同對掃描圖作不同處理鸣个,比如移動和縮放羞反。首先,讓繪制圖形的這個類繼承一個接口 —— View.OnTouchListener囤萤,然后再實現(xiàn)該接口中的 onTouch 方法昼窗。

@Override
// 實現(xiàn)接口 View.OnTouchListener 的 onTouch 方法
public boolean onTouch(View v, MotionEvent event) {
    // ...
    return false;
}

只要有手指觸碰到繪制的圖形,就會觸發(fā) onTouch 方法涛舍,因此我們只要可以監(jiān)測到觸碰到圖形的手指正在進行什么動作澄惊,就可以對圖形做相應的處理。比如,如果 onTouch 監(jiān)測到有一根手指從屏幕的左邊滑到了右邊掸驱,那么說明圖形應該向右移肛搬,如果 onTouch 監(jiān)測到有兩根手指觸碰到了屏幕,并且它們的距離在不斷減小毕贼,那很顯然温赔,圖形應該被縮小∷У叮可是让腹,手指的動作這么靈活,該怎么監(jiān)測呢扣溺?下面我們就來解決這個問題骇窍。

無論是什么動作,手指肯定需要先觸碰到屏幕锥余,最后再離開屏幕腹纳,這樣才能完成一整個動作。Android 提供了一個方法來專門監(jiān)測這兩個動作以及更多的動作:

event.getAction()
<small><i>(event 是 onTouch 方法的第二個參數(shù))</i></small>

getAction() 會返回一個 int 型的值驱犹,不同的動作對應著不同的值嘲恍,比如手指按下對應 0,手指抬起對應 1 等等雄驹。當然佃牛,這么多動作和值,我們不可能全記得医舆,好在 Android 將不同的值都取了一個名字并保存在 MotionEvent 類中俘侠,比如

MotionEvent.ACTION_DOWN = 0
MotionEvent.ACTION_UP = 1
MotionEvent.MOVE = 2
...

既然這么方便,我們就可以通過 switch-case 結(jié)構(gòu)來精準監(jiān)測不同的動作了蔬将,看一下下面的代碼:

@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
        // 手指按下
        case MotionEvent.ACTION_DOWN:
            // ...針對該動作爷速,對圖形作出處理
            break;
        // 最后一根手指抬起
        case MotionEvent.ACTION_UP:
            // ...針對該動作,對圖形作出處理
            break;
        // 手指移動
        case MotionEvent.ACTION_MOVE:
            // ...針對該動作霞怀,對圖形作出處理
            break;
        // ...更多的動作
        default:
            break;
    }
    return false;
}

onTouch方法 通過 event.getAction() 獲取到的值惫东,自動判斷執(zhí)行哪一個 case 中的代碼,即通過監(jiān)測不同的動作來對圖形作出相應處理毙石。我們的處理主要就是移動和縮放廉沮,那么下面分別介紹這兩方面該如何處理。

移動

Android 提供了兩個方法 event.getX()event.getY()徐矩,這兩個方法可以獲取到當前手指在屏幕上的坐標值滞时,那么只要將當前的坐標值減去之前的坐標值就可以得到手指在 x 和 y 方向分別移動了多少,再讓圖形移動這么多就可以了丧蘸。下面是具體步驟:

  1. 我們先在繪制圖形類中新增兩個 float 型成員變量 xDownyDown漂洋,用來分別記錄手指當前的 x 坐標和 y 坐標遥皂。

  2. onTouch 方法中的 switch-case 結(jié)構(gòu)中的 MotionEvent.ACTION_DOWN case 中,記錄下手指剛按下時的坐標:

xDown = event.getX();
yDown = event.getY();

(只有手指剛按下去的一刻才會觸發(fā)MotionEvent.ACTION_DOWN中的代碼)

  1. onTouch 方法中的 switch-case 結(jié)構(gòu)中的 MotionEvent.ACTION_MOVE case 中刽漂,動態(tài)更新每次手指移動的坐標距離:
xTranslate += (event.getX() - xDown) / xScale;
xDown = event.getX();
yTranslate += (event.getY() - yDown) / yScale;
yDown = event.getY();

稍微解釋一下演训,手指每移動一小距離都會執(zhí)行以上代碼,其中 xTranslateyTranslate 是用來控制圖形移動的贝咙,初始值是 0样悟,只要它們的值變化了,圖形就會移動庭猩;xScaleyScale 是用來控制圖形縮放的窟她,初始值是 1,只要它們的值變化了蔼水,圖形就會縮放震糖。拿 xTranslate 來說,手指每移動一小距離趴腋,都把當前手指的 x 坐標值減去移動之前的 x 坐標值吊说,然后除以當前縮放的比例,再把這個值賦給 xTranslate优炬,這時圖形就會移動相應的距離颁井,并且移動的距離和你手指移動的距離完全相等。需要注意的是蠢护,在手指移動的過程中雅宾,需要不斷的把當前手指的 x 坐標值賦給 xDown,即 xDown = event,getX()葵硕,因為 event.getX() 的值始終比 xDown 先變化眉抬,這樣就能保證它們之間始終有一個微小的差值,這個差值就是圖形每次移動的那一點微小的距離贬芥,因為距離實在太小吐辙,所以整個過程看起來就是連續(xù)移動了宣决。簡而言之蘸劈,圖形的一整段移動是由無數(shù)段微小的移動組成的。

  1. 加上當前手指數(shù)目的判斷尊沸。因為當手指移動時威沫,可能是一根手指也可能是兩根手指,如果是兩根手指洼专,要實現(xiàn)的功能就是縮放而不是移動了棒掠,因此需要加上手指數(shù)目的判斷,這個很好完成屁商,因為 Android 提供了一個方法來獲取手指數(shù)目的方法:event.getPointerCounter()烟很,這個方法可以直接返回當前觸摸到屏幕的手指數(shù)目,然后通過 if 語句加入到 MotionEvent.ACTION_MOVE case 中就可以了,如果返回 1雾袱,就執(zhí)行有關(guān)圖形移動的代碼恤筛,如果返回 2,就執(zhí)行有關(guān)圖形縮放的代碼芹橡。

縮放

縮放的原理也很好理解毒坛。首先,要實現(xiàn)縮放林说,一定有兩根手指觸碰到屏幕煎殷,那么,我們可以獲取當前兩根手指的距離和之前兩根手指的距離腿箩,然后算出比例豪直,這個比例就是圖形應該縮放的比例。比如之前手指間的距離是 1珠移,現(xiàn)在是 2顶伞,那么圖形應該被放大 \(\frac{2}{1}\) 即 2 倍。

下面來看具體步驟:

  1. 我們先要獲取兩根手指觸碰到屏幕時它們之間的距離剑梳。之前提到過唆貌,手指的每一個動作都對應著一個 int 型的值,兩根手指觸碰到屏幕這個動作對應的值是 261垢乙。然后我們可以通過 event.getX(0)event.getX(1) 分別獲取兩根手指的坐標锨咙,然后相減即可得到兩根手指在 x 軸方向的距離,同樣的方法也能得到 y 軸方向的距離追逮,然后這兩個距離平方相加即可得到兩根手指之間的距離酪刀,代碼如下:
case 261:
    double xLenDown = Math.abs(event.getX(0) - event.getX(1));
    double yLenDown = Math.abs(event.getY(0) - event.getY(1));
    lenDown = Math.sqrt(xLenDown * xLenDown + yLenDown * yLenDown);
    break;
  1. 每次移動手指,都記錄下當前手指間的距離钮孵,然后除以上次移動時手指間的距離骂倘,再減去 1,就得到了這次移動后圖形應該縮放的比例巴席,如果大于 0历涝,圖形就會放大,否則就會縮小漾唉,并且為了不讓圖形縮小到消失荧库,加入一條 if 語句,設(shè)置最小縮放比例為 0.4赵刑。代碼如下:
else if (event.getPointerCount() == 2) {
    // 實現(xiàn)掃描圖縮放
    double xLenMove = Math.abs(event.getX(0) - event.getX(1));
    double yLenMove = Math.abs(event.getY(0) - event.getY(1));
    double lenMove = Math.sqrt(xLenMove * xLenMove + yLenMove * yLenMove);
    // 動態(tài)更新
    // 設(shè)置最小縮放比例為 0.4
    if (xScale + (lenMove / lenDown - 1) > 0.4) {
        xScale += (lenMove / lenDown - 1);
        yScale += (lenMove / lenDown - 1);
        lenDown = lenMove;
    }
}

首頁折線圖和掃描圖同步移動和縮放

這個功能的目的是分衫,當折線圖或者掃描圖任何一者移動或者縮放時,另一者也要移動或縮放同樣的距離或程度般此。其中蚪战,另一者只在橫軸方向上保持同步移動牵现,并且二者縮放時均以當前圖形的中心點為縮放中心。

這個功能分為兩個部分邀桑,一個是改變折線圖的同時改變掃描圖施籍,一個是改變掃描圖的同時改變折線圖,先說簡單的概漱。

改變折線圖的同時改變掃描圖

如果上面的移動和縮放弄清楚了丑慎,那么這個功能其實不難實現(xiàn)。關(guān)鍵在于同步改變 xTranslatexScale瓤摧。

FragmentDataMeasure 類中竿裂,折線圖的實例是 mGraphicaView,那么監(jiān)控折線圖的手勢照弥,當出現(xiàn)移動和縮放的手勢時腻异,同步更改掃描圖中的 xTranslatexScale ,另外在注意一些細節(jié)即可这揣。這里就不在贅述了悔常。

改變掃描圖的同時改變折線圖

這個功能的困難在于,雖然繪制折線圖的庫 GraphicaView 是以 canvas 為基礎(chǔ)封裝成的给赞,但對于繪制圖形的方法机打,兩者有很大的區(qū)別,比如 canvas 在繪制圖形時是直接根據(jù)給出的像素坐標值確定位置的片迅,這個坐標值是基于屏幕自身的残邀;而 GraphicaView 是根據(jù)對應于坐標軸上的坐標值確定位置的,這個坐標值是基于用戶自己確定的坐標軸的長度的柑蛇。要解決這個問題芥挣,需要找到折線圖和掃描圖的一個共同特征作為橋梁,將兩種坐標值聯(lián)系起來耻台。

不過在研究 GraphicaView 庫后發(fā)現(xiàn)空免,GraphicaView 類中提供了兩個方法苏揣,可以分別獲取和設(shè)置當前屏幕上顯示出來的 x 軸的最小和最大坐標贸人,即圖中所示的兩個位置的坐標

image

有了這個方法衙猪,這個功能的實現(xiàn)就應該有思路了恰梢。我們先考慮移動時的同步。

移動時同步

我們先考慮一下折線圖和掃描圖的共同特征是什么寻咒,由于兩幅圖在 x 軸方向上都顯示的是掃描的距離仁锯,因此這個距離應該是相等的,這個距離就是共同特征匙姜。

ScanningService 類中,有一個 xDistance 屬性冯痢,專門用來記錄這個距離氮昧,而且框杜,xDistance 的值與折線圖中的 x 軸長度是相等的,如圖所示:

image

圖中折線圖的紅色箭頭之間的距離大致為 0.35袖肥,掃描圖的綠色箭頭之間的距離也大致為 0.35咪辱,而 0.35 其實就是 xDistance 的值。

當移動掃描圖時椎组,由于我們現(xiàn)在可以獲取到手指移動的距離 xDistance(注意這個距離是基于屏幕坐標系的油狂,而不是折線圖的坐標系),那么只要知道掃描圖的 x 軸方向的總距離 width(基于屏幕坐標系)寸癌,然后讓 xDistance 除以 width专筷,就得到了移動距離占總距離的比例,最后讓這個比例乘以 xDistance蒸苇,就得到了基于折線圖坐標系的距離磷蛹。Android 正好提供了一個方法 canvas.getWidth() 用來獲取 x 軸方向的距離,因此三個值都有了溪烤,那么折線圖移動的距離就可以算出來了味咳,代碼如下:

// 同步折線圖
public void syncGraphicalView(double xTrans) {
    // 更新折線圖
    FragmentDataMeasure.getInstance().mService.getMultipleSeriesRenderer()
                .setXAxisMin(-xTrans);
    FragmentDataMeasure.getInstance().mService.getMultipleSeriesRenderer()
                .setXAxisMax(scanView.getXDistance() - xTrans);
    // 重繪折線圖
    FragmentDataMeasure.getInstance().mGraphicalView.repaint();
}

其中 setXAxisMin()setXAxisMax() 是設(shè)置折線圖 x 軸最小和最大坐標的方法,由于圖形向右移檬嘀,屏幕同樣位置的坐標值就會減小槽驶,因此參數(shù)前帶有負號。

接下來考慮縮放時的同步鸳兽。

縮放時同步

縮放比移動復雜一點捺檬。

以下兩幅圖分別是掃描圖縮小前和縮小后的圖像

image
image

很明顯縮小后,橫軸所顯示的長度比縮小前更長了贸铜,由于縮放中心是圖形的中心點堡纬,因此左右兩邊多出的距離應該是相同的,除以二就可以得到兩邊各自多出的距離蒿秦,這個距離就是折線圖的 x 軸左右兩邊應該移動的量烤镐。

用代碼來描述就是如下形式:

(scanView.getXDistance() / scanView.getXScale() - scanView.getXDistance()) / 2

其中,getXScale() 用來獲取當前縮放的比例棍鳖,之后用縮放后的 xDistance 減去縮放前的炮叶,然后除以二就得到了折線圖 x 軸左側(cè)和右側(cè)各應該移動的距離(左側(cè)坐標減小右側(cè)坐標變大即為放大折線圖,反之則為縮小折線圖)渡处。

最后我們發(fā)現(xiàn)镜悉,其實移動和縮放折線圖的方法都是通過設(shè)置折線圖 x 軸左右兩側(cè)的坐標實現(xiàn)的,因此可以將移動和縮放的代碼加在一起医瘫。如下所示:

// 同步折線圖
public void syncGraphicalView(double xTrans) {
    // 更新折線圖
    FragmentDataMeasure.getInstance().mService.getMultipleSeriesRenderer()
                .setXAxisMin(-xTrans -
                        (scanView.getXDistance() / scanView.getXScale() - scanView.getXDistance()) / 2);
    FragmentDataMeasure.getInstance().mService.getMultipleSeriesRenderer()
                .setXAxisMax(scanView.getXDistance() - xTrans +
                        (scanView.getXDistance() / scanView.getXScale() - scanView.getXDistance()) / 2);
    // 重繪折線圖
    FragmentDataMeasure.getInstance().mGraphicalView.repaint();
}

原文地址:Canvas 在 LCJCSys 中的運用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侣肄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子醇份,更是在濱河造成了極大的恐慌稼锅,老刑警劉巖吼具,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異矩距,居然都是意外死亡拗盒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門锥债,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陡蝇,“玉大人,你說我怎么就攤上這事哮肚∫阏” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵绽左,是天一觀的道長悼嫉。 經(jīng)常有香客問我,道長拼窥,這世上最難降的妖魔是什么戏蔑? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮鲁纠,結(jié)果婚禮上总棵,老公的妹妹穿的比我還像新娘。我一直安慰自己改含,他們只是感情好情龄,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捍壤,像睡著了一般骤视。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹃觉,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天专酗,我揣著相機與錄音,去河邊找鬼盗扇。 笑死祷肯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的疗隶。 我是一名探鬼主播佑笋,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼斑鼻!你這毒婦竟也來了蒋纬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颠锉,沒想到半個月后法牲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體史汗,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡琼掠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了停撞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓷蛙。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖戈毒,靈堂內(nèi)的尸體忽然破棺而出艰猬,到底是詐尸還是另有隱情,我是刑警寧澤埋市,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布冠桃,位于F島的核電站,受9級特大地震影響道宅,放射性物質(zhì)發(fā)生泄漏食听。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一污茵、第九天 我趴在偏房一處隱蔽的房頂上張望樱报。 院中可真熱鬧,春花似錦泞当、人聲如沸迹蛤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盗飒。三九已至,卻和暖如春陋桂,著一層夾襖步出監(jiān)牢的瞬間箩兽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工章喉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留汗贫,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓秸脱,卻偏偏與公主長得像落包,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摊唇,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫咐蝇、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,118評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,277評論 25 707
  • 倚窗而望 雨絲牽線而下 秋意涼然 不覺打了寒顫 端起一杯菊花冰糖茶 苦澀帶著甜甜的味道滲入舌下 多日的身體不適 讓...
    田萍閱讀 328評論 0 7
  • 【談情說愛專題周刊稿】 西伯利亞的寒風低聲嘶吼巷查,哐哐地推搡著店門有序。眼前一碗金針菇醬鹵的豆腐腦抹腿,裊裊升騰著熱氣。纖薄...
    嬰兒看世界閱讀 759評論 17 16
  • 我和愛人相伴十余載旭寿,竟如雙生般有了心靈感應警绩。好多次,我正給他撥打電話盅称,電話鈴突然響了肩祥,嚇得我差點把電話摞了,還好我...
    清風徐徐霞笑江湖閱讀 398評論 4 6