前言
Popupwindow的使用概率之大,我想做开发的人员应该都知道,也是我们入门就该掌握的控件.但是使用该控件存在版本兼容性问题,也存在不少需要注意事项.这篇文章就按照我个人开发经验去梳理.
1.场景
在实际开发中,我们总会被要求实现可拖拽的悬浮窗,首先我们会想到使用WindowManager,但是直接使用这个会存在各种各样的问题,那替代方案呢?答案是popupWindow(其实该方案也是有问题的,后面我们会讲到).
2.popupWindow的使用
poupwindow的使用其实网上就比较多了,我就贴个其他人写的博客地址就好了,比较popupWindow的使用主要是在一些注意项上.
博客地址
1、PopUpWindow基本使用(全屏显示)
2、PopUpWindow使用详解系列(很全面)
3.popupWindow使用注意事项
注意事项一: 设置setOutsideTouchable (false)是没有作用的,点击区域外依然会dismiss
正常来说 如dialog设置setOutsideTouchable (false),点击区域外部不会消失,但是popupwindow不会,先说处理办法
需要也设置setFocusable(false)
原因参考源码解释
/**
* <p>Controls whether the pop-up will be informed of touch events outside
* of its window. This only makes sense for pop-ups that are touchable
* but not focusable, which means touches outside of the window will
* be delivered to the window behind. The default is false.</p>
*
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to one of
* the {@link #update()} methods.</p>
*
* @param touchable true if the popup should receive outside
* touch events, false otherwise
*
* @see #isOutsideTouchable()
* @see #isShowing()
* @see #update()
*/
public void setOutsideTouchable(boolean touchable) {
mOutsideTouchable = touchable;
}
最上面部分翻译成中文是:控制是否向弹出窗口通知窗口外的触摸事件。这仅对可触摸但不能聚焦的弹出窗口有意义,这意味着窗口外部的触摸*将传递到后面的窗口。默认为false(其实感觉没有说清楚,先mark吧)
注意事项二:popupwindow.showAtLocation的时候报unable to add window -- token null is not valid; is your activity running
这种我碰到的是activty没有加载完,但是我们却调用了popupwindow.showAtLocation方法造成的,这是因为popupwindow控件需要绑定Activity的context
解决办法
个人采用的是 : 通过handler延迟加载 (会在acitvity加载完才调用这里面的回调)
private Handler popupHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
popupWindow.showAtLocation(rootView, Gravity.CENTER,0, 0);
popupWindow.update();
break;
}
}
};
还有一种场景可参考: unable to add window -- token null is not valid; is your activity running 错误解决办法
注意事项三: showAsDropDown和showAtLocation区别 以及方法参数意义
区别在于:
除此之外,其他参数的意义可参考
PopuWindow的showAsDropDown和showAtLocation参数详解
3.popupWindow实现拖动效果
正常view 我们可以通过重写onTouch方法在触屏的时候进行处理,但是popupWindow是一个不继承任何的类(可以看作view的管理器吧),那实现步骤呢?
1、设置popupWindow布局里面的根布局可点击setClickable(true)
2、设置popupWindow布局里面的根布局setOnTouchListener方法并重写该View.OnTouchListener方法
3、在case MotionEvent.ACTION_MOVE:场景下进行调用popupWindow.update(offsetX, offsetY, -1, -1, true)来进行位置的更新;
这样就将拖动功能的实现转嫁到view上了,具体讲解可参考Android Popupwindow 拖动
windowManager在不同版本注意事项
可以先查看这篇博客Android浮窗小球的特殊权限申请的变动和解决
1、SYSTEM_ALERT_WINDOW权限申请
正常只需要在AndroidMainfest.xml中声明,但是在6.0以上版本 这个权限并不能动态申请,可以通过跳转到系统设置里面去手动打开
if (! Settings.canDrawOverlays(MainActivity.this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent,10);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == 10) {
if (!Settings.canDrawOverlays(this)) {
// SYSTEM_ALERT_WINDOW permission not granted...
Toast.makeText(MainActivity.this,"not granted",Toast.LENGTH_SHORT);
}
}
}
2、悬浮窗如何悬浮窗在屏幕上?
windowmanager存在不同版本兼容性问题(不然会导致崩溃) 针对26以上 (但是这种方式会导致手机上不显示悬浮窗 如我自己的荣耀手机)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;(都是系统级别)
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;(都是系统级别)
}
4.实战1:windowManager实现悬浮按钮
------------------------------
package cn.com.essence.android.widget;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Build;
import android.support.annotation.NonNull;
import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.PopupWindow;
import android.widget.ScrollView;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;
/**
* Debug窗口,用于将一些debug信息显示到UI界面,方面能直观看到调试信息。
* @author lijh6@essence.com.cn
* @date 2019.05.09 16:59
* @description
*/
public class DebugPopupWindow {
private static DebugPopupWindow sInstance;
private static final String HH_MM_SS = "HH:mm:ss";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(HH_MM_SS);
private static final int DEBUG_MAX_LINE_COUNT = 100;
private DebugPopupWindow() {
}
/**
* 获取API实例。
* @return
*/
public static DebugPopupWindow getInstance() {
if (sInstance == null) {
synchronized (DebugPopupWindow.class) {
if (sInstance == null) {
sInstance = new DebugPopupWindow();
}
}
}
return sInstance;
}
private PopupWindow popupWindow;
private ScrollView scrollView;
private TextView contentView;
private boolean isShow = true;
private Boolean isDebugable;
private StringBuffer debugInfo = new StringBuffer("Debug Window:\n");
private Queue<String> debugQueue = new LinkedList<String>();
/**
* 重置PopupWindow信息。
* @param context context
*/
public void resetPopupWindow(Activity context) {
if (!isDebugable(context)) {
return;
}
if (popupWindow != null) {
destory();
}
scrollView = new ScrollView(context);
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
scrollView.post(new Runnable() {
public void run() {
scrollView.fullScroll(View.FOCUS_DOWN);
}
});
}
});
scrollView.setPadding(40,100,40,100);
contentView = new TextView(context);
contentView.setTextColor(Color.parseColor("#CC0000FF"));
contentView.setFocusable(true);
contentView.setText(debugInfo);
contentView.setBackgroundColor(Color.parseColor("#26FFFFFF"));
contentView.setTextSize(10);
contentView.setPadding(10,10,10,10);
scrollView.addView(contentView);
popupWindow = new PopupWindow(scrollView,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, false);
popupWindow.setTouchable(false);
contentView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return true;
}
return false;
}
});
if (context.getWindow().isActive()) {
if (!isShow) {
popupWindow.dismiss();
} else {
popupWindow.showAtLocation(contentView.getRootView(), Gravity.CENTER,0,0);
}
}
}
private boolean isDebugable(Activity context) {
if (isDebugable == null) {
try {
ApplicationInfo info = context.getApplicationInfo();
isDebugable = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
} catch (Exception e) {
e.printStackTrace();
}
}
return isDebugable == null ? false : isDebugable;
}
public void destory() {
if (popupWindow != null) {
popupWindow.dismiss();
popupWindow = null;
scrollView = null;
contentView = null;
}
}
/**
* 切换显示和隐藏。
*/
public void switchShowOrHidden() {
isShow = !isShow;
if (popupWindow != null) {
if (popupWindow.isShowing()) {
popupWindow.dismiss();
} else {
popupWindow.showAtLocation(contentView.getRootView(), Gravity.CENTER,0,0);
}
}
}
/**
* 设置显示。
* @param isShow
*/
public void show(boolean isShow) {
this.isShow = isShow;
if (popupWindow == null) {
return;
}
if (isShow && !popupWindow.isShowing()) {
popupWindow.showAtLocation(contentView.getRootView(), Gravity.CENTER,0,0);
} else if (!isShow && popupWindow.isShowing()) {
popupWindow.dismiss();
}
}
/**
* 更新Debug信息。
* @param info info
*/
public void updateDebugInfo(String info) {
this.debugInfo = new StringBuffer();
this.debugInfo.append("Debug Info:\r\n" + info);
if (contentView != null && popupWindow.isShowing()) {
contentView.setText(this.debugInfo);
}
}
/**
* 添加日志信息。
* @param info
*/
public void appendDebugInfo(String info) {
this.debugInfo = new StringBuffer();
debugQueue.add("\n"+DATE_FORMAT.format(new Date())+"> "+info);
if (debugQueue.size() > DEBUG_MAX_LINE_COUNT) {
debugQueue.poll();
}
for (String d : debugQueue) {
debugInfo.append(d);
}
if (contentView != null && popupWindow.isShowing()) {
contentView.setText(this.debugInfo);
}
}
}
package cn.com.essence.android.widget.util;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import cn.com.essence.android.widget.R;
public class Float {
WindowManager.LayoutParams wmParams;
private WindowManager windowManager;
private Button button;
private static Float sInstance;
private Float() {
}
/**
* 获取API实例。
* @return
*/
public static Float getInstance() {
if (sInstance == null) {
synchronized (Float.class) {
if (sInstance == null) {
sInstance = new Float();
}
}
}
return sInstance;
}
/**
* 初始化windowManager
*/
public void resetButton(Activity activity) {
if (button != null) {
return;
}
windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
wmParams = getParams();//设置好悬浮窗的参数
// 悬浮窗默认显示以左上角为起始坐标
wmParams.gravity = Gravity.RIGHT | Gravity.BOTTOM;
// 获取浮动窗口视图所在布局
button = (Button) LayoutInflater.from(activity).inflate(R.layout.floating_layout,
null);
}
public void show2(Activity activity) {
//Looper.prepare();
final WindowManager wm =
(WindowManager) activity.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams para = new WindowManager.LayoutParams();
//设置弹窗的宽高
para.height = WindowManager.LayoutParams.WRAP_CONTENT;
para.width = WindowManager.LayoutParams.WRAP_CONTENT;
//期望的位图格式。默认为不透明
para.format = 1;
//当FLAG_DIM_BEHIND设置后生效。该变量指示后面的窗口变暗的程度。
//1.0表示完全不透明,0.0表示没有变暗。
para.dimAmount = 0.6f;
para.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
//设置为系统提示
if (Build.VERSION.SDK_INT > 25) {
para.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
if (! Settings.canDrawOverlays(activity)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + activity.getPackageName()));
activity.startActivityForResult(intent, 10);
}
} else {
para.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
//获取要显示的View
button = (Button) LayoutInflater.from(activity).inflate(R.layout.floating_layout,
null);
//单击View是关闭弹窗
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
wm.removeView(button);
}
});
//显示弹窗
wm.addView(button, para);
//Looper.loop();
}
public void show() {
// 添加悬浮窗的视图
windowManager.addView(button, wmParams);
}
public void hideButton() {
if (button != null && windowManager != null) {
button = null;
onDestroy();
}
}
/**
* 对windowManager进行设置
*
* @return
*/
public WindowManager.LayoutParams getParams() {
wmParams = new WindowManager.LayoutParams();
//设置window type 下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
//wmParams.type = LayoutParams.TYPE_PHONE;
//wmParams.type = LayoutParams.TYPE_SYSTEM_ALERT;
//wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
//设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
//wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}
//设置可以显示在状态栏上
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
//设置悬浮窗口长宽数据
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
return wmParams;
}
private void onDestroy() {
if (button != null) {
// 移除悬浮窗口
windowManager.removeView(button);
}
}
}
4. 实战2: popupwindow实现可拖动悬浮窗
package cn.com.essence.mobile.library.common.base.sensorplate;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import cn.com.essence.mobile.library.common.R;
import org.json.JSONObject;
/**
* Debug窗口,用于将一些debug信息显示到UI界面,方面能直观看到调试信息
* 神策埋点可视化工具面板。
*/
public class DragSensorWindow {
private static DragSensorWindow instance;
private final List<SensorBean> dataList = new ArrayList<>();
private LinearLayout rootLayout;
private ListAdapter listAdapter;
private PopupWindow popupWindow;
private Boolean isDebuggable;
private Activity activity;
//开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
private int touchStartX;
private int touchStartY;
//拖拽控件偏移值
private int offsetX = 0;
private int offsetY = 0;
/**
* 是否显示弹窗。
*/
private boolean showPop;
private HandleDataListView lvData;
private DragSensorWindow() {
}
/**
* 获取API实例。
*
* @return 实例
*/
public static DragSensorWindow getInstance() {
if (instance == null) {
synchronized (DragSensorWindow.class) {
if (instance == null) {
instance = new DragSensorWindow();
}
}
}
return instance;
}
/**
* 展示布局。
*
* @param context context
*/
@SuppressLint("InflateParams")
public void resetWindow(final Activity context) {
if (!isDebuggable(context)) {
return;
}
//说明当前activity还是存在 且与跳到下一个页面是同一个activity
if (activity != null && activity.getClass().getName().equals(context.getClass().getName())
&& popupWindow != null) {
return;
}
if (popupWindow != null) {
popupWindow.dismiss();
popupWindow = null;
}
activity = context;
rootLayout = (LinearLayout) LayoutInflater.from(context)
.inflate(R.layout.drag_debug_sensor_window_layout, null);
popupWindow = new PopupWindow(rootLayout,
getScreenW(), getScreenH() / 3, false);
popupWindow.setTouchable(true);
//注意:这个生效的前提是 focusable为false
popupWindow.setOutsideTouchable(false);
View rootView = rootLayout.findViewById(R.id.root_view);
//设置布局可以点击
rootView.setClickable(true);
lvData = rootLayout.findViewById(R.id.lv_data);
listAdapter = new ListAdapter();
lvData.setAdapter(listAdapter);
listAdapter.notifyDataSetChanged();
TextView tvClearData = rootLayout.findViewById(R.id.tv_clear_data);
ImageView ivClose = rootLayout.findViewById(R.id.iv_close);
ivClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hide();
}
});
tvClearData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dataList.clear();
listAdapter.notifyDataSetChanged();
Toast.makeText(context, "清空数据成功", Toast.LENGTH_SHORT).show();
}
});
//设置触摸监听器
rootView.setOnTouchListener(new MyTouchListener());
//针对back键监听
rootView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return keyCode == KeyEvent.KEYCODE_BACK;
}
});
if (showPop) {
show();
}
}
/**
* 只在测试环境才弹。
*
* @param context context
* @return 结果
*/
private boolean isDebuggable(Activity context) {
try {
ApplicationInfo info = context.getApplicationInfo();
isDebuggable = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
} catch (Exception e) {
e.printStackTrace();
}
return isDebuggable;
}
/**
* 显示。
*/
public void show() {
if (popupWindow != null && activity != null) {
showPop = true;
if (popupWindow.isShowing()) {
return;
}
new Handler().post(new Runnable() {
public void run() {
//Gravity.BOTTOM 弹窗显示在屏幕下边界的中心位置,并以PopupWindow下边界中心为坐标原点(0,0)
// 来偏移,x + 表示向左偏移,y +表示向上偏移
popupWindow.showAtLocation(rootLayout.getRootView(), Gravity.BOTTOM, offsetX,
-offsetY);
}
});
}
}
/**
* 隐藏 同时销毁。
*/
public void hide() {
if (popupWindow != null && popupWindow.isShowing()) {
popupWindow.dismiss();
showPop = false;
offsetX = 0;
offsetY = 0;
}
}
/**
* 更新Debug信息。
*
* @param sensorEventType 埋点类型 页面埋点/按钮埋点/自定义埋点
* @param dataJson dataJson
* @param labelJson labelJson
*/
public void updateDebugInfo(String sensorEventType, JSONObject dataJson, JSONObject labelJson) {
if (dataJson == null || labelJson == null) {
return;
}
if (popupWindow != null && popupWindow.isShowing()) {
SensorBean sensorBean = createSensorBean(sensorEventType,dataJson, labelJson);
dataList.add(sensorBean);
listAdapter.notifyDataSetChanged();
lvData.setSelection(lvData.getBottom());
}
}
/**
* 创建神策埋点bean。
*
* @param sensorEventType 埋点类型 页面埋点/按钮埋点/自定义埋点
* @param dataJson 数据json
* @param labelJson 标签json
* @return bean
*/
private SensorBean createSensorBean(String sensorEventType, JSONObject dataJson,
JSONObject labelJson) {
SensorBean sensorBean = new SensorBean();
Iterator<String> keys = dataJson.keys();
StringBuilder content = new StringBuilder();
while (keys.hasNext()) {
String key = keys.next();
String dataString = dataJson.optString(key);
String labelString = labelJson.optString(key);
if (!TextUtils.isEmpty(dataString) && !TextUtils.isEmpty(labelString)) {
content.append(labelString).append(":").append(dataString).append(" ");
}
}
sensorBean.setContent(content.toString());
//埋点时间
sensorBean.setTime(nowDate());
//埋点类型
sensorBean.setPageType(sensorEventType == null ? "" : sensorEventType);
sensorBean.setAppType("");
return sensorBean;
}
/**
* 悬浮窗布局监听。
*/
private class MyTouchListener implements View.OnTouchListener {
@Override
public boolean onTouch(View arg0, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
touchStartX = (int) event.getRawX();
touchStartY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int touchCurrentX = (int) event.getRawX();
int touchCurrentY = (int) event.getRawY();
offsetX += touchCurrentX - touchStartX;
offsetY += touchCurrentY - touchStartY;
//width 为-1 表示不会更改本身布局大小
//兼容7.0版本及以下
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
popupWindow.update(offsetX, getScreenH() / 3 * 2 + offsetY, -1, -1);
} else {
//7.0以上版本
popupWindow.update(offsetX, -offsetY, -1, -1);
}
touchStartX = touchCurrentX;
touchStartY = touchCurrentY;
break;
case MotionEvent.ACTION_UP:
default:
break;
}
return false;
}
}
/**
* 埋点adapter。
*/
class ListAdapter extends BaseAdapter {
@Override
public int getCount() {
return dataList.size();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@SuppressLint("InflateParams")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(activity).inflate(R.layout.sensor_item_pop_layout,
null);
viewHolder.tvItemTime = convertView.findViewById(R.id.tv_item_time);
viewHolder.tvItemPageType = convertView.findViewById(R.id.tv_item_page_type);
viewHolder.tvItemAppType = convertView.findViewById(R.id.tv_item_app_type);
viewHolder.tvItemContent = convertView.findViewById(R.id.tv_item_content);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
SensorBean sensorBean = dataList.get(position);
if (sensorBean != null) {
viewHolder.tvItemTime.setText(sensorBean.getTime());
viewHolder.tvItemPageType.setText(sensorBean.getPageType());
viewHolder.tvItemAppType.setText(sensorBean.getAppType());
String content = sensorBean.getContent();
if (content == null) {
viewHolder.tvItemContent.setText("");
} else {
viewHolder.tvItemContent.setText(setColorByContent(content));
}
}
return convertView;
}
/**
* 主要是为了指定字段文案变色。
* @param content 上下文
* @return
*/
private SpannableString setColorByContent(String content) {
int index = -1;
if (content.contains("安信页面名称")) {
index = content.indexOf("安信页面名称");
} else if (content.contains("安信按钮名称")) {
index = content.indexOf("安信按钮名称");
}
SpannableString spanString = new SpannableString(content);
if (index >= 0) {
ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
spanString.setSpan(span, index, content.length() - 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spanString;
}
//自定义ViewHolder类
class ViewHolder {
TextView tvItemTime; //时间
TextView tvItemPageType; //页面类型
TextView tvItemAppType; //App类型
TextView tvItemContent; //内容
}
}
/**
* 清空数据。
*/
public void clearData() {
if (dataList != null) {
dataList.clear();
listAdapter.notifyDataSetChanged();
}
}
/**
* 获取屏幕高度。
*/
private int getScreenH() {
Point outPoint = getPoint();
return outPoint.y;
}
/**
* 获取屏幕宽度。
*/
private int getScreenW() {
Point outPoint = getPoint();
return outPoint.x;
}
/**
* 获取屏幕Point。
*/
@NonNull
private Point getPoint() {
Point outPoint = new Point();
try {
WindowManager windowManager =
(WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
final Display display = windowManager.getDefaultDisplay();
display.getRealSize(outPoint);
} catch (Exception e) {
e.printStackTrace();
}
return outPoint;
}
/**
* 获取当前日期。
* @return 日期字符串
*/
private String nowDate() {
String dateTime;
Date dt = new Date();
//最后的aa表示“上午”或“下午” HH表示24小时制 如果换成hh表示12小时制
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
dateTime = sdf.format(dt);
return dateTime;
}
}
5.windowManager跟Popupwindow还存在的问题
问题1: Popupwindow是依赖activity的 如果跳转下一个activity pop会被覆盖 这就会导致悬浮窗不能一直存在于整个应用内,相当于acitivty级别的悬浮窗,暂时的办法是存了一个静态pop(不合理)
问题2: WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 这个属性会导致悬浮窗不止存在于应用内,更是存在于整个手机屏幕,相当于是系统级别弹窗(不合理)
思考:如何实现一个应用级别的悬浮窗????(如有读者实现了 可提供思路)
Mark:
1、有些悬浮窗里面是一个列表 可以参考listview的使用
2、WindowManager.LayoutParams.type属性