天天看點

AndroidDemo - FloatWindowDemo

安卓懸浮窗Demo

在桌面上建立一個小的懸浮窗。點選小懸浮窗後會彈出一個大的視窗。大視窗上有2個按鍵,分别為傳回與關閉。點選大視窗外的部分能傳回小視窗。

小視窗可以自由拖動。小視窗上顯示的是目前記憶體使用率。

AndroidDemo - FloatWindowDemo
AndroidDemo - FloatWindowDemo

Demo包含以下幾個主要檔案:

-------------------------------

FloatWindowService.java        主服務

FloatWindowManager.java    管理類

MainActivity.java                     

WindowBig.java                       大視窗

WindowSmall.java                   小視窗

activity_main.xml 

window_big.xml                      大視窗布局

window_small.xml                   小視窗布局

點選圖示後,直接啟動FloatWindowService,顯示小視窗。是以可以不加載首頁面activity_main.xml 

/****************************************************************************************************************************************/

以下為window_big.xml

<?xml verl sion="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dip"
    android:layout_height="130dip"
    android:id="@+id/big_window_layout"
    android:background="@drawable/bg_blue"
    android:orientation="vertical" >   
    
    <TextView 
        android:id="@+id/base"
        android:layout_width="0dip"
    	android:layout_height="0dip"
    	android:layout_centerHorizontal="true"
    	android:layout_centerVertical="true"
        />

	<TextView
    	android:id="@+id/text1"
    	android:layout_width="100dp"
    	android:layout_height="wrap_content"
    	android:layout_alignParentTop="true"
    	android:layout_alignRight="@+id/text2"
    	android:textColor="#00FFFF" />

	<Button
   		android:id="@+id/btn_big_1"
    	android:layout_width="100dip"
    	android:layout_height="40dip"
	    android:layout_below="@id/base"
	    android:background="@android:color/transparent"
	    android:layout_centerHorizontal="true"
	    android:text="close" />

	<TextView
	    android:id="@+id/text2"
	    android:layout_width="100dp"
	    android:layout_height="wrap_content"
	    android:layout_alignParentLeft="true"
	    android:layout_below="@+id/text1"
	    android:textColor="#00FFFF" />

	<Button
	    android:id="@+id/btn_big_2"
	    android:layout_width="100dip"
	    android:layout_height="40dip"
	    android:background="@android:color/transparent"
	    android:layout_alignLeft="@+id/btn_big_1"
	    android:layout_alignTop="@+id/text2"
	    android:text="back" />

</RelativeLayout>
      

大窗體上放2個按鈕。分别實作傳回與關閉程式功能。

大窗體的id是big_window_layout

以下為小視窗

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/small_window_layout"
	android:layout_width="60dip"
	android:layout_height="25dip"
	android:background="@drawable/bg_small"
    >
    <TextView 
        android:id="@+id/percent"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center"
        android:textColor="#ffffff"
        />
</LinearLayout>      

放置一個TextView,用來顯示記憶體使用百分率。

id是small_window_layout

WindowSmall繼承LinearLayout

package com.rust.floatwindowdemo;

import java.lang.reflect.Field;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;

public class WindowSmall extends LinearLayout{

	private WindowManager windowManager;
	
	private WindowManager.LayoutParams mParams;
	
	private static int barHeight;
	
	public static int viewWidth;
	public static int viewHeight;
    
	private float firstX;
	private float firstY;
	private float currentX;
	private float currentY;
	
	private float viewX;
	private float viewY;
	
	public WindowSmall(Context context) {
		super(context);
		windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		LayoutInflater.from(context).inflate(R.layout.window_small, this);
		View view = findViewById(R.id.small_window_layout);
		viewWidth = view.getLayoutParams().width;
		viewHeight = view.getLayoutParams().height;
		TextView memoryUsePercent = (TextView) findViewById(R.id.percent);
		memoryUsePercent.setText(FloatWindowManager.getUsePercent(context));
	}
	
	public void setParams(WindowManager.LayoutParams params){
		mParams = params;
	}
	
	
	@Override
	public boolean onTouchEvent(MotionEvent event){
	
		switch(event.getAction()){
		case MotionEvent.ACTION_DOWN:
			viewX = event.getX();
			viewY = event.getY();
			firstX = event.getRawX();
			firstY = event.getRawY() - getBarHeight();
			currentX = event.getRawX();
			currentY = event.getRawY() - getBarHeight();
			break;
		
		case MotionEvent.ACTION_MOVE:
			currentX = event.getRawX();
			currentY = event.getRawY() - getBarHeight();
			freshSmallWindow();
			break;
		
		case MotionEvent.ACTION_UP:
			if(firstX == currentX && firstY == currentY){
				startBigWindow(getContext());
			}
			break;
		}
		return true;
		
	}
	
