Android繪圖軟件開(kāi)發(fā)(1)-框架概述

引言

不知道您有沒(méi)有厭倦了做一個(gè)諸如學(xué)生管理哗伯、倉(cāng)庫(kù)管理屉符、圖書館管理的系統(tǒng)?除了增刪改查還是增刪改查良哲,做完后會(huì)感覺(jué)成就感很少盛卡,因?yàn)檫@樣的系統(tǒng)已經(jīng)遍地開(kāi)花了,很難給人以新鮮感和沖擊力筑凫。
今天想講講自己的一個(gè)小軟件滑沧,一個(gè)基于Android平臺(tái)開(kāi)發(fā)的繪圖APP。這個(gè)APP加入了很多新鮮和創(chuàng)新的元素巍实,不僅僅是繪圖這么簡(jiǎn)單滓技,但出于篇幅與重點(diǎn)考慮,本章僅講解對(duì)于繪圖軟件傳統(tǒng)功能的開(kāi)發(fā)思路棚潦,且盡量脫離某一具體平臺(tái)講解(本文以Android平臺(tái)為例令漂,采用java語(yǔ)言描述),不過(guò)度深入實(shí)現(xiàn)細(xì)節(jié),而僅給出一個(gè)可行性叠必、維護(hù)性和擴(kuò)展性都較好的開(kāi)發(fā)架構(gòu)荚孵。

繪圖軟件有什么

PS、CDR纬朝、Windows畫圖……相信大家對(duì)繪圖軟件并不陌生收叶,三下五除二就總結(jié)出了它的基本功能,下面是我總結(jié)的:

  1. 畫圖形:可以畫直線玄组、曲線滔驾、折線、隨筆線俄讹、圓形哆致、橢圓、矩形患膛、多邊形等
  2. 編輯圖形:可以選中摊阀、平移、縮放踪蹬、旋轉(zhuǎn)胞此、拷貝、刪除圖形
  3. 填充圖形:可以對(duì)畫布上任意封閉區(qū)域填充顏色
  4. 調(diào)整顏色:提供一個(gè)調(diào)色板跃捣,改變畫筆的顏色
  5. 調(diào)整畫筆:提供若干風(fēng)格迥異的畫筆

開(kāi)發(fā)思路

說(shuō)實(shí)話漱牵,最初看到這么多功能,我也是一頭霧水疚漆,無(wú)從下手酣胀。但仔細(xì)思考,通過(guò)歸納這些功能的特性與共性娶聘,會(huì)發(fā)現(xiàn)這5大功能其實(shí)分為兩類:

1. 有狀態(tài)功能

這些功能只有在被選中了后才能生效闻镶,且他們之間是互斥使用的。比如當(dāng)選中了“畫圓按鈕”后丸升,在畫布上繪出的就是圓铆农;當(dāng)選中了“畫矩形按鈕”后,在畫布上繪出的就是矩形狡耻;當(dāng)選中了“平移按鈕”后墩剖,就可以對(duì)畫布上的任一圖形進(jìn)行平移;當(dāng)選中了“填充按鈕”后夷狰,點(diǎn)擊畫布就會(huì)填色涛碑。上節(jié)的前3大功能均屬于該類。

2. 無(wú)狀態(tài)功能

這些功能被觸發(fā)后孵淘,隨即生效蒲障。比如點(diǎn)擊“調(diào)色板按鈕”后選擇畫筆顏色,確認(rèn)后顏色馬上發(fā)生改變;點(diǎn)擊“畫筆按鈕”后選擇畫筆樣式揉阎,確認(rèn)后也會(huì)立馬生效庄撮。上節(jié)的后2大功能均屬于該類。
劃分好這兩大類功能后毙籽,思路就明朗了很多洞斯,因?yàn)闊o(wú)狀態(tài)功能不外乎就是對(duì)一些全局參數(shù)的設(shè)置,是很容易實(shí)現(xiàn)的坑赡。下面我們先講下有狀態(tài)功能中“畫圖形”是怎么實(shí)現(xiàn)的烙如。

抽取圖形類

畫布上的每個(gè)圖形都有自己獨(dú)一無(wú)二的形狀、所占區(qū)域毅否、顏色亚铁、風(fēng)格,因此我們可以馬上抽取出圖形類Pel的結(jié)構(gòu):

class Pel
{
    Path path; //形狀軌跡
    Region region; //所占區(qū)域
    Paint paint; //風(fēng)格與顏色
}

