Android悬浮窗的实现--可以置顶,可以设置优先级的view

悬浮窗,顾名思义,显示在window界面之上的一种视图。如今,有这么个需求:设计一个能够在任意界面上显示的60s倒计时弹窗,60s之后执行其他操作。注意哦,这里的任意不仅限于当前的应用,而是所有的界面。效果如下图:

Android悬浮窗的实现--可以置顶,可以设置优先级的view_第1张图片


这里,我们用WindowManager + view来实现,代码不多,实现起来也简单,不过会遇到几个坑:


1,项目结构:

Android悬浮窗的实现--可以置顶,可以设置优先级的view_第2张图片


2,布局文件:



①activity_main.xml代码:



    

效果图:

Android悬浮窗的实现--可以置顶,可以设置优先级的view_第3张图片


②countdown_weight.xml代码:




    

        

        

        

效果图见第一张图:


③new_activity.xml其实就是一张上了颜色的Activity,这里只是为了起演示作用


3,为了让view始终置顶显示,就像手机音乐里的桌面歌词一样,无论怎么切换,view都不会消失。我们用结合WindowManager,让view显示,而且用WindowManager的好处是,如果有多个view需要显示,我们还可以通过设置WindowManager.LayoutParams相关的参数,让view按我们设定的优先级显示,类似层级图一样。这也是为什么,不用PopupWindow的原因所在,它和窗口小部件还是有区别的!

代码如下:

package com.example.floateweight;

import java.util.Timer;
import java.util.TimerTask;

import com.example.countdownweight.R;

import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

/**
 * 倒计时60s悬浮窗
 * 
 * @author zy
 * 
 */
public class FloateActivity extends Activity {
	protected static final String TAG = "CountDownActivity";
	protected static final int TIME = 1;

	private Context context = FloateActivity.this;
	private TextView tv_time;
	private Button cancle;
	private static Timer countDown = null;
	private int mValue = 60;
	
	WindowManager wm;
	WindowManager.LayoutParams params;
	View countDownView;
	
	Handler post = new Handler();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	/**
	 * 点击显示悬浮窗
	 * 
	 * @param view
	 */
	public void show(View v) {
		wm = (WindowManager) getApplicationContext().getSystemService(
				WINDOW_SERVICE); // 注意:这里必须是全局的context
		// 判断UI控件是否存在,存在则移除,确保开启任意次应用都只有一个悬浮窗
		if (countDownView != null) {
			wm.removeView(countDownView);
		}
		params = new WindowManager.LayoutParams();
		// 系统级别的窗口
		params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
				| WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
		// 居中显示
		params.gravity = Gravity.CENTER;
		// 设置背景透明
		params.format = PixelFormat.TRANSPARENT;

		countDownView = new View(getApplicationContext()); // 不依赖activity的生命周期
		countDownView = View.inflate(getApplicationContext(),
				R.layout.countdown_weight, null);

		cancle = (Button) countDownView.findViewById(R.id.cancle);
		tv_time = (TextView) countDownView.findViewById(R.id.tv_time);
		tv_time.setText("60");
		wm.addView(countDownView, params);

		// 设置“取消”倒计时按钮的监听
		cancle.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View view) {
				Log.e(TAG, "取消倒计时");
				wm.removeView(countDownView);
				countDownView = null;
				countDown.cancel();
				mValue = 60;
			}
		});

		// 添加倒计时功能
		countDown = new Timer();
		countDown.schedule(new TimerTask() {
			@Override
			public void run() {
				mValue--;
				post.post(drawCount);
				if (mValue == 0) {
					// 执行关机操作(这里可以使任意其他操作,根据自己的需求)
					Log.e(TAG, "关机");
					wm.removeView(countDownView);
					countDownView = null;
					// 取消定时
					countDown.cancel();
					finish();
				}
			}
		}, 0, 1000);
	}

	/**
	 * 模拟其他操作
	 * @param view
	 */
	public void other(View view) {
		Toast.makeText(context, "别的操作", Toast.LENGTH_SHORT).show();
		startActivity(new Intent(context, NewActivity.class));
	}

	Runnable drawCount = new Runnable() {

		@Override
		public void run() {
			tv_time.setText(Integer.toString(mValue));
		}
	};
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		Log.e(TAG, "倒计时结束");
	};
}

①首先是关于WindowManager.LayoutParams的设置,这里就不赘述了,可以参考以下资料:

http://blog.csdn.net/calvin_zhou/article/details/53009758

http://blog.csdn.net/u012165769/article/details/51907306

这里,我将type设置为系统级别的,这样view的优先级最高,会置顶在所有界面之上。同理,如果将type设置为优先级较低的窗口,那么在在显示时,优先级高的会覆盖掉优先级低的,原理就是这样子。


②创建view的时候,需要传一个上下文进去,这里要注意:一定要传全局的context,如果仅仅是当期的上下文环境,那么在退出当前界面时,会引发“窗体泄露”的bug,那为什么呢?其实,原因很简单:在在创建view的时候,当前view的生命周期依附于当前的Activity,当退出当前Activity时,当前Activity的context就不存在了,我们的view的就没有可依附的窗体,所以就会报android.view.WindowLeaked窗体泄露的异常。所以,在创建view的时候,一定要传一个全局的上下文环境。


③倒计时的逻辑很简单,创建一个定时任务TimerTask,然后每隔一秒执行一次,更新UI,使用了handler去post一个请求,在每一秒执行定时任务的时候去setText(),就实现了关机倒计时的逻辑。


你可能感兴趣的:(Android悬浮窗的实现--可以置顶,可以设置优先级的view)