Android进阶之路 - popupWindow使用详解

Behind every successful man there’s a lot of unsuccessful years.

花式弹框兄弟篇

popupWindow是一款较常用的弹框控件,他主要不同于Dialog一样的刻板内容、样式,我们可以填充自己喜欢的样式,相对而言比较灵活;

在使用中遮盖层是个不大不小的问题,本文已经解决,同时还有一些其他问题的解决方式,也都在代码中进行了注释,大家请直接往下看吧(PS:最后又惊喜,或许你想要的答案就在最后哦)

实现效果

2018年 在文末补入通用的自定义popupWindow(较初级写法,但是相对比较在项目中实用)

Effect

这里写图片描述

补入的Effect

Effect 1 :

这里写图片描述

Effect 2 :

这里写图片描述

效果实践

Tips

//popupWindow的两种显示方式:

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)

以下三种实现方式相对应上方的三种效果

基础使用

MainActivity

package com.example.dow.popupwindow;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private PopupWindow popupWindow;
    private TextView mBtn;
    private TextView mContent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBtn = (TextView) findViewById(R.id.btn);
        mContent =(TextView)  findViewById(R.id.content);

        mBtn.setOnClickListener(new View.OnClickListener() {
            private PopupWindow popupWindow;

            @Override
            public void onClick(View view) {
                // 获取自定义布局文件activity_popupwindow_left.xml的视图
                View popupWindow_view = getLayoutInflater().inflate(R.layout.item_popupwindow, null,false);

                //这一点非常重要,因为大多时候我们会因为遮盖层而苦恼
                backgroundAlpha(0.4f);
                TextView m1 = (TextView) popupWindow_view.findViewById(R.id.item_one);
                TextView m2 = (TextView) popupWindow_view.findViewById(R.id.item_two);
                TextView m3 = (TextView) popupWindow_view.findViewById(R.id.item_three);
                m1.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getApplicationContext(),"item-one",Toast.LENGTH_LONG).show();
                        mContent.setText("item-one");
                        //1.0就是遮盖层的取消,或者说为我们原来的背景颜色
                        backgroundAlpha(1.0f);
                        popupWindow.dismiss();
                    }
                });
                m2.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getApplicationContext(),"item-two",Toast.LENGTH_LONG).show();
                        mContent.setText("item-two");
                        //1.0就是遮盖层的取消,或者说为我们原来的背景颜色
                        backgroundAlpha(1.0f);
                            popupWindow.dismiss();
                    }
                });
                m3.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getApplicationContext(),"item-three",Toast.LENGTH_LONG).show();
                        mContent.setText("item-three");
                        //1.0就是遮盖层的取消,或者说为我们原来的背景颜色
                        backgroundAlpha(1.0f);
                        popupWindow.dismiss();

                    }
                });

                // 创建PopupWindow实例,200,LayoutParams.MATCH_PARENT分别是宽度和高度
                popupWindow =  new PopupWindow(popupWindow_view, 200, ViewGroup.LayoutParams.WRAP_CONTENT, true);
                // 设置动画效果
                popupWindow.setAnimationStyle(R.style.popup_window_anim);
                //设置可以获取焦点
                popupWindow.setFocusable(true);
                //设置可以触摸弹出框以外的区域
                popupWindow.setOutsideTouchable(true);
                //放在具体控件下方
				//popupWindow.showAsDropDown(mBtn,Gravity.CENTER,Gravity.RIGHT);
                // 这里是位置显示方式,在屏幕的侧
                popupWindow.showAtLocation(view, Gravity.RIGHT, 400, 100);
		       // 点击其他地方消失  
			   //!!重要注意-如果这里写完依旧无效!那么按照下面“常见问题”的第一条重新编写此处代码!!
                popupWindow_view.setOnTouchListener(new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        if (popupWindow != null && popupWindow.isShowing()) {
                            popupWindow.dismiss();
                            
                            backgroundAlpha(1.0f);
                            popupWindow = null;
                        }
                        return false;
                    }
                });
            }
        });
    }

    /**
     * 添加遮盖层的方法
     */
    private void backgroundAlpha(float f){
        WindowManager.LayoutParams lp=this.getWindow().getAttributes();
        lp.alpha=f;
        this.getWindow().setAttributes(lp);
    }
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.dow.popupwindow.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:layout_height="wrap_content"
        android:id="@+id/btn"
        android:gravity="center"
        android:padding="5dp"
        android:text="PopupWindow" />
        
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/content"
        android:gravity="center"
        android:padding="5dp"/>

