源生控件PopupWindow

版权声明:随便转都是学别人的 https://blog.csdn.net/weixin_35691921/article/details/81145018

个人笔记,持续更新

参考与学习:
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);

猜你喜欢

转载自blog.csdn.net/weixin_35691921/article/details/81145018