Android 高仿懂球帝我是教練效果

作者:IAM四十二
鏈接:http://www.reibang.com/p/d06c1d10bf7f
著作權(quán)歸作者所有陡舅。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)日裙,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處镊掖。

天下無(wú)敵

前言

這幾天很多歐洲球隊(duì)來(lái)中國(guó)進(jìn)行熱身賽域慷,不知道喜歡足球的各位小伙伴們有沒(méi)有看球环形。喜歡足球的朋友可能知道懂球帝APP冲九,鄙人也經(jīng)常使用這個(gè)應(yīng)用谤草,里面有一個(gè)我是教練的功能挺好玩,就是可以模擬教練員的身份,排兵布陣莺奸;本著好奇心簡(jiǎn)單模仿了一下丑孩,在這里和大家分享。

效果圖

老規(guī)矩灭贷,先上效果圖看看模仿的像不温学。

add_player.gif

move_player

玩過(guò)我是教練這個(gè)功能的小伙伴可以對(duì)比一下。

總的來(lái)說(shuō)甚疟,這樣的一個(gè)效果仗岖,其實(shí)很簡(jiǎn)單,就是一個(gè)view隨著手指在屏幕上移動(dòng)的效果览妖,外加一個(gè)圖片替換的動(dòng)畫(huà)轧拄。但就是這些看似簡(jiǎn)單的效果,在實(shí)現(xiàn)的過(guò)程中也是遇到了很多坑讽膏,漲了許多新姿勢(shì)檩电。好了,廢話(huà)不說(shuō)府树,代碼走起(??ˇ?ˇ?)是嗜。

自定義View-BallGameView

整個(gè)內(nèi)容中最核心的就是一個(gè)自定義View-BallGameView,就是屏幕中綠色背景挺尾,有氣泡和球員圖片的整個(gè)view鹅搪。

說(shuō)到自定義View,老生常談遭铺,大家一直都在學(xué)習(xí)丽柿,卻永遠(yuǎn)都覺(jué)得自己沒(méi)有學(xué)會(huì),但是自定義View的知識(shí)本來(lái)就很多呀魂挂,想要熟練掌握甫题,必須假以時(shí)日

既然是自定View就從大家最關(guān)心的兩個(gè)方法 onMeasure和onDraw 兩個(gè)方法說(shuō)起涂召。這里由于是純粹繼承自View坠非,就不考慮onLayout的實(shí)現(xiàn)了。

測(cè)量-onMeasure

這里onMeasure()方法的實(shí)現(xiàn)很簡(jiǎn)單果正,簡(jiǎn)單的用屏幕的寬度規(guī)定了整個(gè)View 的寬高炎码;至于1.3這個(gè)倍數(shù)盟迟,完全一個(gè)估算值,不必深究潦闲。

繪制-onDraw

onDraw()方法是整個(gè)View中最核心的方法攒菠。

可以看到,在onDraw方法里歉闰,我們主要使用了canvas.drawBitmap 方法辖众,繪制了很多圖片。下面就簡(jiǎn)單了解一下canvas.drawBitmap 里的兩個(gè)重載方法和敬。

  • drawBitmap(Bitmap bitmap,Rect src,Rect dst,Paint paint)

drawBitmap(Bitmap bitmap,Rect src,Rect dst,Paint paint),這個(gè)重載方法主要是通過(guò)兩個(gè)Rectangle 決定了bitmap以怎樣的形式繪制出來(lái)凹炸。簡(jiǎn)單來(lái)說(shuō),src 這個(gè)長(zhǎng)方形決定了“截取”bitmap的大小昼弟,dst 決定了最終繪制出來(lái)時(shí)Bitmap應(yīng)該占有的大小啤它。。就拿上面的代碼來(lái)說(shuō)

? ? ? ?

bitmapRect 是整個(gè)backgroundBitmap的大小私杜,mViewRect也就是我們?cè)趏nMeasure里規(guī)定的整個(gè)視圖的大小蚕键,這樣相當(dāng)于把battle_bg這張圖片,以scaleType="fitXY"的形式畫(huà)在了視圖大小的區(qū)域內(nèi)衰粹。這樣锣光,你應(yīng)該理解這個(gè)重載方法的含義了。

  • drawBitmap(Bitmap bitmap, float left, float top, Paint paint)

?

這個(gè)重載方法應(yīng)該很容易理解了铝耻,left誊爹,top 規(guī)定了繪制Bitmap的左上角的坐標(biāo),然后按照其大小正常繪制即可瓢捉。

這里我們所有的氣泡(球員位置)都是使用這個(gè)方法繪制的频丘。足球場(chǎng)上有11個(gè)球員,因此我們通過(guò)數(shù)組預(yù)先定義了11個(gè)氣泡的初始位置泡态,然后通過(guò)其坐標(biāo)位置搂漠,繪制他們。為了繪制精確某弦,需要減去每張圖片自身的寬高桐汤,這應(yīng)該是很傳統(tǒng)的做法了。

同時(shí)靶壮,在之后的觸摸反饋機(jī)制中怔毛,我們會(huì)根據(jù)手指的滑動(dòng),修改這些坐標(biāo)值腾降,這樣就可以隨意移動(dòng)球員在場(chǎng)上的位置了拣度;具體實(shí)現(xiàn),結(jié)合代碼中的注釋?xiě)?yīng)該很容易理解了,就不再贅述抗果;可以查看完整源碼BallGameView筋帖。

文字居中繪制

這里再說(shuō)一個(gè)在繪制過(guò)程中遇到一個(gè)小問(wèn)題,可以看到在整個(gè)視圖底部窖张,繪制了一個(gè)半透明的圓角矩形幕随,并在他上面繪制了一行黃色的文字蚁滋,這行文字在水平和垂直方向都是居中的宿接;使用TextPaint 繪制文字實(shí)現(xiàn)水平居中是很容易的事情,只需要設(shè)置mTipPaint.setTextAlign(Paint.Align.CENTER)即可辕录,但是在垂直方向?qū)崿F(xiàn)居中睦霎,就沒(méi)那么簡(jiǎn)單了,這里需要考慮一個(gè)文本繪制時(shí)基線(xiàn)的問(wèn)題走诞,具體細(xì)節(jié)可以參考這篇文章副女,分析的很詳細(xì)。

我們?cè)谶@里為了使文字在圓角矩形中居中蚣旱,如下實(shí)現(xiàn)碑幅。

? ? ?

圓角矩形的垂直中心點(diǎn)的基礎(chǔ)上,再一次做修正塞绿,確保實(shí)現(xiàn)真正的垂直居中沟涨。

好了,結(jié)合扔物線(xiàn)大神所總結(jié)的自定義View關(guān)鍵步驟异吻,以上兩點(diǎn)算是完成了繪制和布局的工作,下面就看看觸摸反饋的實(shí)現(xiàn)裹赴。

觸摸反饋-onTouchEvent

這里觸摸反饋機(jī)制,使用到了GestureDetector這個(gè)類(lèi)诀浪;這個(gè)類(lèi)可以用來(lái)進(jìn)行手勢(shì)檢測(cè)棋返,用于輔助檢測(cè)用戶(hù)的單擊、滑動(dòng)雷猪、長(zhǎng)按睛竣、雙擊等行為。內(nèi)部提供了OnGestureListener求摇、OnDoubleTapListener和OnContextClickListener三個(gè)接口射沟,并提供了一系列的方法,比如常見(jiàn)的

  • onSingleTapUp : 手指輕觸屏幕離開(kāi)

  • onScroll : 滑動(dòng)

  • onLongPress: 長(zhǎng)按

  • onFling: 按下后月帝,快速滑動(dòng)松開(kāi)(類(lèi)似切水果的手勢(shì))

  • onDoubleTap : 雙擊

可以看到躏惋,使用這個(gè)類(lèi)可以更加精確的處理手勢(shì)操作。

這里引入GestureDetector的原因是這樣的嚷辅,單獨(dú)在onTouchEvent處理所有事件時(shí)簿姨,在手指點(diǎn)擊屏幕的瞬間,很容易觸發(fā)MotionEvent.ACTION_MOVE事件,導(dǎo)致每次觸碰氣泡扁位,被點(diǎn)擊氣泡的位置都會(huì)稍微顫抖一下准潭,位置發(fā)生輕微的偏移,體驗(yàn)十分糟糕域仇。采用GestureDetector對(duì)手指滑動(dòng)的處理刑然,對(duì)點(diǎn)擊和滑動(dòng)的檢測(cè)顯得更加精確

這里m_gestureDetector.onTouchEvent(event),這樣就可以讓GestureDetector在他自己的回調(diào)方法OnGestureListener里暇务,處理觸摸事件泼掠。

上面的邏輯很簡(jiǎn)單,動(dòng)畫(huà)正在進(jìn)行是垦细,直接返回择镇。MotionEvent.ACTION_DOWN事件發(fā)生時(shí)的處理邏輯,通過(guò)注釋很容易理解括改,就不再贅述腻豌。

當(dāng)我們點(diǎn)擊到某個(gè)氣泡時(shí),就獲取到了當(dāng)前選中位置currentPos嘱能;下面看看GestureDetector的回調(diào)方法吝梅,是怎樣處理滑動(dòng)事件的。

SimpleOnGestureListener 默認(rèn)實(shí)現(xiàn)了OnGestureListener惹骂,OnDoubleTapListener, OnContextClickListener這三個(gè)接口中所有的方法苏携,因此非常方便我們使用GestureDetector進(jìn)行特定手勢(shì)的處理。

這里的處理很簡(jiǎn)單析苫,當(dāng)氣泡被選中時(shí)moveEnable=true兜叨,通過(guò)onScroll回調(diào)方法返回的距離,不斷更新當(dāng)前位置的坐標(biāo)衩侥,同時(shí)記得限制一下手勢(shì)滑動(dòng)的邊界国旷,總不能把球員移動(dòng)到場(chǎng)地外面吧o(╯□╰)o,最后的postInvalidate()是關(guān)鍵茫死,觸發(fā)onDraw方法跪但,實(shí)現(xiàn)重新繪制。

這里有一個(gè)細(xì)節(jié)峦萎,不知你發(fā)現(xiàn)沒(méi)有屡久,我們?cè)诟伦鴺?biāo)的時(shí)候,每次都是在當(dāng)前坐標(biāo)的位置爱榔,減去了滑動(dòng)距離(distanceX/distanceY)被环。這是為什么(⊙o⊙)?,為什么不是加呢详幽?

我們可以看看這個(gè)回調(diào)方法的定義

? ?

可以看到筛欢,這里特定強(qiáng)調(diào)了This is NOT the distance between {@code e1}and {@code e2}浸锨,就是說(shuō)這個(gè)距離并不是兩次事件e1和e2 之間的距離。那么這個(gè)距離又是什么呢版姑?那我們就找一找到底是在哪里觸發(fā)了這個(gè)回調(diào)方法.

最終在GestureDetector類(lèi)的onTouchEvent()方法里找到了觸發(fā)這個(gè)方法發(fā)生的地方:

這里還涉及到多指觸控的考慮柱搜,情況較為復(fù)雜;簡(jiǎn)單說(shuō)一下結(jié)論剥险,在ACTION_MOVE時(shí)聪蘸,會(huì)從上一次手指離開(kāi)的距離,減去此次手指觸碰的位置表制;這樣當(dāng)scrollX>0時(shí)健爬,就是在向右滑動(dòng),反之向左夫凸;scrollY > 0 時(shí)浑劳,是在向上滑動(dòng)阱持,反之向下夭拌;因此,這兩個(gè)距離和我們習(xí)以為常的方向恰好都是相反的衷咽,因此鸽扁,在更新坐標(biāo)時(shí),需要做相反的處理镶骗。

有興趣的同學(xué)桶现,可以把上面的“-”改成“+”,嘗試運(yùn)行一下代碼鼎姊,就會(huì)明白其中的道理了骡和。

好了,到了這里按照繪制相寇,布局慰于,觸摸反饋的順序我們已經(jīng)完成了BallGameView這個(gè)自定義View自己的內(nèi)容了,但是我們還看到在點(diǎn)擊下面的球員頭像時(shí)唤衫,還有一個(gè)簡(jiǎn)單的動(dòng)畫(huà)婆赠,下面就看看動(dòng)畫(huà)是如何實(shí)現(xiàn)的。