</LinearLayout>

item_popupwindowpopupwindow样式 )

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:background="#f00"
    android:layout_height="wrap_content">
    
    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:id="@+id/item_one"
    android:padding="8dp"
    android:textColor="#fff"
    android:text="item1"
    />
    
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:id="@+id/item_two"
        android:padding="8dp"
        android:textColor="#fff"
        android:text="item2"/>
        
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:id="@+id/item_three"
        android:padding="8dp"
        android:textColor="#fff"
        android:text="item3"/>
</LinearLayout>

Effect1 实践

当年写的小工具,回头看来写的复杂化了,确实有点鸡肋

主要简单封装了一个工具类,不过有点鸡肋,只减少了一些创建的重复性代码,我目前主要用在了我当前项目的版本升级这里

VersionView (自定义类)

package com.bakheet.garage.home.view;

import android.app.Activity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.PopupWindow;

/**
 * @author yongliu
 * date  2018/4/4.
 * desc:
 */

public class VersionView extends PopupWindow {
    private int layout;
    private Activity mContext;
    private PopupWindow popupWindow;

    public VersionView(Activity context, int layout) {
        this.mContext = context;
        this.layout = layout;
    }

    /**
     * 进行popupWindow配置,监听填充布局与popWin实例
     * */
    public void setView(ViewGroup parent, final WindowGet windowGet, final ViewGet viewGet) {
        View view = getView();
        // 创建PopupWindow实例
        popupWindow = new PopupWindow(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
        // 设置动画效果
        //popupWindow.setAnimationStyle(R.style.popup_window_anim);
        //设置可以获取焦点
        popupWindow.setFocusable(true);
        //设置可以触摸弹出框以外的区域
        popupWindow.setOutsideTouchable(true);
        //放在具体控件下方
        // popupWindow.showAsDropDown(mBtn,Gravity.CENTER,Gravity.RIGHT);
        // 这里是位置显示方式,在屏幕的侧
        popupWindow.showAtLocation(parent, Gravity.CENTER, 0, 0);
        //设置点击外部的监听
        popupWindow.setOnDismissListener(new popupDismissListener());
        //获取内部填充的View
        viewGet.viewGet(view);
        //获取内部的popupWindow控制器
        windowGet.windowGet(popupWindow);
    }

    /**
     * 获取内部填充View
     */
    public interface ViewGet {
        void viewGet(View view);
    }

    /**
     * 获取内部的popWindow
     */
    public interface WindowGet {
        void windowGet(PopupWindow windowGet);
    }

    /**
     * 填充需求布局
     */
    public View getView() {
        View view = mContext.getLayoutInflater().inflate(layout, null);
        return view;
    }

    /**
     * 点击外部监听
     */
    class popupDismissListener implements PopupWindow.OnDismissListener {
        @Override
        public void onDismiss() {
            backgroundAlpha(1f);
        }
    }

    /**
     * 添加遮盖层的方法
     */
    private void backgroundAlpha(float f) {
        WindowManager.LayoutParams lp = mContext.getWindow().getAttributes();
        lp.alpha = f;
        mContext.getWindow().setAttributes(lp);
    }
}

使用方式(以点击事件,作为示例)

注意 :在 windowGet 对 全局变量 popupWindow 进行赋值,不然无法调用 popupWindow 属性

    public PopupWindow popupWindow;
    mBtn.setOnClickListener(new View.OnClickListener() {
           @Override
          public void onClick(View view) {
				//设置背景变灰
				 backgroundAlpha(0.6f);
				 //获取实例,传入我们的Layout ,mParent 代表的是当前布局的最外层,也就是我们当前所依赖的布局
                final VersionView versionView = new VersionView(this, R.layout.popup_version_updata);
                versionView.setView(mParent, new VersionView.WindowGet() {
                    @Override
                    public void windowGet(PopupWindow windowGet) {
	                    //重新对popupWindow赋值
                        popupWindow = windowGet;
                    }
                }, new VersionView.ViewGet() {
                    @Override
                    public void viewGet(View view) {
                    //获取我们填充布局的View
                        TextView mUpdate = (TextView) view.findViewById(R.id.tv_version_update);
                        mUpdate.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View view) {
                                ToastUtils.shortShow("升级");
                                backgroundAlpha(1f);
                                popupWindow.dismiss();
                            }
                        });

                        ImageView mImg = (ImageView) view.findViewById(R.id.iv_version_close);
                        mImg.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View view) {
                                ToastUtils.shortShow("关闭");
                                backgroundAlpha(1f);
                                popupWindow.dismiss();
                            }
                        });
                    }
                });
         }
	});