	/**
	 * 
	 * @return 傳回狀态欄高度
	 */	
	private int getBarHeight(){
		if(barHeight == 0){
			try{
				Class<?> c = Class.forName("com.android.internal.R$dimen");
				Object o = c.newInstance();
				Field field = c.getField("status_bar_height");
				int x = (Integer) field.get(o);
				barHeight = getResources().getDimensionPixelSize(x);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
		
		return barHeight;
	}
	
	private void freshSmallWindow(){
		mParams.x = (int) (currentX - viewX);
		mParams.y = (int) (currentY - viewY);
		windowManager.updateViewLayout(this, mParams);
	}
	
	private void startBigWindow(Context context){
		FloatWindowManager.removeSmallWindow(getContext());
		FloatWindowManager.createBigWindow(getContext());
	}
}
      

WindowBig繼承RelativeLayout

package com.rust.floatwindowdemo;

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

public class WindowBig extends RelativeLayout{

	private WindowManager windowManager;
	
	private WindowManager.LayoutParams mParams;
	
	private String strViewX;
	private String strfirstX;
	
	public static int bigViewWidth;
	
	public static int bigViewHeight;
	private static int screenWidth;
	private static int screenHeight;
	private float firstX;
	private float firstY;
	private float currentX;
	private float currentY;
	
	private float viewX;
	private float viewY;
	private int deltaX;
	private int deltaY;
	public WindowBig(final Context context) {
		super(context);
		windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		LayoutInflater.from(context).inflate(R.layout.window_big, this);
		screenWidth = windowManager.getDefaultDisplay().getWidth();
		screenHeight = windowManager.getDefaultDisplay().getHeight();
		View view = findViewById(R.id.big_window_layout);
		Button btn1 = (Button) findViewById(R.id.btn_big_1);//close all
		Button btn2 = (Button) findViewById(R.id.btn_big_2);//close big window
		bigViewWidth = view.getLayoutParams().width;
		bigViewHeight = view.getLayoutParams().height;
		
		deltaX = screenWidth/2 - bigViewWidth/2;
		
		btn1.setOnClickListener(new OnClickListener(){

			@Override
			public void onClick(View v) {
				FloatWindowManager.removeBigWindow(context);
				FloatWindowManager.removeSmallWindow(context);
				Toast.makeText(context, "bye bye", Toast.LENGTH_SHORT).show();;
				Intent intent = new Intent(getContext(),FloatWindowService.class);
				context.stopService(intent);
			}
			
		});
		
		btn2.setOnClickListener(new OnClickListener(){

			@Override
			public void onClick(View v) {
				FloatWindowManager.removeBigWindow(context);
				FloatWindowManager.createSmallWindow(context);
			}
			
		});
		
		TextView tv1 = (TextView)findViewById(R.id.text1);
		
	}
	

	@Override
	public boolean onTouchEvent(MotionEvent event){
	
		switch(event.getAction()){
		case MotionEvent.ACTION_DOWN:
			viewX = event.getX();
			viewY = event.getY();
			firstX = event.getRawX();
			firstY = event.getRawY();
			currentX = event.getRawX();
			currentY = event.getRawY();
			
//			showXY();
			
			break;
		
		case MotionEvent.ACTION_MOVE:
			currentX = event.getRawX();
			currentY = event.getRawY();
			
			break;
		
		case MotionEvent.ACTION_UP:
			if(viewX < 0 || viewX > bigViewWidth 
					|| viewY < 0 || viewY > bigViewHeight){
				FloatWindowManager.removeBigWindow(getContext());
				FloatWindowManager.createSmallWindow(getContext());
			}
			break;
		}
		return true;
		
	}
	
	private void showXY(){
		strViewX = Float.toString(bigViewHeight);
		TextView text1 = (TextView)findViewById(R.id.text1);
		text1.setText(strViewX);
		strfirstX = Float.toString(firstY);
		TextView text2 = (TextView)findViewById(R.id.text2);
		text2.setText(strfirstX);
	}
	

}
      

onTouchEvent中判斷手指點選螢幕的位置。當點選到大窗體之外的部分,會顯示小視窗。

getRawX()得到的是原點在螢幕最上角的X值。getX()得到的是以窗體左上角為原點的X值。

最開始是用計算的方式來判斷是否點選到大窗體外。現在直接使用getRaw得到的值來和窗

體高度寬度相比較。

showXY()用來顯示點選坐标。僅用于測試。

MainActivity直接開啟FloatWindowService

package com.rust.floatwindowdemo;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
//		setContentView(R.layout.activity_main);
		Intent intent = new Intent(MainActivity.this , FloatWindowService.class);
		startService(intent);
		
		finish();
//		Button startButton = (Button) findViewById(R.id.btn_1);
//		startButton.setOnClickListener(new OnClickListener(){
//
//			@Override
//			public void onClick(View v) {
//				Intent intent = new Intent(MainActivity.this , FloatWindowService.class);
//				startService(intent);
//				
//				finish();			
//			}
//			
//		});
	}      

FloatWindowService

Service裡開啟一個0.5秒重新整理一次的定時器。根據條件,決定懸浮窗的顯示。

建立與删除窗體的方法在FloatWindowManager中。

package com.rust.floatwindowdemo;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.IBinder;

public class FloatWindowService extends Service {


