近期有网友根据 Android 必知必会 - DialogFragment 使用总结  做一些业务,但是目标却是用 DialogFragment 实现类似 PopupWindow 效果:
只拦截自身所占空间部分的事件,其余空间的点击事件不处理 
可以根据某个 View 定位自身位置 
 
虽然在功能上 PopupWindow 更符合需要,但是使用 DialogFragment 代码更简洁、更方便封装功能模块。
基础知识点 WindowManager.LayoutParams.flags WindowManager.LayoutParams.flags 修改 Window 的表现行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29  /** * Various behavioral options/flags.  Default is none. * * @see #FLAG_ALLOW_LOCK_WHILE_SCREEN_ON * @see #FLAG_DIM_BEHIND * @see #FLAG_NOT_FOCUSABLE * @see #FLAG_NOT_TOUCHABLE * @see #FLAG_NOT_TOUCH_MODAL * @see #FLAG_TOUCHABLE_WHEN_WAKING * @see #FLAG_KEEP_SCREEN_ON * @see #FLAG_LAYOUT_IN_SCREEN * @see #FLAG_LAYOUT_NO_LIMITS * @see #FLAG_FULLSCREEN * @see #FLAG_FORCE_NOT_FULLSCREEN * @see #FLAG_SECURE * @see #FLAG_SCALED * @see #FLAG_IGNORE_CHEEK_PRESSES * @see #FLAG_LAYOUT_INSET_DECOR * @see #FLAG_ALT_FOCUSABLE_IM * @see #FLAG_WATCH_OUTSIDE_TOUCH * @see #FLAG_SHOW_WHEN_LOCKED * @see #FLAG_SHOW_WALLPAPER * @see #FLAG_TURN_SCREEN_ON * @see #FLAG_DISMISS_KEYGUARD * @see #FLAG_SPLIT_TOUCH * @see #FLAG_HARDWARE_ACCELERATED * @see #FLAG_LOCAL_FOCUS_MODE * @see #FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS */ 
以上可以看到它有很多可选项,加上可以多个相互组合,能满足很多需求,这里重点关注三个属性值:
更详细的介绍请点击 文档 
其中 FLAG_NOT_TOUCH_MODAL 可达到『只拦截自身所占空间部分的事件,其余空间的点击事件不处理』的需求,而 FLAG_TRANSLUCENT_NAVIGATION 和 FLAG_TRANSLUCENT_STATUS 主要是用来调整使用沉浸式状态栏 时显示自身位置不正确的问题。
获取 View 位置的时机 如果需要让 DialogFragment 在 onCreate() 等生命周期函数内直接调用显示到某个 View 的位置处,可能无法正确获取到该 View 的坐标,具体参考 Android必知必会-获取View坐标和长宽的时机  一文。
但是,如果在界面显示给用户后,DialogFragment 的显示交给用户触发的话,就不需要在意这个问题了。
代码实现 
TopFragment.java
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public  class  TopFragment  extends  DialogFragment  {    private  static  final  String  EXT_Y  =  "y value" ;     private  static  final  String  EXT_BAR  =  "isTranslucentDecor" ;     private  int  y;     private  boolean  isTranslucentDecor;     public  static  TopFragment getInstant (int  y)  {         return  getInstant(y, false );     }     public  static  TopFragment getInstant (int  y, boolean  isTranslucentDecor)  {         TopFragment  fragment  =  new  TopFragment ();         Bundle  ext  =  new  Bundle ();         ext.putInt(EXT_Y, y);         ext.putBoolean(EXT_BAR, isTranslucentDecor);         fragment.setArguments(ext);         return  fragment;     }     @Override      public  void  onCreate (@Nullable  Bundle savedInstanceState)  {         super .onCreate(savedInstanceState);         setStyle(DialogFragment.STYLE_NO_TITLE, R.style.dialogFrag);         Bundle  args  =  getArguments();         if  (args != null ) {             y = args.getInt(EXT_Y, 0 );             isTranslucentDecor = args.getBoolean(EXT_BAR, false );         } else  {             y = 0 ;             isTranslucentDecor = false ;         }     }     @Override      public  View onCreateView (LayoutInflater inflater, ViewGroup container,                               Bundle savedInstanceState)  {        getDialog().setCanceledOnTouchOutside(false );         View  rootView  =  inflater.inflate(R.layout.fragment_top, container, false );                  final  Window  window  =  getDialog().getWindow();         window.setBackgroundDrawableResource(android.R.color.transparent);         window.getDecorView().setPadding(0 , 0 , 0 , 0 );         WindowManager.LayoutParams  wlp  =  window.getAttributes();         wlp.width = WindowManager.LayoutParams.MATCH_PARENT;         wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;         wlp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;         if  (isTranslucentDecor) {             wlp.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;         }         wlp.gravity = Gravity.TOP;         wlp.y = y;         window.setAttributes(wlp);       	         rootView.findViewById(R.id.vvv).setOnClickListener(new  View .OnClickListener() {             @Override              public  void  onClick (View v)  {                 Toast.makeText(getActivity(), "dialogFragment 响应了点击事件" , Toast.LENGTH_SHORT).show();             }         });         return  rootView;     } } 
MainActivity.java
 
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected  void  onCreate (Bundle savedInstanceState)  {	super .onCreate(savedInstanceState); 	setContentView(R.layout.activity_main);   	findViewById(R.id.bt_menu).setOnClickListener(new  View .OnClickListener() {     	@Override          public  void  onClick (View v)  { 			TextView  title  =  (TextView) findViewById(R.id.tv_title); 			TopFragment.getInstant(title.getBottom()).show(getSupportFragmentManager(), "tags" );         }     }); } 
注意:如果当前 Activity 使用了沉浸式状态栏 ,需要使用 TopFragment.getInstant(int y, boolean isTranslucentDecor) 方法,并且 isTranslucentDecor 传值为 true 。
效果图 
未使用沉浸式状态栏、 isTranslucentDecor 传值为 false ,位置正确 
使用沉浸式状态栏、 isTranslucentDecor 传值为 false ,位置定位差个状态栏高度 
使用沉浸式状态栏、 isTranslucentDecor 传值为 true ,位置正确 
 
 
 
总结 总的来说,这里基本完成了要求的效果,但是定位只能指定其顶部开始的位置,不方便底部定位到某个 View 的上面,因为高度自适应的话,在页面渲染完成前是不能知道它的高度的。当然,你可以使用固定高度布局的方式来实现随意定位。
有什么建议或者问题可以随时联系我,共同探讨学习: