个人使用建议,容易犯错:先设置属性再显示,而不是先出来了,再设置都没用了,显示一般是用showatlocation,或者showasdropdown

个人建议2:popupWindow的显示的两个方法的位置一般是再某个组件的左下部或者用bottom属性,都是比较绝对的位置

如果想显示再某个组件的上部,可以使用showAtLocation(anctor,gravity,0,a.getHight());其中a为该组件的名称

转载:http://blog.csdn.net/harvic880925/article/details/49272285 

先看一下我们要做的效果:

这个效果很容易理解:当点击btn时,在底部弹出PopupWindow,然后点击各个item弹出对应toast。

一、概述

1、PopupWindow与AlertDialog的区别

最关键的区别是AlertDialog不能指定显示位置,只能默认显示在屏幕最中间(当然也可以通过设置WindowManager参数来改变位置)。而PopupWindow是可以指定显示位置的,随便哪个位置都可以,更加灵活。
有关Dialog的相关知识,大家可以参考我的系列博客:《详解Dialog(一)——基础元素构建》

2、PopupWindow的相关函数

(1)、构造函数:

  1. //方法一:
  2. public PopupWindow (Context context)
  3. //方法二:
  4. public PopupWindow(View contentView)
  5. //方法三:
  6. public PopupWindow(View contentView, int width, int height)
  7. //方法四:
  8. public PopupWindow(View contentView, int width, int height, boolean focusable)

首要注意:看这里有四个构造函数,但要生成一个PopupWindow最基本的三个条件是一定要设置的:View contentView,int width, int height ;少任意一个就不可能弹出来PopupWindow!!!!
所以,如果使用方法一来构造PopupWindow,那完整的构造代码应该是这样的:

  1. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  2. PopupWindwo popWnd = PopupWindow (context);
  3. popWnd.setContentView(contentView);
  4. popWnd.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
  5. popWnd.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);

有关为什么一定要设置width和height的原因,我们后面会讲,这里说一下为什么样强制设置contentView;很简单的原因是因为PopupWindow没有默认布局,它不会像AlertDialog那样只setTitle,就能弹出来一个框。PopupWindow是没有默认布局的,它的布局只有通过我们自己设置才行。由于方法三中,含有了这三个必备条件,不用单独设置contentview或者width、height,所以构造方法三是用的最多的一个构造方法。
最后,方法四中的focusable变量不是必须的,有关它的方法和意义,我们会在下一篇中细讲。

(2)显示函数

显示函数主要使用下面三个:

  1. //相对某个控件的位置(正左下方),无偏移
  2. showAsDropDown(View anchor):
  3. //相对某个控件的位置,有偏移;xoff表示x轴的偏移,正值表示向左,负值表示向右;yoff表示相对y轴的偏移,正值是向下,负值是向上;
  4. showAsDropDown(View anchor, int xoff, int yoff):
  5. //相对于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以设置偏移或无偏移
  6. showAtLocation(View parent, int gravity, int x, int y):

这里有两种显示方式:
1、显示在某个指定控件的下方
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)、其它函数

  1. public void dismiss()
  2. //另外几个函数,这里不讲其意义,下篇细讲
  3. public void setFocusable(boolean focusable)
  4. public void setTouchable(boolean touchable)
  5. public void setOutsideTouchable(boolean touchable)
  6. public void setBackgroundDrawable(Drawable background)

这几个函数里,这篇只会用到dismiss(),用于不需要的时候,将窗体隐藏掉。

好了,废话不多说了,我们就做一个上面的例子来看一下。

二、简单示例(showAtLocation显示窗体)

在这个例子中,我们实现两个功能,弹出popupWindow和Item点击响应

1、主布局(main.xml)

从效果图中也可以看到主布局只有一个button,什么都没有,所以它的布局代码哪下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <Button
  7. android:id="@+id/btn"
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:text="show popWindow"/>
  11. </LinearLayout>

2、PopupWindow布局(popuplayout.xml)

在概述中,我们提到了,必须为PopupWindow设置布局,从效果图中,我也可以看到它的布局有三个item,中间用横线分开。所以这里布局使用Listview应该更合适,但为了减轻代码难度,我们直接使用TextView和分隔线来代替,代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="wrap_content"
  6. android:background="#ffffff"
  7. android:orientation="vertical"
  8. android:paddingBottom="2dp">
  9. <View
  10. android:layout_width="match_parent"
  11. android:layout_height="2.25dp"
  12. android:background="#fa7829"
  13. android:layout_alignParentTop="true"/>
  14. <TextView
  15. android:id="@+id/pop_computer"
  16. android:layout_width="match_parent"
  17. android:layout_height="wrap_content"
  18. style="@style/pop_text_style"
  19. android:text="计算机"/>
  20. <View
  21. android:layout_width="match_parent"
  22. android:layout_height="1dp"
  23. android:background="@drawable/list_line"/>
  24. <TextView
  25. android:id="@+id/pop_financial"
  26. android:layout_width="match_parent"
  27. android:layout_height="wrap_content"
  28. style="@style/pop_text_style"
  29. android:text="金融"/>
  30. <View
  31. android:layout_width="match_parent"
  32. android:layout_height="1dp"
  33. android:background="@drawable/list_line"/>
  34. <TextView
  35. android:id="@+id/pop_manage"
  36. android:layout_width="match_parent"
  37. android:layout_height="wrap_content"
  38. style="@style/pop_text_style"
  39. android:text="管理"/>
  40. <View
  41. android:layout_width="match_parent"
  42. android:layout_height="1dp"/>
  43. </LinearLayout>

3、MainActivity代码

