android ontimeset,Android 4.x(API 19) TimePickerDialog 中 onTimeSet 回调问题

1.问题发现

什么!!!都0202年的了,竟然还有人在用4.x的系统。

是的这两天接手了一个项目,需要设置一个起止时间,想到系统已有的轮子TimePickerDialog,

TimePickerDialog(Context context, OnTimeSetListener listener, int hourOfDay, int minute,boolean is24HourView)

一行代码搞定还蛮简单,接着运行到设备看了下效果:

b4223c137760

nobtn.png

竟然没有确定&取消按钮,而是直接在dialog消失时候回调到OnTimeSetListener#onTimeSet方法,虽然可以接收到设置的时间信息。显然这样不太符合常理,不过添加按钮也是有API的

TimePickerDialog#setButton(int whichButton, CharSequence text, OnClickListener listener)

另外有几个重载的方法就不详细介绍了,接着再看下效果:

b4223c137760

hasbtn1.png

还算可以,但是没有想到点击 确定|取消|Dialog外部 都可以接收到OnTimeSetListener#onTimeSet的回调,我擦这也不合理,不应该啊!这是什么原因呢?于是去查看了下4.4(19)的源码

TimePickerDialog.java

private void tryNotifyTimeSet() {

if (mCallback != null) {

mTimePicker.clearFocus();

mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),

mTimePicker.getCurrentMinute());//mCallback就是传进去的OnTimeSetListener

}

}

@Override

protected void onStop() {

tryNotifyTimeSet();

super.onStop();

}

原因很简单,只要dialog消失就调用回调,于是去网上查了下,一致都说是系统版本的bug,那好吧,这个也好办新建一个类继承TimePickerDialog然后重写onStop方法,大致是这样

MyTimePickerDialog.java

@Override

protected void onStop() {

// super.onStop();

Log.d(TAG, "onStop: MyTimePicker");

//android4.1和4.2存在的一个bug,点击确定和取消按钮时,会出发onTimeSet;

//在dialog的onStop(比如dialog dismiss时)中,也调用了onTimeSet方法

}

很好,继续运行测试效果,震惊竟然 点击 确定|取消|Dialog外部 都不能接收到OnTimeSetListener#onTimeSet的回调,翻车!!!

这个问题很明显就是setButton对应的没有起到作用的问题,继续查看源码

AlertDialog.java

private AlertController mAlert;

public void setButton(int whichButton, CharSequence text, OnClickListener listener) {

mAlert.setButton(whichButton, text, listener, null);//msg是null

}

//调用到下边setButton

AlertController.java

public void setButton(int whichButton, CharSequence text,

DialogInterface.OnClickListener listener, Message msg) {

if (msg == null && listener != null) {

msg = mHandler.obtainMessage(whichButton, listener); //listener在这里消失了,包装到msg

}

switch (whichButton) { //根据按钮的类型对不同属性赋值

case DialogInterface.BUTTON_POSITIVE:

mButtonPositiveText = text;

mButtonPositiveMessage = msg;

break;

case DialogInterface.BUTTON_NEGATIVE:

mButtonNegativeText = text;

mButtonNegativeMessage = msg;

break;

case DialogInterface.BUTTON_NEUTRAL:

mButtonNeutralText = text;

mButtonNeutralMessage = msg;

break;

default:

throw new IllegalArgumentException("Button does not exist");

}

}

由于我用的setButton方法没有传入msg参数,所以 (msg == null && listener != null) == true ,不管怎样接下来就是赋值给

mButtonPositiveMessage、mButtonNegativeMessage 、mButtonNeutralMessage(以下简称msg) 三个中的一个,

Android开发肯定都了解Handler机制,了解Handler机制的肯定都知道 msg 最后处理一般都在mHandler类中,那么接下来就是看看

1.这个mHandler在哪里?

2.以及什么什么时候发送的这个msg?

3.在mHandler里边又对msg做了什么操作?

AlertController.java 的内部类

//问题1 :接着看源码可以找到mHandler就是ButtonHandler

private static final class ButtonHandler extends Handler {

// Button clicks have Message.what as the BUTTON{1,2,3} constant

private static final int MSG_DISMISS_DIALOG = 1;

private WeakReference mDialog;

public ButtonHandler(DialogInterface dialog) {

mDialog = new WeakReference<>(dialog);

}

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

//这里三个类型都是同一个代码

//问题三3. 都是调用了Button设置的点击监听,即我们setButton传入的对象

case DialogInterface.BUTTON_POSITIVE:

case DialogInterface.BUTTON_NEGATIVE:

case DialogInterface.BUTTON_NEUTRAL:

((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);

break;

case MSG_DISMISS_DIALOG:

((DialogInterface) msg.obj).dismiss();

}

}

}

