日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

360加速球效果实现

發布時間:2023/12/10 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 360加速球效果实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

效果演示:
懸浮小球在移動時會換成一張圖片,當松開時,會自動停靠在一側,并且恢復原來的形式。
點擊懸浮小球,會從屏幕底部滑入一個菜單欄。
雙擊加速球,會有水不斷注入的動畫效果,并且水面逐漸平靜下來。
單擊加速球,水面會震蕩,最后恢復平靜。

這個的實現是觀看了慕課網視頻后,在其基礎上做了一些適當的修改,如:在android23以上,如何申請動態權限。因為這個實現需要用到一個危險權限,彈出窗口。有興趣的可以前往:http://www.imooc.com/learn/693。

整體思路:
可以看下項目結構。在MainActivity開啟了一個服務MyFloatService,這個服務獲得了FloatViewManager管理類的實例,view包下有懸浮球的view以及加速球的view。而這個管理類則可控制這些view的顯示,隱藏,位置。開啟服務后,顯示了懸浮球,通過對懸浮球進行Touch事件的監聽,實現了拖動改變圖片,自動停靠在一側,單擊出現菜單欄。在菜單欄的FloatMenuView類中,實現了對自身的Touch監聽,雙擊出現水不斷注入效果,單擊水面震蕩。單擊加速球其他的地方則隱藏菜單欄。

源碼已經開放在github上:https://github.com/My-Zzw/FloatView360Demo

在這篇文章中,簡單提一下使用到的技巧,以及一些主要的思路,一些思想,一些需要注意的地方。

1.權限問題
當點擊主界面的開啟懸浮窗按鈕時,開啟MyFloatService服務。當服務創建時,進行判斷,如果運行在大于等于6.0系統上,則跳轉到應用信任界面,允許該應用所有權限。在這個項目中,需要用到 SYSTEM_ALERT_WINDOW 權限,記得要在manifest中注冊。

