PopupWindow的使用原理以及实现卡片效果

前记:

  本人菜鸟一枚,第一次写博客,写得不好。不喜勿喷,有疑问的地方可以留言回复~。

 PopupWindow的使用思路:

  在说明他的相关使用方法之前,我们先理清一下它的使用思路。以方便我们针对性的研究各个问题。

  一· 我们大致会使用PopupWindow的场景:

      1.希望点击某些东西之后,能弹出一个独立的操作区域,以承载更多的内容。

      2.满足一些比较不错的设计效果。

     例如下面将提示删除的地方做成一个卡片的形状,并且再赋给它一个动画,那么它会满足material design的设计风格。一个卡片进入界面时从低端滑倒中端进入,退出时从中端滑倒上端退出。

     PopupWindow的使用原理以及实现卡片效果


  

   二·PopupWindow的使用方法:

       1. PopupWindow的构造方法:

    PopupWindow的使用原理以及实现卡片效果

     这是PopupWindow的第一个构造方法,传入上下文Context。API中这个构造方法里写的是this(context,null);为什么与会有一个null,那是因为这个构造方法,实质上是调用了

    
      从这个构造方法来看,他还是调用了本类中的其他构造方法。在往后追溯呢,实质上俩个参数的构造方法又调用
了三个参数的构造方法。该方法,我就不传图片了,我直接上API里的代码:
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}
其实从这个也能看出,它是调用了四个参数的构造方法,最后发现,其实四个参数的构造方法,才是真正的构造
方法:
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    mContext = context;
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

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

    setEnterTransition(enterTransition);
    setExitTransition(exitTransition);
    setBackgroundDrawable(bg);
}
这个构造方法,我就不一一解释了,因为一些默认属性的设置以及相应初始化方法的步骤。我们会在后面
一点一点的代入进来。

当然PopupWindow
还有一个无参的构造方法,他实质上是调用了三参的构造方法。那么最终也会是调用四参的构造方法:

public PopupWindow() {
    this(null, 0, 0);
}

    以上是传入Context的构造方法,PopupWindow还有一种传入View的构造方法。后面我们会详细解释这个。
 2.介绍完PopupWindow的构造方法,接下来,我们应该要去了解PopupWindow的组成。
    

    PopupWindow实质上是一个比较特殊的组件,Android中的组件基本上都是继承自View或者ViewGroup。而ViewGroup是继承自View
    那么也就是说,Android中的组件都是继承自View。但是PopupWindow是什么都不继承。所以PopupWindow里面没有什么onDraw方法,也没有什么
onSizeChanged之类的方法让我们可以自定义绘制或者更改组件效果的方法。但是我们可以通过setContentView的方法来设置PopupWindow里的内容
,通过
setBackgroundDrawable
(Drawable background)方法来设置PopupWindow的背景。
   实质上从java的GUI编程中去理解它,

PopupWindow是一个容器。BackgroundDrawable就是容器的形状,而ContentView


就是容器里放的内容。


   三.介绍完PopupWindow的构造方法和它的组成,接下来就是介绍PopupWindow的使用了。


    1.使用PopupWindow,先在创建一个PopupWindow的实例,并且将要放入PopupWindow的内容View实例化。
        
       
//        实例化popupWindow
        this.popupWindow = new PopupWindow(mContext);

//        将要放入PopupWindow里的View Inflate出来

         contentview = mInflater.inflate(R.layout.popup_process, null);


      2.接下来就是相应的参数设置了。
通过PopupWindow的setWidth()和setHeight()方法设置PopupWindow的宽高。你可以设置直接的数值,也可以
设置MATCH_PARENT或者WRAP_CONTENT;

//                通过LayoutParams的内质静态变量来设置宽高
                popupWindow.setWidth(FrameLayout.LayoutParams.MATCH_PARENT);
//                通过自己赋相应的值进去
                popupWindow.setHeight((int) TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_DIP,
                        80,
                        mContext.getResources().getDisplayMetrics()));
当然你还需要设置
PopupWindow的contentView,这也是非常重要的。至于设置contentView的宽高,可以通过很多种
方式:
    第一种是在XML文件中设置你inflate进来的布局文件的宽高
    第二种是通过将inflate出来的View的Params属性修改一下
