Android实现全局弹窗的三种方式

img1

前期需求

我们公司最近需要开发一个功能,大致要求是:通过复制一个分享出去的文字(这段文字有一定的规则,比如满足某种格式#文字#….url….)当用户复制了这段文字,打开了我们的app,就会对文字进行检查,满足条件,
就提取相应的条件去服务器请求,当数据请求下来后,就会通过弹窗的形式提示用户,并提供直达相应页面的跳转。需求大概就是这样了。

1.弹窗的创建

要创建弹窗,首先想到是的通过哪几种方式来创建,我想到了三种可以创建弹窗的方式:

1.通过PopWindow来实现
2.通过WindowManager的addview的方式来创建
3.使用dialog样式的Activity来实现

以上三种方式都是可以的。

2.弹窗的实现方式

最开始我选择了使用PopWindow,感觉比较轻量级,选择了组件后,就考虑此弹窗是否有一定的通用性,弹窗主要分为图片部分,文字部分以及取消和确定
按钮,其实是比较通用的一个弹窗,所以,就把它抽取出来,使用通用的方式来处理.具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package com.xiu.commLib.widget.CommView;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;

import com.nostra13.universalimageloader.core.ImageLoader;
import com.xiu.commLib.R;

import java.util.List;

/**
* Created by allen on 2/25/2016 10:45.
*/

public class CommLeftImgPopWindow extends PopupWindow implements View.OnClickListener {

private Activity mContext;
private List<String> mTextList;
private String mImgUrl;

// @Bind(R.id.xiu_command_goods_img)
private ImageView mImgView;
// @Bind(R.id.xiu_command_goods_brandname)
private TextView mTextBrandName;
private TextView mTextGoodsName;
private TextView mTextGoodsPrice;
private Button mLeftButton;
private Button mRightButton;


private ButtonListener mButtonListener;
private static final String POP_SHOW = "popopwindow show";
private static final String POP_HIDE = "popopwindow hide";

/**
* @param context
* @param textList 将文字以集合的方式添加进来,长度最多为3,可以为3以下,而且参数顺序必须与展示的顺序一致
* @param imgUrl 图片url
*/

public CommLeftImgPopWindow(Activity context, List<String> textList, String imgUrl) {
mContext = context;
mTextList = textList;
mImgUrl = imgUrl;
initView();
handleDataShow();
}


private void initView() {
View view = LayoutInflater.from(mContext).inflate(R.layout.xiu_command_goods_layout, null);
mImgView = (ImageView) view.findViewById(R.id.xiu_command_goods_img);
mTextBrandName = (TextView) view.findViewById(R.id.xiu_command_goods_brandname);
mTextGoodsName = (TextView) view.findViewById(R.id.xiu_command_goods_goodsname);
mTextGoodsPrice = (TextView) view.findViewById(R.id.xiu_command_goods_price);
mLeftButton = (Button) view.findViewById(R.id.xiu_command_goods_left_button);
mRightButton = (Button) view.findViewById(R.id.xiu_command_goods_right_button);

mLeftButton.setOnClickListener(this);
mRightButton.setOnClickListener(this);

setContentView(view);
handlePopBg(PopisShow.POP_SHOW);
handleClick();

// ButterKnife.bind(this, view);


}

private void handleClick() {

}

private void handlePopBg(PopisShow isShow) {
if (isShow.equals(PopisShow.POP_SHOW)) {
WindowManager.LayoutParams attributes = mContext.getWindow().getAttributes();
attributes.alpha = 0.4f;
mContext.getWindow().setAttributes(attributes);
} else {
WindowManager.LayoutParams attributes = mContext.getWindow().getAttributes();
attributes.alpha = 1.0f;
mContext.getWindow().setAttributes(attributes);
}
}

private void handleDataShow() {
setWindowLayoutMode(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
setFocusable(true);
//显示图片
ImageLoader.getInstance().displayImage(mImgUrl, mImgView);
handleTextShow();
}

private void handleTextShow() {
if (mTextList == null || mTextList.size() > 3 || mTextList.size() == 0) {
throw new RuntimeException("文字list长度必须小于等于3,并且不能等于0以及list不能为null");
}

switch (mTextList.size()) {
case 1:
mTextBrandName.setText(mTextList.get(0));
mTextGoodsPrice.setVisibility(View.GONE);
mTextGoodsName.setVisibility(View.GONE);
break;

case 2:
mTextBrandName.setText(mTextList.get(0));
mTextGoodsName.setText(mTextList.get(1));
mTextGoodsPrice.setVisibility(View.GONE);
break;

case 3:
mTextBrandName.setText(mTextList.get(0));
mTextGoodsName.setText(mTextList.get(1));
mTextGoodsPrice.setText(mTextList.get(2));
break;
}
}

@Override
public void onClick(View v) {
int currentClickId = v.getId();
if (currentClickId == R.id.xiu_command_goods_left_button) {
//左边按钮点击
if (mButtonListener != null) {
mButtonListener.leftButtonClick();
}
handlePopBg(PopisShow.POP_HIDE);
} else if (currentClickId == R.id.xiu_command_goods_right_button) {
//右边按钮点击
if (mButtonListener != null) {
mButtonListener.rightButtonClick();
}
handlePopBg(PopisShow.POP_HIDE);
}
}

public interface ButtonListener {

public void rightButtonClick();

public void leftButtonClick();
}

public void setButtonListener(ButtonListener listener) {
this.mButtonListener = listener;
}

enum PopisShow {
POP_SHOW, POP_HIDE;
}
}

3.使用Service来启动弹窗

由于只要app从后台回到前台就需要去检查剪切板是否有符合规则的文字,所以就很自然的想起了使用服务
来做这样子的一件事情,当符合条件后,就弹起相应的弹窗提示用户。在服务里主要做了三件事情:

1.获取剪切板里的数据,进行匹配,并提取匹配到的数据
2.使用匹配到的数据去服务器请求相应的要显示在对话框里的数据
3.创建对话框将从服务器请求来的数据进行展示

以上三件事情是每次从后台切换到前台都要做的事情,而服务里的 onStartCommand每次启动服务都会被调
用。上面的逻辑可以写到onStartCommand方法里

4.全局开启服务

到这里有一个需要解决的问题是:在哪里调用服?因为app从后台到前台就需要调用一次服务。不管当前是
在哪个页面。最先想到的就是Application了。应该有一个类似于Activity生命周期onResume()的方法,
可是application里就只有一个onCreate()方法,于是打开Application源码的源码,刚开始看就看到了这个:

Alt text

这其实就是Activity生命周期被调用的地方了。也就是说当每次app从后台到前台都会先调用
onActivityStarted,onActivityResumed以及onActivityPaused,onActivityStopped这几个方法,所以服务
的开启就可以在onActivityStarted,onActivityResumed这两个方法里来启动。

这里有一个问题?就是直接实现上面的接口,实现这几个方法,好像相应方法并没有被调用?而我使用了注册的方式,就可以了。
registerActivityLifecycleCallbacks(this);

5.使用popupWindow实现弹窗的问题

一切貌似都Ok,没看到有什么问题,当我真正跑起来才发现,有个致命的细节,给我带来了麻烦?我们app背景为白色的,而弹窗也是白色
的,那么问题来了,两个白色的,中间需要改变一下当前窗体的颜色,这样显示才比较自然,可是我们的popupwindow是在service被调用的
而改变当前window的alpha值只有在Activity里才能改变。由于这个问题的存在,我改用了使用WindowManager的addView方式来实现,当
然,我写的通用的popupWindow我便没有删掉,因为只要在Activity还是能够使用。

6.改用WindowManager的addview方式来实现弹窗

其它部分其实和上面是共用的,只是这个弹窗的实现要改变一下。>在代码部分我分成了两个文件来实现,CommLeftImgbyWM,CommWMConfig,
CommWMConfig:主要完成配置的部分,因为这部分是可以通用的,所以将这部分逻辑抽取出来。下面是CommWMConfig的代码:

import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;

/**
 * Created by allen on 2/26/2016 15:48.
 */
public class CommWMConfig {
    private WindowManager wm;
    private WindowManager.LayoutParams mWinParams;
    private View mView;

    public CommWMConfig(Context context, View view) {
        wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mWinParams = new WindowManager.LayoutParams();
        configParams();
        this.mView = view;

        show();

        handleBackKey();
    }

    private void handleBackKey() {
        mView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_BACK) {
                    dissmiss();
                    return true;
                }
                return false;
            }
        });
    }

    private void show() {
        wm.addView(mView, mWinParams);
    }

    public void dissmiss() {
        wm.removeView(mView);
    }

    private void configParams() {
        //设置window type
        mWinParams.type = WindowManager.LayoutParams.TYPE_TOAST;
        //设置图片格式,效果为背景透明
        mWinParams.format = PixelFormat.RGBA_8888;
//        //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
//        mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        //调整悬浮窗显示居中
        mWinParams.gravity = Gravity.NO_GRAVITY;

        //设置悬浮窗口长宽
        mWinParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        mWinParams.height = WindowManager.LayoutParams.MATCH_PARENT;
    }
}

