android仿苹果悬浮窗(自动停靠、随手指滑动、返回主屏幕)
說(shuō)明:本人寫博客一來(lái)是為了方便日后查看項(xiàng)目,二來(lái)是希望能夠和廣大的程序猿相互交流學(xué)習(xí),文章布局簡(jiǎn)單,如有嫌棄,請(qǐng)繞行,如有錯(cuò)誤,請(qǐng)指出,謝謝。
實(shí)驗(yàn)環(huán)境:安卓6.0 魅族手機(jī)
懸浮窗主要有以下幾個(gè)功能:
1、跟隨手指的滑動(dòng)而滑動(dòng)(也可以用鼠標(biāo)滑動(dòng))
2、在手指彈起的時(shí)候,懸浮窗會(huì)自動(dòng)停靠在左右兩側(cè)
3、點(diǎn)擊懸浮窗按鈕可以返回到桌面
MainActivity中添加6.0訪問(wèn)權(quán)限
6.0權(quán)限問(wèn)題:Google在6.0時(shí)加入權(quán)限管理機(jī)制,6.0之后,android需要?jiǎng)討B(tài)獲取權(quán)限,要使用權(quán)限,不僅要在manifest文件中定義,還要在代碼中動(dòng)態(tài)獲取。點(diǎn)我了解權(quán)限問(wèn)題
manifest中添加權(quán)限聲明
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /><uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />MainActivity中代碼如下:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (Settings.canDrawOverlays(MainActivity.this)) {Intent intent = new Intent(MainActivity.this, FloatViewService.class);Toast.makeText(MainActivity.this, "已開(kāi)啟懸浮窗", Toast.LENGTH_SHORT).show();startService(intent);finish();} else {//若沒(méi)有權(quán)限,提示獲取.Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);intent.setData(Uri.parse("package:" + getPackageName()));Toast.makeText(MainActivity.this, "需要取得權(quán)限以使用懸浮窗", Toast.LENGTH_SHORT).show();startActivity(intent);finish();}} }代碼說(shuō)明:如果手機(jī)已授予該app使用懸浮窗的功能,界面會(huì)自動(dòng)開(kāi)啟懸浮窗,MainActivity被finish,否則直接跳轉(zhuǎn)到本手機(jī)開(kāi)啟懸浮窗權(quán)限的界面,親測(cè)有效,比如魅族手機(jī)開(kāi)啟權(quán)限的界面如下圖所示:
問(wèn)題:只有在第一次安裝app的時(shí)候才會(huì)跳轉(zhuǎn)到打開(kāi)權(quán)限的界面,之后打開(kāi)app則不會(huì)跳轉(zhuǎn),這部分不太理解,有知悉的評(píng)論區(qū)見(jiàn)。
懸浮窗界面的繪制
Android的窗口是基于WindowManager實(shí)現(xiàn)的,它面向的對(duì)象一端是屏幕,另一端就是View,比如我們之前使用的setContentView(R.layout.activity_main), 就是將view顯示在屏幕上,代碼的底層都是經(jīng)過(guò)WindowManager實(shí)現(xiàn)的,整個(gè)系統(tǒng)只有一個(gè)WindowManager。點(diǎn)我了解界面繪制詳解
Service實(shí)現(xiàn)后臺(tái)運(yùn)行
當(dāng)app沒(méi)有被關(guān)閉時(shí),懸浮窗同樣可以運(yùn)行,這時(shí)候就需要Service來(lái)實(shí)現(xiàn)后臺(tái)運(yùn)行。這里可自行百度Service具體實(shí)現(xiàn)的過(guò)程,本篇不做解釋。
跟隨手指的滑動(dòng)而滑動(dòng)
說(shuō)明:需要監(jiān)聽(tīng)手勢(shì),所以設(shè)置了setOnTouchListener,識(shí)別按下、移動(dòng)、彈起三個(gè)動(dòng)作,移動(dòng)的過(guò)程需要動(dòng)態(tài)獲取觸摸的坐標(biāo),所以首先要在按下的過(guò)程中獲取按下的坐標(biāo),rawX = event.getRawX(); rawY = event.getRawY(),在移動(dòng)的過(guò)程中進(jìn)行刷新, wmParams.x = wmParams.x - distanceX;wmParams.y = wmParams.y - distanceY。
// 設(shè)置監(jiān)聽(tīng)浮動(dòng)窗口的觸摸移動(dòng)go_mainhome.setOnTouchListener(new View.OnTouchListener() {private float rawX;private float rawY;@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: // Log.i("qqq", "onTouch------------------------------ACTION_DOWN: ");mFloatLayout.setAlpha(1.0f);//設(shè)置其透明度myCountDownTimer.cancel();//取消計(jì)時(shí)rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_MOVE: // Log.i("qqq", "onTouch------------------------------ACTION_MOVE: ");// getRawX是觸摸位置相對(duì)于屏幕的坐標(biāo),getX是相對(duì)于按鈕的坐標(biāo)int distanceX = (int) (event.getRawX() - rawX);int distanceY = (int) (event.getRawY() - rawY);//mFloatView.getMeasuredWidth()和mFloatView.getMeasuredHeight()都是100wmParams.x = wmParams.x - distanceX;wmParams.y = wmParams.y - distanceY;// 刷新mWindowManager.updateViewLayout(mFloatLayout, wmParams);rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_UP:myCountDownTimer.start();//重新開(kāi)始計(jì)時(shí)if (wmParams.x < screenWidth / 2) {//在屏幕右側(cè)wmParams.x = 0;wmParams.y = wmParams.y - 0;} else {//在屏幕左側(cè)wmParams.x = screenWidth;wmParams.y = wmParams.y - 0;}mWindowManager.updateViewLayout(mFloatLayout, wmParams);break;}return false;//此處必須返回false,否則OnClickListener獲取不到監(jiān)聽(tīng)}});獲取屏幕大小
嘗試了好幾種獲取屏幕大小的代碼,此方法親測(cè)有效。
停靠功能
說(shuō)明:當(dāng)手指滑動(dòng)到屏幕中央右側(cè)時(shí),比如在圖中的A點(diǎn)(x,y),最終懸浮窗將會(huì)停靠在圖中的B點(diǎn),A點(diǎn)向右平移到B點(diǎn),縱坐標(biāo)不變,橫坐標(biāo)為0,在屏幕左側(cè)同理,可詳見(jiàn)代碼case MotionEvent.ACTION_UP部分,前提是需要設(shè)置 wmParams.gravity = Gravity.RIGHT | Gravity.BOTTOM,可以滑動(dòng)最下面看詳細(xì)代碼。
點(diǎn)擊懸浮窗按鈕可以返回到桌面
其實(shí)就是對(duì)按鈕設(shè)置了監(jiān)聽(tīng)setOnClickListener,點(diǎn)擊之后跳轉(zhuǎn)到桌面的main。
Service中的代碼
package com.lightingcontour.toucher;import android.annotation.SuppressLint; import android.app.Service; import android.content.Intent; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; import android.os.CountDownTimer; import android.os.IBinder; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.Toast;public class FloatViewService extends Service {private static final String TAG = "FloatViewService";// 定義浮動(dòng)窗口布局private LinearLayout mFloatLayout;private WindowManager.LayoutParams wmParams;// 創(chuàng)建浮動(dòng)窗口設(shè)置布局參數(shù)的對(duì)象private WindowManager mWindowManager;private ImageButton go_mainhome;private ImageButton go_back; // private LinearLayout toucher_layout;private int screenHeight;private int screenWidth;private MyCountDownTimer myCountDownTimer;@Overridepublic void onCreate() {super.onCreate();Log.i(TAG, "onCreate");createFloatView();myCountDownTimer = new MyCountDownTimer(2500, 1000); //設(shè)置計(jì)時(shí)2.5smyCountDownTimer.start();}@SuppressWarnings("static-access")@SuppressLint("InflateParams")private void createFloatView() {wmParams = new WindowManager.LayoutParams();// 通過(guò)getApplication獲取的是WindowManagerImpl.CompatModeWrappermWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE);Display display = mWindowManager.getDefaultDisplay();Point point = new Point();display.getRealSize(point);screenWidth = point.x;screenHeight = point.y;Log.i("qqq", "screenWidth------: " + screenWidth + "\n" + "screenHeight---" + screenHeight);// 設(shè)置window typewmParams.type = WindowManager.LayoutParams.TYPE_PHONE;// 設(shè)置圖片格式,效果為背景透明wmParams.format = PixelFormat.RGBA_8888;// 設(shè)置浮動(dòng)窗口不可聚焦(實(shí)現(xiàn)操作除浮動(dòng)窗口外的其他可見(jiàn)窗口的操作)wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;// 調(diào)整懸浮窗顯示的停靠位置為右側(cè)底部wmParams.gravity = Gravity.RIGHT | Gravity.BOTTOM;// 以屏幕左上角為原點(diǎn),設(shè)置x、y初始值,相對(duì)于gravitywmParams.x = 0;wmParams.y = 0;// 設(shè)置懸浮窗口長(zhǎng)寬數(shù)據(jù)wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;LayoutInflater inflater = LayoutInflater.from(getApplication());// 獲取浮動(dòng)窗口視圖所在布局mFloatLayout = (LinearLayout) inflater.inflate(R.layout.toucherlayout, null);// 添加mFloatLayoutmWindowManager.addView(mFloatLayout, wmParams);// 浮動(dòng)窗口按鈕go_mainhome = (ImageButton) mFloatLayout.findViewById(R.id.go_mainhome);go_back = (ImageButton) mFloatLayout.findViewById(R.id.go_back);//UNSPECIFIED是未指定模式mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));// 設(shè)置監(jiān)聽(tīng)浮動(dòng)窗口的觸摸移動(dòng)go_mainhome.setOnTouchListener(new View.OnTouchListener() {private float rawX;private float rawY;@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: // Log.i("qqq", "onTouch------------------------------ACTION_DOWN: ");mFloatLayout.setAlpha(1.0f);//設(shè)置其透明度myCountDownTimer.cancel();//取消計(jì)時(shí)rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_MOVE: // Log.i("qqq", "onTouch------------------------------ACTION_MOVE: ");// getRawX是觸摸位置相對(duì)于屏幕的坐標(biāo),getX是相對(duì)于按鈕的坐標(biāo)int distanceX = (int) (event.getRawX() - rawX);int distanceY = (int) (event.getRawY() - rawY);//mFloatView.getMeasuredWidth()和mFloatView.getMeasuredHeight()都是100wmParams.x = wmParams.x - distanceX;wmParams.y = wmParams.y - distanceY;// 刷新mWindowManager.updateViewLayout(mFloatLayout, wmParams);rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_UP:myCountDownTimer.start();//重新開(kāi)始計(jì)時(shí)if (wmParams.x < screenWidth / 2) {//在屏幕右側(cè)wmParams.x = 0;wmParams.y = wmParams.y - 0;} else {wmParams.x = screenWidth;wmParams.y = wmParams.y - 0;}mWindowManager.updateViewLayout(mFloatLayout, wmParams);break;}return false;//此處必須返回false,否則OnClickListener獲取不到監(jiān)聽(tīng)}});// 設(shè)置監(jiān)聽(tīng)浮動(dòng)窗口的觸摸移動(dòng)go_back.setOnTouchListener(new View.OnTouchListener() {private float rawX;private float rawY;@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: // Log.i("qqq", "onTouch------------------------------ACTION_DOWN: ");mFloatLayout.setAlpha(1.0f);//設(shè)置其透明度myCountDownTimer.cancel();//取消計(jì)時(shí)rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_MOVE: // Log.i("qqq", "onTouch------------------------------ACTION_MOVE: ");// getRawX是觸摸位置相對(duì)于屏幕的坐標(biāo),getX是相對(duì)于按鈕的坐標(biāo)int distanceX = (int) (event.getRawX() - rawX);int distanceY = (int) (event.getRawY() - rawY);//mFloatView.getMeasuredWidth()和mFloatView.getMeasuredHeight()都是100wmParams.x = wmParams.x - distanceX;wmParams.y = wmParams.y - distanceY;// 刷新mWindowManager.updateViewLayout(mFloatLayout, wmParams);rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_UP:myCountDownTimer.start();//重新開(kāi)始計(jì)時(shí)if (wmParams.x < screenWidth / 2) {//在屏幕右側(cè)wmParams.x = 0;wmParams.y = wmParams.y - 0;} else {wmParams.x = screenWidth;wmParams.y = wmParams.y - 0;}mWindowManager.updateViewLayout(mFloatLayout, wmParams);break;}return false;//此處必須返回false,否則OnClickListener獲取不到監(jiān)聽(tīng)}});go_mainhome.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(FloatViewService.this, "返回到桌面",Toast.LENGTH_SHORT).show();Intent intent = new Intent();// 為Intent設(shè)置Action、Category屬性intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setAction(Intent.ACTION_MAIN);// "android.intent.action.MAIN"intent.addCategory(Intent.CATEGORY_HOME); //"android.intent.category.HOME"CATEGORY_HOME 目標(biāo)Activity是HOME Activity,即手機(jī)開(kāi)機(jī)啟動(dòng)后顯示的Activity,或按下HOME鍵后顯示的ActivitystartActivity(intent);}});go_back.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(FloatViewService.this, "返回",Toast.LENGTH_SHORT).show();}});}@Overridepublic void onDestroy() {super.onDestroy();if (mFloatLayout != null) {// 移除懸浮窗口mWindowManager.removeView(mFloatLayout);}}@Overridepublic IBinder onBind(Intent intent) {return null;}public class MyCountDownTimer extends CountDownTimer {public MyCountDownTimer(long millisInFuture, long countDownInterval) {super(millisInFuture, countDownInterval);}@Overridepublic void onTick(long millisUntilFinished) {}@Overridepublic void onFinish() {mFloatLayout.setAlpha(0.4f);}}}manifest中的代碼
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.lightingcontour.toucher"><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /><uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><service android:name=".FloatViewService" /></application></manifest>布局中的代碼
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:id="@+id/toucher_layout"android:orientation="vertical"android:layout_width="wrap_content"android:layout_height="wrap_content"><ImageButtonandroid:id="@+id/go_mainhome"android:layout_marginBottom="10dp"android:layout_width="50dp"android:layout_height="50dp"android:background="@drawable/go_mainhome" /><ImageButtonandroid:id="@+id/go_back"android:layout_width="50dp"android:layout_height="50dp"android:background="@drawable/go_back" /></LinearLayout></LinearLayout>總結(jié)
以上是生活随笔為你收集整理的android仿苹果悬浮窗(自动停靠、随手指滑动、返回主屏幕)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 搜索练习2(P6207 [USACO06
- 下一篇: 微信小程序(一):微信小程序申请注册与开