	private Handler handler = new Handler();

	/**
	 * 定時器
	 */
	private Timer timer;

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// 開啟定時器,每隔0.5秒重新整理一次
		if (timer == null) {
			timer = new Timer();
			timer.scheduleAtFixedRate(new SettingTask(), 0, 500);
		}
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		// Service被終止的同時也停止定時器繼續運作
		timer.cancel();
		timer = null;
	}

	class SettingTask extends TimerTask {

		@Override
		public void run() {
			// 目前界面是桌面,且沒有懸浮窗顯示,則建立懸浮窗。
			if (OnTop() && !FloatWindowManager.isWindowShowing()) {
				handler.post(new Runnable() {
					@Override
					public void run() {
						FloatWindowManager.createSmallWindow(getApplicationContext());
					}
				});
			}
			// 目前界面不是桌面,且有懸浮窗顯示,則移除懸浮窗。
			else if (!OnTop() && FloatWindowManager.isWindowShowing()) {
				handler.post(new Runnable() {
					@Override
					public void run() {
						FloatWindowManager.removeSmallWindow(getApplicationContext());
						FloatWindowManager.removeBigWindow(getApplicationContext());
					}
				});
			}
//			 目前界面是桌面,且有懸浮窗顯示,則更新記憶體資料。
			else if (OnTop() && FloatWindowManager.isWindowShowing()) {
				handler.post(new Runnable() {
					@Override
					public void run() {
						FloatWindowManager.updateUsedPercent(getApplicationContext());
					}
				});
			}
		}

	}

	/**
	 * 判斷目前界面是否是桌面
	 */
	private boolean OnTop() {
		ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
		List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
		return getNames().contains(rti.get(0).topActivity.getPackageName());
	}

	/**
	 * 獲得屬于桌面的應用的應用包名稱
	 * 
	 * @return 傳回包含所有包名的字元串清單
	 */
	private List<String> getNames() {
		List<String> names = new ArrayList<String>();
		PackageManager packageManager = this.getPackageManager();
		Intent intent = new Intent(Intent.ACTION_MAIN);
		intent.addCategory(Intent.CATEGORY_HOME);
		List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
				PackageManager.MATCH_DEFAULT_ONLY);
		for (ResolveInfo ri : resolveInfo) {
			names.add(ri.activityInfo.packageName);
		}
		return names;
	}
}
      

WindowManager,是用于控制的工具類。集合了常用功能。

package com.rust.floatwindowdemo;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.TextView;

public class FloatWindowManager {

	/**
	 * 小懸浮窗View的執行個體
	 */
	private static WindowSmall smallWindow;

	/**
	 * 大懸浮窗View的執行個體
	 */
	private static WindowBig bigWindow;

	/**
	 * 小懸浮窗View的參數
	 */
	private static LayoutParams smallWindowParams;

	/**
	 * 大懸浮窗View的參數
	 */
	private static LayoutParams bigWindowParams;

	/**
	 * 用于控制在螢幕上添加或移除懸浮窗
	 */
	private static WindowManager mWindowManager;

	/**
	 * 用于擷取手機可用記憶體
	 */
	private static ActivityManager mActivityManager;

	/**
	 * 建立一個小懸浮窗。初始位置為螢幕的右部中間位置。
	 * 
	 * @param context
	 *            必須為應用程式的Context.
	 */
	public static void createSmallWindow(Context context) {
		WindowManager windowManager = getWindowManager(context);
		int screenWidth = windowManager.getDefaultDisplay().getWidth();
		int screenHeight = windowManager.getDefaultDisplay().getHeight();
		if (smallWindow == null) {
			smallWindow = new WindowSmall(context);
			if (smallWindowParams == null) {
				smallWindowParams = new LayoutParams();
				smallWindowParams.type = LayoutParams.TYPE_PHONE;
				smallWindowParams.format = PixelFormat.RGBA_8888;
				smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
						| LayoutParams.FLAG_NOT_FOCUSABLE;
				smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
				smallWindowParams.width = WindowSmall.viewWidth;
				smallWindowParams.height = WindowSmall.viewHeight;
				smallWindowParams.x = screenWidth;
				smallWindowParams.y = screenHeight / 2;
			}
			smallWindow.setParams(smallWindowParams);
			windowManager.addView(smallWindow, smallWindowParams);
		}
	}

