360加速球效果实现
效果演示:
懸浮小球在移動(dòng)時(shí)會(huì)換成一張圖片,當(dāng)松開時(shí),會(huì)自動(dòng)停靠在一側(cè),并且恢復(fù)原來的形式。
點(diǎn)擊懸浮小球,會(huì)從屏幕底部滑入一個(gè)菜單欄。
雙擊加速球,會(huì)有水不斷注入的動(dòng)畫效果,并且水面逐漸平靜下來。
單擊加速球,水面會(huì)震蕩,最后恢復(fù)平靜。
這個(gè)的實(shí)現(xiàn)是觀看了慕課網(wǎng)視頻后,在其基礎(chǔ)上做了一些適當(dāng)?shù)男薷?#xff0c;如:在android23以上,如何申請(qǐng)動(dòng)態(tài)權(quán)限。因?yàn)檫@個(gè)實(shí)現(xiàn)需要用到一個(gè)危險(xiǎn)權(quán)限,彈出窗口。有興趣的可以前往:http://www.imooc.com/learn/693。
整體思路:
可以看下項(xiàng)目結(jié)構(gòu)。在MainActivity開啟了一個(gè)服務(wù)MyFloatService,這個(gè)服務(wù)獲得了FloatViewManager管理類的實(shí)例,view包下有懸浮球的view以及加速球的view。而這個(gè)管理類則可控制這些view的顯示,隱藏,位置。開啟服務(wù)后,顯示了懸浮球,通過對(duì)懸浮球進(jìn)行Touch事件的監(jiān)聽,實(shí)現(xiàn)了拖動(dòng)改變圖片,自動(dòng)停靠在一側(cè),單擊出現(xiàn)菜單欄。在菜單欄的FloatMenuView類中,實(shí)現(xiàn)了對(duì)自身的Touch監(jiān)聽,雙擊出現(xiàn)水不斷注入效果,單擊水面震蕩。單擊加速球其他的地方則隱藏菜單欄。
源碼已經(jīng)開放在github上:https://github.com/My-Zzw/FloatView360Demo
在這篇文章中,簡(jiǎn)單提一下使用到的技巧,以及一些主要的思路,一些思想,一些需要注意的地方。
1.權(quán)限問題
當(dāng)點(diǎn)擊主界面的開啟懸浮窗按鈕時(shí),開啟MyFloatService服務(wù)。當(dāng)服務(wù)創(chuàng)建時(shí),進(jìn)行判斷,如果運(yùn)行在大于等于6.0系統(tǒng)上,則跳轉(zhuǎn)到應(yīng)用信任界面,允許該應(yīng)用所有權(quán)限。在這個(gè)項(xiàng)目中,需要用到 SYSTEM_ALERT_WINDOW 權(quán)限,記得要在manifest中注冊(cè)。
2.在FloatViewManager中,是如何控制懸浮球以及菜單欄顯示和隱藏的。
首先已經(jīng)實(shí)例化了懸浮球View和菜單欄的View
已經(jīng)實(shí)例化了View。那么View要顯示在界面,就需要一些布局參數(shù),比如view的寬和高,位置在哪里?所以需要一個(gè) WindowManager.LayoutParams 對(duì)象。這樣,要顯示的view已經(jīng)拿到,如何顯示也拿到,那么就可以添加到界面顯示了。顯示到界面上讓用戶看到,需要實(shí)例化一個(gè) WindowManager 的對(duì)象,通過其addView方法添加到窗口。例如顯示懸浮窗。
//顯示菜單欄private void showFloatMenuView() {WindowManager.LayoutParams params2 = new WindowManager.LayoutParams();params2.width = getScreenWidth();params2.height = getScreenHeight() - getStatusHeigt();//高度為全屏。params2.gravity = Gravity.BOTTOM | Gravity.LEFT;params2.x = 0;params2.y = 0;params2.type = WindowManager.LayoutParams.TYPE_PHONE;//布局參數(shù)的類型為手機(jī)類型。意味著 在所有頁(yè)面的上面。params2.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//不和其他應(yīng)用搶焦點(diǎn)params2.format = PixelFormat.RGBA_8888;//設(shè)置透明度windowManager.addView(floatMenuView, params2);//通過manager添加視圖到窗口。第一個(gè)參數(shù)為要添加的view,第二個(gè)為view的布局參數(shù)}3.實(shí)現(xiàn)懸浮窗在點(diǎn)擊移動(dòng)時(shí)變成一張火箭的圖片,松開時(shí)自動(dòng)附著在其中一側(cè)。
首先對(duì)懸浮窗注冊(cè)一個(gè)事件監(jiān)聽器。類型為觸摸事件。
接著實(shí)現(xiàn)這個(gè)事件監(jiān)聽器的觸發(fā)事件。當(dāng)用戶按下,移動(dòng),抬起時(shí)。
需要說明的是,當(dāng)監(jiān)聽到用戶在移動(dòng)這個(gè)懸浮球時(shí),調(diào)用了懸浮球?qū)ο蟮膕etDrawState方法通知到這個(gè)懸浮球在移動(dòng)中,那么懸浮球?qū)ο缶蜁?huì)調(diào)用invalidate進(jìn)行重繪。在onDraw方法去drawBitmap。
當(dāng)用戶松開時(shí),判斷最后松開的懸浮窗x坐標(biāo)如果大于或者小于屏幕的一半,修改懸浮窗的params.x布局參數(shù)的x坐標(biāo)。最后調(diào)用updateViewLayout方法,更新懸浮窗。
4.獲取狀態(tài)欄高度。
這里是采用了反射去獲取。這樣的好處是能獲取到程序運(yùn)行到具體的設(shè)備或者模擬器的狀態(tài)欄高度。
5.隱藏懸浮球或者菜單欄。
例如隱藏菜單欄,拿到窗口管理對(duì)象remove即可。
6.懸浮球 FloatCircleView 的實(shí)現(xiàn)。
通過實(shí)際效果可以知道,其實(shí)現(xiàn)原理很簡(jiǎn)單。首先是如何繪制。
繪制:繪制有兩種狀態(tài)。一是不移動(dòng)狀態(tài)下的繪制,使用兩只畫筆,一個(gè)畫筆繪制圓形,一個(gè)畫筆繪制文本。另一個(gè)是移動(dòng)狀態(tài)下的繪制。移動(dòng)狀態(tài)下是將一個(gè)在drawable下的資源圖片進(jìn)行繪制。
如何通知到懸浮球什么時(shí)候在移動(dòng)?通過調(diào)用下面這個(gè)方法。這個(gè)方法會(huì)改變狀態(tài)的標(biāo)識(shí),并且調(diào)用invalidate去重新繪制。
// 在移動(dòng)中,則進(jìn)行狀態(tài)的改變public void setDrawState(boolean b) {draw = b;invalidate();//每當(dāng)狀態(tài)改變的時(shí)候,需要重新執(zhí)行draw方法,進(jìn)行重新繪制}要保證移動(dòng)時(shí)候,改變的圖片的大小也要和懸浮球大小一致,則需要
// 初始化移動(dòng)需要的bitmap。并且縮放到合適大小Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.hj);bitmap = Bitmap.createScaledBitmap(src,width,height,true);7.進(jìn)度球 ProcessView 的實(shí)現(xiàn)。
進(jìn)度球放置在了菜單欄里面。通過實(shí)際效果知道,需要三支畫筆。繪制圓形,繪制水,繪制文本。
7.1 雙擊進(jìn)度球動(dòng)畫效果的實(shí)現(xiàn):水不斷被注入,并且水面越來越平緩
圖解是這樣的:
一支畫筆繪制了圓形,另外一支畫筆在圓形上繪制了一個(gè)“矩形”,不過這個(gè)矩形有點(diǎn)特殊的是上面的邊是個(gè)曲線,其他三邊都是直線。
在繪制這個(gè)特殊的矩形,是用畫筆通過Path路徑去繪制,先用lineTo方法依次繪制三條直線的邊,在繪制到這個(gè)曲線的時(shí)候再用rQuadTo方法繪制曲線,也叫貝塞爾曲線。
那么動(dòng)畫效果:水不斷被注入,并且水面越來越平緩。是如何實(shí)現(xiàn)的呢?
水不斷注入,其實(shí)就是這個(gè)特殊的矩形不斷的重新繪制,繪制。并且每次繪制時(shí),矩形的高都是不斷增加的,在時(shí)間很短的情況下,感覺水面在不斷的升高。
水面越來越平緩,則是每次在重新繪制的時(shí)候,貝塞爾曲線的振幅不斷的在減小。
需要了解貝塞爾曲線可能才能明白在說什么,所以有不明白的可以去百度下Android貝塞爾曲線。
繪制的矩形是在覆蓋在圓形上,那么矩形超出圓形的部分如何隱藏?
我們可以在初始化畫筆時(shí),設(shè)置繪制矩形的畫筆的過度模式,也就是圖像混合模式。當(dāng)矩形畫筆繪制的圖像和其他圖像混合時(shí),設(shè)置其模式為重疊。也就是說,只顯示重疊的部分。
7.2 單擊進(jìn)度球動(dòng)畫效果的實(shí)現(xiàn):水面不斷震蕩,最后平緩。
圖解:
其實(shí)還是不斷的去重新繪制矩形,不過矩形的高度不需要變化,但是貝塞爾曲線需要變化,每繪制一次貝塞爾曲線,那么在下一次則繪制相反的貝塞爾曲線,再下一次則恢復(fù)原來的貝塞爾曲線即可。
并且在每次重新繪制的時(shí)候,同樣需要將振幅減小。
如何每次去計(jì)算振幅的減小 以及 如何判斷哪次需要將貝塞爾曲線取反。讀者可以查看代碼結(jié)合注釋。這里不詳細(xì)介紹。
//在這進(jìn)行繪制.@Overrideprotected void onDraw(Canvas canvas) {//畫圓bitmapCanvas.drawCircle(width/2,height/2,width/2,circlePaint);//繪制水波紋進(jìn)度。基本思想是繪制一個(gè)矩形。不過矩形除了上面的邊使用貝塞爾曲線外,其他都是直線。path.reset();//重置path的所有屬性//起始點(diǎn)定義。重新規(guī)定路徑的起始坐標(biāo),默認(rèn)為(0,0)。moveTo()移動(dòng)畫筆。//這里將起始點(diǎn)的x移動(dòng)到右邊,y則是變化的。也就是矩形的右上角的點(diǎn)float y = (1 - (float)current_progress/max_progress) * height;path.moveTo(width,y);//第二個(gè)點(diǎn)定義。右下角,繪制直線。也就是右面的邊path.lineTo(width,height);//第三個(gè)點(diǎn)定義。左下角,繪制直線。也就是下面的邊path.lineTo(0,height);//第四個(gè)點(diǎn)定義。左上角,繪制直線。也就是左面的邊path.lineTo(0,y);//繪制上面的邊。也就是貝塞爾曲線。//根據(jù)進(jìn)度球?qū)挾?#xff0c;設(shè)定需要的貝塞爾曲線的周期。如:250的寬度,那么7個(gè)周期為40的即可。//繪制貝賽爾曲線需要起始點(diǎn),控制點(diǎn)和結(jié)束點(diǎn)。rQuadTo方法只需要兩個(gè)參數(shù),起始點(diǎn)默認(rèn)為 未閉合路徑的最后一個(gè)點(diǎn)。//一個(gè)循環(huán)意味著一個(gè)周期的貝塞爾曲線if (!isSingleTab){//雙擊//雙擊效果:貝塞爾曲線逐漸變得平緩,最后成為直線的效果。//其實(shí)就是控制了貝賽爾曲線的振幅。也就是參數(shù)的控制點(diǎn)的y坐標(biāo)。使得y坐標(biāo)逐漸變成0。也就是rQuadTo中的第二個(gè)參數(shù)//在這里設(shè)定了振幅為10的話,也就是控制點(diǎn)的y坐標(biāo),隨著當(dāng)前進(jìn)度不斷增加到接近目標(biāo)進(jìn)度,那么百分比就會(huì)不斷增加//這時(shí)用1減去他們的百分比,就會(huì)不斷減小。用這結(jié)果去乘振幅。就能使得振幅不斷減小.float d = (1 - ((float)current_progress / progress)) * 10;for (int i = 0; i < 7 ; i++){path.rQuadTo(10,d,20,0);path.rQuadTo(10,-d,20,0);}} else {//單擊//單擊效果:水不斷震蕩,最后便于平緩。//其實(shí)就是貝塞爾曲線每次周期不斷取反,也就是高的變低,低的變高.//這里是水波紋波動(dòng)50次,也就是50個(gè)周期.為什么要模2?//因?yàn)閏ount是每次減1。如果模2的結(jié)果是0,那么為一個(gè)周期。再減1,模2的結(jié)果不是0.則為另外一個(gè)周期,就可以去實(shí)現(xiàn)相反的效果。//要使得貝賽爾曲線逐漸變得平緩,和上面寫的一樣道理。使得振幅不斷減小.注意count是每次-1//第一解決貝塞爾曲線不斷取反。第二解決貝塞爾曲線振幅也就是控制點(diǎn)的y坐標(biāo)不斷減小。//以后凡是遇到類似這種,需要多個(gè)來配合的,那么先解決一個(gè),再解決下一個(gè)。float d = (float)count/50 * 10;if (count%2 == 0){for (int i = 0; i < 7 ; i++){path.rQuadTo(20,d,40,0);path.rQuadTo(20,-d,40,0);}} else {for (int i = 0; i < 7 ; i++){path.rQuadTo(20,-d,40,0);path.rQuadTo(20,d,40,0);}}}path.close();//路徑閉合bitmapCanvas.drawPath(path,progressPaint);//繪制文本String text = (int)(((float)current_progress/max_progress) * 100) + "%";//獲取文本寬度。便于規(guī)定繪制文本時(shí)候的x坐標(biāo)float textWidth = textPaint.measureText(text);//獲取文本規(guī)格。便于規(guī)定繪制文本時(shí)候的y坐標(biāo);Paint.FontMetrics metrics = textPaint.getFontMetrics();float baseLine = height/2 - (metrics.ascent + metrics.descent)/2;//開始繪制文本bitmapCanvas.drawText(text, width/2-textWidth/2,baseLine,textPaint);//利用自定義bitmap的畫布繪制完畢后。再通過顯示的畫布,繪制這自定義的bitmapcanvas.drawBitmap(bitmap,0,0,null);}7.3 如何不斷去重新繪制?
可以采用Timer定時(shí)器去不斷重繪。但是這里使用的是 Handle.postDelayed 方法。這里面有個(gè)小技巧。也就是postDelayed 方法去執(zhí)行runable的代碼時(shí),如果條件沒達(dá)到,則再次調(diào)用自身,如果條件達(dá)到則remove。比如雙擊動(dòng)畫:
被雙擊的時(shí),先執(zhí)行一次
接著在執(zhí)行的方法去做判斷,也就是DoubleTabRunable
//雙擊時(shí),需要執(zhí)行的線程。刷新數(shù)據(jù),重繪界面。class DoubleTabRunable implements Runnable {@Overridepublic void run() {current_progress++;if (current_progress <= progress){invalidate();//重新繪制,調(diào)用onDraw方法。因?yàn)閏urrent_progress是變化的,所以重新繪制會(huì)使得進(jìn)度條有變化。handler.postDelayed(this,50);//再次調(diào)用自己} else {current_progress = 0;handler.removeCallbacks(this);}}}7.4 進(jìn)度球的繪制,先繪制在了一個(gè)自定義的bitmap中,繪制完畢后,再講這個(gè)bitmap繪制在界面中顯示。
//自己畫圖。創(chuàng)建一個(gè)空的bitmap,并且在這bitmap上傳入一個(gè)畫布才能進(jìn)行繪制。bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);bitmapCanvas = new Canvas(bitmap);8.菜單欄中放置進(jìn)度球。
FloatMenuView 繼承了LinearLayout。所以這個(gè)類本質(zhì)還是一個(gè)View。既然是View的話就會(huì)有樣式。
之前的懸浮球FloatCircleView 和 進(jìn)度球 ProcessView 都是通過畫筆在OnDraw中去繪制樣式。
在FloatMenuView ,則是事先將一個(gè)寫好的xml格式的布局文件寫好,通過View.inflate方法找到這個(gè)xml格式的布局文件后,再通過addView方法添加這個(gè)布局文件到FloatMenuView ,也就是將這個(gè)找到的布局文件和FloatMenuView 綁定。這樣在其他地方實(shí)例化FloatMenuView 后,顯示在窗口的話,樣子就是xml布局文件的樣式。
那么進(jìn)度球是如何顯示到了菜單欄中?很簡(jiǎn)單,在菜單欄的xml布局文件中,也就是剛剛說的FloatMenuView 綁定的這個(gè)xml布局文件。在里面將其實(shí)例化
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#33000000"><LinearLayout android:id="@+id/linearLayout"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#F02F3942"android:layout_alignParentBottom="true"android:clickable="true"><!--左上角文本提示--><LinearLayout android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><ImageView android:layout_width="50dp"android:layout_height="50dp"android:layout_marginLeft="10dp"android:layout_gravity="center_vertical"android:src="@drawable/four"/><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="22sp"android:textColor="#1296db"android:text="四月加速球"android:layout_marginLeft="10dp"android:layout_gravity="center_vertical"/></LinearLayout><!--實(shí)例化自定義的進(jìn)度球--><view.ProcessView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_margin="10dp"/></LinearLayout></RelativeLayout>8.1或者說是bug,或者說是技巧吧,在這里用到了。具體原因不是很清楚,以后有待查證。
當(dāng)點(diǎn)擊進(jìn)度球(包括灰色區(qū)域)以外的地方,需要隱藏菜單欄(進(jìn)度球是菜單欄的子控件)。但是點(diǎn)擊事件監(jiān)聽的是整個(gè)父控件,也就是菜單欄。而點(diǎn)擊進(jìn)度球是有其自身動(dòng)畫效果需要實(shí)現(xiàn)的。
也就是子控件不需要因?yàn)楦缚丶o監(jiān)聽到。
這樣解決:給進(jìn)度球在xml文件或者通過代碼的方式設(shè)置一個(gè)屬性:clickable=”true”。
當(dāng)給整個(gè)父控件設(shè)置了點(diǎn)擊或者觸摸的監(jiān)聽,如果不需要其子控件也給監(jiān)聽到(或者子控件的監(jiān)聽有額外的事件處理)時(shí),給子控件設(shè)置屬性:clickable=”true”。
最后,感謝你耐心的看完。水平有限,不足或者有誤之處,請(qǐng)諒解!!!
源碼已經(jīng)開放在github上:https://github.com/My-Zzw/FloatView360Demo
總結(jié)
以上是生活随笔為你收集整理的360加速球效果实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么Audition cc2017扫描
- 下一篇: android imageview 图片