android 應用內懸浮框,並在指定頁面顯示
一、實現懸浮
懸浮框基本的實現方式有兩種:
1、
在一個頁面內,可以用FrameLayout 或者RelativeLayout。FrameLayout 中view是在左上角堆疊的,也就是說是z-order的,所以可以頁面的基佈局是FrameLayout,然後在上面放一個view,並且更新view的translationX ,translationY來改變位置。
RelativeLayout 可以用alignParent屬性確定在父檢視的位置。不過位置不可變。
如果這種方式顯示全應用懸浮,那麼就要每個頁面都加,顯然有點工作量。
2、
windowmanager 新增檢視。這種方式比較靈活,不同activity都可以懸浮,甚至可以作為系統圖層,放在應用之上。
這裡使用第二種方式
先上程式碼
package com.android.aat;
import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;
/**
* Created by yueshaojun on 16/6/22.
*/
public class FloatView extends ImageView implements View.OnClickListener {
private WindowManager wm;
private WindowManager.LayoutParams wlp;
private float x;
private float y;
private float newX;
private float newY;
private boolean isRelease;
private boolean isLongPress;
private final long minTime = 300;
private Context mContext;
private boolean isAdded;
Runnable run = new Runnable() {
@Override
public void run() {
//短按
if(isRelease){
onClick(FloatView.this);
return;
}
//長按
isLongPress = true;
}
};
public FloatView(Context context) {
super(context);
mContext = context;
wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wlp = new WindowManager.LayoutParams();
setOnClickListener(this);
}
public FloatView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FloatView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
x=event.getRawX();
y=event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i("EVENT","DOWN");
isRelease = false;
newX=event.getX();
newY=event.getY();
//300ms後檢測 如果沒有擡起手認為是長按
postDelayed(run,minTime);
break;
case MotionEvent.ACTION_MOVE:
if(isLongPress){
Log.i("EVENT", "MOVE");
update();
}
break;
case MotionEvent.ACTION_UP:
Log.i("EVENT","UP");
//標記已經擡起手
isRelease = true;
if (isLongPress) {
isLongPress = false;
}
break;
}
return true;
}
private void update() {
if(wlp==null){
return;
}
//取view左上角座標
wlp.x = (int)(x-newX);
wlp.y=(int)(y-newY);
wm.updateViewLayout(this,wlp);
}
@Override
public void onClick(View v) {
Toast.makeText(mContext,"click!",Toast.LENGTH_LONG).show();
}
public void show(){
//如果windowmanager已經新增過,則不處理
if(isAdded){
return;
}
isAdded = true;
wlp.gravity= Gravity.LEFT| Gravity.TOP;
wlp.width = 200;
wlp.height = 200;
wlp.x=0;
wlp.y=0;
wlp.format = PixelFormat.TRANSLUCENT;
wlp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM|
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
wlp.type = WindowManager.LayoutParams.TYPE_TOAST;
wm.addView(this,wlp);
}
public void dismiss(){
if(isAdded) {
isAdded = false;
wm.removeView(this);
}
}
}
核心思想就是在windowmanager中新增view、移除view,監聽觸控事件更新view。windowmanager具體使用這裡不詳細講。這裡只簡單的繼承了imageView,當然可以通過繼承如linearLayout等佈局,新增自己定義的view上去。show的時候新增view,dismiss的時候移除view。
二、應用內懸浮
很尷尬的是,上面的方法顯示出來的懸浮框,在應用按了home鍵或者退出到桌面的時候還在。但是很多場景只想它在自己的應用裡懸浮,甚至只在指定的頁面懸浮。
方法網上有不少方法可以參考,我是這麼做的:
用一個helper維護FloatView 的例項,檢測當前activity是不是位於前臺,如果位於前臺,windowmanager新增檢視,位於後臺移除檢視;同樣的,檢查當前activity是否在過濾列表,在就不顯示。這樣就可以達到在應用內的指定頁面懸浮。
程式碼如下:
public class FloatViewHelper {
private static final String TAG = "FloatViewHelper";
private static FloatView mFloatView;
private static List<String> notShowList = new ArrayList();
static {
//新增不需要顯示的頁面
notShowList.add("MainActivity");
}
public static void showFloatView(final Context context){
if(!CommonUtils.isAppOnForeground(context)||CommonUtils.isTargetRunningForeground(context,notShowList)){
return;
}
if(mFloatView == null){
mFloatView = new FloatView(context.getApplicationContext());
}
mFloatView.show();
}
public static void removeFloatView(Context context){
if(CommonUtils.isAppOnForeground(context)&&CommonUtils.isTargetRunningForeground(context,notShowList)){
return;
}
if(mFloatView ==null||mFloatView.getWindowToken()==null){
return;
}
mFloatView.dismiss();
}
public static void addFilterActivities(List<String> activityNames){
notShowList.addAll(activityNames);
}
}
isAppOnForeground 方法貼出來:
/**
* 程式是否在前臺執行
*
* @return
*/
public static boolean isAppOnForeground(Context context) {
// Returns a list of application processes that are running on the
// device
ActivityManager activityManager = (ActivityManager) context
.getApplicationContext()
.getSystemService(Context.ACTIVITY_SERVICE);
String packageName = context.getApplicationContext().getPackageName();
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager
.getRunningAppProcesses();
if (appProcesses == null)
return false;
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
// The name of the process that this object is associated with.
if (appProcess.processName.equals(packageName)
&& appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
return false;
}
檢查當前頁面是否在過濾列表:
public static boolean isTargetRunningForeground(Context context,List<String> targetActivityNames) {
String topActivityName = ((Activity)context).getClass().getSimpleName();
if (!TextUtils.isEmpty(topActivityName) && targetActivityNames.contains(topActivityName)) {
return true;
}
return false;
}
然後在BaseActivity的onStart()中使用FloatViewHelper.showFloatView(this);onPause()中使用FloatViewHelper.removeFloatView(this);之後所有activity都繼承這個BaseActivity。
為什麼在onStart()和onPause()中使用
這個要說到activity的生命週期。正常情況下,從activity A跳轉到activity B,經歷的生命週期是:
A:onCreate()–>onStart()–> onResume()–>onPause()->B:onCreate()–>onStart()–>onResume()–>A :onStop()–>onDestory()。
那我們要實現的就是,前一個頁面跳轉到後一個頁面的時候,前一個頁面remove,後一個頁面show,所以從週期來講,應該在onResum()方法中show,在onPause()方法中remove。
到此就大功告成啦!
在android7.0以上(自測7.1.1),因為對視窗許可權做了限制,所以要做相容處理:
FloatView.java
if(Build.VERSION.SDK_INT > 24) {
wlp.type = WindowManager.LayoutParams.TYPE_PHONE;
}else {
wlp.type = WindowManager.LayoutParams.TYPE_TOAST;
}
然後在使用之前要在AndroidManifest.xml中新增android.permission.SYSTEM_ALERT_WINDOW 這個許可權,然後在使用之前,判斷有沒有許可權:
private static boolean hasPermission(Context context){
if( Build.VERSION.SDK_INT > 24 ) {
return Settings.canDrawOverlays(context);
}
return true;
}
注意,試過checkPermission()等方法,發現並不起作用,要使用Settings.canDrawOverlays(context)判斷。