動(dòng)畫(huà)效果

首先說(shuō)明一下佳励,底部球員列表是一個(gè)橫向的RecyclerView休里,這樣一個(gè)橫向滑動(dòng)的雙列展示的RecyclerView 應(yīng)該很簡(jiǎn)單了,這里就不再詳述赃承。文末有源碼妙黍,最后可以查看。

這里看一下每一個(gè)RecyclerView中item的點(diǎn)擊事件

這里可以看到調(diào)用了GameView的updatePlayer方法:

這個(gè)動(dòng)畫(huà)瞧剖,簡(jiǎn)單來(lái)說(shuō)就是一個(gè)一階貝塞爾曲線(xiàn)拭嫁。根據(jù)RecyclerView中item在屏幕中的位置,構(gòu)造一個(gè)一模一樣的ImageView添加到根視圖中,然后通過(guò)一個(gè)屬性動(dòng)畫(huà)噩凹,在屬性值不斷更新時(shí)巴元,在回調(diào)方法中不斷調(diào)用setTranslation方法,改變這個(gè)ImageView的位置驮宴,呈現(xiàn)出動(dòng)畫(huà)的效果逮刨。動(dòng)畫(huà)結(jié)束后,將這個(gè)ImageView從視圖移除堵泽,同時(shí)氣泡中的數(shù)據(jù)即可修己,最后再次invalidate導(dǎo)致整個(gè)視圖重新繪制,這樣動(dòng)畫(huà)完成時(shí)迎罗,氣泡就被替換為真實(shí)的頭像了睬愤。

到這里,基本上所有功能纹安,都實(shí)現(xiàn)了尤辱。最后就是把自己排出來(lái)的陣型,保存為圖片分享給小伙伴了厢岂。這里主要說(shuō)一下保存圖片的實(shí)現(xiàn)光督;分享功能,就不作為重點(diǎn)討論了塔粒。

自定義View保存為Bitmap

一個(gè)典型的AsyncTask實(shí)現(xiàn)结借,文件流的輸出,沒(méi)什么多說(shuō)的卒茬。主要是存儲(chǔ)目錄的選擇船老,這里有個(gè)技巧,如果沒(méi)有特殊限制圃酵,平時(shí)我們做開(kāi)發(fā)的時(shí)候柳畔,可以 把一些存儲(chǔ)路徑做如下定義

  • mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES):代表/storage/emulated/0/Android/data/{packagname}/files/Pictures

  • mContext.getExternalCacheDir() 代表 /storage/emulated/0/Android/data/{packagname}/cache

對(duì)于mContext.getExternalFilesDir還可定義為Environment.DIRECTORY_DOWNLOADS,Environment.DIRECTORY_DOCUMENTS等目錄辜昵,對(duì)應(yīng)的文件夾名稱(chēng)也會(huì)變化荸镊。

這個(gè)目錄中的內(nèi)容會(huì)隨著用戶(hù)卸載應(yīng)用,一并刪除堪置。最重要的是躬存,讀寫(xiě)這個(gè)目錄是不需要權(quán)限的,因此省去了每次做權(quán)限判斷的麻煩舀锨,而且也避免了沒(méi)有權(quán)限時(shí)的窘境岭洲。

到這里,模仿功能坎匿,全部都實(shí)現(xiàn)了盾剩。下面稍微來(lái)一點(diǎn)額外的擴(kuò)展雷激。

我們希望圖片保存后可以在通知欄提示用戶(hù),點(diǎn)擊通知欄后可以通過(guò)手機(jī)相冊(cè)查看保存的圖片告私。

擴(kuò)展-Android ?Notification & FileProvider 的使用

Android 系統(tǒng)中的通知欄屎暇,隨著版本的升級(jí),已經(jīng)形成了固定了寫(xiě)法驻粟,在Builder模式的基礎(chǔ)上根悼,通過(guò)鏈?zhǔn)綄?xiě)法,可以非常方便的設(shè)置各種屬性蜀撑。這里重點(diǎn)說(shuō)一下PendingIntent的用法挤巡,我們知道這個(gè)PendingIntent 顧名思義,就是處于Pending狀態(tài)酷麦,當(dāng)我們點(diǎn)擊通知欄矿卑,就會(huì)觸發(fā)他所包含的Intent。

