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_popupwindow(popupwindow
样式 )
<?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) 的兼容性问题