栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Android PopupWindow

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android PopupWindow

1.PopupWindow

PopupWindow类用来实现一个弹出框,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。

2.用法

点击按钮弹出PopupWindow:

private void showPopupWindow(View view) {

    //自定义布局,作为PopupWindow显示的内容

    View contentView = LayoutInflater.from( mContext).inflate(R.layout.pop_window, null);

    final PopupWindow popupWindow = new PopupWindow(contentView,LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);

    popupWindow.setTouchable(true);

    popupWindow.setTouchInterceptor(new OnTouchListener() {

       @Override

       public boolean onTouch(View v, MotionEvent event) {

            Log.i("mengdd", "onTouch : ");

            return false; //这里如果返回true的话,touch事件将被拦截,拦截后 PopupWindow的onTouchEvent不被调用,这样点击外部区域无法dismiss

       }

   });

      // 如果不设置PopupWindow的背景,无论是点击外部区域还是Back键都无法dismiss弹框

     popupWindow.setBackgroundDrawable( getResources().getDrawable(R.drawable.selectmenu_bg_downward));

     // 设置好参数之后再show

     popupWindow.showAsDropDown(view);

}

第一次实现的时候遇到了问题,就是弹出框不会在按下Back键的时候消失,点击弹框外区域也没有正常消失,搜索了一下,都说只要设置背景就好了。然后就找了个图片,果然弹框能正常dismiss了(见注释)。

 

3.源码分析

显示提供了两种形式:

①showAtLocation()显示在指定位置,有两个方法重载:

public void showAtLocation(View parent, int gravity, int x, int y) {

    mParentRootView = new WeakReference<>(parent.getRootView());

    showAtLocation(parent.getWindowToken(), gravity, x, y);

}

public void showAtLocation(IBinder token, int gravity, int x, int y)  {

    if(isShowing() || m content view == null) {

        return;

    }

    TransitionManager.endTransitions( mDecorView);

    detachFromAnchor();

    mIsShowing = true;

    mIsDropdown = false;

    mGravity = gravity;

    final WindowManager.LayoutParams p = createPopupLayoutParams(token);

    preparePopup(p);

    p.x = x;

    p.y = y;

    invokePopup(p);

}

②showAsDropDown()显示在一个参照物View的周围,有三个方法重载:

public void showAsDropDown(View anchor) {

    showAsDropDown(anchor, 0, 0);

}

public void showAsDropDown(View anchor, int xoff, int yoff) {

    showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY);

}

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {   

    if(isShowing() || m content view == null) {

        return;

    }

    TransitionManager.endTransitions( mDecorView);

    attachToAnchor(anchor, xoff, yoff, gravity);

    mIsShowing = true;

    mIsDropdown = true;

    final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getApplicationWindowToken());

    preparePopup(p);

   final boolean aboveAnchor = findDropDownPosition( anchor, p, xoff, yoff, p.width, p.height, gravity, mAllowScrollingAnchorParent);

    updateAboveAnchor(aboveAnchor);

    p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;

    invokePopup(p);

}

可以看出来,弹出的方法中首先都需要preparePopup() ,最后再invokePopup() 。

prepare的方法中可以看到有无背景的分别:

 

 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.");

    }

    if (mBackground != null) {

        mBackgroundView =createBackgroundView( mContentView);

        mBackgroundView.setBackground( mBackground);

      } else {

        mBackgroundView = mContentView;

     }

      mDecorView = createDecorView( mBackgroundView);

    mDecorView.setIsRootNamespace(true);

    mBackgroundView.setElevation(mElevation);

    p.setSurfaceInsets(mBackgroundView, true, true);

    mPopupViewInitialLayoutDirectionInherited =(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);

}

private PopupBackgroundView createBackgroundView(View contentView) {

    final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();

    final int height;

    if(layoutParams != null && layoutParams.height == WRAP_CONTENT) {

        height = WRAP_CONTENT;

    } else {

        height = MATCH_PARENT;

    }

    final PopupBackgroundView backgroundView = new PopupBackgroundView(mContent);

    final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(MATCH_PARENT, height);

    BackgroundView.addView(contentView, listParams);

    return backgroundView;

}

背景是否为空对Touch事件的影响:

如果有背景,则会在contentView外面包一层PopupViewContainer之后作为mPopupView,如果没有背景,则直接用contentView作为mPopupView。

而这个PopupViewContainer是一个内部私有类,它继承了FrameLayout,在其中重写了Key和Touch事件的分发处理: 

@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) {

         KeyEvent.DispatcherState state = getKeyDispatcherState();

         if (state != null) {

          state.startTracking(event, this);

         }

         return true;

      } else if (event.getAction() == KeyEvent.ACTION_UP) {

         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);

     }

}

由于PopupView本身并没有重写Key和Touch事件的处理,所以如果没有包这个外层容器类,点击Back键或者外部区域是不会导致弹框消失的。

补充Case: 弹窗不消失,但是事件向下传递

如上所述:

设置了PopupWindow的background,点击Back键或者点击弹窗的外部区域,弹窗就会dismiss。相反,如果不设置PopupWindow的background,那么点击back键和点击弹窗的外部区域,弹窗是不会消失的.

那么,如果我想要一个效果,点击外部区域,弹窗不消失,但是点击事件会向下面的activity传递,比如下面是一个WebView,我想点击里面的链接等.

研究了半天,说是要给Window设置一个Flag,WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL

看了源码,这个Flag的设置与否是由一个叫mNotTouchModal的字段控制,但是设置该字段的set方法被标记为@hide。

所以要通过反射的方法调用: 

 public static void setPopupWindowTouchModal( PopupWindow popupWindow, boolean touchModal) {

     if (null == popupWindow) {

          return;

     }

     Method method;

     try {

        method = PopupWindow.class.getDecl aredMethod("setTouchModal", boolean.class);

       method.setAccessible(true);

       method.invoke(popupWindow, touchModal);

    } catch (Exception e) {

        e.printStackTrace();

    }

}

然后在程序中:

UIUtils.setPopupWindowTouchModal(popupWindow, false);

该popupWindow外部的事件就可以传递给下面的Activity了。

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/872561.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号