1、PopupWindow的相关函数
(1)、构造函数:
//方法一:
public PopupWindow (Context context)
//方法二:
public PopupWindow(View contentView)
//方法三:
public PopupWindow(View contentView, int width, int height)
//方法四:
public PopupWindow(View contentView, int width, int height, boolean focusable)
所以,如果使用方法一来构造PopupWindow,那完整的构造代码应该是这样的:
View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
PopupWindwo popWnd = PopupWindow (context);
popWnd.setContentView(contentView);
popWnd.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
popWnd.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
这里的WRAP_CONTENT可以换成match_parent也可以是具体的数值,它是指PopupWindow的大小,也就是contentview的大小,注意popupwindow根据这个大小显示你的View,如果你的View本身是从xml得到的,那么xml的第一层view的大小属性将被忽略。相当于popupWindow的width和height属性直接和第一层View相对应。
设想下面一种场景:
popupWindow 设置为WRAP_CONTENT ,我想得到的是一个宽150dip 高80dip的popupwindow,需要额外加一层LinearLayout,这个LinearLayout 的layout_width和layout_height为任意值。而我们真正想显示的View 放在第二层,并且 android:layout_width="150.0dip" android:layout_height="80.0dip"
如:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout
android:background="@drawable/shape_ret_loading_bg"
android:layout_width="150.0dip"
android:layout_height="80.0dip"
android:orientation="vertical"
android:gravity="center">
<TextView
android:textSize="14dip"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10.0dip"
android:text="加载中..."
/>
</LinearLayout>
</LinearLayout>
(2)显示函数
//相对某个控件的位置(正左下方),无偏移
showAsDropDown(View anchor):
//相对某个控件的位置,有偏移;xoff表示x轴的偏移,正值表示向左,负值表示向右;yoff表示相对y轴的偏移,正值是向下,负值是向上;
showAsDropDown(View anchor, int xoff, int yoff):
//相对于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以设置偏移或无偏移
showAtLocation(View parent, int gravity, int x, int y):
这里有两种显示方式:
showAsDropDown(View anchor):
showAsDropDown(View anchor, int xoff, int yoff);
2、指定父视图,显示在父控件的某个位置(Gravity.TOP,Gravity.RIGHT等)
showAtLocation(View parent, int gravity, int x, int y);
(3)、其它函数
public void dismiss()
//另外几个函数,这里不讲其意义,下篇细讲
public void setFocusable(boolean focusable)
public void setTouchable(boolean touchable)
public void setOutsideTouchable(boolean touchable)
public void setBackgroundDrawable(Drawable background)
setFocusable(true);
如果PopupWindow中有Editor的话,focusable要为true。
而setFocusable(false);
则PopUpWindow只是一个浮现在当前界面上的view而已,不影响当前界面的任何操作。
是一个“没有存在感”的东西。
一般情况下 setFocusable(true);
(4)、 为PopupWindow添加动画
<?xml version="1.0" encoding="utf-8"?>
<!-- 上下滑入式 -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromYDelta="100%p"
android:toYDelta="0"/>
<alpha
android:duration="200"
android:fromAlpha="0.0"
android:toAlpha="1.0"
/>
</set>
出场动画(push_bottom_ou.xml):
<?xml version="1.0" encoding="utf-8"?>
<!-- 上下滑入式 -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromYDelta="0"
android:toYDelta="50%p"/>
<alpha
android:duration="200"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>
最后产生一个对应的style -- contextMenuAnim
<style name="PopupAnimation" parent="android:Animation">
<item name="android:windowEnterAnimation">@anim/push_bottom_in</item> //进场动画
<item name="android:windowExitAnimation">@anim/push_bottom_out</item> //出场动画
</style>
源码解析:
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
// Preserve default behavior from Gingerbread. If the animation is
// undefined or explicitly specifies the Gingerbread animation style,
// use a sentinel value.
if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
if (animStyle == R.style.Animation_PopupWindow) {
mAnimationStyle = ANIMATION_STYLE_DEFAULT;
} else {
mAnimationStyle = animStyle;
}
} else {
mAnimationStyle = ANIMATION_STYLE_DEFAULT;
}
final Transition enterTransition = getTransition(a.getResourceId(
R.styleable.PopupWindow_popupEnterTransition, 0));
final Transition exitTransition;
if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
exitTransition = getTransition(a.getResourceId(
R.styleable.PopupWindow_popupExitTransition, 0));
} else {
exitTransition = enterTransition == null ? null : enterTransition.clone();
}
a.recycle();
setEnterTransition(enterTransition);
setExitTransition(exitTransition);
setBackgroundDrawable(bg);
}
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
mContext = contentView.getContext();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); //获取WindowManager
}
setContentView(contentView);//设置内容,因为popupWindow需要我们去设置其中的View,
setWidth(width);//设置宽
setHeight(height);//设置高
setFocusable(focusable);//设置它是否获取焦点
}
public void showAtLocation(IBinder token, int gravity, int x, int y) {
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);//结束View的动画
unregisterForScrollChanged();//并注销掉滚动改变监听事件
mIsShowing = true;
mIsDropdown = false;
//准备LayoutParams,这个就是WindowManager需要用到的,这个就是用来在屏幕显示用到的
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
//准备弹框
preparePopup(p);
// Only override the default if some gravity was specified.
if (gravity != Gravity.NO_GRAVITY) {
p.gravity = gravity;
}
p.x = x;
p.y = y;
//弹框
invokePopup(p);
}
private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final 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.flags = computeFlags(p.flags); //设置弹框Flag,其中就有是否让其聚焦和是否可触摸
p.type = mWindowLayoutType; //设置为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,设置其层级关系
p.token = token; //设置它所依赖的View
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
if (mHeightMode < 0) {
p.height = mLastHeight = mHeightMode;
} else {
p.height = mLastHeight = mHeight;
}
if (mWidthMode < 0) {
p.width = mLastWidth = mWidthMode;
} else {
p.width = mLastWidth = mWidth;
}
// Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
}
private void preparePopup(WindowManager.LayoutParams p) {
if (mContentView == null || mContext == null || mWindowManager == null) {
throw new IllegalStateException("You must specify a valid content view by "
+ "calling setContentView() before attempting to show the popup.");
}
// The old decor view may be transitioning out. Make sure it finishes
// and cleans up before we try to create another one.
if (mDecorView != null) { //取消动画
mDecorView.cancelTransitions();
}
// When a background is available, we embed the content view within
// another view that owns the background drawable.
//设置背景,这个通过查看createBackgroundView()源码,我们可以看到,它其实就重新封装了一层FrameLayout,他会重新调用PopupBackgroundView这个内部类,下面会介绍
//当有背景的时候,就封装一层,没有,就直接将设置View复制给mBackgroundView
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
//这个就是再次封装一个FrameLayout,并且该FrameLayout做了事件的拦截和分发。
mDecorView = createDecorView(mBackgroundView);
// The background owner should be elevated so that it casts a shadow.
mBackgroundView.setElevation(mElevation);
// We may wrap that in another view, so we'll need to manually specify
// the surface insets.
final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
p.hasManualSurfaceInsets = true;
mPopupViewInitialLayoutDirectionInherited =
(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth = p.width;
mPopupHeight = p.height;
}
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
//windowManager添加View,让其显示在屏幕上
mWindowManager.addView(decorView, p);
if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);//设置进场动画
}
}
那么他的执行过程就是和WindowManager如何添加一个窗口一样,只是其中多了很多其它细节。
private PopupBackgroundView createBackgroundView(View contentView) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); //创建LayoutParams,获取contentView中的高
final int height;
if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
height = ViewGroup.LayoutParams.MATCH_PARENT;
}
final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext); //创建PopupBackgroundView,内部类,继承了FrameLayout
final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height);
backgroundView.addView(contentView, listParams); //给contentView封装了一成FrameLayout
return backgroundView;
}
private class PopupBackgroundView extends FrameLayout {
public PopupBackgroundView(Context context) {
super(context);
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mAboveAnchor) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
return drawableState;
} else {
return super.onCreateDrawableState(extraSpace);
}
}
}
private PopupDecorView createDecorView(View contentView) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
final int height;
if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
height = ViewGroup.LayoutParams.MATCH_PARENT;
}
final PopupDecorView decorView = new PopupDecorView(mContext);
decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height);
decorView.setClipChildren(false);
decorView.setClipToPadding(false);
return decorView;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { //监听的是返回按钮
if (getKeyDispatcherState() == null) {
return super.dispatchKeyEvent(event);
}
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null && state.isTracking(event) && !event.isCanceled()) {
dismiss();
return true;
}
}
return super.dispatchKeyEvent(event);
} else {
return super.dispatchKeyEvent(event);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
return true;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
if ((event.getAction() == MotionEvent.ACTION_DOWN)
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
return true;
} else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
dismiss();
return true;
} else {
return super.onTouchEvent(event);
}
}
可能把这两个放在一块,大家可能就恍然大悟了,表着急,一个个来看。
先看看setOutsideTouchable(boolean touchable)的代码:
public void setOutsideTouchable(boolean touchable) {
mOutsideTouchable = touchable;
}
然后再看看mOutsideTouchable哪里会用到下面代码,我做了严重精减,等下会再完整再讲这一块。
private int computeFlags(int curFlags) {
curFlags &= ~(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
…………
if (mOutsideTouchable) {
curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
}
…………
return curFlags;
}
curFlags &= ~(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
if (mOutsideTouchable) {
curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
}
MainActivity.java
package com.xianlaxuankuang.selectWindown;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener,OnItemClickListener{
ListView listView;
EditText editText;
List<String> list;
PopupWindow popupWindow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(this);
editText=(EditText)findViewById(R.id.eidtTextId);
list = new ArrayList<String>();
}
//重写点击事件
@Override
public void onClick(View v) {
showPopupWindow();
}
private void showPopupWindow() {
initListView();
//显示下拉选择框
popupWindow=new PopupWindow(listView, editText.getWidth(), 300);
//设置点击外部区域,自动隐藏
popupWindow.setOutsideTouchable(true); //外部可触摸
popupWindow.setBackgroundDrawable(new BitmapDrawable());//设置一个空的背景,响应点击事件
popupWindow.setFocusable(true);//设置可获取焦点
//指定在哪个控件下面
popupWindow.showAsDropDown(editText, 0, -10);
}
private void initListView() {
listView = new ListView(this);
for(int i=0;i<30;i++){
list.add(10000+i+"");
}
listView.setBackgroundResource(R.drawable.listview_background);//设置背景图片
listView.setAdapter(new MyAdapter());
listView.setOnItemClickListener(this);
}
class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view;
if(convertView==null){
view=View.inflate(parent.getContext(), R.layout.item_nuber, null);
}else{
view=convertView;
}
TextView textView=(TextView)view.findViewById(R.id.textViewId);
textView.setText(list.get(position));
ImageButton button=(ImageButton)view.findViewById(R.id.image_delete);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
list.remove(position);
notifyDataSetChanged();//更新见面
if(list.size()==0){
popupWindow.dismiss();//如果删除的是最后一条那么把该popupWindow隐藏
}
}
});
return view;
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
editText.setText(list.get(position));
popupWindow.dismiss();
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<RelativeLayout
android:layout_width="200dp"
android:layout_centerHorizontal="true"
android:layout_height="match_parent">
<EditText
android:id="@+id/eidtTextId"
android:layout_width="match_parent"
android:layout_height="50dp"
/>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
android:background="@null"
android:padding="10dp"
android:layout_alignParentRight="true"
android:src="@drawable/down_arrow"/>
</RelativeLayout>
</RelativeLayout>
item_nuber.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:descendantFocusability="blocksDescendants"
android:gravity="center_vertical"
android:orientation="horizontal" >
<!-- android:descendantFocusability="blocksDescendants" -->
<!-- 表示区块点击 -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/user"/>
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:id="@+id/textViewId"
android:layout_height="wrap_content" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/image_delete"
android:padding="10dp"
android:background="@null"
android:src="@drawable/delete"/>
<\LinearLayout>