@Overridepublic void onCreate() {FloatViewManager manager = FloatViewManager.getInstance(this);manager.showFloatCircleView();//彈出一個窗口,需要權限。>=23,需要動態申請。super.onCreate();}

2.在FloatViewManager中,是如何控制懸浮球以及菜單欄顯示和隱藏的。
首先已經實例化了懸浮球View和菜單欄的View

//實例化懸浮球ViewfloatCircleView = new FloatCircleView(context);//實例化菜單ViewfloatMenuView = new FloatMenuView(context);

已經實例化了View。那么View要顯示在界面,就需要一些布局參數,比如view的寬和高,位置在哪里?所以需要一個 WindowManager.LayoutParams 對象。這樣,要顯示的view已經拿到,如何顯示也拿到,那么就可以添加到界面顯示了。顯示到界面上讓用戶看到,需要實例化一個 WindowManager 的對象,通過其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;//布局參數的類型為手機類型。意味著 在所有頁面的上面。params2.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//不和其他應用搶焦點params2.format = PixelFormat.RGBA_8888;//設置透明度windowManager.addView(floatMenuView, params2);//通過manager添加視圖到窗口。第一個參數為要添加的view,第二個為view的布局參數}

3.實現懸浮窗在點擊移動時變成一張火箭的圖片,松開時自動附著在其中一側。
首先對懸浮窗注冊一個事件監聽器。類型為觸摸事件。

//注冊觸摸事件監聽器 floatCircleView.setOnTouchListener(circleViewOnTouchListener);

接著實現這個事件監聽器的觸發事件。當用戶按下,移動,抬起時。
需要說明的是,當監聽到用戶在移動這個懸浮球時,調用了懸浮球對象的setDrawState方法通知到這個懸浮球在移動中,那么懸浮球對象就會調用invalidate進行重繪。在onDraw方法去drawBitmap。
當用戶松開時,判斷最后松開的懸浮窗x坐標如果大于或者小于屏幕的一半,修改懸浮窗的params.x布局參數的x坐標。最后調用updateViewLayout方法,更新懸浮窗。

//監聽的內部類private View.OnTouchListener circleViewOnTouchListener = new View.OnTouchListener() {@Overridepublic boolean onTouch(View view, MotionEvent motionEvent) {switch (motionEvent.getAction()) {case MotionEvent.ACTION_DOWN://當按下的時候,獲取相對屏幕密度的xy坐標startX = motionEvent.getRawX();startY = motionEvent.getRawY();startX0 = motionEvent.getRawX();startY0 = motionEvent.getRawY();break;case MotionEvent.ACTION_MOVE://移動的時候,懸浮球跟著移動//獲取移動中的坐標float x = motionEvent.getRawX();float y = motionEvent.getRawY();//偏移量float dx = x - startX;float dy = y - startY;//獲取布局參數對象。重新設置懸浮球的xy位置params.x += dx;params.y += dy;//移動的過程中,改變樣式。通知在移動。floatCircleView.setDrawState(true);//刷新界面。指定用新的 布局參數params 刷新 floatCircleView在界面的顯示windowManager.updateViewLayout(floatCircleView,params);//起始位置變化為移動后的位置startX = x;startY = y;break;case MotionEvent.ACTION_UP://當抬起時,懸浮球附著在兩旁float endX = motionEvent.getRawX();//獲取最后的X坐標//進行判斷.當在屏幕中間線右邊或者左邊的時,往兩邊靠攏if (endX > getScreenWidth()/2){params.x = getScreenWidth() - floatCircleView.width;} else {params.x = 0;}//當抬起時,通知移動狀態停止。并且懸浮球會重新繪制自身樣式floatCircleView.setDrawState(false);//刷新界面。刷新的是懸浮球floatCircleView在界面的布局參數。而懸浮球樣式的改變在其內部。windowManager.updateViewLayout(floatCircleView,params);//解決可能有的 觸摸事件和點擊事件 的沖突.//如果移動后的X坐標 大于 起始的X坐標6個單位距離,則認為是觸摸事件,要終止點擊事件的執行if (Math.abs(endX - startX0) > 6){//取絕對值return true;//返回true的時候,則不會繼續往下執行到 onClick 事件。} else {return false;}default:break;}return false;}};

4.獲取狀態欄高度。
這里是采用了反射去獲取。這樣的好處是能獲取到程序運行到具體的設備或者模擬器的狀態欄高度。

//獲取狀態欄的高度public int getStatusHeigt (){//通過反射的方法獲取狀態欄的高度try {Class<?> c = Class.forName("com.android.internal.R$dimen");//反射.class獲取類Object o = c.newInstance();//實例化這個類,得到一個具體的對象Field field = c.getField("status_bar_height");//獲取這個類的field(域)。這個域的對象類型是 這個類里面的一個屬性int x = (Integer)field.get(o);//再從具體對象的一個屬性的值return context.getResources().getDimensionPixelSize(x);//返回。值轉換成px} catch (Exception e) {e.printStackTrace();return 0;}}

5.隱藏懸浮球或者菜單欄。
例如隱藏菜單欄,拿到窗口管理對象remove即可。

//隱藏 菜單欄public void hideFloatMenuView() {windowManager.removeView(floatMenuView);}

6.懸浮球 FloatCircleView 的實現。

通過實際效果可以知道,其實現原理很簡單。首先是如何繪制。
繪制:繪制有兩種狀態。一是不移動狀態下的繪制,使用兩只畫筆,一個畫筆繪制圓形,一個畫筆繪制文本。另一個是移動狀態下的繪制。移動狀態下是將一個在drawable下的資源圖片進行繪制。

// 繪制方法@Overrideprotected void onDraw(Canvas canvas) {//如果移動中,則顯示圖片,否則正常顯示if (draw){canvas.drawBitmap(bitmap,0,0,null);} else {//繪制圓形canvas.drawCircle(width/2,height/2,width/2,circlePaint);//繪制文本float textWidth = textPaint.measureText(text);//用畫筆去測量文本的寬度float x = width/2 - textWidth/2;//確定文本的x坐標Paint.FontMetrics metrics = textPaint.getFontMetrics();//獲得畫筆繪制下的文本規格 // 確定文本的y坐標.descent ascent,基準線下的高度,基準線下的高度。為什么不是除2,而是除4?為什么是+而不是-? // + 是因為文本的初始位置在圓的上方。除4 則可能是因為有默認的行距。以后凡是需要精確的數值,則可以將其都顯示出來,再一個個去測試值。float y = height/2 + (metrics.descent - metrics.ascent)/4;canvas.drawText(text,x,y ,textPaint);}}

如何通知到懸浮球什么時候在移動?通過調用下面這個方法。這個方法會改變狀態的標識,并且調用invalidate去重新繪制。

// 在移動中,則進行狀態的改變public void setDrawState(boolean b) {draw = b;invalidate();//每當狀態改變的時候,需要重新執行draw方法,進行重新繪制}

要保證移動時候,改變的圖片的大小也要和懸浮球大小一致,則需要

// 初始化移動需要的bitmap。并且縮放到合適大小Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.hj);bitmap = Bitmap.createScaledBitmap(src,width,height,true);

7.進度球 ProcessView 的實現。

進度球放置在了菜單欄里面。通過實際效果知道,需要三支畫筆。繪制圓形,繪制水,繪制文本。

7.1 雙擊進度球動畫效果的實現:水不斷被注入,并且水面越來越平緩

圖解是這樣的:

一支畫筆繪制了圓形,另外一支畫筆在圓形上繪制了一個“矩形”,不過這個矩形有點特殊的是上面的邊是個曲線,其他三邊都是直線。
在繪制這個特殊的矩形,是用畫筆通過Path路徑去繪制,先用lineTo方法依次繪制三條直線的邊,在繪制到這個曲線的時候再用rQuadTo方法繪制曲線,也叫貝塞爾曲線。
那么動畫效果:水不斷被注入,并且水面越來越平緩。是如何實現的呢?
水不斷注入,其實就是這個特殊的矩形不斷的重新繪制,繪制。并且每次繪制時,矩形的高都是不斷增加的,在時間很短的情況下,感覺水面在不斷的升高。
水面越來越平緩,則是每次在重新繪制的時候,貝塞爾曲線的振幅不斷的在減小。
需要了解貝塞爾曲線可能才能明白在說什么,所以有不明白的可以去百度下Android貝塞爾曲線。

繪制的矩形是在覆蓋在圓形上,那么矩形超出圓形的部分如何隱藏?
我們可以在初始化畫筆時,設置繪制矩形的畫筆的過度模式,也就是圖像混合模式。當矩形畫筆繪制的圖像和其他圖像混合時,設置其模式為重疊。也就是說,只顯示重疊的部分。

//下面這句代碼則是 設置了progressPaint畫筆繪制的圖像,只顯示重疊部分progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

7.2 單擊進度球動畫效果的實現:水面不斷震蕩,最后平緩。

圖解:

其實還是不斷的去重新繪制矩形,不過矩形的高度不需要變化,但是貝塞爾曲線需要變化,每繪制一次貝塞爾曲線,那么在下一次則繪制相反的貝塞爾曲線,再下一次則恢復原來的貝塞爾曲線即可。
并且在每次重新繪制的時候,同樣需要將振幅減小。

如何每次去計算振幅的減小 以及 如何判斷哪次需要將貝塞爾曲線取反。讀者可以查看代碼結合注釋。這里不詳細介紹。

//在這進行繪制.@Overrideprotected void onDraw(Canvas canvas) {//畫圓bitmapCanvas.drawCircle(width/2,height/2,width/2,circlePaint);//繪制水波紋進度。基本思想是繪制一個矩形。不過矩形除了上面的邊使用貝塞爾曲線外,其他都是直線。path.reset();//重置path的所有屬性//起始點定義。重新規定路徑的起始坐標,默認為(0,0)。moveTo()移動畫筆。//這里將起始點的x移動到右邊,y則是變化的。也就是矩形的右上角的點float y = (1 - (float)current_progress/max_progress) * height;path.moveTo(width,y);//第二個點定義。右下角,繪制直線。也就是右面的邊path.lineTo(width,height);//第三個點定義。左下角,繪制直線。也就是下面的邊path.lineTo(0,height);//第四個點定義。左上角,繪制直線。也就是左面的邊path.lineTo(0,y);//繪制上面的邊。也就是貝塞爾曲線。//根據進度球寬度,設定需要的貝塞爾曲線的周期。如:250的寬度,那么7個周期為40的即可。//繪制貝賽爾曲線需要起始點,控制點和結束點。rQuadTo方法只需要兩個參數,起始點默認為 未閉合路徑的最后一個點。//一個循環意味著一個周期的貝塞爾曲線if (!isSingleTab){//雙擊//雙擊效果:貝塞爾曲線逐漸變得平緩,最后成為直線的效果。//其實就是控制了貝賽爾曲線的振幅。也就是參數的控制點的y坐標。使得y坐標逐漸變成0。也就是rQuadTo中的第二個參數//在這里設定了振幅為10的話,也就是控制點的y坐標,隨著當前進度不斷增加到接近目標進度,那么百分比就會不斷增加//這時用1減去他們的百分比,就會不斷減小。用這結果去乘振幅。就能使得振幅不斷減小.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 {//單擊//單擊效果:水不斷震蕩,最后便于平緩。//其實就是貝塞爾曲線每次周期不斷取反,也就是高的變低,低的變高.//這里是水波紋波動50次,也就是50個周期.為什么要模2?//因為count是每次減1。如果模2的結果是0,那么為一個周期。再減1,模2的結果不是0.則為另外一個周期,就可以去實現相反的效果。//要使得貝賽爾曲線逐漸變得平緩,和上面寫的一樣道理。使得振幅不斷減小.注意count是每次-1//第一解決貝塞爾曲線不斷取反。第二解決貝塞爾曲線振幅也就是控制點的y坐標不斷減小。//以后凡是遇到類似這種,需要多個來配合的,那么先解決一個,再解決下一個。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) + "%";//獲取文本寬度。便于規定繪制文本時候的x坐標float textWidth = textPaint.measureText(text);//獲取文本規格。便于規定繪制文本時候的y坐標;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定時器去不斷重繪。但是這里使用的是 Handle.postDelayed 方法。這里面有個小技巧。也就是postDelayed 方法去執行runable的代碼時,如果條件沒達到,則再次調用自身,如果條件達到則remove。比如雙擊動畫:
被雙擊的時,先執行一次

//開啟雙擊動畫private void startDoubleTapAnimation() {//利用handler執行一個延遲50毫秒的線程。//每次線程首先將當前進度++,并且判斷。//如果當前進度沒有達到指定進度,先重新繪制,然后再延遲50毫秒后執行同樣的線程//如果達到了,則讓當前進度為0,并且關閉。//本質的效果像開了個定時器,每隔50毫秒去執行,當達到一定條件停止執行handler.postDelayed(new DoubleTabRunable(),50);}

接著在執行的方法去做判斷,也就是DoubleTabRunable

//雙擊時,需要執行的線程。刷新數據,重繪界面。class DoubleTabRunable implements Runnable {@Overridepublic void run() {current_progress++;if (current_progress <= progress){invalidate();//重新繪制,調用onDraw方法。因為current_progress是變化的,所以重新繪制會使得進度條有變化。handler.postDelayed(this,50);//再次調用自己} else {current_progress = 0;handler.removeCallbacks(this);}}}

7.4 進度球的繪制,先繪制在了一個自定義的bitmap中,繪制完畢后,再講這個bitmap繪制在界面中顯示。

//自己畫圖。創建一個空的bitmap,并且在這bitmap上傳入一個畫布才能進行繪制。bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);bitmapCanvas = new Canvas(bitmap);

8.菜單欄中放置進度球。
FloatMenuView 繼承了LinearLayout。所以這個類本質還是一個View。既然是View的話就會有樣式。
之前的懸浮球FloatCircleView 和 進度球 ProcessView 都是通過畫筆在OnDraw中去繪制樣式。
在FloatMenuView ,則是事先將一個寫好的xml格式的布局文件寫好,通過View.inflate方法找到這個xml格式的布局文件后,再通過addView方法添加這個布局文件到FloatMenuView ,也就是將這個找到的布局文件和FloatMenuView 綁定。這樣在其他地方實例化FloatMenuView 后,顯示在窗口的話,樣子就是xml布局文件的樣式。

View root = View.inflate(getContext(), R.layout.float_menu_view,null);//找到xml文件樣式 ... ...addView(root);//將xml樣式文件添加到這個View。也就是綁定。

那么進度球是如何顯示到了菜單欄中?很簡單,在菜單欄的xml布局文件中,也就是剛剛說的FloatMenuView 綁定的這個xml布局文件。在里面將其實例化

<?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><!--實例化自定義的進度球--><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,或者說是技巧吧,在這里用到了。具體原因不是很清楚,以后有待查證。
當點擊進度球(包括灰色區域)以外的地方,需要隱藏菜單欄(進度球是菜單欄的子控件)。但是點擊事件監聽的是整個父控件,也就是菜單欄。而點擊進度球是有其自身動畫效果需要實現的。
也就是子控件不需要因為父控件而給監聽到。
這樣解決:給進度球在xml文件或者通過代碼的方式設置一個屬性:clickable=”true”。
當給整個父控件設置了點擊或者觸摸的監聽,如果不需要其子控件也給監聽到(或者子控件的監聽有額外的事件處理)時,給子控件設置屬性:clickable=”true”。

最后,感謝你耐心的看完。水平有限,不足或者有誤之處,請諒解!!!

源碼已經開放在github上:https://github.com/My-Zzw/FloatView360Demo

總結

以上是生活随笔為你收集整理的360加速球效果实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。