轉(zhuǎn)載請(qǐng)注明出處:Android懸浮窗用法總結(jié)
最近項(xiàng)目里用到了懸浮窗贮泞,在這里做一下總結(jié)。
WindowManager
懸浮窗主要是通過(guò)WindowManager這個(gè)類實(shí)現(xiàn)的幔烛,這個(gè)類有3個(gè)方法:
void addView (View view, WindowManager.LayoutParams params)//添加一個(gè)懸浮窗
void removeView (View view)//移除懸浮窗
void updateViewLayout (View view, WindowManager.LayoutParams params)//更新懸浮窗參數(shù)
WindowManager.LayoutParams
提供懸浮窗需要的參數(shù)啃擦,常用的屬性有:
x:如果忽略默認(rèn)的gravity,它表示窗口的x坐標(biāo)饿悬。設(shè)置gravity為L(zhǎng)EFT或START令蛉、RIGHT、END后狡恬,x表示到特定邊的距離珠叔。
y:如果忽略默認(rèn)的gravity,它表示窗口的y坐標(biāo)弟劲。設(shè)置gravity為TOP或BOTTOM后祷安,y表示到特定邊的距離。
gravity:窗口的對(duì)齊方式兔乞,一般設(shè)為左上角汇鞭,方便計(jì)算位置。
width:窗口寬度庸追。
height:窗口高度霍骄。
type:窗口類型。
type有3種主要類型:
- Application window:取值在 FIRST_APPLICATION_WINDOW 和 >LAST_APPLICATION_WINDOW 之間锚国。是通常的腕巡、頂層的應(yīng)用程序窗口。必須將 >token 設(shè)置成 activity 的 token 血筑。
- Sub_windows:取值在 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之>間绘沉。與頂層窗口相關(guān)聯(lián)煎楣,token 必須設(shè)置為它所附著的宿主窗口的 token。
- Systemwindows: 取值在 FIRST_SYSTEM_WINDOW 和 >LAST_SYSTEM_WINDOW 之間车伞。用于特定的系統(tǒng)功能择懂。它不能用于應(yīng)用程序,使>用時(shí)需要特殊權(quán)限另玖。
常用的type取值:- TYPE_SYSTEM_ALERT:系統(tǒng)提示困曙,總是出現(xiàn)在應(yīng)用程序窗口之上。
- TYPE_SYSTEM_OVERLAY:系統(tǒng)頂層窗口谦去,顯示在其它一切內(nèi)容上慷丽。此窗口不能>獲得輸入焦點(diǎn),否則會(huì)影響鎖屏鳄哭。
- TYPE_SYSTEM_ERROR:系統(tǒng)內(nèi)部錯(cuò)誤提示要糊,顯示在所有內(nèi)容之上。
- TYPE_PHONE:電話窗口妆丘,用于電話交互(特別是呼入)锄俄。顯示在所有應(yīng)用程序>之上,狀態(tài)欄之下勺拣。
- flag:窗口行為奶赠,如不可聚焦,非模態(tài)對(duì)話框等
常用的flag取值:
- FLAG_NOT_FOCUSABLE:不許獲得焦點(diǎn)药有。
- FLAG_NOT_TOUCHABLE:不接受觸摸屏事件毅戈。
- FLAG_LAYOUT_NO_LIMITS:允許窗口延伸到屏幕外。
- FLAG_LAYOUT_IN_SCREEN:窗口占滿整個(gè)屏幕塑猖,忽略周圍的裝飾邊框(例如>狀態(tài)欄)竹祷。此窗口需要考慮到裝飾邊框的內(nèi)容。
- FLAG_WATCH_OUTSIDE_TOUCH:如果設(shè)置了這個(gè)flag羊苟,當(dāng)觸屏事件發(fā)生在窗>口之外時(shí),可以通過(guò)設(shè)置此標(biāo)志接收到一個(gè) MotionEvent.ACTION_OUTSIDE事>件感憾。注意蜡励,你不會(huì)收到完整的down/move/up事件,只有第一次down事件時(shí)可以收>到 ACTION_OUTSIDE阻桅。
- FLAG_NOT_TOUCH_MODAL:當(dāng)窗口可以獲得焦點(diǎn)(沒(méi)有設(shè)置 > - FLAG_NOT_FOCUSALBE 選項(xiàng))時(shí)凉倚,仍然將窗口范圍之外的點(diǎn)擊事件(鼠標(biāo)、觸>摸屏)發(fā)送給后面的窗口處理嫂沉。否則它將獨(dú)占所有的點(diǎn)擊事件稽寒,而不管它們是不是>發(fā)生在窗口范圍內(nèi)。
- FLAG_SECURE:不允許屏幕截圖趟章。
- FLAG_HARDWARE_ACCELERATED:開(kāi)啟硬件加速杏糙。
示例
這個(gè)例子演示創(chuàng)建慎王、移除懸浮窗,并且監(jiān)聽(tīng)OnTouch事件宏侍,獲取手指移動(dòng)距離來(lái)更新懸浮窗位置赖淤。
聲明權(quán)限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
注意:在MIUI上需要在"安全中心-授權(quán)管理-應(yīng)用權(quán)限管理"打開(kāi)“顯示懸浮窗”開(kāi)關(guān),并重啟應(yīng)用谅河。
創(chuàng)建懸浮窗管理類
/**
* 懸浮窗管理類
*/
public class FloatingManager {
private WindowManager mWindowManager;
private static FloatingManager mInstance;
private Context mContext;
public static FloatingManager getInstance(Context context) {
if (mInstance == null) {
mInstance = new FloatingManager(context);
}
return mInstance;
}
private FloatingManager(Context context) {
mContext = context;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);//獲得WindowManager對(duì)象
}
/**
* 添加懸浮窗
* @param view
* @param params
* @return
*/
protected boolean addView(View view, WindowManager.LayoutParams params) {
try {
mWindowManager.addView(view, params);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 移除懸浮窗
* @param view
* @return
*/
protected boolean removeView(View view) {
try {
mWindowManager.removeView(view);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 更新懸浮窗參數(shù)
* @param view
* @param params
* @return
*/
protected boolean updateView(View view, WindowManager.LayoutParams params) {
try {
mWindowManager.updateViewLayout(view, params);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
懸浮窗view
/**
* 懸浮窗view
*/
public class FloatingView extends FrameLayout {
private Context mContext;
private View mView;
private ImageView mImageView;
private int mTouchStartX, mTouchStartY;//手指按下時(shí)坐標(biāo)
private WindowManager.LayoutParams mParams;
private FloatingManager mWindowManager;
public FloatingView(Context context) {
super(context);
mContext = context.getApplicationContext();
LayoutInflater mLayoutInflater = LayoutInflater.from(context);
mView = mLayoutInflater.inflate(R.layout.floating_view, null);
mImageView = (ImageView) mView.findViewById(R.id.imageview);
mImageView.setImageResource(R.drawable.img_loading);
mImageView.setOnTouchListener(mOnTouchListener);
mWindowManager = FloatingManager.getInstance(mContext);
}
public void show() {
mParams = new WindowManager.LayoutParams();
mParams.gravity = Gravity.TOP | Gravity.LEFT;
mParams.x = 0;
mParams.y = 100;
//總是出現(xiàn)在應(yīng)用程序窗口之上
mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
//設(shè)置圖片格式咱旱,效果為背景透明
mParams.format = PixelFormat.RGBA_8888;
mParams.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;
mParams.width = LayoutParams.WRAP_CONTENT;
mParams.height = LayoutParams.WRAP_CONTENT;
mWindowManager.addView(mView, mParams);
//逐幀動(dòng)畫
AnimationDrawable animationDrawable=(AnimationDrawable)mImageView.getDrawable();
animationDrawable.start();
}
public void hide() {
mWindowManager.removeView(mView);
}
private OnTouchListener mOnTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTouchStartX = (int) event.getRawX();
mTouchStartY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
mParams.x += (int) event.getRawX() - mTouchStartX;
mParams.y += (int) event.getRawY() - mTouchStartY;//相對(duì)于屏幕左上角的位置
mWindowManager.updateView(mView, mParams);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
};
}
懸浮窗view布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent">
<ImageView
android:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/loading1"/>
</RelativeLayout>
主界面
用兩個(gè)按鈕來(lái)控制懸浮窗顯示、移除绷耍。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button mBtnShow;
private Button mBtnHide;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnShow = (Button) findViewById(R.id.btn_show);
mBtnHide = (Button) findViewById(R.id.btn_hide);
mBtnShow.setOnClickListener(this);
mBtnHide.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this, MyService.class);
switch (v.getId()) {
case R.id.btn_show:
intent.putExtra(MyService.ACTION, MyService.SHOW);
break;
case R.id.btn_hide:
intent.putExtra(MyService.ACTION, MyService.HIDE);
break;
}
startService(intent);
}
}
主界面布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<Button
android:id="@+id/btn_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="show"/>
<Button
android:id="@+id/btn_hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="hide"/>
</LinearLayout>
使用Service控制懸浮窗
public class MyService extends Service{
public static final String ACTION="action";
public static final String SHOW="show";
public static final String HIDE="hide";
private FloatingView mFloatingView;
@Override
public void onCreate(){
super.onCreate();
mFloatingView=new FloatingView(this);
}
@Override
public IBinder onBind(Intent intent){
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
String action=intent.getStringExtra(ACTION);
if(SHOW.equals(action)){
mFloatingView.show();
}else if(HIDE.equals(action)){
mFloatingView.hide();
}
}
return super.onStartCommand(intent, flags, startId);
}
}
service聲明
<service android:name=".MyService"/>
一點(diǎn)動(dòng)畫
懸浮窗view里的ImageView用了逐幀動(dòng)畫吐限,img_loading.xml的代碼是
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="@drawable/loading1" android:duration="200" />
<item android:drawable="@drawable/loading2" android:duration="200" />
<item android:drawable="@drawable/loading3" android:duration="200" />
</animation-list>
注意
懸浮窗的view里面有TextureView的情況下,需要開(kāi)啟硬件加速褂始,否則onSurfaceTextureAvailable不會(huì)被調(diào)用诸典。如果通過(guò)Activity顯示懸浮窗,可以在Manifest里面設(shè)置硬件加速病袄,如果是Service搂赋,需要對(duì)懸浮窗設(shè)置LayoutParams.FLAG_HARDWARE_ACCELERATED。