先上個成品圖
實現(xiàn):
就倆對象:CardView、GameView
·CardView:卡片類(每個數(shù)字方塊對應(yīng)的對象)
屬性:取值(value)瀑罗,取值如:2,4,8,16....2048? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?顏色(color)雏掠,不同的取值有對應(yīng)不同的顏色值(我是通過colorspy一個一個取的==)
方法:同時這個類暴露一個setValue方法來改變方塊的取值。調(diào)用這個方法的同時不僅要改變? ? ? ? ? ? ? ?value的值摧玫,由于value的改變而帶來color的變化绑青,所以需要調(diào)用invalidate方法來重繪這? ? ? ? ? ? ?個View,(PS這里還有個縮放動畫)颅停。
注意: 1)這是一個圓角矩形掠拳。繪制圓角矩形有兩種方法:canvas.drawRoundRect(rectf,? ? ? ? ? ? ? ? ? ? ? ? radius,radius, paint)纸肉、canvas.drawRoundRect(l, t, r, b, rx, ry, paint)柏肪。我使用的是第一? ? ? ? ? ? ? ? 種,因為可以兼容到21以下烦味。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2)這次我才發(fā)現(xiàn)自定義View如果要可以wrap_content的話,需要重寫onMeasure方法柏靶。? ? ? ? ? ? ? 哈屎蜓,不要笑我,我之前就知道重寫onDraw辆苔。主要搞清楚MeasureSpec的三種Mode就可? ? ? ? ? ? ? 以了(AT_MOST,EXACTLY和UNSPECIFIED)扼劈。網(wǎng)上有很多文章,我這里不說了哈街佑。
·GameView:游戲主界面
這是一個ViewGroup捍靠,這是我第一自定義ViewGroup吼吼吼。最重要的就是重寫onLayout方法磁携,告訴它每一個子View要擺在哪良风。具體請閱讀hongyang大神的博客之 Android 手把手教您自定義ViewGroup(一)
游戲中烟央,我們可以注意到每個卡片移動后,它的背景就顯示出來了(或者說它的位置就空出來了)粮呢。我把這些背景看做是16個值為0的CardView钞艇。這16個背景(或者說空位)的位置是不會變化的。用一個List來維護(hù)這16個背景挺物,每次onLayout的時候飘弧,把它們繪制到對應(yīng)的位置上即可砚著。
再次就是那些可以移動的卡片了赖草,因為它們出現(xiàn)的位置并不固定剪个,而且有的位置可以為空,并且可能不連續(xù)乎折,所以我用一個HashMap來維護(hù)它們侵歇,HashMap的鍵用來存儲它的位置(index),值來存對應(yīng)的CardView坟冲。
接下來溃蔫,我們要重寫onTouchEvent方法來監(jiān)聽它的觸摸事件伟叛,判斷用戶是進(jìn)行的操作是否有效,進(jìn)行了什么操作统刮。具體實現(xiàn)就是在ACTION_DOWN的時候把觸碰點的xy坐標(biāo)記錄下來侥蒙,在ACTION_MOVE的時候和當(dāng)前的的坐標(biāo)進(jìn)行對比。不細(xì)說了学搜。需要注意的是醋旦,只要進(jìn)行了有效的滑動,就會觸發(fā)相關(guān)事件(例如卡片的移動,卡片的合并)咧最,那么這次(到ACTION_UP為止)的觸碰事件就結(jié)束了御雕,繼續(xù)移動是不會再次觸發(fā)的。
在說卡片的移動和合并之前滥搭,再說一個比較簡單的細(xì)節(jié)酸纲。每次移動后都會在隨機(jī)的一個空位中出現(xiàn)一個2或者4。
最后來說一下卡片的移動和合并瑟匆,其實這一步如果不給卡片的移動加上動畫并不難闽坡。那就先說不加動畫的吧。
用向左移動當(dāng)做例子吧:我們只需要看一行愁溜,其他行循環(huán)處理就行(用i的循環(huán)行)疾嗅。從左向右看(用j來循環(huán)列),會有如下兩種情況:
如果這個位置上(j)的CardView是空的話冕象,我們要從這一行的(j+1)開始去尋找第一個不為空的CardView,讓它移動到j(luò)上代承。j不變渐扮。如果找了一圈沒找到的話论悴,那說明j之后已經(jīng)沒有CardView了, 這個循環(huán)就可以break了墓律。
如果這個位置上的CardView不為空的話膀估,同樣我們要從這一行的(j+1)開始去尋找第一個不為空的CardView,如果它的value和當(dāng)前CardView(j)上的value相同的話耻讽,就把當(dāng)前CardView的值加倍玖像,把找到的位置上的CardView置空。j++齐饮。如果找了一圈沒找到的話捐寥,那說明j之后已經(jīng)沒有CardView了, 這個循環(huán)就可以break了祖驱。
其他三種情況是類似的握恳。我就不多講了。其實邏輯并不復(fù)雜捺僻。
加上動畫就麻煩了乡洼。我們知道動畫的執(zhí)行是需要時間的。如果在動畫執(zhí)行期間匕坯,已經(jīng)進(jìn)入了下一次的循環(huán)束昵,就會導(dǎo)致數(shù)據(jù)混亂。我的做法就是添加一個布爾類型的全局變量(isMoveAnimating)葛峻,在動畫執(zhí)行期間它的值是true锹雏,只有它執(zhí)行完成后才回變成false。只有當(dāng)isMoveAnimating為false的時候循環(huán)才能繼續(xù)术奖。但是礁遵,還是以向左移動為例轻绞,它每一行之間動畫的執(zhí)行是不會互相影響的,所以就考慮到把每一行要做的事情抽取出來變?yōu)橐粋€單獨的Thread佣耐,而isMoveAnimating就是每一個Thread的局部變量政勃,不會互相影響。