先贴出来完整代码,然后再逐步讲解:

  1. public class MainActivity extends Activity implements View.OnClickListener{
  2. private PopupWindow mPopWindow;
  3. @Override
  4. public void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.main);
  7. Button btn = (Button) findViewById(R.id.btn);
  8. btn.setOnClickListener(new View.OnClickListener() {
  9. @Override
  10. public void onClick(View v) {
  11. showPopupWindow();
  12. }
  13. });
  14. }
  15. private void showPopupWindow() {
  16. //设置contentView
  17. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  18. mPopWindow = new PopupWindow(contentView,
  19. LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
  20. mPopWindow.setContentView(contentView);
  21. //设置各个控件的点击响应
  22. TextView tv1 = (TextView)contentView.findViewById(R.id.pop_computer);
  23. TextView tv2 = (TextView)contentView.findViewById(R.id.pop_financial);
  24. TextView tv3 = (TextView)contentView.findViewById(R.id.pop_manage);
  25. tv1.setOnClickListener(this);
  26. tv2.setOnClickListener(this);
  27. tv3.setOnClickListener(this);
  28. //显示PopupWindow
  29. View rootview = LayoutInflater.from(MainActivity.this).inflate(R.layout.main, null);
  30. mPopWindow.showAtLocation(rootview, Gravity.BOTTOM, 0, 0);
  31. }
  32. @Override
  33. public void onClick(View v) {
  34. int id = v.getId();
  35. switch (id){
  36. case R.id.pop_computer:{
  37. Toast.makeText(this,"clicked computer",Toast.LENGTH_SHORT).show();
  38. mPopWindow.dismiss();
  39. }
  40. break;
  41. case R.id.pop_financial:{
  42. Toast.makeText(this,"clicked financial",Toast.LENGTH_SHORT).show();
  43. mPopWindow.dismiss();
  44. }
  45. break;
  46. case R.id.pop_manage:{
  47. Toast.makeText(this,"clicked manage",Toast.LENGTH_SHORT).show();
  48. mPopWindow.dismiss();
  49. }
  50. break;
  51. }
  52. }
  53. }

(1)首先看OnCreate()

在OnCreate()中只做了一个操作,当点击Button时,显示窗体:

  1. public void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.main);
  4. Button btn = (Button) findViewById(R.id.btn);
  5. btn.setOnClickListener(new View.OnClickListener() {
  6. @Override
  7. public void onClick(View v) {
  8. showPopupWindow();
  9. }
  10. });
  11. }

(2)、显示PopupWindow

下面是有关窗体操作的代码:

  1. private void showPopupWindow() {
  2. //设置contentView
  3. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  4. mPopWindow = new PopupWindow(contentView,
  5. LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
  6. //设置各个控件的点击响应
  7. TextView tv1 = (TextView)contentView.findViewById(R.id.pop_computer);
  8. TextView tv2 = (TextView)contentView.findViewById(R.id.pop_financial);
  9. TextView tv3 = (TextView)contentView.findViewById(R.id.pop_manage);
  10. tv1.setOnClickListener(this);
  11. tv2.setOnClickListener(this);
  12. tv3.setOnClickListener(this);
  13. //显示PopupWindow
  14. View rootview = LayoutInflater.from(MainActivity.this).inflate(R.layout.main, null);
  15. mPopWindow.showAtLocation(rootview, Gravity.BOTTOM, 0, 0);
  16. }

这里同样分为三部分:
第一部分:设置ContentView

  1. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  2. mPopWindow = new PopupWindow(contentView,LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);

利用LayoutInflater获取R.layout.popuplayout对应的View,然后利用我们上面所讲的构造函数三来生成mPopWindow;
这里

要注意一个问题:在这个构造函数里,我们传进去了width和height全部都是WRAP_CONTENT;而在R.layout.popuplayou的根布局中,我们定义的width和height代码是:layout_width="fill_parent",layout_height="wrap_content";原代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="wrap_content"
  6. android:background="#ffffff"
  7. android:orientation="vertical"
  8. android:paddingBottom="2dp">
  9. ………………
  10. </LinearLayout>

这里就有冲突了,那显示出来的popupWindow是按哪个来的呢?
从效果图中来看,明显PopupWindow宽度并没有全屏,显然是按代码中的布局为准。
这说明了:
**如果在代码中重新设置了popupWindow的宽和高,那就以代码中所设置为准。**(至于原因,下篇会讲)

第二部分:设置各个控件的点击响应

  1. TextView tv1 = (TextView)contentView.findViewById(R.id.pop_computer);
  2. TextView tv2 = (TextView)contentView.findViewById(R.id.pop_financial);
  3. TextView tv3 = (TextView)contentView.findViewById(R.id.pop_manage);
  4. tv1.setOnClickListener(this);
  5. tv2.setOnClickListener(this);
  6. tv3.setOnClickListener(this);

这部分没什么好讲了,设置PopupWindow中各个控件的点击响应,但一定要注意的是,PopupWindow中各个控件的所在的布局是contentView,而不是在Activity中,如果大家直接使用

  1. TextView tv1 = (TextView)findViewById(R.id.pop_computer);

肯定会报错,因为R.id.pop_computer这个ID值在当前Activtiy的布局文件中是找不到的。只有在R.layout.popuplayout的布局文件中才会有!所以,这就是为什么,要在findViewById(R.id.pop_computer)前指定contentView的原因!!!在实际项目中,很容易遇到像这种需要指定根布局的情况,大家需要注意。
有关响应,就没什么好讲的了,因为我们在类顶部派生了View.OnClickListener,所以在OnClick函数中,直接处理即可,代码如下:(在点击不同的Item时,一边弹出不同的toast,一边将PopupWindow隐藏掉)

  1. @Override
  2. public void onClick(View v) {
  3. int id = v.getId();
  4. switch (id){
  5. case R.id.pop_computer:{
  6. Toast.makeText(this,"clicked computer",Toast.LENGTH_SHORT).show();
  7. mPopWindow.dismiss();
  8. }
  9. break;
  10. case R.id.pop_financial:{
  11. Toast.makeText(this,"clicked financial",Toast.LENGTH_SHORT).show();
  12. mPopWindow.dismiss();
  13. }
  14. break;
  15. case R.id.pop_manage:{
  16. Toast.makeText(this,"clicked manage",Toast.LENGTH_SHORT).show();
  17. mPopWindow.dismiss();
  18. }
  19. break;
  20. }
  21. }

第三部分:showAtLocation显示窗体

  1. View rootview = LayoutInflater.from(MainActivity.this).inflate(R.layout.main, null);
  2. mPopWindow.showAtLocation(rootview, Gravity.BOTTOM, 0, 0);

showAtLocation的显示就将PopupWindow的实例放在一个父容器中,然后指定显示在父容器中的位置。
由于,我们要将mPopWindow放在整个屏幕的最低部,所以我们将R.layout.main做为它的父容器。将其显示在BOTTOM的位置。

到这里,有关PopupWindow的显示及其中控件响应基本都讲完了,下面,我们就讲讲showAsDropDown显示窗体的用法。

源码在文章底部给出

三、另一示例(showAsDropDown显示窗体)

大家先看下面这个效果图:

这个效果图演示的是,在点击标题栏右方的“菜单”按钮时,在其下方显示一个自定义的菜单列表。

1、同样,我们先看看主布局代码(main.xml)

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <RelativeLayout android:layout_width="fill_parent"
  7. android:layout_height="wrap_content"
  8. android:background="#ffffff">
  9. <TextView android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_alignParentLeft="true"
  12. android:textColor="#50484b"
  13. android:padding="10dp"
  14. android:text="返回"/>
  15. <TextView
  16. android:id="@+id/menu"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:layout_alignParentRight="true"
  20. android:textColor="#50484b"
  21. android:padding="10dp"
  22. android:text="菜单"/>
  23. </RelativeLayout>
  24. </LinearLayout>

这段代码的布局很简单,就是生成一个标题栏,上面有两个按钮,“返回”和“菜单”

2、PopupWindow布局代码(popuplayout.xml)

这部分布局也不难,只得利用纯代码硬生成一个列表的布局。在实际项目中,大家应该使用listview来动态生成列表,这样生成的popupWindow就是可以复用的了。有关布局就不再多讲,跟上面的布局基本一样,只是换了背景。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="wrap_content"
  6. android:background="@drawable/pop_bg"
  7. android:orientation="vertical"
  8. android:paddingBottom="2dp">
  9. <TextView
  10. android:id="@+id/pop_computer"
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. style="@style/pop_text_style"
  14. android:text="计算机"/>
  15. <View
  16. android:layout_width="match_parent"
  17. android:layout_height="1dp"
  18. android:background="@drawable/list_line"/>
  19. <TextView
  20. android:id="@+id/pop_financial"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. style="@style/pop_text_style"
  24. android:text="金融"/>
  25. <View
  26. android:layout_width="match_parent"
  27. android:layout_height="1dp"
  28. android:background="@drawable/list_line"/>
  29. <TextView
  30. android:id="@+id/pop_manage"
  31. android:layout_width="wrap_content"
  32. android:layout_height="wrap_content"
  33. style="@style/pop_text_style"
  34. android:text="管理"/>
  35. <View
  36. android:layout_width="match_parent"
  37. android:layout_height="1dp"/>
  38. </LinearLayout>

3、MainActivity代码

同样是先贴出来完整代码,然后再细讲。

  1. public class MainActivity extends Activity implements View.OnClickListener{
  2. private PopupWindow mPopWindow;
  3. private TextView mMenuTv;
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.main);
  8. mMenuTv = (TextView)findViewById(R.id.menu);
  9. mMenuTv.setOnClickListener(new View.OnClickListener() {
  10. @Override
  11. public void onClick(View v) {
  12. showPopupWindow();
  13. }
  14. });
  15. }
  16. private void showPopupWindow() {
  17. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  18. mPopWindow = new PopupWindow(contentView);
  19. mPopWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
  20. mPopWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
  21. TextView tv1 = (TextView)contentView.findViewById(R.id.pop_computer);
  22. TextView tv2 = (TextView)contentView.findViewById(R.id.pop_financial);
  23. TextView tv3 = (TextView)contentView.findViewById(R.id.pop_manage);
  24. tv1.setOnClickListener(this);
  25. tv2.setOnClickListener(this);
  26. tv3.setOnClickListener(this);
  27. mPopWindow.showAsDropDown(mMenuTv);
  28. }
  29. @Override
  30. public void onClick(View v) {
  31. int id = v.getId();
  32. switch (id){
  33. case R.id.pop_computer:{
  34. Toast.makeText(this, "clicked computer", Toast.LENGTH_SHORT).show();
  35. mPopWindow.dismiss();
  36. }
  37. break;
  38. case R.id.pop_financial:{
  39. Toast.makeText(this,"clicked financial",Toast.LENGTH_SHORT).show();
  40. mPopWindow.dismiss();
  41. }
  42. break;
  43. case R.id.pop_manage:{
  44. Toast.makeText(this,"clicked manage",Toast.LENGTH_SHORT).show();
  45. mPopWindow.dismiss();
  46. }
  47. break;
  48. }
  49. }
  50. }

