android實現可自由移動、監聽點選事件的懸浮窗
阿新 • • 發佈:2019-01-04
最近因為專案需要,自己實現了個可以自由移動,並且長按可以跳出一個控制播放的,額,大的懸浮窗。
好,開始吧。首先我們先聊許可權,懸浮窗需要在manifest中宣告一個許可權:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
然後呢,嗯,我們來講講關於懸浮窗實現的原理。
在Andriod中,所有的介面元素都要通過windowmanger來實現,像Activity、Fragment等等這些也是在其上實現。因此,我們的懸浮窗自然要通過這個實現。
這個專案中,我們自定義了兩個懸浮窗view。我們以其中一個比較簡單的為例:
我們自定義一個管理可以統一管理懸浮窗的類MyWindowManager,負責建立,刪除懸浮窗
/**
* Created by shiwe on 2017/3/7.
* 懸浮窗管理
* 建立,移除
* 單例模式
*/
public class MyWindowManager {
private FloatNormalView normalView;
private FloatControlView controlView;
private static MyWindowManager instance;
private MyWindowManager() {
}
public static MyWindowManager getInstance () {
if (instance == null)
instance = new MyWindowManager();
return instance;
}
/**
* 建立小型懸浮窗
*/
public void createNormalView(Context context) {
if (normalView == null)
normalView = new FloatNormalView(context);
}
/**
* 移除懸浮窗
*
* @param context
*/
public void removeNormalView(Context context) {
if (normalView != null) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.removeView(normalView);
normalView = null;
}
}
/**
* 建立小型懸浮窗
*/
public void createControlView(Context context) {
if (controlView == null)
controlView = new FloatControlView(context);
}
/**
* 移除懸浮窗
*
* @param context
*/
public void removeControlView(Context context) {
if (controlView != null) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.removeView(controlView);
controlView = null;
}
}
}
然後看看我們自定義的一個view,其繼承自LinearLayout,我們在initLayoutParams初始化這個控制元件的位置等其他引數;在initEvent方法中定義隨手指移動的監聽事件以及長按的監聽事件。
public class FloatNormalView extends LinearLayout {
private Context context = null;
private View view = null;
private ImageView ivShowControlView = null;
private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
private static WindowManager windowManager;
private float mTouchStartX;
private float mTouchStartY;
private float x;
private float y;
private boolean initViewPlace = false;
private MyWindowManager myWindowManager;
private boolean isControlViewShowing = false;
public FloatNormalView(Context context) {
super(context);
this.context = context;
myWindowManager = MyWindowManager.getInstance();
LayoutInflater.from(context).inflate(R.layout.float_normal_view, this);
view = findViewById(R.id.ll_float_normal);
ivShowControlView = (ImageView) findViewById(R.id.iv_show_control_view);
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
initLayoutParams();
initEvent();
}
/**
* 初始化引數
*/
private void initLayoutParams() {
//螢幕寬高
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight();
//總是出現在應用程式視窗之上。
lp.type = WindowManager.LayoutParams.TYPE_PHONE;
// FLAG_NOT_TOUCH_MODAL不阻塞事件傳遞到後面的視窗
// FLAG_NOT_FOCUSABLE 懸浮視窗較小時,後面的應用圖示由不可長按變為可長按,不設定這個flag的話,home頁的劃屏會有問題
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 = screenHeight / 2 + view.getLayoutParams().height * 2;
//懸浮窗的寬高
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.format = PixelFormat.TRANSPARENT;
windowManager.addView(this, lp);
}
/**
* 設定懸浮窗監聽事件
*/
private void initEvent() {
ivShowControlView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (!isControlViewShowing) {
myWindowManager.createControlView(context);
isControlViewShowing = true;
} else {
myWindowManager.removeControlView(context);
isControlViewShowing = false;
}
return true;
}
});
view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!initViewPlace) {
initViewPlace = true;
//獲取初始位置
mTouchStartX += (event.getRawX() - lp.x);
mTouchStartY += (event.getRawY() - lp.y);
} else {
//根據上次手指離開的位置與此次點選的位置進行初始位置微調
mTouchStartX += (event.getRawX() - x);
mTouchStartY += (event.getRawY() - y);
}
break;
case MotionEvent.ACTION_MOVE:
// 獲取相對螢幕的座標,以螢幕左上角為原點
x = event.getRawX();
y = event.getRawY();
updateViewPosition();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
});
}
/**
* 更新浮動視窗位置
*/
private void updateViewPosition() {
lp.x = (int) (x - mTouchStartX);
lp.y = (int) (y - mTouchStartY);
windowManager.updateViewLayout(this, lp);
}
最後,只需要在Activity中呼叫mywindowManager中呼叫createxxx方法就可以。
public class MainActivity extends AppCompatActivity {
MyWindowManager myWindowManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myWindowManager = MyWindowManager.getInstance();
myWindowManager.createNormalView(this.getApplicationContext());
}
}