IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android PopupWindow -> 正文阅读

[移动开发]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的方法中可以看到有无背景的分别:

? /**Prepare the popup by embedding in into a new ViewGroup if the?background drawable is not null. If embedding is required, the layout?parameters' height is mnodified to take into account the background's?padding.

? * @param p the layout parameters of the popup's content view

? */

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

}

/*Wrap a content view in a PopupViewContainer.

@params contentView : the content view to wrap

@return: a PopupViewContainer that wraps the content view*/

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。

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

/**

? * Set whether this window is touch modal or if outside touches will be sent to other windows behind it.

? */

?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了。

?

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-10 12:02:43  更:2022-05-10 12:04:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 1:21:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码