这段代码的意义就是点击menu弹出popupwindow,然后对各个item进行响应,我们主要讲讲showPopupWindow() 这部分,对于item响应的部分与上个示例都一样,就不再细讲。

  1. private void showPopupWindow() {
  2. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  3. mPopWindow = new PopupWindow(contentView);
  4. mPopWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
  5. mPopWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
  6. TextView tv1 = (TextView)contentView.findViewById(R.id.pop_computer);
  7. TextView tv2 = (TextView)contentView.findViewById(R.id.pop_financial);
  8. TextView tv3 = (TextView)contentView.findViewById(R.id.pop_manage);
  9. tv1.setOnClickListener(this);
  10. tv2.setOnClickListener(this);
  11. tv3.setOnClickListener(this);
  12. mPopWindow.showAsDropDown(mMenuTv);
  13. }

这里首先注意的一个地方,在这个示例中,我们是使用的构造方法二来生成的PopupWindow实例的,同样,再强调一遍:contentView,Width,Height这三个元素是必须设置的,缺一不可!至于为什么要显式设置Width,Height,我们下篇会讲到。

  1. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  2. mPopWindow = new PopupWindow(contentView);
  3. mPopWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
  4. mPopWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);

然后就是使用showAsDropDown()显示PopupWindow:

  1. mPopWindow.showAsDropDown(mMenuTv);

大家也现样可以使用showAsDropDown(View anchor, int xoff, int yoff):来添加相对x轴和y轴的位移量。具体用法就不再细讲,没什么难度,大家试一试即可。
好了,这部分示例也讲完了,下面我们就在这个示例上升级一个功能:讲讲怎么在弹出PopupWindow的同时利用阴影把背景全部给遮罩起来。

源码在文章底部给出

四、提高:为菜单添加阴影

这部分的效果图是下面这样的:

从效果图中可以看出,这部分是上一个示例的升级版,就是在点出PopupWindow的同时,把背景用半透明黑色遮罩住,像弹出AlertDialog一样的效果。下面就来看看这个效果是怎么实现的吧。

1、PopupWindow布局(popuplayout.xml)