Path螟加、Region徘溢、Paint三個(gè)類都是Android SDK中自帶的,其中:path負(fù)責(zé)存儲(chǔ)圖形的軌跡捆探,可通過(guò)調(diào)用它的若干繪制函數(shù)結(jié)合坐標(biāo)形成然爆;region負(fù)責(zé)存儲(chǔ)圖元所構(gòu)成的區(qū)域,可由path轉(zhuǎn)換得到黍图,用處是方便選中圖形曾雕;paint負(fù)責(zé)指定該圖形的樣式,包括了畫筆風(fēng)格和顏色助被。

存儲(chǔ)圖形

圖形是有了剖张,但它們都是相對(duì)獨(dú)立的個(gè)體,我們還需要建立合適的數(shù)據(jù)結(jié)構(gòu)統(tǒng)一管理它們恰起⌒扌担考慮到用戶繪制的圖形個(gè)數(shù)是沒(méi)有限制的趾牧,繪制過(guò)程中涉及對(duì)圖形的頻繁增刪检盼,這里我們選擇用一個(gè)鏈表List<Pel> pelList序列化存儲(chǔ)繪制在畫布上的圖形,如下圖所示翘单。


獲取圖形坐標(biāo)

圖形的存儲(chǔ)已經(jīng)有了一個(gè)歸宿吨枉,但要繪制出圖形來(lái),我們肯定需要知道坐標(biāo)哄芜,那坐標(biāo)是怎么獲取到的呢貌亭?這里就需要引出圖層類View,它的內(nèi)部有一個(gè)onTouchEvent(MotionEvent event)的回調(diào)方法认臊,用戶對(duì)這個(gè)圖層進(jìn)行觸摸時(shí)都會(huì)調(diào)用圃庭,且將觸摸事件類型(如手指落下事件、移動(dòng)事件、抬起事件等)和觸摸數(shù)據(jù)(如坐標(biāo))封裝進(jìn)了MotionEvent對(duì)象中剧腻,下面是獲取坐標(biāo)的代碼框架:

public boolean onTouchEvent(MotionEvent event)
{
    float x = event.getX();
    float y = event.getY();
    switch (event.getAction())
    {
        case MotionEvent.ACTION_DOWN:{處理落下事件};break;
        case MotionEvent.ACTION_MOVE:{處理移動(dòng)事件};break;
        case MotionEvent.ACTION_UP:{處理抬起事件};break;
    }
    return true;
}

繪制圖形

我們知道:path用來(lái)存儲(chǔ)圖形的軌跡拘央,坐標(biāo)指示了用戶手指所在的位置,如果能把坐標(biāo)“畫”進(jìn)path里面书在,就實(shí)現(xiàn)了圖形的存儲(chǔ)灰伟。所幸的是,Path類提供了這樣的函數(shù)替我們轉(zhuǎn)換儒旬,如畫隨筆線quadTo()栏账、畫矩形addRect()、畫橢圓addOval()等栈源。具體怎么實(shí)現(xiàn)呢挡爵?其實(shí)繪制就是圍繞以上三個(gè)觸摸事件展開(kāi)的,下面給出事件處理的大致思路:

  • MotionEvent.ACTION_DOWN:利用Path類的moveTo()方法固定軌跡的起始點(diǎn)
  • MotionEvent.ACTION_MOVE:利用Path類的各種繪制方法凉翻,以當(dāng)前坐標(biāo)作為參數(shù)繪出圖形了讨,并刷新到畫布上
  • MotionEvent.ACTION_UP:確定圖形的最終軌跡,構(gòu)建pel對(duì)象制轰,存入pelList中

擴(kuò)展更多功能

其實(shí)實(shí)現(xiàn)“畫圓”并不難前计,實(shí)現(xiàn)“畫矩形”也不難,難的是要實(shí)現(xiàn)上面“所有的”有狀態(tài)功能垃杖,這該如何是好男杈?有兩種方案:

1. 用狀態(tài)標(biāo)志實(shí)現(xiàn)

