Dialog的不完全事件分发机制

        说是不完全,一是觉得自己语言表述不完美,二是觉得没有方方面面都分析到。

        首先我们知道以下知识:
        setCancelable(false); dialog弹出后会点击屏幕或物理返回键,dialog不消失
        setCanceledOnTouchOutside(false); dialog弹出后点击屏幕,dialog不消失,点击物理返回键dialog消失
        看了上面的知识后,我们得问凭啥你说不消失就不消失,你说消失就消失?毫无疑问,答案肯定在Dialog的事件分发机制里面。


/**
 * Sets whether this dialog is cancelable with the
 * {@link KeyEvent#KEYCODE_BACK BACK} key.
 */
public void setCancelable(boolean flag) {
    mCancelable = flag;
}

/**
 * Sets whether this dialog is canceled when touched outside the window's
 * bounds. If setting to true, the dialog is set to be cancelable if not
 * already set.
 *
 * @param cancel Whether the dialog should be canceled when touched outside
 *            the window.
 */
public void setCanceledOnTouchOutside(boolean cancel) {
    if (cancel && !mCancelable) {
        mCancelable = true;
    }

    mWindow.setCloseOnTouchOutside(cancel);
}

        上面的两个方法都涉及到mCancelable,而onTouchEvent恰好也有这个变量,应该不能说恰好,应该说必然。

        类似于Activity的dispatchTouchEvent方法,Dialog也有一个window窗口对象,窗口里面肯定也有DecorView这个根View。
        从下面Dialog的dispatchTouchEvent方法看,要么DecorView可以消费掉这个触摸事件,不然就得给Dialog的onTouchEvent方法处理。

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mWindow.superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}


        当按下物理返回键时,执行onBackPressed方法,该方法会进行判断mCancelable的真假,当mCancelable为假时,就不做任何处理了。而mCancelable为真时,继续执行cancel方法,取消dialog的显示。默认创建的dialog对象时,mCancelable为真。下面的代码换言之,按下物理返回键,是否取消Dialog的显示,淹看mCancelable是否为真。

/**
 * Called when the dialog has detected the user's press of the back
 * key.  The default implementation simply cancels the dialog (only if
 * it is cancelable), but you can override this to do whatever you want.
 */
// 物理返回键的回调方法
public void onBackPressed() {
    if (mCancelable) {
        cancel();// 取消dialog的显示
    }
}

        我们可以看一下cancel这个方法究竟干了什么。
        可以看到,Dialog消失是肯定的,但是当mCanceled为假(mCanceled默认就是假)且mCancelMessage不为null时,可以发送出去一个消息,用于对此情况的处理,开发者可根据需要自由设置逻辑。

/**
 * Cancel the dialog.  This is essentially the same as calling {@link #dismiss()}, but it will
 * also call your {@link DialogInterface.OnCancelListener} (if registered).
 */
public void cancel() {
    if (!mCanceled && mCancelMessage != null) {
        mCanceled = true;
        // Obtain a new message so this dialog can be re-used
        // 发送给对应handler一个mCancelMessage消息,告诉它在mCanceled为假的情况下,dialog依然取消显示了
        // 可以在对应handler的handlemessage中根据该消息进行一些逻辑设置
        // 通过setCancelMessage方法可以传入mCancelMessage
        // 如果mCanceled为真,说明Dialog是允许取消显示的,也就不需要发送消息了
        Message.obtain(mCancelMessage).sendToTarget();
    }
    dismiss();
}

/**
 * Set a message to be sent when the dialog is canceled.
 * @param msg The msg to send when the dialog is canceled.
 * @see #setOnCancelListener(android.content.DialogInterface.OnCancelListener)
 */
public void setCancelMessage(final Message msg) {
    mCancelMessage = msg;
}


        在onTouchEvent方法中,需要满足3个条件,才会执行cancel方法,取消显示。
        (1)mCancelable为真
        (2)mShowing为真
        (3)mWindow.shouldCloseOnTouch(mContext, event)为真
        也就是说,当setCancelable(false)后,点击Dialog窗口外面位置,Dialog也不会消失。到此,setCancelable(false)的原因已经解释清楚了。

/**
 * Called when a touch screen event was not handled by any of the views
 * under it. This is most useful to process touch events that happen outside
 * of your window bounds, where there is no view to receive it.
 *
 * @param event The touch screen event being processed.
 * @return Return true if you have consumed the event, false if you haven't.
 *         The default implementation will cancel the dialog when a touch
 *         happens outside of the window bounds.
 */
public boolean onTouchEvent(MotionEvent event) {
    if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) {
        cancel();
        return true;
    }

    return false;
}

        还没结束,setCanceledOnTouchOutside(false)还没分析完呢。
        接着上面的代码setCanceledOnTouchOutside源码看,执行mWindow.setCloseOnTouchOutside方法。在Window.java中的源码如下,主要就是设置了一个标志位。因为在mWindow.shouldCloseOnTouch方法中要用到这个标志位。

/** @hide */
public void setCloseOnTouchOutside(boolean close) {
    mCloseOnTouchOutside = close;
    mSetCloseOnTouchOutside = true;
}

        接着看mWindow.shouldCloseOnTouch方法都干了啥。

/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    // 核心就是判断
    // 判断mCloseOnTouchOutside标记是否为ACTION_DOWN事件
    // 同时判断event的x,y坐标是不是超出Bounds
    // 然后检查FrameLayout的content的id的DecorView是否为空
    // 此处的处理对于window边界之外的Touch事件有判断价值
    if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
            && isOutOfBounds(context, event) && peekDecorView() != null) {
        return true;
    }
    return false;
}

        当setCanceledOnTouchOutside(false)后,mCloseOnTouchOutside也就是false,那么在Dialog的onTouchEvent方法里,也就因为不满足条件,从而不执行cancel了。
        上面代码中要mWindow.shouldCloseOnTouch返回true,要满足四个条件:
        (1)mCloseOnTouchOutside为真
        (2)DOWN事件
        (3)isOutOfBounds返回真
        (4)peekDecorView()不为null
        要isOutOfBounds返回真,则DOWN事件的触摸点在Dialog的外面。

// 判断是否超出DecorView的范围
private boolean isOutOfBounds(Context context, MotionEvent event) {
    final int x = (int) event.getX();
    final int y = (int) event.getY();
    final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
    final View decorView = getDecorView();
    return (x < -slop) || (y < -slop)
            || (x > (decorView.getWidth()+slop))
            || (y > (decorView.getHeight()+slop));
}

        综上所述,
        Dialog的onTouchEvent方法是针对在Dialog窗口外的触摸点的事件处理,即是否要取消Dialog的显示。
        setCancelable(false)是让Dialog类里的一个标志位为false,onTouchEvent和onBackPressed都会对该标志位进行真假判断,如果为假,就不会执行cancel方法;
        setCanceledOnTouchOutside(false)是让Dialog所对应的window窗口里的一个标志位为false,onTouchEvent也会对该window窗口的这个标志位进行判断,如果为false,则不会执行cancel方法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值