那么什么时候发送的这个msg呢?

AlertController.java

private final View.OnClickListener mButtonHandler = new View.OnClickListener() {

@Override

public void onClick(View v) {

final Message m;

if (v == mButtonPositive && mButtonPositiveMessage != null) {

m = Message.obtain(mButtonPositiveMessage); //在这里赋值的

} else if (v == mButtonNegative && mButtonNegativeMessage != null) {

m = Message.obtain(mButtonNegativeMessage); //在这里赋值的

} else if (v == mButtonNeutral && mButtonNeutralMessage != null) {

m = Message.obtain(mButtonNeutralMessage); //在这里赋值的

} else {

m = null;

}

if (m != null) {

m.sendToTarget(); //问题2:在这里发送的

}:

// Post a message so we dismiss after the above handlers are executed

mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialog)

.sendToTarget();

}

};

那么问题又来了mButtonHandler 这又是啥用的,可以看出来是一个View.OnClickListener对象,那无疑就是给确认&取消按钮设置的监听。

到这里可能有点乱懵逼。那就重新理一下思路,我们通过setButton给TimePickerDialog 设置不同的按钮,但其实我们并没有传Button对象进去,只是设置了一些属性、回调,其实在TimePickerDialog创建时已经创建了确认、取消等按钮并为其设置了监听即mButtonHandler,然后通过点击按钮再调用我们传进去的DialogInterface.OnClickListener#onClick() 绕了一圈又回来了,

2.解决问题

问题回溯:我们自定义MyTimePickerDialog并重写onStop方法

@Override

protected void onStop() {

// super.onStop();

Log.d(TAG, "onStop: MyTimePicker");

//android4.1和4.2存在的一个bug,点击确定和取消按钮时,会出发onTimeSet;在dialog的onStop(比如dialog dismiss时)中,也调用了onTimeSet方法

}

这个时候我们通过setButton设置按钮,并设置监听回调,但是这个时候不能接收onTimeSet回调更新时间了。

解决方法有三个:

1.在setButton的监听回调中手动调用父类TimePickerDialog.onClick方法;

2.通过反射父类私有的TimePickerDialog.tryNotifyTimeSet方法,并调用;

timePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, "确定", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

Log.d(TAG, "showTimeSettingDialog#onClick:api " + Build.VERSION.SDK_INT);

if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { //若适配其他系统版本请看源码

timePickerDialog.onClick(dialog, which); //方法一

}

// try {//这里是解决4.4源码bug ,具体原因请看源码 方法二

// Class clz = Class.forName("android.app.TimePickerDialog");

// Method tryNotifyTimeSetMethod = clz.getDeclaredMethod("tryNotifyTimeSet",null);

// tryNotifyTimeSetMethod.setAccessible(true);

// tryNotifyTimeSetMethod.invoke(timePickerDialog,null);

// } catch (Exception e) {

// Log.i(TAG, "showTimeSettingDialog#onClick: occour an exception:" + e.getMessage());

// e.printStackTrace();

// }

}

});

3.第三种不同上边,不需要自定义MyTimePickerDialog类,重写其onStop方法,这个情况下我们点击确定、取消、dialog边缘取消 都会接收到onTimeSet的回调,我们只需要定义一个布尔变量doTryNotifyTimeSet来判断是否执行我们需要的逻辑;

private boolean doTryNotifyTimeSet;//变量

private TimePickerDialog.OnTimeSetListener mTimeSetListener = new TimePickerDialog.OnTimeSetListener() {

@Override //android4.1和4.2存在的一个bug,点击确定和取消按钮时,会出发onTimeSet;在dialog的onStop(比如dialog dismiss时)中,也调用了onTimeSet方法

public void onTimeSet(TimePicker view, int hourOfDay, int minute) {

Log.i(TAG, String.format("showTimeSettingDialog#onTimeSet: select time >> %d:%d",hourOfDay, minute));

if (doTryNotifyTimeSet) {

//做你想做的事

doTryNotifyTimeSet = false;

}

}

};

//timePickerDialog setButton 会替换监听 onClick 导致 tryNotifyTimeSet >> onTimeSet 无法调用,可以用反射重改下源码 或者重写父类的onClick

timePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, "确定", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

doTryNotifyTimeSet = true;

}

});

到此全剧终,感谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值