其实原理很简单,使PopupWindow的界面充满全屏,而实际的列表菜单只是其中的一个子布局即可,所以此时的PopupWindow的布局代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. android:background="#66000000">
  7. <LinearLayout
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:background="@drawable/pop_bg"
  11. android:orientation="vertical"
  12. android:paddingBottom="2dp"
  13. android:layout_alignParentRight="true">
  14. <TextView
  15. android:id="@+id/pop_computer"
  16. android:layout_width="wrap_content"
  17. android:layout_height="wrap_content"
  18. style="@style/pop_text_style"
  19. android:text="计算机"/>
  20. <View
  21. android:layout_width="match_parent"
  22. android:layout_height="1dp"
  23. android:background="@drawable/list_line"/>
  24. <TextView
  25. android:id="@+id/pop_financial"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. style="@style/pop_text_style"
  29. android:text="金融"/>
  30. <View
  31. android:layout_width="match_parent"
  32. android:layout_height="1dp"
  33. android:background="@drawable/list_line"/>
  34. <TextView
  35. android:id="@+id/pop_manage"
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. style="@style/pop_text_style"
  39. android:text="管理"/>
  40. <View
  41. android:layout_width="match_parent"
  42. android:layout_height="1dp"/>
  43. </LinearLayout>
  44. </RelativeLayout>

可见在列表项外面又包了一层RelativeLayout,将列表的根布局LinearLayout靠父窗口右边显示即可。给RelativeLayout添加了半透明背景android:background="#66000000"
这样要非常注意的是,根布局RelativeLayout设置的android:layout_width和android:layout_height是无意义的,因为我们会通过代码重新设置:

  1. private void showPopupWindow() {
  2. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  3. mPopWindow = new PopupWindow(contentView);
  4. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);
  5. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);
  6. ………………//设置各控件点击响应
  7. mPopWindow.showAsDropDown(mMenuTv);
  8. }

在这里,我们通过代码将PopupWindow的width和height设置为LayoutParams.FILL_PARENT。那大家可能会有疑问:为什么我在xml中布局中明明写好了根布局RelativeLayout的宽和高,为什么非要我们在代码中重新设置呢?不设置还显示不出来…………
下篇,我们将会解答这个问题。
好啦,这个示例关键部分讲完了,其它的大家就看源码吧。

源码在文章底部给出

五、为PopupWindow添加动画

先看看效果:

为PopupWindow添加动画并不难,只需要使用一个函数即可:

  1. //设置动画所对应的style
  2. mPopWindow.setAnimationStyle(R.style.contextMenuAnim);

下面就来看看具体是如何添加动画的

1、生成动画对应的style

(1)、进入时的动画:(context_menu_enter.xml)

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <set xmlns:android="http://schemas.android.com/apk/res/android">
  3. <translate
  4. android:duration="@android:integer/config_shortAnimTime"
  5. android:fromXDelta="0"
  6. android:fromYDelta="100%p"
  7. android:interpolator="@android:anim/accelerate_decelerate_interpolator"
  8. android:toXDelta="0"
  9. android:toYDelta="0"/>
  10. </set>

这段代码的意义就是从底部进入,即从Y轴100%的位置移动到0的位置。有关动画的知识,大家可以参考《Animation 动画详解(一)——alpha、scale、translate、rotate、set的xml属性及用法》      这个系列文章。

(2)、退出时动画(context_menu_exit.xml)

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <set xmlns:android="http://schemas.android.com/apk/res/android" >
  3. <translate
  4. android:duration="@android:integer/config_shortAnimTime"
  5. android:fromXDelta="0"
  6. android:fromYDelta="0"
  7. android:interpolator="@android:anim/accelerate_decelerate_interpolator"
  8. android:toXDelta="0"
  9. android:toYDelta="100%p" />
  10. </set>

效果是从上向下移动。

(3)、最后,生成对应的style---contextMenuAnim

  1. <style name="contextMenuAnim" parent="@android:style/Animation.Activity">
  2. <item name="android:windowEnterAnimation">@anim/context_menu_enter</item>
  3. <item name="android:windowExitAnimation">@anim/context_menu_exit</item>
  4. </style>

android:windowEnterAnimation设置进场动画,android:windowExitAnimation设置出场动画。

2、使用AnimationStyle

使用时非常简单,直接将对应的style通过setAnimationStyle设置进PopupWindow实例即可,代码如下,难度不大,不再细讲。

  1. private void showPopupWindow() {
  2. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  3. mPopWindow = new PopupWindow(contentView);
  4. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);
  5. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);
  6. ………………//设置各子项点击响应
  7. mPopWindow.setAnimationStyle(R.style.contextMenuAnim);
  8. mPopWindow.showAsDropDown(mMenuTv);
  9. }

到这里,这个示例的代码就讲完了。源码在文章底部一起给出。这篇讲述了有关PopupWindow的基本使用方法,下篇将针对还未讲述的几个函数,以及问题点结合源码深入剖析。

源码内容:

1、《PopshowAtLocation》:第二部分:简单示例(showAtLocation显示窗体)对应源码

2、《PopupShowAsDropDown》:第三部分:另一示例(showAsDropDown显示窗体) 对应源码

3、《PopDropDownBg》:第四部分:提高:为菜单添加阴影  对应源码

4、《PopupAnim》:第五部分:为PopupWindow添加动画  对应源码

如果本文有帮到你,记得加关注哦

源码下载地址:http://download.csdn.net/detail/harvic880925/9195255

请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/49272285   谢谢

另外:

关于弹出的组件还有一个PopupMenu和PopupWindow

PopupMenu显示效果类似上下文菜单(Menu),而PopupWindow的显示效果实际上类似对话框(Dialog),两者效果如下图所示:

PopupMenu显示效果

PopupWindow显示效果

上篇为大家基本讲述了有关PopupWindow的基本使用,但还有几个相关函数还没有讲述,我们这篇将着重看看这几个函数的用法并结合源码来讲讲具体原因,最后是有关PopupWindow在使用时的疑问,给大家讲解一下。

一、常用函数讲解

这段将会给大家讲下下面几个函数的意义及用法,使用上篇那个带背景的例子为基础。

  1. public void setTouchable(boolean touchable)
  2. public void setFocusable(boolean focusable)
  3. public void setOutsideTouchable(boolean touchable)
  4. public void setBackgroundDrawable(Drawable background)