	/**
	 * 将小懸浮窗從螢幕上移除。
	 * 
	 * @param context
	 *            必須為應用程式的Context.
	 */
	public static void removeSmallWindow(Context context) {
		if (smallWindow != null) {
			WindowManager windowManager = getWindowManager(context);
			windowManager.removeView(smallWindow);
			smallWindow = null;
		}
	}

	/**
	 * 建立一個大懸浮窗。位置為螢幕正中間。
	 * 
	 * @param context
	 *            必須為應用程式的Context.
	 */
	public static void createBigWindow(Context context) {
		WindowManager windowManager = getWindowManager(context);
		int screenWidth = windowManager.getDefaultDisplay().getWidth();
		int screenHeight = windowManager.getDefaultDisplay().getHeight();
		if (bigWindow == null) {
			bigWindow = new WindowBig(context);
			if (bigWindowParams == null) {
				bigWindowParams = new LayoutParams();
				bigWindowParams.x = screenWidth / 2 - WindowBig.bigViewWidth / 2;
				bigWindowParams.y = screenHeight / 2 - WindowBig.bigViewHeight / 2;
				bigWindowParams.type = LayoutParams.TYPE_PHONE;
				bigWindowParams.format = PixelFormat.RGBA_8888;
				bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
				bigWindowParams.width = WindowBig.bigViewWidth;
				bigWindowParams.height = WindowBig.bigViewHeight;
			}
			windowManager.addView(bigWindow, bigWindowParams);
		}
	}

	/**
	 * 将大懸浮窗從螢幕上移除。
	 * 
	 * @param context
	 *            必須為應用程式的Context.
	 */
	public static void removeBigWindow(Context context) {
		if (bigWindow != null) {
			WindowManager windowManager = getWindowManager(context);
			windowManager.removeView(bigWindow);
			bigWindow = null;
		}
	}

	/**
	 * 更新小懸浮窗的TextView上的資料,顯示記憶體使用的百分比。
	 * 
	 * @param context
	 *            可傳入應用程式上下文。
	 */
	public static void updateUsedPercent(Context context) {
		if (smallWindow != null) {
			TextView percentView = (TextView) smallWindow.findViewById(R.id.percent);
			percentView.setText(getUsePercent(context));
		}
	}

	/**
	 * 是否有懸浮窗(包括小懸浮窗和大懸浮窗)顯示在螢幕上。
	 * 
	 * @return 有懸浮窗顯示在桌面上傳回true,沒有的話傳回false。
	 */
	public static boolean isWindowShowing() {
		return smallWindow != null || bigWindow != null;
	}

	/**
	 * 如果WindowManager還未建立,則建立一個新的WindowManager傳回。否則傳回目前已建立的WindowManager。
	 * 
	 * @param context
	 *            必須為應用程式的Context.
	 * @return WindowManager的執行個體,用于控制在螢幕上添加或移除懸浮窗。
	 */
	private static WindowManager getWindowManager(Context context) {
		if (mWindowManager == null) {
			mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		}
		return mWindowManager;
	}

	/**
	 * 如果ActivityManager還未建立,則建立一個新的ActivityManager傳回。否則傳回目前已建立的ActivityManager。
	 * 
	 * @param context
	 *            可傳入應用程式上下文。
	 * @return ActivityManager的執行個體,用于擷取手機可用記憶體。
	 */
	private static ActivityManager getActivityManager(Context context) {
		if (mActivityManager == null) {
			mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
		}
		return mActivityManager;
	}

	/**
	 * 計算已使用記憶體的百分比,并傳回。
	 * 
	 * @param context
	 *            可傳入應用程式上下文。
	 * @return 已使用記憶體的百分比,以字元串形式傳回。
	 */
	public static String getUsePercent(Context context) {
		String dir = "/proc/meminfo";
		try {
			FileReader fr = new FileReader(dir);
			BufferedReader br = new BufferedReader(fr, 2048);
			String memoryLine = br.readLine();
			String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
			br.close();
			long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
			long availableSize = getAvailableMemory(context) / 1024;
			int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * 100);
			return percent + "%";
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "懸浮窗";
		
	}

	/**
	 * 擷取目前可用記憶體,傳回資料以位元組為機關。
	 * 
	 * @param context
	 *            可傳入應用程式上下文。
	 * @return 目前可用記憶體。
	 */
	private static long getAvailableMemory(Context context) {
		ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
		getActivityManager(context).getMemoryInfo(mi);
		return mi.availMem;
	}

}
      

使用API19,需要開啟2個權限。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.rust.floatwindowdemo"
    android:versionCode="1"
    android:versionName="1.0" >
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.GET_TASKS"/>
    
    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".FloatWindowService">
        </service>
    </application>

</manifest>
      

繼續閱讀