preface

Popupwindow probability of use, I want to do the development of the staff should all know, but also we should start to master the control. However, the use of this control version compatibility problems, there are also a lot of matters needing attention. This article according to my personal development experience to comb.

1. The scene

In practical development, we are always asked to implement draggable hover Windows. The first thing that comes to mind is using WindowManager, but there are all sorts of problems with using this directly. What’s the alternative? The answer is popupWindow(which is problematic as well, but we’ll get to that later).

2. The use of popupWindow

In fact, the use of poupWindow is more on the Internet, I will post a blog address written by others, compared to popupWindow mainly in some attention items. 1, PopUpWindow basic use (full screen display) 2, PopUpWindow use detailed series (very comprehensive)

3. Precautions for using popupWindow

Note 1: setOutsideTouchable (false) has no effect, click outside the region will still be dismissed

Normally, if dialog sets setOutsideTouchable (false), the outside of the click area does not disappear, but popupWindow does not. The handler should also set 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; }Copy the code

Controls whether to notify a pop-up window of out-of-window touch events. This only makes sense for touchable but unfocused pop-ups, which means that touches * outside the window are passed to subsequent Windows. Default: false

Note 2: the popupwindow. ShowAtLocation newspaper unable to add the window – token null is not valid. is your activity running

To me this is activty didn’t finish loading, but we call the popupwindow showAtLocation method, this is because the popupwindow controls need to bind the Activity of the context solution personal USES is: Lazy loading via handler (callback will be called after AcitVity is loaded)

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; }}};Copy the code

Unable to add window — token null is not valid; Is your activity running

Note 3: Difference between showAsDropDown and showAtLocation and meaning of method parameters

The difference is: ShowAsDropDown (Viewanchor intxoff, intyoff) PopupWindow on the lower left corner of the anchor coordinate origin coordinates are displayed at the bottom of the anchor, and showAtLocation specified pop-up window relative to the precise location of the screen, and concrete Color {red}{showAsDropDown(View Anchor, int xoff, anchor) int Yoff) takes the coordinates of the lower left corner of the anchor as the origin coordinates of PopupWindow and displays it below the anchor, while showAtLocation specifies the exact position of popup relative to the screen, which has nothing to do with the specific anchorView. The following three parameters determine the position of popup}showAsD RopDown (Viewanchor intxoff, intyoff) PopupWindow on the lower left corner of the anchor coordinate origin coordinates are displayed at the bottom of the anchor, and showAtLocation specified pop-up window relative to the precise location of the screen, and the specific anchorV Iew does not matter. The last three parameters determine the popover location. For the meanings of other parameters, see the showAsDropDown and showAtLocation parameters of PopuWindow

3. PopupWindow implements drag effect

A normal view can be handled by overriding the onTouch method on the touch screen, but popupWindow is a class that doesn’t inherit anything. What about implementation steps? To set the popupWindow root layout, click setClickable(true). To set the popupWindow root layout setOnTouchListener method and override the view. OnTouchListener method Popupwindow. update(offsetX, offsetY, -1, -1, true) is called in case MotionEvent.ACTION_MOVE: to update the position; In this way, the drag function is transferred to the view, for specific explanation, please refer to the Android Popupwindow drag

WindowManager notes in different versions

You can first check out this blog about Android floating ball’s special permission application changes and resolution

1. Apply for the SYSTEM_ALERT_WINDOW permission

Normally, it only needs to be declared in AndroidMainfest.xml. However, in version 6.0 and above, this permission cannot be applied dynamically. You can manually open it by jumping to system Settings

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); }}}Copy the code

2. How to hover the window on the screen?

Windows Manager has compatibility issues with different versions (otherwise it will crash) for above 26 (but this way it will not show floating Windows on phones like my own Glory phone)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; (all system level)} else {wmParams. Type = WindowManager. LayoutParams. TYPE_PHONE; (Both system level)}Copy the code

4. Actual combat 1:windowManager to achieve the hover button

------------------------------ 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 window, which displays debugging information to the UI. * @author [email protected] * @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() {} /** * gets an API instance. * @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>(); /** * Resets PopupWindow information. * @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 (40100,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); ,10,10,10 contentView. SetPadding (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; }} /** * Toggle show and hide. */ public void switchShowOrHidden() { isShow = ! isShow; if (popupWindow ! = null) { if (popupWindow.isShowing()) { popupWindow.dismiss(); } else {popupWindow. ShowAtLocation (contentView. GetRootView (), Gravity, CENTER, 0, 0). }}} /** * Set display. * @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(); }} /** * Updates debugging information. * @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); }} /** * Adds log information. * @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); }}}Copy the code
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() {} /** * gets an API instance. * @return */ public static Float getInstance() { if (sInstance == null) { synchronized (Float.class) { if (sInstance == null) { sInstance = new Float(); } } } return sInstance; } /** * public void resetButton(Activity Activity) {if (button! = null) { return; } windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE); wmParams = getParams(); / / set the parameters of the floating window / / suspended the default display window with the opening of the upper left corner coordinates wmParams. Gravity = gravity. The RIGHT | gravity. The 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(); / / set the width of the window high para. Height = WindowManager. LayoutParams. WRAP_CONTENT; para.width = WindowManager.LayoutParams.WRAP_CONTENT; // Desired bitmap format. Default: opaque para.format = 1; // When FLAG_DIM_BEHIND is set. This variable indicates how dark the following window is. //1.0 means completely opaque, 0.0 means not darkened. Para. DimAmount = 0.6 f; para.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_DIM_BEHIND; / / set to prompt the 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; } // Get the View to display. Button = (button) LayoutInflater. From (activity).inflate(R.layout.floating_layout, null); / / click View is close the pop-up button. SetOnClickListener (new View. An OnClickListener () {@ Override public void onClick (View v) { wm.removeView(button); }}); // display window wm.addView(button, para); //Looper.loop(); } public void show() {windowManager.addView(button, wmParams); } public void hideButton() { if (button ! = null && windowManager ! = null) { button = null; onDestroy(); }} / * * @ * * * to set the windowManager return * / public windowManager. LayoutParams getParams () {wmParams = new WindowManager.LayoutParams(); // wmparams.type = layoutparams.type_phone; // Wmparams.type = layoutparams.type_phone; // Wmparams.type = layoutparams.type_phone; //wmParams.type = LayoutParams.TYPE_SYSTEM_ALERT; //wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; Flags = layoutparams.flag_not_focusable; // Wmparams.flags = layoutparams.flag_not_focusable; // 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; } / / set can be displayed in the status bar on wmParams. The 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. For floating window width data width = WindowManager. LayoutParams. WRAP_CONTENT; wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; return wmParams; } private void onDestroy() { if (button ! Windowmanager.removeview (button); }}}Copy the code

4. Actual combat 2: PopupWindow can be dragged suspension window

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;
    }

}

Copy the code

5. There are still problems between windowManager and Popupwindow

Problem 1: Popupwindow is activity-dependent. If a Popupwindow jumps to the next activity, its pop will be overwritten. This will result in the suspension window not remaining in the entire application.

Question 2: WindowManager. LayoutParams. TYPE_APPLICATION_OVERLAY this attribute will lead to suspension not only exist in the application window, but also exists in the whole mobile phone screen, the equivalent system level popup window (unreasonable)

Thinking: How to implement an application-level suspension window???? (If any readers can provide ideas)

Mark:

1, some suspended inside the window is a list You can refer to the use of the listview. 2, WindowManager LayoutParams. The type attribute