android 应用内悬浮框,并在指定页面显示

xiaoxiao2021-02-28  129

一、实现悬浮

悬浮框基本的实现方式有两种: 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)判断。

转载请注明原文地址: https://www.6miu.com/read-27995.html

最新回复(0)