下面是CommLeftImgbyWM的代码部分:

package com.xiu.commLib.widget.CommView;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.nostra13.universalimageloader.core.ImageLoader;
import com.xiu.commLib.R;

import java.util.List;

/**
 * Created by allen on 2/26/2016 11:06.
 */
public class CommLeftImgbyWM implements View.OnClickListener {
    private Context mContext;
    private List<String> mTextList;
    private String mImgUrl;

    private ImageView mImgView;
    private TextView mTextBrandName;
    private TextView mTextGoodsName;
    private TextView mTextGoodsPrice;
    private Button mLeftButton;
    private Button mRightButton;

    private ButtonListener mButtonListener;

    private CommWMConfig mconfig;

    public CommLeftImgbyWM(Context context, List<String> textList, String imgUrl) {
        this.mContext = context;
        this.mTextList = textList;
        this.mImgUrl = imgUrl;

        handleShow();

        handleShowData();
    }

    private void handleShowData() {
        //显示图片
        ImageLoader.getInstance().displayImage(mImgUrl, mImgView);
        handleTextShow();
    }

    private void handleTextShow() {
        if (mTextList == null || mTextList.size() > 3 || mTextList.size() == 0) {
            throw new RuntimeException("文字list长度必须小于等于3,并且不能等于0以及list不能为null");
        }

        switch (mTextList.size()) {
            case 1:
                mTextBrandName.setText(mTextList.get(0));
                mTextGoodsPrice.setVisibility(View.GONE);
                mTextGoodsName.setVisibility(View.GONE);
                break;

            case 2:
                mTextBrandName.setText(mTextList.get(0));
                mTextGoodsName.setText(mTextList.get(1));
                mTextGoodsPrice.setVisibility(View.GONE);
                break;

            case 3:
                mTextBrandName.setText(mTextList.get(0));
                mTextGoodsName.setText(mTextList.get(1));
                mTextGoodsPrice.setText(mTextList.get(2));
                break;
        }
    }