Effect2 实践

使用方式(贴出部分关键代码,这里是 adapter 的长按监听,当然你也可以把他们写在点击事件下,无关紧要)

carAdapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
            @Override
            public boolean onItemChildLongClick(BaseQuickAdapter adapter, final View view, final int position) {
				//获取到当前listview对应position的view  上面第二个参数已经传进行来了
                view.setBackgroundColor(getResources().getColor(R.color.white2));
                //填充布局
                final View popup_view = getLayoutInflater().inflate(R.layout.item_delete_car, null, false);
		        //popup_view 内的某个空间id,无关紧要
                LinearLayout mView = popup_view.findViewById(R.id.item_dealer_parent);
                mView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
	                   Toast.makeText(this,"你别听了,我已经看到了",Toast.LENGTH_SHORT).show();
                        popupWindow.dismiss();
                    }
                });

                // 创建PopupWindow实例,200,LayoutParams.MATCH_PARENT分别是宽度和高度
                popupWindow = new PopupWindow(popup_view, 300, ViewGroup.LayoutParams.WRAP_CONTENT, true);
                //设置可以获取焦点
                popupWindow.setFocusable(true);
                //设置可以触摸弹出框以外的区域
                popupWindow.setOutsideTouchable(true);
                //注意点:获取屏幕宽度
                WindowManager systemService = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
                //注意点:相当于中间
                int xpos = systemService.getDefaultDisplay().getWidth() / 2 - popupWindow.getWidth() / 2;
                //注意点:放在具体控件下方  这里的x,y坐标是小关键
                popupWindow.showAsDropDown(view, xpos, -50);
                // 点击其他地方消失
                popupWindow.setOnDismissListener(new popupDismissListener(view));
                return true;
            }
        });

外部监听

	/**
     * 点击外部监听
     */
    class popupDismissListener implements PopupWindow.OnDismissListener {
        private View mView;
        public popupDismissListener(View view) {
            this.mView = view;
        }

        @Override
        public void onDismiss() {
            mView.setBackgroundColor(getResources().getColor(R.color.white));
        }
    }

注意事项:

  • 我们的需求是操作对应的position下的view,让我们的 popupWiondow位于其下!
  • 因为需要位于其他的居中位置,同时偏上一些,所以我们代码中用到屏幕一半的距离来定位x坐标,采用-50来实现y坐标上移
  • 因为有背景变色的原因,我这里是在popupWindow触发的时候设置先变为灰色,之后通过外部监听改变背景色,在这里记得要以成员方法的方式把position对应的View传入,不然比较麻烦(个人感觉)

常见问题

经我发现Android6.0之后,背景层可能面临无法取消的问题,因此多了一个回调的机制 popupDismissListener,查询多篇文章之后找到的解决方法,可借鉴 此篇文章 解决现有问题!

点击外部关闭popupWindow,同时切换背景为白色

步骤 1:删除上面代码中的以下代码

 popupWindow_view.setOnTouchListener(new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        if (popupWindow != null && popupWindow.isShowing()) {
                            popupWindow.dismiss();
                            
                            backgroundAlpha(1.0f);
                            popupWindow = null;
                        }
                        return false;
                    }
                });

步骤 2:外部添加一个方法(可直接Copy)

class popupDismissListener implements PopupWindow.OnDismissListener{
    @Override
    public void onDismiss() {
      // TODO Auto-generated method stub
      //Log.v("List_noteTypeActivity:", "我是关闭事件");
      backgroundAlpha(1f);
    }
}

步骤 3:在之前我们删除代码的地方,加入以下代码:

 popupWindow_view.setOnDismissListener(new popupDismissListener());

至于出现这样的原因,我一直没去仔细查看,大家先处理了当前问题在说把,知道原因的也顺带告诉以下我哈!谢谢!

如点击外部,希望不关闭当前的popupWindow,请尝试以下方法

设置双重失焦

 //设置可以获取焦点
 popupWindow.setFocusable(false);
 //设置可以触摸弹出框以外的区域
 popupWindow.setOutsideTouchable(false);

弹框出入场动画效果

code 中的 anim