1、setTouchable(boolean touchable)

设置PopupWindow是否响应touch事件,默认是true,如果设置为false,即会是下面这个结果:(所有touch事件无响应,包括点击事件)

对应代码:

  1. private void showPopupWindow() {
  2. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  3. mPopWindow = new PopupWindow(contentView);
  4. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);
  5. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);
  6. mPopWindow.setTouchable(false);
  7. ………………//单项点击
  8. mPopWindow.showAsDropDown(mMenuTv);
  9. }

2、setFocusable(boolean focusable)

该函数的意义表示,PopupWindow是否具有获取焦点的能力,默认为False。一般来讲是没有用的,因为普通的控件是不需要获取焦点的,而对于EditText则不同,如果不能获取焦点,那么EditText将是无法编辑的。
所以,我们在popuplayout.xml最底部添加一个EditText,分别演示两段不同的代码,即分别将setFocusable设置为false和设置为true;看看有什么不同:
(1)setFocusable(true)
代码如下:

  1. private void showPopupWindow() {
  2. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  3. mPopWindow = new PopupWindow(contentView);
  4. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);
  5. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);
  6. //是否具有获取焦点的能力
  7. mPopWindow.setFocusable(true);
  8. …………//各item点击响应
  9. mPopWindow.showAsDropDown(mMenuTv);
  10. }

明显在点击EditText的时候,会弹出编辑框。

(2)setFocusable(false)
同样上面一段代码,那我们将setFocusable设置为false,会是怎样呢?

  1. private void showPopupWindow() {
  2. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  3. mPopWindow = new PopupWindow(contentView);
  4. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);
  5. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);
  6. //是否具有获取焦点的能力
  7. mPopWindow.setFocusable(false);
  8. …………//各item点击响应
  9. mPopWindow.showAsDropDown(mMenuTv);
  10. }

效果图下:
可见,点击EditText没有出现任何反应!所以如果PopupWindow没有获取焦点的能力,那么它其中的EditText当然是没办法获取焦点的,EditText无法获取焦点,那对它而言整个EditText控件就是不可用的。

3、setOutsideTouchable(boolean touchable)

这个函数的意义,就是指,PopupWindow以外的区域是否可点击,即如果点击PopupWindow以外的区域,PopupWindow是否会消失。
下面这个是点击会消息的效果图:

看看它对应的代码:

  1. private void showPopupWindow() {
  2. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  3. mPopWindow = new PopupWindow(contentView);
  4. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);
  5. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);
  6. //外部是否可以点击
  7. mPopWindow.setBackgroundDrawable(new BitmapDrawable());
  8. mPopWindow.setOutsideTouchable(true);
  9. …………//各ITEM点击响应
  10. mPopWindow.showAsDropDown(mMenuTv);
  11. }

这里要非常注意的一点:

  1. mPopWindow.setBackgroundDrawable(new BitmapDrawable());
  2. mPopWindow.setOutsideTouchable(true);

大家可能要疑问,为什么要加上mPopWindow.setBackgroundDrawable(new

BitmapDrawable());这句呢,从代码来看没并没有真正设置Bitmap,而只是new了一个空的bitmap,好像并没起到什么作用。那如果我们把这句去掉会怎样:
把代码改成这样子:(只使用setOutsideTouchable)

  1. private void showPopupWindow() {
  2. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  3. mPopWindow = new PopupWindow(contentView);
  4. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);
  5. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);
  6. //外部是否可以点击
  7. mPopWindow.setOutsideTouchable(true);
  8. …………//各ITEM点击响应
  9. mPopWindow.showAsDropDown(mMenuTv);
  10. }


看到了没,点击外部没反应………………这就有点坑了,至于原因,我们在setBackgroundDrawable()中讲。

4、setBackgroundDrawable(Drawable background)

这个函数可是吊了,这个函数不只能设置背景……,因为你加上它之后,setOutsideTouchable()才会生效;

而且,只有加上它之后,PopupWindow才会对手机的返回按钮有响应:即,点击手机返回按钮,可以关闭PopupWindow;如果不加setBackgroundDrawable()将关闭的PopupWindow所在的Activity.
这个函数要怎么用,这里应该就不用讲了吧,可以填充进去各种Drawable,比如new BitmapDrawable(),new ColorDrawable(),等;
我们这里主要从源码的角度来看看setBackgroundDrawable()后,PopupWindow都做了些什么。
首先看看setBackgroundDrawable(),将传进去的background赋值给mBackground;

  1. void setBackgroundDrawable(Drawable background) {
  2. mBackground = background;
  3. }

然后再看看显示showAsDropDown()显示的时候,都做了些什么。代码如下:

  1. public void showAsDropDown(View anchor, int xoff, int yoff) {
  2. …………
  3. //准备窗口
  4. WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
  5. preparePopup(p);
  6. …………
  7. //显示窗口
  8. invokePopup(p);
  9. }

在这段代码中,先是准备窗口用来显示,然后再利用invokePopup()来显示窗体。
我们看看在preparePopup(p)中是怎么准备窗体的:

  1. private void preparePopup(WindowManager.LayoutParams p) {
  2. if (mBackground != null) {
  3. final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
  4. int height = ViewGroup.LayoutParams.MATCH_PARENT;
  5. if (layoutParams != null &&
  6. layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
  7. height = ViewGroup.LayoutParams.WRAP_CONTENT;
  8. }
  9. // when a background is available, we embed the content view
  10. // within another view that owns the background drawable
  11. PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
  12. PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
  13. ViewGroup.LayoutParams.MATCH_PARENT, height
  14. );
  15. popupViewContainer.setBackgroundDrawable(mBackground);
  16. popupViewContainer.addView(mContentView, listParams);
  17. mPopupView = popupViewContainer;
  18. } else {
  19. mPopupView = mContentView;
  20. }
  21. mPopupWidth = p.width;
  22. mPopupHeight = p.height;
  23. }

从上面可以看出,如果mBackground不这空,会首先生成一个PopupViewContainer的ViewContainer,然后把mContentView做为子布局add进去,然后把popupViewContainer做为PopupWindow做为根布局。

  1. popupViewContainer.addView(mContentView, listParams);

那如果mBackground不为空,那就直接把mContentView做为View传递给PopupWindow窗体。

  1. mPopupView = mContentView