    private WindowManager.LayoutParams wmParams;
    private WindowManager wm;
    private RelativeLayout view;

    private void handleShow() {

        view = (RelativeLayout) LayoutInflater.from(mContext)
                .inflate(R.layout.xiu_command_goods_layout, null);

        initView(view);

        mconfig = new CommWMConfig(mContext, view);

    }

    private void initView(View view) {
        mImgView = (ImageView) view.findViewById(R.id.xiu_command_goods_img);
        mTextBrandName = (TextView) view.findViewById(R.id.xiu_command_goods_brandname);
        mTextGoodsName = (TextView) view.findViewById(R.id.xiu_command_goods_goodsname);
        mTextGoodsPrice = (TextView) view.findViewById(R.id.xiu_command_goods_price);
        mLeftButton = (Button) view.findViewById(R.id.xiu_command_goods_left_button);
        mRightButton = (Button) view.findViewById(R.id.xiu_command_goods_right_button);

        mLeftButton.setOnClickListener(this);
        mRightButton.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        int currentClickId = v.getId();
        if (currentClickId == R.id.xiu_command_goods_left_button) {
            //左边按钮点击
            if (mButtonListener != null) {
                mButtonListener.leftButtonClick();
            }
        } else if (currentClickId == R.id.xiu_command_goods_right_button) {
            //右边按钮点击
            if (mButtonListener != null) {
                mButtonListener.rightButtonClick();
            }
        }
    }

    public interface ButtonListener {

        public void rightButtonClick();

        public void leftButtonClick();
    }

    public void setButtonListener(ButtonListener listener) {
        this.mButtonListener = listener;
    }

    /**
     * 移除dialog
     */
    public void dissmiss() {
        mconfig.dissmiss();
    }

    public void show(List<String> textList, String imgUrl) {
        this.mTextList = textList;
        this.mImgUrl = imgUrl;
        handleShow();
        handleShowData();
    }
}

因为CommLeftImgbyWM是依赖于CommWMConfig的,这里可以通过构造函数注入的方式将CommWMConfig注入到CommLeftImgbyWM里来使用。

7.使用dialog样式的Activity实现

其实,使用Activity样式来实现也是比较简单的,主要是要给Activity设置dialog样式的theme,其它的实现和上面的弹窗没有太大的不同,只是觉得使用Activity,每次去开启,销毁还是一件比较消耗资源的事情。