这个需要写在style内 

 <!-- pupupwindow的弹出和消失动画 -->
    <style name="popup_window_anim">
        <item name="android:windowEnterAnimation">@anim/in</item>
        <item name="android:windowExitAnimation">@anim/out</item>
    </style>
  • anim in
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="1000"
        android:fromAlpha="0.01"
        android:toAlpha="1" />
</set>
  • anim out
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="1000"
        android:fromAlpha="1"
        android:toAlpha="0.01" />
</set>

PopupWindow 当宽设为MATCH_PARENT时 不能铺满全屏 ,或设置底对齐显示时 距底边框有一小段距离

解决方案

 setBackgroundDrawable(null)  

弹出 popwindow 就抢占 edittext 的焦点

原因:popwindow 没有焦点时 , android device monitor 获取的 view 树是不含有 popwindow 的 view的 ,当 pop有焦点时, 获取的 view 树仅含有 pop的 view

解决方案

 //这样弹出的 popwindow 就不会抢占 edittext 的焦点了, 而且 popwindow 上的按钮也可以点
 popwindow -> setFocusable(false);

popup弹出时为满屏状态,但是没有遮盖住状态栏

解决方案:

 //设置此条属性
 popupWindow.setClippingEnabled(false);

popwindow已经dismiss关闭,但是软键盘没有自动收回

解决方案(主要部分)

 /**
  *清单文件内找到当前的Activity,并加入下面这行代码
 */
  android:windowSoftInputMode="adjustResize"

popupWindow配置(次要部分):Activity类中我配置了键盘的Mode

 popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
 popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

刚进入某个Activity时,在onCreate中弹出popup,报出 android.view.WindowManager$BadTokenException 错误!

原因:主依赖的activity视图没有渲染完毕,但是我们已经设置了popup的依赖展示范围,此时指向的是一个NULL的状态!

解决思想:等Activity的视图渲染完毕,在弹出popupWindow

解决方案:

//重写此方法
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    //在这里写popupWindow的代码既可~~~~
}

Android 7.0 PopupWindow弹出位置的适配问题

Android 7.0 PopupWindow弹出位置的适配问题

可以直接在设置位置的地方:

if (Build.VERSION.SDK_INT < 24) {
    popWindow.showAsDropDown(parent,0,60);
} else {
    int[] a = new int[2];
    parent.getLocationInWindow(a);
    popWindow.showAtLocation(getWindow().getDecorView(), Gravity.NO_GRAVITY, 0, parent.getHeight()+a[1]+60);
    popWindow.update();
}

关于此问题的处理方式网上有很多blog,基本都大同小异,如果相学习查看其它解决方案也可以查看 PopupWindow在Android7.0和7.1系统上显示位置不正确的问题解决,如果想要详细了解的话也可以查看PopupWindow 在 Android N(7.0) 的兼容性问题

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
PopupWindow是一种可以在当前界面上方显示的弹出窗口,通常用于显示一些额外的信息或者提供用户操作的选项。在Android中,可以使用PopupWindow类来创建弹出窗口。 以下是使用PopupWindow的一般步骤: 1. 创建PopupWindow对象:使用PopupWindow的构造函数创建一个PopupWindow对象。 2. 设置PopupWindow的属性:设置PopupWindow的大小、位置、背景等属性。 3. 设置PopupWindow的内容视图:使用setContentView方法设置PopupWindow的内容视图,这可以是一个布局文件或者一个View对象。 4. 显示PopupWindow使用showAsDropDown、showAtLocation等方法显示PopupWindow。 5. 处理PopupWindow的事件:设置PopupWindow的监听器,处理PopupWindow的各种事件。 以下是一个简单的例子,展示如何使用PopupWindow: ``` // 创建PopupWindow对象 PopupWindow popupWindow = new PopupWindow(context); // 设置PopupWindow的属性 popupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT); popupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); popupWindow.setFocusable(true); // 设置PopupWindow的内容视图 View contentView = LayoutInflater.from(context).inflate(R.layout.popup_layout, null); popupWindow.setContentView(contentView); // 显示PopupWindow popupWindow.showAsDropDown(anchorView); // 处理PopupWindow的事件 contentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 处理点击事件 popupWindow.dismiss(); } }); ``` 在上面的代码中,我们创建了一个PopupWindow对象,并设置了宽高、背景等属性。然后,我们使用LayoutInflater加载了一个布局文件作为PopupWindow的内容视图,并使用setContentView方法设置了PopupWindow的内容视图。最后,我们使用showAsDropDown方法显示了PopupWindow,并设置了一个点击事件处理器来处理用户点击PopupWindow的事件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值