到此,我们知道,如果mBackground不为空,会在我们设置的contentView外再包一层布局。
那下面,我们再看看包的这层布局都干了什么:
先列出来完整的代码,然后再分步讲(已做精简,如需知道更多,可参看源码)

  1. private class PopupViewContainer extends FrameLayout {
  2. private static final String TAG = "PopupWindow.PopupViewContainer";
  3. public PopupViewContainer(Context context) {
  4. super(context);
  5. }
  6. …………
  7. @Override
  8. public boolean dispatchKeyEvent(KeyEvent event) {
  9. if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
  10. if (event.getAction() == KeyEvent.ACTION_DOWN
  11. && event.getRepeatCount() == 0) {
  12. …………
  13. } else if (event.getAction() == KeyEvent.ACTION_UP) {
  14. KeyEvent.DispatcherState state = getKeyDispatcherState();
  15. if (state != null && state.isTracking(event) && !event.isCanceled()) {
  16. dismiss();
  17. return true;
  18. }
  19. }
  20. return super.dispatchKeyEvent(event);
  21. } else {
  22. return super.dispatchKeyEvent(event);
  23. }
  24. }
  25. @Override
  26. public boolean onTouchEvent(MotionEvent event) {
  27. final int x = (int) event.getX();
  28. final int y = (int) event.getY();
  29. if ((event.getAction() == MotionEvent.ACTION_DOWN)
  30. && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
  31. dismiss();
  32. return true;
  33. } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
  34. dismiss();
  35. return true;
  36. } else {
  37. return super.onTouchEvent(event);
  38. }
  39. }
  40. …………
  41. }

这里总共需要注意三部分:
(1)、PopupViewContainer派生自FrameLayout
从PopupViewContainer声明上可以看到,PopupViewContainer派生自FrameLayout;所以,这也是它能将我们传进来的contentView添加为自己的子布局的原因。
(2)、返回按钮捕捉

  1. public boolean dispatchKeyEvent(KeyEvent event) {
  2. if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
  3. if (event.getAction() == KeyEvent.ACTION_DOWN
  4. && event.getRepeatCount() == 0) {
  5. …………
  6. } else if (event.getAction() == KeyEvent.ACTION_UP) {
  7. //抬起手指时
  8. KeyEvent.DispatcherState state = getKeyDispatcherState();
  9. if (state != null && state.isTracking(event) && !event.isCanceled()) {
  10. //隐藏窗体
  11. dismiss();
  12. return true;
  13. }
  14. }
  15. return super.dispatchKeyEvent(event);
  16. } else {
  17. return super.dispatchKeyEvent(event);
  18. }
  19. }

从上面的代码来看,PopupViewContainer捕捉了KeyEvent.KEYCODE_BACK事件,并且在用户在点击back按钮,抬起手指的时候(event.getAction() == KeyEvent.ACTION_UP)将窗体隐藏掉。
所以,添加上mBackground以后,可以在用户点击返回按钮时,隐藏窗体!
(3)、捕捉Touch事件——onTouchEvent

  1. public boolean onTouchEvent(MotionEvent event) {
  2. final int x = (int) event.getX();
  3. final int y = (int) event.getY();
  4. if ((event.getAction() == MotionEvent.ACTION_DOWN)
  5. && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
  6. dismiss();
  7. return true;
  8. } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
  9. dismiss();
  10. return true;
  11. } else {
  12. return super.onTouchEvent(event);
  13. }
  14. }

从这代码来看,PopupViewContainer捕捉了两种touch事件,MotionEvent.ACTION_DOWN和MotionEvent.ACTION_OUTSIDE;将接收到这两个事件时,会将窗体隐藏掉。
MotionEvent.ACTION_DOWN的触发很好理解,即当用户点击到PopupViewContainer事件时,就隐藏掉;
所以,下面的情况就来了:
假如有一个TextView,我们没有对它设置点击响应。那只要加了background,那点击事件就会传给下层的PopupViewContainer,从而使窗体消失。
那还有个问题,MotionEvent.ACTION_OUTSIDE是个什么鬼?它是怎么触发的。我们在下面开一段细讲。
(4)MotionEvent.ACTION_OUTSIDE与setOutsideTouchable(boolean touchable)
可能把这两个放在一块,大家可能就恍然大悟了,表着急,一个个来看。
先看看setOutsideTouchable(boolean touchable)的代码:

  1. public void setOutsideTouchable(boolean touchable) {
  2. mOutsideTouchable = touchable;
  3. }

然后再看看mOutsideTouchable哪里会用到
下面代码,我做了严重精减,等下会再完整再讲这一块

  1. private int computeFlags(int curFlags) {
  2. curFlags &= ~(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
  3. …………
  4. if (mOutsideTouchable) {
  5. curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
  6. }
  7. …………
  8. return curFlags;
  9. }

这段代码主要是用各种变量来设置window所使用的flag;
首先,将curFlags所在运算的各种Flag,全部置为False;代码如下:

  1. curFlags &= ~(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);

然后,再根据用户设置的变量来开启:

  1. if (mOutsideTouchable) {
  2. curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
  3. }

既然讲到FLAG_WATCH_OUTSIDE_TOUCH,那我们来看看FLAG_WATCH_OUTSIDE_TOUCH所代表的意义:

这段话的意思是说,如果窗体设置了FLAG_WATCH_OUTSIDE_TOUCH这个flag,那么 用户点击窗体以外的位置时,将会在窗体的MotionEvent中收到MotionEvetn.ACTION_OUTSIDE事件。
参见:《WindowManager.LayoutParams》

所以在PopupViewContainer中添加了对MotionEvent.ACTION_OUTSIDE的捕捉!当用户点击PopupViewContainer以外的区域时,将dismiss掉PopupWindow

  1. public boolean onTouchEvent(MotionEvent event) {
  2. final int x = (int) event.getX();
  3. final int y = (int) event.getY();
  4. if ((event.getAction() == MotionEvent.ACTION_DOWN)
  5. && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
  6. dismiss();
  7. return true;
  8. } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
  9. dismiss();
  10. return true;
  11. } else {
  12. return super.onTouchEvent(event);
  13. }
  14. }