接下来就是设置
PopupWindow的外框背景了,你可以放一个ColorDrawable,或者从res文件中获取一个Drawable文件

                popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

 
     3.接下来是contentView的一些内容:
前面提到的PopupWindow的构造方法中,最后提了一句,
PopupWindow的构造方法中拥有这样一些构造方法:
public PopupWindow(View contentView) {
    this(contentView, 0, 0);
}
这里我就不要一一赘述了,因为它的构造方法虽然多,但是最后还是调用很多参数的那个构造方法。
    这里我提一下,我们知道
PopupWindow的构造方法中必须含有Context,那么这种构造方法的
Context是从哪里来的呢?
    其实这样的构造方法,Context是来自于contentView里的Context;
    同时我们也可以一步到位创建PopupWindow,就是调用
PopupWindow(View contentView,int width,
int Hegiht);直接将
PopupWindow的内容View和PupupWindow的宽高设置好。

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

    4.
PopupWindow里的事件监听:
    前面提到过PopupWindow的使用场景了。我们在使用场景中肯定会去监听一些事件,例如监听Button,EditText
等组件的相应事件。所以这里我们看看
PopupWindow中该如何实现相应的监听
    第一个,我们如果要监听ContentView中的内容,我们必须要设置

       popupWindow.setFocusable(true);
如果不设置这个Focusable为True,那么用户点击contentView上的相应组件,会是无效的。甚至连EditText
的焦点获取都会失效。
    如果我们要监听相应的组件。必须要先将相应的组件实例化出来,一定要通过contentView(你放在PoupWindow
里的View)
去调用findViewById()方法

TextView delete =  (TextView) contentview.findViewById(R.id.popup_delete);
TextView back =  (TextView) contentview.findViewById(R.id.popup_back);
TextView Content =  (TextView) contentview.findViewById(R.id.popup_content);
接下来就是常规操作了。什么SetOnclickListener,这里就不赘述了

      其次,还有一个叫做
setOutsideTouchable()方法,这个方法的意思是设置除了ContenView以外的区域是否
可以Touch。
    如果说,你想用户点击其他区域时,让PopupWindow消失。必须要这样写才会生效:
popupWindow.setOutsideTouchable(true);
contentview.setOnKeyListener(new View.OnKeyListener() {
    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            popupWindow.dismiss();
            return true;
        }
        return false;
    }
});
    5.PopupWindow的显示位置:
    PopupWindow最大的优势是在于,PopupWindow可以在任意位置显示,这是一个比较易控制的特殊组件。
    它的显示方法有一下几种:
     第一个方法:showAtLocation()的意思,是在指定View的区域显示,第一个参数的意思是被指定的View,
第二个参数的意思是相对于这个View的位置,例如Gravity.CENTER的意思就是PupopWindow相对于View的中间显示
,剩下俩个参数是相对组件Gravity设置基点之后的X,Y偏移量
        剩下的三个方法,可以看出来,基本上类似的。我们从API源码来看区别:
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() || mContentView == null) {
        return;
    }

    TransitionManager.endTransitions(mDecorView);

    attachToAnchor(anchor, xoff, yoff, gravity);

    mIsShowing = true;
    mIsDropdown = true;

    final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
    preparePopup(p);

    final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
            p.width, p.height, gravity);
    updateAboveAnchor(aboveAnchor);
    p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;

    invokePopup(p);
}

 通过源码API来看,最后一种才是真正的实际上去操作的方法。我们就直接解释最后一个:
    showAsDropDown()方法的意思是,位于某个View的下面显示,第一个是被附着的View,第二个和第三个是X和Y
轴的偏移量。第四个是Gravity,它的意思是相对于被附着的View的位置。


   四.暂时就写这么多了。过些天在写关于实现卡片效果的PopupWindow的博客。第一次写博客,感觉很多东西讲
不完,因为我要把一个东西讲细,要延伸好多其它的知识点= =。感觉自己写的不是很好,有不对的地方或者疑问的
地方,可以直接发邮件给我
   我的邮箱是:[email protected]
    欢迎邮件我~