通常情况下,如果想显示一个界面,首先想到的是建立一个Activity,然后所有的操作在Activity里面实现,或者是一个Dialog或者Toast。本文通过对PopupWindow的实现过程解析,指出添加界面的另外一种方式:直接用WindowManager显示添加或删除View的过程。
一、PopupWindow向Window添加视图的过程
PopupWindow是最简单的浮动窗口,显示在当前activity窗口上面,显示一个PopupWindow的代码非常简单,如下:
<span style="font-size:18px;"> //弹出pop窗口
private void PopView(){
View view =View.inflate(this, R.layout.list_item, null);
PopupWindow popup=new PopupWindow(
view,
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
true);
popup.setBackgroundDrawable(getResources().getDrawable(R.drawable.ic_launcher));
popup.showAsDropDown(bt);
}</span>
很显然,new PopupWindo()以及setBackgroundDrawabl()都只是做了一些初始化的工作,Android加载PopupWindow浮动窗口的所有逻辑过程和工作,都是在showAsDropDown()方法中进行的,以此为突破口,可以逐级分析浮动窗口内部加载过程。
<span style="font-size:18px;"> /**
* Display the content view in a popup window at the specified location. If the popup window cannot fit on screen, it will be clipped. See android.view.WindowManager.LayoutParams for more information on how gravity and the x and y parameters are related.
*/
public void showAtLocation(View parent, int gravity, int x, int y) {
showAtLocation(parent.getWindowToken(), gravity, x, y);
}
</span>
parent一般是当前activity的view,主要是传递popupwindow当前activity界面的IBinder,继续往下走:
public void showAtLocation(IBinder token, int gravity, int x, int y) {
WindowManager.LayoutParams p = createPopupLayout(token);
...
invokePopup(p);
}
核心方法只有两个:createPopupLayout和invokePopup。createPopupLayout主要是设置PopupWindow相对窗体的LayoutParams:
private WindowManager.LayoutParams createPopupLayout(IBinder token) {
// 生成layout parameters
WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// 默认gravity在左上角
// X、Y分别是相对偏移量
p.gravity = Gravity.START | Gravity.TOP;
p.width = mLastWidth = mWidth;
p.height = mLastHeight = mHeight;
//必须设置背景
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
//
p.flags = computeFlags(p.flags);
//
p.type = mWindowLayoutType;
//
p.token = token;
//
p.softInputMode = mSoftInputMode;
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
}
invokePopup最终调用WindowManager.addView()方法,将PopupWindow中的内容视图及其LayoutParams属性,添加到Window:
/**
* 将PopupWindow的内容视图添加到window manger
*/
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManager.addView(mPopupView, p); //将view添加到window中,p是相对window的属性
}
分析到这里,我们可以总结一下:
Android平台是一个又一个的Activity组成的,每一个Activity有一个或者多个View构成。通常情况下,如果想显示一个界面,首先想到的是建立一个Activity,然后所有的操作在Activity里面实现,或者是一个Dialog或者Toast。这种方式固然简单,但是在有些情况下,我们要求的只是简单的显示,用Activity显然是多余,这个时候,我们如何处理呢?
通过PopupWindow的实现过程,我们了解,原来整个Android的窗口机制是基于一个叫做WindowManager,这个接口可以添加view到屏幕,也可以从屏幕删除view。它面向的对象一端是屏幕,另一端就是View,直接忽略我们以前的Activity或者Dialog之类的东东。其实我们的Activity或者Diolog底层的实现也是通过WindowManager,这个WindowManager是全局的,整个系统就是这个唯一的东东。它是显示View的最底层了。
所以,PopupWindow的过程,其实就是直接用WindowManager显示View的过程。
二、模仿PopupWindow的一个自定义CustomPopupWindow
这个Demo仅仅实现了showAtLocation、createPopupLayout、invokePopup三个方法,就可以实现简单的PopupWindow效果。
自定义CustomPopupWindow控件类:
package com.custom.view;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.PopupWindow;
public class CustomPoupuWindow {
private int height;
private int width;
private View contView;
private Context context;
private WindowManager wm;
public CustomPoupuWindow(View contView,int width,int height){
this.contView=contView;
this.context=contView.getContext();
this.width=width;
this.height=height;
wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
//在指定位置显示popupwindow
public void showAtLocation(int gravity, int x, int y) {
WindowManager.LayoutParams p = createPopupLayout();
//p.windowAnimations = computeAnimationResource();
//preparePopup(p);
if (gravity == Gravity.NO_GRAVITY) {
gravity = Gravity.TOP | Gravity.START;
}
p.gravity = gravity;
p.x = x;
p.y = y;
invokePopup(p);
}
//设置popupWindow的contView相对window的属性
private WindowManager.LayoutParams createPopupLayout() {
//获取WindowManager
// generates the layout parameters
WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// these gravity settings put the view at the top left corner of the
// screen. The view is then positioned to the appropriate location
// by setting the x and y offsets to match the anchor's bottom
// left corner
p.gravity = Gravity.START | Gravity.TOP;
p.width = width;
p.height = height;
/* if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}*/
p.format = PixelFormat.TRANSLUCENT;
p.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
p.type = WindowManager.LayoutParams.TYPE_PHONE;
//p.token = token;
//p.softInputMode = mSoftInputMode;
p.setTitle("CustomPopupWindow:" );
return p;
}
private void invokePopup(WindowManager.LayoutParams p) {
//mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
//setLayoutDirectionFromAnchor();
wm.addView(contView, p);
}
}
调用CustomPopupWindow过程
private void CustomPopView(){
View view =View.inflate(this, R.layout.list_item, null);
CustomPoupuWindow popup=new CustomPoupuWindow(
view,
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
popup.showAtLocation( Gravity.NO_GRAVITY, 0, 100);
}
自定义XML文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/file_name"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/ic_launcher"
/>
</LinearLayout>
效果图: