Android 懸浮窗:可點選並顯示/隱藏多功能列表
阿新 • • 發佈:2019-01-11
前言
最近在一個專案中,需要製作錄屏的功能,原先是在應用中有錄屏/控制的按鈕,思考之下覺得這種效果並不好,因此就想製作一個可以懸浮的懸浮窗,這樣不論手機在什麼介面中都可以對錄屏功能進行控制。
這裡就來構建一個桌面的懸浮窗,使用了DataBinding的MVVM模式,這些方面就不再多提。
FloatNormalView
這個是一個普通的懸浮窗,懸浮窗只有一個按鈕,點選按鈕顯示更多的按鈕。
首先是頁面佈局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" >
<data>
<variable
name="viewModel"
type="com.example.zjt.floatrecorder.FloatNormalViewModel"/>
</data>
<LinearLayout
android:layout_width="50dp"
android:layout_height="50dp"
android:gravity="center">
<RelativeLayout
android:id="@+id/root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 圖示,點選後彈出後面的按鈕 -->
<ImageView
android:id="@+id/float_id"
android:layout_width ="40dp"
android:layout_height="40dp"
android:background="@drawable/ic_launcher_background"
android:onClick="@{viewModel::onControlClick}"/>
</RelativeLayout>
</LinearLayout>
</layout>
下面一步步的介紹這個懸浮窗的建立。
1 懸浮窗的顯示
// 建立WindowManager物件
private WindowManager windowManager;
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 建立懸浮窗的LayoutParams
private void initLayoutParams() {
try {
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
screenWidth = metrics.widthPixels;
screenHeight = metrics.heightPixels;
lp = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
lp.type = WindowManager.LayoutParams.TYPE_TOAST;
}
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
lp.gravity = Gravity.START | Gravity.TOP;
lp.x = screenWidth - view.getLayoutParams().width * 2;
lp.y = 0;
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.format = PixelFormat.TRANSPARENT;
} catch (Exception e) {
}
}
上面分別建立了控制懸浮窗顯示的WindowManager和控制懸浮窗佈局的LayoutParams
然後使用如下程式碼就可展示懸浮窗了:
public void show() {
if (!isShowing) {
isShowing = true;
windowManager.addView(this, lp);
}
}
想要移除懸浮窗也很簡單,如下程式碼:
public void dismiss() {
if (isShowing) {
isShowing = false;
windowManager.removeView(this);
}
}
2 觸控事件
觸控事件可以使得懸浮窗跟隨手指進行移動
// 介面
FloatLayoutBinding layoutBinding = DataBindingUtil.inflate(LayoutInflater.from(context),R.layout.float_layout,this,false);
FloatNormalViewModel floatNormalViewModel = new FloatNormalViewModel(context,layoutBinding,onClickCallback);
layoutBinding.setViewModel(floatNormalViewModel);
addView(layoutBinding.getRoot());
view = layoutBinding.root;
isShowControlView = layoutBinding.floatId;//這就是控制按鈕
// 控制的變數
private float downX, downY;
private float moveX, moveY;
// 觸控事件
isShowControlView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
downX = motionEvent.getRawX();
downY = motionEvent.getRawY();
break;
case MotionEvent.ACTION_MOVE:
moveX = motionEvent.getRawX() - downX;
moveY = motionEvent.getRawY() - downY;
downX += moveX;
downY += moveY;
updateViewPosition();
break;
}
return false;
}
});
private void updateViewPosition() {
lp.x += (int) (moveX);
lp.y += (int) (moveY);
windowManager.updateViewLayout(this, lp);
}
3 點選事件
點選事件是實現了一個回撥函式,因為點選事件的邏輯不應該在此處完成,應當交給主佈局進行控制,所以定義了一個點選介面。
這裡事件的處理順序是:點選了按鈕後,按鈕將點選事件通過回撥函式來處理,而回調函式是由建立這個View的Activity或者Fragment、Service等提供的,就將事件處理交到了外部。
// 點選的介面
public interface OnClickCallback {
public void onClick(View view);
}
// 控制按鈕點選事件
public void onControlClick(View view){
if(onClickCallback != null)
onClickCallback.onClick(view);
}
多功能懸浮窗
多功能懸浮窗與上面類似,只不過在點選事件上較多而已。
而如何完成兩個懸浮窗的切換呢,就可以利用之前所使用的OnClickCallback回撥介面了,將一個顯示、另一個隱藏即可,且兩個懸浮窗若採用同一個LayoutParams就可以讓兩個顯示在同一個位置。
private void init() {
floatNormalView = new FloatNormalView(context, new OnClickCallback() {
@Override
public void onClick(View view) {
floatControlView.setLayoutParams(floatNormalView.getLayoutParams());
floatControlView.show();
floatNormalView.dismiss();
}
});
floatControlView = new FloatControlView(context, new OnClickCallback() {
@Override
public void onClick(View view) {
floatNormalView.setLayoutParams(floatControlView.getLayoutParams());
floatNormalView.show();
floatControlView.dismiss();
}
}, new FloatControlViewModel.OnVisibleChangeListener() {
@Override
public void onChange(boolean isVisible) {
if (isControlVisible) {
floatControlView.show();
floatNormalView.dismiss();
} else {
floatControlView.dismiss();
floatNormalView.show();
}
}
});
floatNormalView.show();
}