嚴(yán)格來(lái)說(shuō)沃饶,通過(guò)自己的應(yīng)用想用手機(jī)自帶相冊(cè)打開(kāi)一張圖片是無(wú)法實(shí)現(xiàn)的母廷,因?yàn)闊o(wú)法保證每一種手機(jī)上面相冊(cè)的包名是一樣的,因此這里我們創(chuàng)建ACTION=Intent.ACTION_VIEW的 Intent绍坝,去匹配系統(tǒng)所有符合這個(gè)Action 的Activity徘意,系統(tǒng)相冊(cè)一定是其中之一。

到這里轩褐,還有一定需要注意,Android 7.0 開(kāi)始玖详,無(wú)法以file://xxxx 形式向外部應(yīng)用提供內(nèi)容了把介,因此需要考慮使用FileProvider。當(dāng)然蟋座,對(duì)這個(gè)問(wèn)題拗踢,Google官方提供了完整的使用實(shí)例,實(shí)現(xiàn)起來(lái)都是套路向臀,沒(méi)有什么特別之處巢墅。

重點(diǎn)記住下面的對(duì)應(yīng)關(guān)系即可:

按照上面,我們存儲(chǔ)圖片的目錄券膀,我們?cè)趂ile_path.xml 做如下定義即可:

在AndroidManifest中完成如下配置 :

? ?

這樣君纫,當(dāng)Build.VERSION.SDK_INT大于等于24及Android7.0時(shí),可以安心的使用FileProvider來(lái)和外部應(yīng)用共享文件了芹彬。

最后

好了蓄髓,從一個(gè)簡(jiǎn)單的自定義View 出發(fā),又牽出了一大堆周邊的內(nèi)容舒帮。好在会喝,總算完整的說(shuō)完了陡叠。

特別申明

以上代碼中所用到的圖片資源,全部源自懂球帝APP內(nèi)肢执;此處對(duì)應(yīng)用解包枉阵,只是本著學(xué)習(xí)的目的,沒(méi)有其他任何用意预茄。


源碼地址: Github-AndroidAnimationExercise岭妖。

有興趣的同學(xué)歡迎 star & ?fork。


如果你有好的文章想和大家分享歡迎投稿反璃,直接向我投遞文章鏈接即可昵慌。



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市淮蜈,隨后出現(xiàn)的幾起案子斋攀,更是在濱河造成了極大的恐慌,老刑警劉巖梧田,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淳蔼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡裁眯,警方通過(guò)查閱死者的電腦和手機(jī)鹉梨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)穿稳,“玉大人存皂,你說(shuō)我怎么就攤上這事》晁遥” “怎么了旦袋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)它改。 經(jīng)常有香客問(wèn)我疤孕,道長(zhǎng),這世上最難降的妖魔是什么央拖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任祭阀,我火速辦了婚禮,結(jié)果婚禮上鲜戒,老公的妹妹穿的比我還像新娘专控。我一直安慰自己,他們只是感情好袍啡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布踩官。 她就那樣靜靜地躺著,像睡著了一般境输。 火紅的嫁衣襯著肌膚如雪蔗牡。 梳的紋絲不亂的頭發(fā)上颖系,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音辩越,去河邊找鬼嘁扼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛黔攒,可吹牛的內(nèi)容都是我干的趁啸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼督惰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼不傅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起赏胚,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤访娶,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后觉阅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體崖疤,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年典勇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劫哼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡割笙,死狀恐怖权烧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咳蔚,我是刑警寧澤豪嚎,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站谈火,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏舌涨。R本人自食惡果不足惜糯耍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囊嘉。 院中可真熱鬧温技,春花似錦、人聲如沸扭粱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琢蛤。三九已至蜓堕,卻和暖如春抛虏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背套才。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工迂猴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人背伴。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓沸毁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親傻寂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子息尺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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