相信大家會(huì)說(shuō),這很簡(jiǎn)單嘛:既然上面說(shuō)了這些有狀態(tài)功能間是互斥使用的调俘,那么就給他們一人一個(gè)狀態(tài)標(biāo)志伶棒,當(dāng)用戶點(diǎn)擊進(jìn)入某一有狀態(tài)功能時(shí),將當(dāng)前狀態(tài)置為該標(biāo)志彩库,然后用戶觸摸屏幕時(shí)肤无,會(huì)觸發(fā)onTouchEvent函數(shù),這時(shí)獲得坐標(biāo)后骇钦,用if語(yǔ)句判斷當(dāng)前是哪種狀態(tài)(是畫圓宛渐?畫矩形?平移圖形眯搭?縮放圖形窥翩?……),然后每個(gè)外層if語(yǔ)句里面再進(jìn)一步用switch語(yǔ)句判斷當(dāng)前的觸摸事件類型鳞仙,最后針對(duì)不同事件進(jìn)行不同處理寇蚊,完事。
這…好吧棍好,為什么我隱隱地感到一絲不安…如果有狀態(tài)功能很少仗岸,用這種方法尚且還行(實(shí)際上也很繁瑣)允耿,但如果功能很多,比如10個(gè)扒怖,可以計(jì)算下總共有多少個(gè)條件分支:10(判狀態(tài))+10*3(判事件)=40磕谅!如果說(shuō)那10條“判狀態(tài)”的分支還算有實(shí)際意義的話同眯,那另外30條“判事件”的分支簡(jiǎn)直就是過(guò)度冗余和重復(fù)了镰踏。
這種實(shí)現(xiàn)方法的弊端是顯而易見(jiàn)的:

  • 代碼量大:條件判斷語(yǔ)句很多坏怪,代碼很冗余
  • 容易出錯(cuò):硬編碼,人工地定義狀態(tài)积糯,人工地進(jìn)行條件判斷掂墓,人工對(duì)應(yīng)他們的關(guān)系,一不留神就出錯(cuò)了
  • 可讀性不好:連續(xù)40個(gè)條件分支看成,每個(gè)分支下面又有對(duì)應(yīng)的處理語(yǔ)句君编,總之根本沒(méi)法讀
  • 可維護(hù)性不好:同上,代碼太多太復(fù)雜川慌,如果想要修改一個(gè)功能吃嘿,要先用ctrl+f搜狀態(tài)標(biāo)志,再搜這個(gè)狀態(tài)下的事件標(biāo)志梦重,再修改兑燥,眼前信息量很大,不好維護(hù)
  • 可擴(kuò)展性不好:如果要新加個(gè)有狀態(tài)功能琴拧,需要找到那一堆條件分支降瞳,在最后補(bǔ)上一個(gè)else if,里面再加個(gè)switch蚓胸,最后針對(duì)不同事件給出不同處理挣饥,擴(kuò)展工作量很大。
    嗯沛膳,所以這個(gè)實(shí)現(xiàn)方法注定是不可取的扔枫,是有違開(kāi)發(fā)規(guī)范和初衷的。下面我介紹一種自己想的方法锹安,若有不足歡迎大家指正短荐。
2. 用繼承和多態(tài)實(shí)現(xiàn)

上面那種方法的思考角度本質(zhì)還是面向過(guò)程,它關(guān)注的焦點(diǎn)是如何一步一步先后地去實(shí)現(xiàn)八毯,這種過(guò)程是鼠目寸光的搓侄,必然有失對(duì)全局的考慮瞄桨。而既然采用的是java語(yǔ)言话速,那我們就要充分利用它面向?qū)ο蟮奶攸c(diǎn),將關(guān)注的焦點(diǎn)轉(zhuǎn)換為一個(gè)個(gè)的對(duì)象芯侥。
由于這些有狀態(tài)功能都是一類泊交,所以它們之間必然存在共同的屬性與操作乳讥,而剩下的就是它們各自特有的屬性與操作了。一旦我們定義好了共性的東西(接口廓俭、基類)云石,就只用專注于去實(shí)現(xiàn)特性的東西(接口實(shí)現(xiàn)、子類覆寫)了研乒,從而輕松完成開(kāi)發(fā)汹忠,不僅如此,還兼顧了程序的可維護(hù)性和擴(kuò)展性雹熬。這就是程序的模塊化設(shè)計(jì)的好處宽菜。
那么問(wèn)題來(lái)了?有狀態(tài)功能間的共性是什么竿报?特性又是什么铅乡?很簡(jiǎn)單,你想烈菌,無(wú)論是畫圓阵幸,還是畫矩形,或是平移圖形芽世,不外乎上面提到的手指落下挚赊、手指移動(dòng)、手指抬起這三個(gè)事件(這就是共性部分)济瓢,我們要分別為這些有狀態(tài)功能分別編寫3種事件的處理代碼咬腕,這些處理代碼是互不相同、獨(dú)一無(wú)二的(這就是特性部分)葬荷。
既然提到共性涨共,沒(méi)錯(cuò),馬上想到的就是繼承宠漩。我們很容易抽象出一個(gè)觸摸的基類Touch举反,它定義三個(gè)公共方法down()、move()扒吁、up()火鼻,再定義若干公共屬性如x、y雕崩、eventType等魁索,再由具體的“子類Touch”去繼承這個(gè)基類Touch,在公共方法中實(shí)現(xiàn)自己的特性操作盼铁,其關(guān)系如下面類圖所示粗蔚。