(5)重看PopupWindow的computeFlags(int curFlags)函数
完整的computeFlags()函数如下:

  1. private int computeFlags(int curFlags) {
  2. curFlags &= ~(
  3. WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
  4. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
  5. WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
  6. WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
  7. WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
  8. WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
  9. WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
  10. if(mIgnoreCheekPress) {
  11. curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
  12. }
  13. if (!mFocusable) {
  14. curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  15. if (mInputMethodMode == INPUT_METHOD_NEEDED) {
  16. curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
  17. }
  18. } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
  19. curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
  20. }
  21. if (!mTouchable) {
  22. curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  23. }
  24. if (mOutsideTouchable) {
  25. curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
  26. }
  27. if (!mClippingEnabled) {
  28. curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
  29. }
  30. if (isSplitTouchEnabled()) {
  31. curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
  32. }
  33. if (mLayoutInScreen) {
  34. curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
  35. }
  36. if (mLayoutInsetDecor) {
  37. curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
  38. }
  39. if (mNotTouchModal) {
  40. curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
  41. }
  42. return curFlags;
  43. }

这段代码同样是分两段:
第一段:将所有要计算的FLAG,全部在结果curFlags中置为FALSE;

  1. curFlags &= ~(
  2. WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
  3. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
  4. WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
  5. WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
  6. WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
  7. WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
  8. WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);

第二段:然后根据用户设置的变量,逐个判断是否打开。比如下面这个:

  1. //看到了吧,我们的setTouchable(boolean touchable)最终也是通过在这里设置的
  2. if (!mTouchable) {
  3. curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  4. }

好了,结合源码就给大家讲到这里了。最后总结一下:
**如果我们给PopupWindow添加了mBackground,那它将会:**

  • setOutsideTouchable(true)将生效,具有外部点击隐藏窗体的功能
  • 手机上的返回键将可以使窗体消失
  • 对于PopupWindow上层没有捕捉的点击事件,点击之后,仍然能使窗体消失。
源码在文章底部给出

二、为什么要强制代码设置PopupWindow的Height、Width

在上篇,我们留了这么个疑问,设置contentView很容易理解,但width和height为什么要强制设置呢?我们在布局代码中不是已经写的很清楚了么?比如我们的popuplayout.xml的根布局:

  1. <RelativeLayout
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:background="#66000000">
  6. …………
  7. </RelativeLayout>

从根布局中,我们明明可以看到layout_width我们设置为了"fill_parent",layout_height设置为了“fill_parent”;为什么非要我们在代码中还要再设置一遍:

  1. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
  2. mPopWindow = new PopupWindow(contentView);
  3. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);
  4. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);

带着这个疑问,我们从两个角度来分析,”源码角度”看看就好,关键的解答在第二部分:布局角度;

1、源码角度

首先,我们从源码我角度来分析为什么要设置Width和Height;我们就以setWidth()为例来追根寻底下
先看下setWidth():

  1. public void setWidth(int width) {
  2. mWidth = width;
  3. }

然后再看看mWidth在哪里用到:

  1. private WindowManager.LayoutParams createPopupLayout(IBinder token) {
  2. // generates the layout parameters for the drop down
  3. // we want a fixed size view located at the bottom left of the anchor
  4. WindowManager.LayoutParams p = new WindowManager.LayoutParams();
  5. // these gravity settings put the view at the top left corner of the
  6. // screen. The view is then positioned to the appropriate location
  7. // by setting the x and y offsets to match the anchor's bottom
  8. // left corner
  9. p.gravity = Gravity.LEFT | Gravity.TOP;
  10. p.width = mLastWidth = mWidth;
  11. p.height = mLastHeight = mHeight;
  12. if (mBackground != null) {
  13. p.format = mBackground.getOpacity();
  14. } else {
  15. p.format = PixelFormat.TRANSLUCENT;
  16. }
  17. p.flags = computeFlags(p.flags);
  18. p.type = mWindowLayoutType;
  19. p.token = token;
  20. p.softInputMode = mSoftInputMode;
  21. p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
  22. return p;
  23. }

上面是createPopupLayout的完整代码,我们提取一下:

  1. private WindowManager.LayoutParams createPopupLayout(IBinder token) {
  2. …………
  3. p.width = mLastWidth = mWidth;
  4. p.height = mLastHeight = mHeight;
  5. …………
  6. return p;
  7. }

从这里便可以清晰的看到窗体的宽度和高度都是通过mWidth和mHeight来设置的。
那问题来了,mWidth在哪里能设置呢:
通篇代码中,总共只有三个函数能设置mWidth,分别如下:
除了setWidth()函数本身,就只有PopupWindow()的两个构造函数了;

  1. public void setWidth(int width) {
  2. mWidth = width;
  3. }
  4. public PopupWindow(View contentView, int width, int height) {
  5. this(contentView, width, height, false);
  6. }
  7. public PopupWindow(View contentView, int width, int height, boolean focusable) {
  8. if (contentView != null) {
  9. mContext = contentView.getContext();
  10. mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
  11. }
  12. setContentView(contentView);
  13. setWidth(width);
  14. setHeight(height);
  15. setFocusable(focusable);
  16. }

那么问题来了,如果我们没有设置width和height那结果会如何呢?
如果我们没有设置width和height,那mWidth和mHeight将会取默认值0!!!!所以当我们没有设置width和height时,并不是我们的窗体没有弹出来,而是因为他们的width和height都是0了!!!!
**那么问题又来了:Google那帮老头,不能从我们contentView的根布局中取参数吗,非要我们自己设?**
当然不是那帮老头的代码有问题,因为这牵涉了更深层次的内容:布局参数的设定问题!我们在下一部分,布局角度来解答。

2、布局角度

这部分我们着重讲一个问题:控件的布局参数从哪里来?
我们看下面这段XML:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <LinearLayout
  7. android:layout_width="match_parent"
  8. android:layout_height="wrap_content"
  9. android:background="@drawable/pop_bg"
  10. android:orientation="vertical"
  11. android:paddingBottom="2dp"
  12. android:layout_alignParentRight="true">
  13. <TextView
  14. android:id="@+id/pop_computer"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. style="@style/pop_text_style"
  18. android:text="计算机"/>
  19. </LinearLayout>
  20. </RelativeLayout>

