源生控件PopupWindow
个人笔记,持续更新
参考与学习:
https://blog.csdn.net/u012585964/article/details/52126989
1.理解
PopupWindow 相当于是一种view,但是它直接继承自Object,基于WindowManager实现,挂在于Window ,悬浮在当前activity之上的。
2.popupwindow简单实现
class KeyBoardPopupWindow {
private final String TAG = this.getClass().getName();
private View mLocationView;
private View mContentView;
private Activity mContext;
public KeyBoardPopupWindow(View mLocationView, Activity context) {
this.mLocationView = mLocationView;
this.mContext = context;
initView();
}
private void initView() {
LogUtils.i(TAG, "initView");
mContentView = LayoutInflater.from(mContext).inflate(R.layout.view_key_board, null, false);
mPopupWindow = new PopupWindow(mContentView, WindowManager.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.MATCH_PARENT,
true);
mPopupWindow.setTouchable(true);
mPswText = mContentView.findViewById(R.id.ly_psw_text);
mPopupWindow.setFocusable(true);
mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
mContentView.setFocusableInTouchMode(true);
mPopupWindow.update();
}
public void show() {
if (mPopupWindow != null) {
if (!mPopupWindow.isShowing()) {
LogUtils.i(TAG, "show");
mPopupWindow.showAtLocation(mLocationView, Gravity.NO_GRAVITY, 0, 0);
}
}
}
public void dismiss() {
if (mPopupWindow != null) {
if (mPopupWindow.isShowing()) {
LogUtils.i(TAG, "dismiss");
mPopupWindow.dismiss();
}
}
}
}
3.方法解读
3.1构造方法解读
3.1.1 PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
参数:
三个参数直接的优先级别为:attrs > defStyleAttr > defStyleRes
AttributeSet attrs 这个参数里面存放的是对应的设置的属性值集合.例如:android:layout_width、android:layout_height、app:text、app:num.即layout设置的属性集中获取attrs中的属性;
int defStyleAttr 在Theme中进行指定,在AndroidManifest文件中会为application设置一个主题,从系统主题中获取attrs中的属性
int defStyleRes:从资源文件定义的style中读取attrs中的属性.
//源码
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//创建mWindowManager
//获取资源文件等属性
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
// Preserve default behavior from Gingerbread. If the animation is
// undefined or explicitly specifies the Gingerbread animation style,
// use a sentinel value.
if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
if (animStyle == R.style.Animation_PopupWindow) {
mAnimationStyle = ANIMATION_STYLE_DEFAULT;
} else {
mAnimationStyle = animStyle;
}
} else {
mAnimationStyle = ANIMATION_STYLE_DEFAULT;
}
final Transition enterTransition = getTransition(a.getResourceId(
R.styleable.PopupWindow_popupEnterTransition, 0));
final Transition exitTransition;
if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
exitTransition = getTransition(a.getResourceId(
R.styleable.PopupWindow_popupExitTransition, 0));
} else {
exitTransition = enterTransition == null ? null : enterTransition.clone();
}
a.recycle();//缓存Style中属性
setEnterTransition(enterTransition);//设置进退场动画
setExitTransition(exitTransition);
setBackgroundDrawable(bg);//设置背景,如果有的话
}
3.1.2 PopupWindow(View contentView, int width, int height, boolean focusable)
参数:
contentView 因为PopupWindow没有默认布局,它不会像AlertDialog那样只setTitle,就能弹出来一个框。PopupWindow是没有默认布局的,它的布局只有通过我们自己设置才行。
width:窗体长度
height:窗体宽度
focusable //是否具有获取焦点的能力
//源码
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
mContext = contentView.getContext();
mWindowManager = (WindowManager)
mContext.getSystemService(Context.WINDOW_SERVICE);
}
//构造器参数
setContentView(contentView);
setWidth(width);
setHeight(height);
setFocusable(focusable);
}
3.2 显示方法解读
3.2.1 void showAtLocation(View parent, int gravity, int x, int y)
绝对布局,DecorView大小不影响popupWindow的大小(屏幕不够大除外)
View 提供给PopupWindow,我们的popupWindow是相对的这个View调整显示位置
gravity 相对位置
x,y 微调位置
3.2.2 void showAsDropDown(View anchor, int xoff, int yoff, int gravity)
3.2.2.1 相对布局,DecorView大小会影响opupWindow的大小,popupWindow跟着DecorView尺寸调节
3.2.2.2 如果希望showAsDropDown方法能够在下面空间不足时自动在anchorView的上面弹出,必须在创建PopupWindow的时候指定高度,不能用wrap_content
3.3 void dismiss()
//源码
public void dismiss() {
//只有当对话框正在显示且对话框视图不为空
if (isShowing() && mPopupView != null) {
//重置标志位
mIsShowing = false;
unregisterForScrollChanged();
try {
//从Activity上移除对话框视图
mWindowManager.removeViewImmediate(mPopupView);
} finally {
if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
//移除其子View
((ViewGroup) mPopupView).removeView(mContentView);
}
mPopupView = null;
//设置对话框移除时的监听事件
if (mOnDismissListener != null) {
mOnDismissListener.onDismiss();
}
}
}
}
3.4 setFocusable(boolean focusable)
// 决定弹窗的外部能否响应事件
public void setFocusable(boolean enable) {
mFocusable = enable;
}
3.5 setTouchable(boolean touchable)
用于设置PopupWindow是否响应touch事件,设置为true之后,PopupWindow内容区域 才可以响应点击事件
3.6 setBackgroundDrawable
如果不设置背景,点击PopupWindow最外层布局以及点击返回键PopupWindow不会消失。参数drawable随便一个什么类型的都可以,只要不为空。
原理:mBackground不为空的时候,PopupViewContainer会作为mContentView的Parent,extends FrameLayout ,重写了boolean dispatchKeyEvent(KeyEvent event) 和 boolean onTouchEvent(MotionEvent event)方法,他们分别负责实现了返回键处理逻辑和点击消失逻辑。
加深理解:https://www.cnblogs.com/popfisher/p/5608717.html
3.7 setOutsideTouchable(boolean touchable)
设置touchable为true时,如果点击PopupWindow以外的区域,PopupWindow是否会消失;设置生效的前提是setTouchable(true)和setFocusable(false)
6.0之前的版本Ok
6.0之后的版本不好用,//TODO
3.8 setOnKeyListener(View.OnKeyListener onKeyListener)
设置当前View,响应按键
mContentView.setOnKeyListener(mOnKeyListener);
private View.OnKeyListener mOnKeyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
// TODO Auto-generated method stub
if (keyCode == KeyEvent.KEYCODE_BACK && mPopupWindow.isShowing()) {
mPopupWindow.dismiss();
return true;
}
return false;
}
};
3.9 setFocusableInTouchMode
focusableInTouchMode="true",当软键盘弹出了。点击本View就会先隐藏掉软键盘,但是不会执行View的点击事件中的代码。
3.10 void update()
更新PopupWindow状态,如果当前已是显示状态,就从当前状态更新。
//Source
public void update() {
if (!isShowing() || mContentView == null) {
return;
}
final WindowManager.LayoutParams p = (WindowManager.LayoutParams) mDecorView.getLayoutParams();
boolean update = false;
final int newAnim = computeAnimationResource();
if (newAnim != p.windowAnimations) {
p.windowAnimations = newAnim;
update = true;
}
final int newFlags = computeFlags(p.flags);
if (newFlags != p.flags) {
p.flags = newFlags;
update = true;
}
//7.0新增
final int newGravity = computeGravity();
if (newGravity != p.gravity) {
p.gravity = newGravity;
update = true;
}
if (update) {
setLayoutDirectionFromAnchor();
mWindowManager.updateViewLayout(mDecorView, p);
}
}
3.11 setClippingEnabled(boolean enabled);