當咱們在手機上安裝360安全衛士時,手機屏幕上時刻都會出現一個小浮動窗口,點擊該浮動窗口可跳轉到安全衛士的操做界面,並且該浮動窗口不受其餘activity的覆蓋影響仍然可見(多米音樂也有相關的和主界面交互的懸浮小窗口)。它能懸浮在手機桌面,且不受Activity界面的影響,說明該懸浮窗口是不隸屬於Activity界面的,也就是說,他是隸屬於啓動它的應用程序所在進程。如360App所在的應用進程,當殺掉它所在的應用進程時,它纔會消失。懸浮窗口的實現涉及到WindowManager(基於4.0源碼分析),它是一個接口,實現類有WindowManagerImpl,CompatModeWrapper(WindowManagerImpl的內部類),LocalWindowManager(Window的內部類)。 java
經過WindowManager的addView()方法,並設置WindowManager.LayoutParams的相關屬性,就能夠往WindowManager中加入所須要的View,而根據WindowManager.LayoutParams屬性不一樣,也就能實現不一樣的效果。好比建立系統頂級窗口,實現懸浮窗口效果。若是須要將View從WindowManager中移除,只須要調用removeView()便可。android
一、代碼實現主界面爲一個Button按鈕點擊跳轉到小懸浮窗口,而後關閉本窗口。安全
package com.example.suspend; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity { private Button suspend; private TextView text; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initUI(); } private void initUI() { // TODO Auto-generated method stub // WindowService wind = new WindowService(); suspend = (Button)findViewById(R.id.suspend); suspend.setOnClickListener(new suspendListener()); text = (TextView)findViewById(R.id.text); text.setText(MyWindowManager.getUsedPercentValue(getApplicationContext())); } public class suspendListener implements OnClickListener{ @Override public void onClick(View arg0) { // TODO Auto-generated method stub //啓動懸浮窗口關閉本窗口 Intent intent = new Intent(MainActivity.this,WindowService.class); startService(intent); finish(); } } }
二、WindowService 中使用了一個定時器,定時爲500ms,在定時器裏建立小窗口,在啓動前先判斷是否在桌面app
/** * 判斷當前界面是否桌面 */ private boolean isHome(){ ActivityManager mactivityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningTaskInfo> rti = mactivityManager.getRunningTasks(1); return getHomes().contains(rti.get(0).topActivity.getPackageName()); } /** * 得到屬於桌面的應用的應用包名稱 * @return 返回包含全部包名的字符串列表 */ private List<String> getHomes(){ 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); System.out.println("packageName" + names); Log.d(TAG,"tag:"+names); } return names; }
完整代碼爲:ide
package com.example.suspend; import android.app.ActivityManager; 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; import android.util.Log; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; public class WindowService extends Service { private static final String TAG = "PACKAGENAME"; //用於線程中建立或移除懸浮窗。 private Handler handler = new Handler(); //定時器,定時進行檢測當前應該建立仍是移除懸浮 private Timer timer; @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return null; } /** * 啓動service的時候,onCreate方法只有第一次會調用,onStartCommand和onStart每次都被調用。 * onStartCommand會告訴系統如何重啓服務,如判斷是否異常終止後從新啓動,在何種狀況下異常終止 * 這個整形能夠有四個返回值:start_sticky、start_no_sticky、START_REDELIVER_INTENT、START_STICKY_COMPATIBILITY。 */ @Override public int onStartCommand(Intent intent, int flags, int startId) { /** * 開啓定時器,每隔500ms刷新一次 */ if (timer == null){ timer = new Timer(); timer.scheduleAtFixedRate(new RefreshTask(),0,500); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); //Service 被終止的同時也中止定時器繼續運行 timer.cancel(); timer = null; } class RefreshTask extends TimerTask{ @Override public void run() { //判斷當前界面是桌面,且沒有懸浮顯示,則建立懸浮窗 if (isHome() && !MyWindowManager.isWindowShowing()){ handler.post(new Runnable() { @Override public void run() { MyWindowManager.createSmallWindow(getApplicationContext()); } }); } //當前界面不是桌面,且有懸浮窗口顯示,則移除懸浮窗口 else if (!isHome() && MyWindowManager.isWindowShowing()){ handler.post(new Runnable() { @Override public void run() { MyWindowManager.removeSmallWindow(getApplicationContext()); MyWindowManager.removeBigWindow(getApplicationContext()); } }) ; } // 當前界面是桌面,且有懸浮窗顯示,則更新內存數據。 else if (isHome() && MyWindowManager.isWindowShowing()){ handler.post(new Runnable() { @Override public void run() { MyWindowManager.updateUsedPercent(getApplicationContext()); } }); } } } /** * 判斷當前界面是否桌面 */ private boolean isHome(){ ActivityManager mactivityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningTaskInfo> rti = mactivityManager.getRunningTasks(1); return getHomes().contains(rti.get(0).topActivity.getPackageName()); } /** * 得到屬於桌面的應用的應用包名稱 * @return 返回包含全部包名的字符串列表 */ private List<String> getHomes(){ 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); System.out.println("packageName" + names); Log.d(TAG,"tag:"+names); } return names; } }
三、建立窗口方法具體代碼有寫源碼分析
public static void createSmallWindow(Context context){ //WindowManager基本用到:addView,removeView,updateViewLayout WindowManager windowManager = getWindowManager(context); //獲取屏幕寬高 abstract Display getDefaultDisplay(); //獲取默認顯示的 Display 對象 int screenWidth = windowManager.getDefaultDisplay().getWidth(); int screenHeight = windowManager.getDefaultDisplay().getHeight(); //設置小懸浮窗口的位置以及相關參數 if (smallWindowActivity == null) { smallWindowActivity = new SmallWindowActivity(context); if (smallWindowParams == null) { smallWindowParams = new LayoutParams();// smallWindowParams.type = LayoutParams.TYPE_PHONE;//設置窗口的window type smallWindowParams.format = PixelFormat.RGBA_8888;//設置圖片格式,效果爲背景透明 smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;//下面的flags屬性的效果形同「鎖定」。 懸浮窗不可觸摸,不接受任何事件,同時不影響後面的事件響應。 smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//調整懸浮窗口位置在左邊中間 smallWindowParams.width = SmallWindowActivity.viewWidth;//設置懸浮窗口的寬高 smallWindowParams.height = SmallWindowActivity.viewHeight; smallWindowParams.x = screenWidth;//設置懸浮窗口位置 smallWindowParams.y = screenHeight / 2; } smallWindowActivity.setParams(smallWindowParams); windowManager.addView(smallWindowActivity, smallWindowParams);//將須要加到懸浮窗口中的View加入到窗口中 } }
要移除窗口可以使用 windowManager.removeView(smallWindowActivity);佈局
小窗口實現的是顯示手機內存百分比,下面爲計算內存百分比的方法:post
/** * 計算已使用內存的百分比,並返回。 * * @param context * 可傳入應用程序上下文。 * @return 已使用內存的百分比,以字符串形式返回。 */ public static String getUsedPercentValue(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 "懸浮窗"; } /** * 更新小懸浮窗的TextView上的數據,顯示內存使用的百分比。 * * @param context * 可傳入應用程序上下文。 */ public static void updateUsedPercent(Context context) { if (smallWindowActivity != null) { TextView percentView = (TextView) smallWindowActivity.findViewById(R.id.percent); percentView.setText(getUsedPercentValue(context)); } } /** * 獲取當前可用內存,返回數據以字節爲單位。 * * @param context * 可傳入應用程序上下文。 * @return 當前可用內存。 */ private static long getAvailableMemory(Context context) { ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); getActivityManager(context).getMemoryInfo(mi); return mi.availMem; } /** * 若是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; }
完整的MyWindowManager類代碼爲ui
package com.example.suspend; import android.app.ActivityManager; import android.content.Context; import android.graphics.PixelFormat; import android.view.Gravity; import android.view.WindowManager.LayoutParams; import android.view.WindowManager; import android.widget.TextView; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class MyWindowManager{ //小懸浮窗View的實例 private static SmallWindowActivity smallWindowActivity; //大懸浮窗View的實例 private static BigWindowActivity bigWindowActivity; //小懸浮View的參數 private static LayoutParams smallWindowParams; //大懸浮View的參數 private static LayoutParams bigWindowParams; //用於控制在屏幕上添加或移除懸浮窗 private static WindowManager mWindowManager; //用於獲取手機可用內存 private static ActivityManager mactivityManager; public static void createSmallWindow(Context context){ //WindowManager基本用到:addView,removeView,updateViewLayout WindowManager windowManager = getWindowManager(context); //獲取屏幕寬高 abstract Display getDefaultDisplay(); //獲取默認顯示的 Display 對象 int screenWidth = windowManager.getDefaultDisplay().getWidth(); int screenHeight = windowManager.getDefaultDisplay().getHeight(); //設置小懸浮窗口的位置以及相關參數 if (smallWindowActivity == null) { smallWindowActivity = new SmallWindowActivity(context); if (smallWindowParams == null) { smallWindowParams = new LayoutParams();// smallWindowParams.type = LayoutParams.TYPE_PHONE;//設置窗口的window type smallWindowParams.format = PixelFormat.RGBA_8888;//設置圖片格式,效果爲背景透明 smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;//下面的flags屬性的效果形同「鎖定」。 懸浮窗不可觸摸,不接受任何事件,同時不影響後面的事件響應。 smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//調整懸浮窗口位置在左邊中間 smallWindowParams.width = SmallWindowActivity.viewWidth;//設置懸浮窗口的寬高 smallWindowParams.height = SmallWindowActivity.viewHeight; smallWindowParams.x = screenWidth;//設置懸浮窗口位置 smallWindowParams.y = screenHeight / 2; } smallWindowActivity.setParams(smallWindowParams); windowManager.addView(smallWindowActivity, smallWindowParams);//將須要加到懸浮窗口中的View加入到窗口中 } } /** * 建立一個大懸浮窗。位置爲屏幕正中間。 * * @param context * 必須爲應用程序的Context. */ // @SuppressWarnings("deprecation") public static void createBigWindow(Context context) { WindowManager windowManager = getWindowManager(context); int screenWidth = windowManager.getDefaultDisplay().getWidth(); int screenHeight = windowManager.getDefaultDisplay().getHeight(); if (bigWindowActivity == null) { bigWindowActivity = new BigWindowActivity(context); if (bigWindowParams == null) { bigWindowParams = new LayoutParams(); bigWindowParams.x = screenWidth / 3 - BigWindowActivity.viewWidth / 3; bigWindowParams.y = screenHeight / 3 - BigWindowActivity.viewHeight / 3; bigWindowParams.type = LayoutParams.TYPE_PHONE; bigWindowParams.format = PixelFormat.RGBA_8888; bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP; bigWindowParams.width = BigWindowActivity.viewWidth; bigWindowParams.height = BigWindowActivity.viewHeight; } windowManager.addView(bigWindowActivity, bigWindowParams); } } /** * 將小懸浮窗從屏幕上移除。 * abstract void removeViewImmediate(View view);//是removeView(View) 的一個特殊擴展, * 在方法返回前可以當即調用該視圖層次的View.onDetachedFromWindow() 方法。 * @param context * 必須爲應用程序的Context. */ public static void removeSmallWindow(Context context) { if (smallWindowActivity != null) { WindowManager windowManager = getWindowManager(context); windowManager.removeView(smallWindowActivity);//移除懸浮窗口 smallWindowActivity = null; } } /** * 將大懸浮窗從屏幕上移除。 * * @param context * 必須爲應用程序的Context. */ public static void removeBigWindow(Context context) { if (bigWindowActivity != null) { WindowManager windowManager = getWindowManager(context); windowManager.removeView(bigWindowActivity); bigWindowActivity = null; } } /** * 是否有懸浮窗(包括小懸浮窗和大懸浮窗)顯示在屏幕上。 * * @return 有懸浮窗顯示在桌面上返回true,沒有的話返回false。 */ public static boolean isWindowShowing() { return smallWindowActivity != null || bigWindowActivity != null; //return smallWindowActivity != 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; } /** * 計算已使用內存的百分比,並返回。 * * @param context * 可傳入應用程序上下文。 * @return 已使用內存的百分比,以字符串形式返回。 */ public static String getUsedPercentValue(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 "懸浮窗"; } /** * 更新小懸浮窗的TextView上的數據,顯示內存使用的百分比。 * * @param context * 可傳入應用程序上下文。 */ public static void updateUsedPercent(Context context) { if (smallWindowActivity != null) { TextView percentView = (TextView) smallWindowActivity.findViewById(R.id.percent); percentView.setText(getUsedPercentValue(context)); } } /** * 獲取當前可用內存,返回數據以字節爲單位。 * * @param context * 可傳入應用程序上下文。 * @return 當前可用內存。 */ private static long getAvailableMemory(Context context) { ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); getActivityManager(context).getMemoryInfo(mi); return mi.availMem; } /** * 若是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; } }
四、小窗口的代碼繼承了LinearLayout,可實現手動觸摸移動,分別設置了手指點擊下、移動和離開的處理。實現代碼爲:this
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //手指按下時記錄必要數據,縱座標的值都須要減去狀態欄高度 xInView = event.getX(); yInView = event.getY(); xDownInScreen = event.getRawX(); yDownInScreen = event.getRawY() - getStatusBarHeight(); xInScreen = event.getRawX(); yInScreen = event.getRawY() - getStatusBarHeight(); break; case MotionEvent.ACTION_MOVE: xInScreen = event.getRawX(); yInScreen = event.getRawY()-getStatusBarHeight(); //手指一動的時候就更新懸浮窗位置 updateViewPosition(); break; case MotionEvent.ACTION_UP: //若是手指離開屏幕時,xDownInScreen和xInScreen相等,且yDownInScreen == yInScreen //則視爲觸發 if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) { MyWindowManager.createBigWindow(getContext());//建立大窗口 MyWindowManager.removeSmallWindow(getContext());//移除小窗口 Toast.makeText(getContext(), "手指離開屏幕!", Toast.LENGTH_SHORT).show(); } break; default: break; } return true; }
完整代碼:
package com.example.suspend; import android.content.Context; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import android.view.WindowManager; import java.lang.reflect.Field; public class SmallWindowActivity extends LinearLayout { public static int viewWidth;//小懸浮寬度 public static int viewHeight;//小懸浮高度 private static int statusBarHeight;//狀態欄高度 private WindowManager windowManager;//更新小懸浮的位置 private WindowManager.LayoutParams mParams;//小懸浮高度 private float xInScreen;//記錄當前手指位置在屏幕上的橫座標值 private float yInScreen;//記錄當前手指位置在屏幕上的縱座標值 private float xDownInScreen;//記錄手指按下時在屏幕上的橫座標的值 private float yDownInScreen;//記錄手指按下時在屏幕上的縱座標的值 private float xInView;//記錄手指按下時在小懸浮窗的View上的橫座標的值 private float yInView;//記錄手指按下時在小懸浮窗的View上的縱座標的值 /** * * @param context */ public SmallWindowActivity(Context context) { super(context); windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); LayoutInflater.from(context).inflate(R.layout.activity_small_window,this); View view = findViewById(R.id.small_window_layout); //獲取手機屏幕寬高 viewWidth = view.getLayoutParams().width; viewHeight = view.getLayoutParams().height; //顯示手機內存空間百分比 TextView percentView = (TextView)findViewById(R.id.percent); percentView.setText(MyWindowManager.getUsedPercentValue(context)); } /** * 手指觸摸屏幕處理 */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //手指按下時記錄必要數據,縱座標的值都須要減去狀態欄高度 xInView = event.getX(); yInView = event.getY(); xDownInScreen = event.getRawX(); yDownInScreen = event.getRawY() - getStatusBarHeight(); xInScreen = event.getRawX(); yInScreen = event.getRawY() - getStatusBarHeight(); break; case MotionEvent.ACTION_MOVE: xInScreen = event.getRawX(); yInScreen = event.getRawY()-getStatusBarHeight(); //手指一動的時候就更新懸浮窗位置 updateViewPosition(); break; case MotionEvent.ACTION_UP: //若是手指離開屏幕時,xDownInScreen和xInScreen相等,且yDownInScreen == yInScreen //則視爲觸發 if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) { MyWindowManager.createBigWindow(getContext());//建立大窗口 MyWindowManager.removeSmallWindow(getContext());//移除小窗口 Toast.makeText(getContext(), "手指離開屏幕!", Toast.LENGTH_SHORT).show(); } break; default: break; } return true; } //將懸浮窗的參數傳入,用於更新小懸浮窗的位置 public void setParams(WindowManager.LayoutParams params) { mParams = params; } //更新小懸浮窗在屏幕中的位置 private void updateViewPosition() { mParams.x = (int) (xInScreen - xInView); mParams.y = (int) (yInScreen - yInView); windowManager.updateViewLayout(this, mParams); } /** * 用於獲取狀態欄高度 * @return 返回狀態欄高度的像素值 */ private int getStatusBarHeight() { if (statusBarHeight == 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); statusBarHeight = getResources().getDimensionPixelSize(x); } catch (Exception e) { e.printStackTrace(); } } return statusBarHeight; } }
小窗口僅實現了顯示內存百分比,添加了一個TextView,小窗口的佈局代碼爲:
<?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>
五、當點擊小窗口的時候添加了監聽就是當手指離開的時候啓動建立一個另外一個窗口而後關閉本窗口。
大窗口也是實現點擊能夠隨意移動,還添加了兩個按鈕的功能,發送短信和返回。實現代碼爲:
package com.example.suspend; import android.content.Context; import android.content.Intent; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; public class BigWindowActivity extends LinearLayout { //記錄大懸浮窗的寬度 public static int viewWidth; //記錄大懸浮窗高度 public static int viewHeight; private Button phone,sms,app,music; int screenWidth,screenHeight; int lastX,lastY;//記錄移動的最後的位置 int dx,dy; public BigWindowActivity(final Context context) { super(context); LayoutInflater.from(context).inflate(R.layout.big_window, this); View view = findViewById(R.id.big_window_layout); viewWidth = view.getLayoutParams().width; viewHeight = view.getLayoutParams().height; initUI(); } private void initUI() { // TODO Auto-generated method stub //獲取屏幕的分辨率 DisplayMetrics dm = getResources().getDisplayMetrics(); screenWidth = dm.widthPixels; screenHeight = dm.heightPixels-50; Button back = (Button)findViewById(R.id.fanhui); //添加觸摸監聽 back.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub //獲取Action int ea = event.getAction(); Log.i("TAG","Touch:"+ea); switch (ea){ case MotionEvent.ACTION_DOWN: lastX = (int)event.getRawX(); lastY = (int)event.getRawY(); break; case MotionEvent.ACTION_MOVE: //移動中動態設置位置 mobilesetting(v,event); break; case MotionEvent.ACTION_UP://當手指離開的時候執行 if (dx==dy){ comeback(getContext()); } break; } return false; } }); sms = (Button)findViewById(R.id.SMS); sms.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub int ea = event.getAction(); Log.i("TAG","Touch:"+ea); switch (ea){ case MotionEvent.ACTION_DOWN: lastX = (int)event.getRawX(); lastY = (int)event.getRawY(); break; case MotionEvent.ACTION_MOVE: //移動中動態設置位置 mobilesetting(v,event); break; case MotionEvent.ACTION_UP://當手指離開的時候執行 if (dx==dy){ sms(); } break; } return false; } }); } /** * 點擊返回移除大窗口建立小窗口 * @param context */ public void comeback(Context context){ // 點擊返回的時候,移除大懸浮窗,建立小懸浮窗 MyWindowManager.removeBigWindow(context); MyWindowManager.createSmallWindow(context); } //發短信 public void sms(){ Intent it = new Intent(Intent.ACTION_VIEW); it.putExtra("sms_body", "The SMS text"); it.setType("vnd.android-dir/mms-sms"); it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getContext().startActivity(it); comeback(getContext()); } /** * 移動控件設置位置 * @param v * @param event */ public void mobilesetting(View v,MotionEvent event){ //移動中動態設置位置 dx = (int)event.getRawX()-lastX;//移動中x當前位置 dy = (int)event.getRawY()-lastY; int left = v.getLeft()+dx; int top = v.getTop()+dy; int right = v.getRight()+dx; int bottom = v.getBottom()+dy; if(left<0){ left=0; right = left+v.getWidth();//0 } if (right>screenWidth){ right = screenWidth; left = right - v.getWidth();//max } if(top < 0){ top = 0; bottom = top + v.getHeight(); } if(bottom > screenHeight){ bottom = screenHeight; top = bottom - v.getHeight(); } v.layout(left, top, right, bottom); //將當前的位置再次設置 lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); } // @Override // public boolean onTouchEvent(MotionEvent event) { // // TODO Auto-generated method stub // //點擊屏幕彈出的框會消失 // //popupWindow.dismiss(); // return super.onTouchEvent(event); // } }
佈局代碼爲四個按鈕控件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/big_window_layout" > <Button android:layout_marginTop="200dip" android:layout_marginLeft="150dip" android:id="@+id/fanhui" android:layout_width="@dimen/bigwidth" android:layout_height="@dimen/bigheight" android:textColor="#ffffff" android:textSize="@dimen/bigtextsize" android:background="@xml/shape" android:text="@string/rt" /> <Button android:id="@+id/app" android:layout_width="@dimen/bigwidth" android:layout_height="@dimen/bigheight" android:layout_above="@+id/music" android:layout_alignLeft="@+id/fanhui" android:background="@xml/shape" android:textSize="@dimen/bigtextsize" android:text="@string/appone" /> <Button android:id="@+id/music" android:layout_width="@dimen/bigwidth" android:layout_height="@dimen/bigheight" android:layout_alignBaseline="@+id/fanhui" android:layout_alignBottom="@+id/fanhui" android:layout_toLeftOf="@+id/app" android:textSize="@dimen/bigtextsize" android:text="@string/mu" android:background="@xml/shape" /> <Button android:id="@+id/SMS" android:layout_width="@dimen/bigwidth" android:layout_height="@dimen/bigheight" android:layout_below="@+id/fanhui" android:layout_toRightOf="@+id/music" android:textSize="@dimen/bigtextsize" android:background="@xml/shape" android:text="@string/sendsms" /> <Button android:id="@+id/Phone" android:layout_width="@dimen/bigwidth" android:layout_height="@dimen/bigheight" android:layout_alignBaseline="@+id/fanhui" android:layout_alignBottom="@+id/fanhui" android:textSize="@dimen/bigtextsize" android:layout_toRightOf="@+id/fanhui" android:background="@xml/shape" android:text="@string/callphone" /> </RelativeLayout>
控件的實現效果是在xml裏面添加了shape.xml,設置了控件的顏色跟形狀。
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <!-- 實心 --> <solid android:color="#ff9d77"/> <!-- 漸變 --> <gradient android:startColor="#ff8c00" android:endColor="#FFFFFF" android:angle="270" /> <!-- 描邊 --> <stroke android:width="2dp" android:color="#dcdcdc" /> <!-- 圓角 --> <corners android:radius="2dp" /> <padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" /> </shape>
六、使用到的權限爲:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.suspend" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <!-- 添加懸浮窗口權限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.suspend.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=".WindowService"></service> </application> </manifest>
實現的效果圖:
代碼下載地址:點擊打開連接