上面只是利用繼承搭好了若干類及他們的關(guān)系,但落實(shí)到具體實(shí)現(xiàn)上饶火,還需要借助多態(tài)鹏控。多態(tài)最妙的一點(diǎn)就是:指向子類對(duì)象的基類引用可以調(diào)用子類覆寫過(guò)的方法致扯,什么意思呢,也就是上面方案1龐雜的條件分支可以神奇地簡(jiǎn)寫成這樣了:

//聲明一個(gè)全局的Touch對(duì)象
Touch touch = null;
//畫圓按鈕
public void onDrawOvalBtn(View view)
{
    touch = new DrawOvalTouch();
}
//畫矩形按鈕
public void onDrawRectBtn(View view)
{
    touch = new DrawRectTouch();
}
......
//平移圖形按鈕
public void onDragPelBtn(View view)
{
    touch = new DragPelTouch();
}
......
public boolean onTouchEvent(MotionEvent event)
{
    float x = event.getX();
    float y = event.getY();
    touch.setPoint(x,y); //傳遞坐標(biāo)
    switch (event.getAction())
    {
        case MotionEvent.ACTION_DOWN:touch.down();break;
        case MotionEvent.ACTION_MOVE:touch.move();break;
        case MotionEvent.ACTION_UP:touch.up();break;
    }
    return true;
}

怎么樣当辐,是不是很神奇抖僵,為什么能簡(jiǎn)化這么多呢,甚至一條if語(yǔ)句都沒(méi)有寫缘揪,那就是因?yàn)槲覀儼褩l件判斷都交給多態(tài)去處理了耍群,又由于子類touch繼承了基類touch,當(dāng)前處于哪種狀態(tài)找筝,當(dāng)前touch對(duì)象的類別就自帶了含義和區(qū)分的功能世吨,當(dāng)子類new給touch的時(shí)候,touch已然“記住”了當(dāng)前狀態(tài)是哪個(gè)呻征,然后再判斷下觸摸事件類型耘婚,對(duì)應(yīng)調(diào)用當(dāng)前子類touch的down()、move()陆赋、up()方法即可完美滿足需要沐祷。

結(jié)語(yǔ)

先就寫這么多啦。本人水平有限攒岛,加上第一次寫這種技術(shù)文章赖临,思路難免有點(diǎn)混亂,若有不足的地方懇請(qǐng)大家批評(píng)指正哈灾锯。下面一章我會(huì)繼續(xù)深入講解“編輯圖形”功能的設(shè)計(jì)與實(shí)現(xiàn)兢榨,今天就先到這里吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末顺饮,一起剝皮案震驚了整個(gè)濱河市吵聪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兼雄,老刑警劉巖吟逝,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赦肋,居然都是意外死亡块攒,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門佃乘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)囱井,“玉大人,你說(shuō)我怎么就攤上這事趣避∨优唬” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵鹅巍,是天一觀的道長(zhǎng)千扶。 經(jīng)常有香客問(wèn)我,道長(zhǎng)骆捧,這世上最難降的妖魔是什么澎羞? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮敛苇,結(jié)果婚禮上妆绞,老公的妹妹穿的比我還像新娘。我一直安慰自己枫攀,他們只是感情好括饶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著来涨,像睡著了一般图焰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹦掐,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天技羔,我揣著相機(jī)與錄音,去河邊找鬼卧抗。 笑死藤滥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的社裆。 我是一名探鬼主播拙绊,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泳秀!你這毒婦竟也來(lái)了标沪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嗜傅,失蹤者是張志新(化名)和其女友劉穎谨娜,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磺陡,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趴梢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了币他。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坞靶。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝴悉,靈堂內(nèi)的尸體忽然破棺而出彰阴,到底是詐尸還是另有隱情,我是刑警寧澤拍冠,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布尿这,位于F島的核電站簇抵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏射众。R本人自食惡果不足惜碟摆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叨橱。 院中可真熱鬧典蜕,春花似錦、人聲如沸罗洗。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)伙菜。三九已至轩缤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贩绕,已是汗流浹背典奉。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丧叽,地道東北人卫玖。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像踊淳,于是被迫代替她去往敵國(guó)和親假瞬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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