很明显,这段代码是个三层结构,TextView是最终的子控件。
那我现在要问了:TextView的显示大小是由谁来决定的?
是由它自己的布局layout_width="wrap_content"、layout_height="wrap_content"来决定的吗?
当然不是!!!!它的大小,应该是在它父控件的基础上决定的。即LinearLayout的显示大小确定了以后,才能确定TextView的大小。
这好比,如果LinearLayout的大小是全屏的,那TextView的大小就由它自己来决定了,那如果LinearLayout的大小只有一像素呢?那TextView的所显示的大小无论它自己怎么设置,最大也就显示一像素!
所以我们的结论来了:控件的大小,是建立在父控件大小确定的基础上的。
那同样:LinearLayout的大小确定是要靠RelativeLayout来决定。
那问题来了:RelativeLayout的大小靠谁决定呢?
当然是它的父控件了。
我们以前讲过ViewTree的概念,即在android中任何一个APP都会有一个根结点,然后它所有的Activity和Fragmentr所对应的布局都会加入到这个ViewTree中;在ViewTree中每一个控件是一个结点:
比如下面这个ViewTree(画的很烂……)

从上面的ViewTree中可以看到,每一个结点都是有父结点的(除了根结点,根结点不是应用的根结点,与我们应用无关),所以每一个控件都是可以找到父控件的的布局大小的。
但我们的contentView是怎么来的呢?

  1. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);

直接inflate出来的,我们对它没有设置根结点!
那问题来了?它的大小由谁来解决呢?
好像没有谁能决定了,因为他没有父结点。那它到底是多大呢?未知!
所以只有通过代码让用户去手动设置了!所以这就是为什么非要用户设置width和height的原因了。

好了,到这里,有关PopupWIndow的东东也就讲完了,希望大家能学到东西。

如果本文有帮到你,记得加关注哦

源码下载地址:http://download.csdn.net/detail/harvic880925/9197073

请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/49278705 谢谢

PopUpWindow使用方法的更多相关文章

  1. 屏幕旋转时调用PopupWindow update方法更新位置失效的问题及解决方案

       接到一个博友的反馈,在屏幕旋转时调用PopupWindow的update方法失效.使用场景如下:在一个Activity中监听屏幕旋转事件,在Activity主布局文件中有个按钮点击弹出一个Pop ...

  2. Android PopupWindow使用方法小结

    前几天要用到PopupWindow,一时竟想不起来怎么用,赶紧上网查了查,自己写了个demo,并在此记录一下PopupWindow的用法. 使用场景 PopupWindow,顾名思义,就是弹窗,在很多 ...

  3. 点击返回键退出popupwindow的方法

    点击返回键退出popupwindow mPopupWindow.setFocusable(true); 这句非常重要,对背景不会有影响 mPopupWindow.setBackgroundDrawab ...

  4. android标题栏上面弹出提示框(二) PopupWindow实现,带动画效果

    需求:上次用TextView写了一个从标题栏下面弹出的提示框.android标题栏下面弹出提示框(一) TextView实现,带动画效果,  总在找事情做的产品经理又提出了奇葩的需求.之前在通知栏显示 ...

  5. 动态添加PopupWindow

    动态添加PopupWindow的方法private void showPopupWindow() { LayoutInflater inflater = LayoutInflater.from(thi ...

  6. android popupwindow低版本报空指针

    在项目中使用Popupwindow pop=new Popupwindow();在2.3版本报 异常信息: Exception: null 堆栈信息: android.widget.PopupWind ...

  7. Android学习之PopupWindow

    Android的对话框有两种:PopupWindow和AlertDialog. 详细说明如下: AlertDialog是非阻塞式对话框:AlertDialog弹出时,后台还可以做事情: AlertDi ...

  8. popupWindow 用法总结 控制位置

    android中的dialog,以及activiy形式的dialog均是模态对话框,对话框不消失时,不能对其他页面进行操作,也就是其他页面不能获得焦点.而PopupWindow是非模态对话框,对话框显 ...

  9. PopupWindow --- 弹出底部窗体

    第一步 : 布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:a ...

随机推荐

  1. enumerate()函数

    for index,value in enumerate(list):       print index,value 等于for i in range(0,len(list)): print i,l ...

  2. Understanding the Effective Receptive Field in Deep Convolutional Neural Networks

    Understanding the Effective Receptive Field in Deep Convolutional Neural Networks 理解深度卷积神经网络中的有效感受野 ...

  3. PCA(主成分分析)

    PCA(Principal Component Analysis)是一种常用的数据分析方法.PCA通过线性变换将原始数据变换为一组各维度线性无关的表示,可用于提取数据的主要特征分量,常用于高维数据的降 ...

  4. 软件测试就业必备知识点&自学软件测试-Dotest-2019

    软件测试就业必备知识点&自学测试&教学大纲-Dotest-2019

  5. AI进阶之路

    一.方法论 二.发展趋势 三.入门查看 1. https://hongyuxie.github.io/MyResume_CN/ 上班后大家还刷算法题吗 编程面试的 10 大算法概念汇总 技术面试宝典: ...

  6. poj 1743 Musical Theme(最长重复子串 后缀数组)

    poj 1743 Musical Theme(最长重复子串 后缀数组) 有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复 ...

  7. jzoj4916. 【GDOI2017模拟12.9】完全背包问题 (背包+最短路)

    题面 题解 考场上蠢了--这么简单的东西都想不到-- 首先排序加去重. 先来考虑一下,形如 \[a_1x_1+a_2x_2+...a_nx_n=w,a_1<a_2<...<a_n,x ...

  8. [Swift实际操作]九、完整实例-(6)创建App欢迎界面

    本文创建一个位于导航控制器之内的欢迎页面,该页面主要用来向用户简要介绍产品的功能.以及提供主要功能的入口. 首先选择自定义视图文件夹[CustomViews],需要在该文件夹下,导入一款第三方类库.该 ...

  9. luogu2513 逆序对数列

    我们令\(f[i][j]\)表示\(i\)的全排列中,逆序数为\(j\)的个数. 我们考虑在\(i-1\)的排列中插入\(i\).\(k\)是这次更新会导致增加多少逆序数. 则\(\begin{ali ...

  10. AngularJs ng-repeat解决循环对象出现重复项报错的问题

    问题:ng-repeat  的循环对象是不能出现重复项的,所以如果有重复的就会报错,应该是 key value的问题吧,不是很了解内部运行机制:经过查询发现 在 循环后面加上  track by $i ...