【需求】:弹出一个非全屏窗口,可以选择时间设置,文本输入; 看似简单确认折腾了一天多时间,下面把实战经历记录如下。
【实现思路及方法】:有两种,一种是通过Dialog实现,一种是通过Popupwindow实现,两者的实现思路差别不大,主要是遇到的问题各有千秋。
一、通过继承Dialog实现
1、编辑界面文件
2、自定义Dialog类,继承自Dialog
主要思路:
1)可以看到界面中定义了两个按钮,外层创建Dialog对象时,构造函数中直接传入点击事件的监听器,并开启监听。监听器可以在外层自己定义,监听两个按键的动作,外层直接执行不同 的操作;
2)时间选择/模式选择分别使用TimePickerView 、OptionsPickerView,点击各自的文本时会在底部弹出时间选择器,选项选择器;
public class MineClockEditPop extends Dialog {
private Activity mContext;
private View.OnClickListener mClickListener;
private Button buttonCancel;
private Button buttonSave;
public EditText editTime;
public EditText editMode;
public EditText editNote;
public MineClockEditPop(Activity context) {
super(context);
this.mContext = mContext;
}
public MineClockEditPop(Activity context, int theme, View.OnClickListener clickListener) {
super(context, theme);
this.mContext = context;
this.mClickListener = clickListener;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.tab_mine_clock_edit);
editTime = (EditText) findViewById(R.id.clock_edit_time);
editMode = (EditText) findViewById(R.id.clock_edit_mode);
editNote = (EditText) findViewById(R.id.clock_edit_note);
buttonCancel = (Button) findViewById(R.id.clock_edit_cancel);
buttonSave = (Button) findViewById(R.id.clock_edit_save);
// 设置弹出窗体的宽和高
/*
* 获取窗口对象及参数对象以修改对话框的布局设置, 可以直接调用getWindow(),表示获得这个Activity的Window
* 对象,这样这可以以同样的方式改变这个Activity的属性.
*/
Window dialogWindow = this.getWindow();
WindowManager m = mContext.getWindowManager();
Display d = m.getDefaultDisplay(); // 获取屏幕宽、高用
WindowManager.LayoutParams p = dialogWindow.getAttributes(); // 获取对话框当前的参数值
p.width = (int) (d.getWidth() * 0.8);
dialogWindow.setAttributes(p);
dialogWindow.setGravity(Gravity.CENTER_VERTICAL);
// 设置按钮监听
buttonCancel.setOnClickListener(mClickListener);
buttonSave.setOnClickListener(mClickListener);
this.setCancelable(false);
//自定义时间选择器
editTime.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
TimePickerView mTimePickerView = new TimePickerView(mContext, TimePickerView.Type.HOURS_MINS);
// 设置是否循环
mTimePickerView.setCyclic(true);
// 设置滚轮文字大小
mTimePickerView.setTitle("选择时间");
mTimePickerView.setTextSize(TimePickerView.TextSize.SMALL);
// 设置时间可选范围(结合 setTime 方法使用,必须在)
// Calendar calendar = Calendar.getInstance();
// mTimePickerView.setRange(calendar.get(Calendar.YEAR) - 100, calendar.get(Calendar.YEAR));
// 设置选中时间
mTimePickerView.setTime(new Date());
mTimePickerView.setOnTimeSelectListener(new TimePickerView.OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date) {
SimpleDateFormat format = new SimpleDateFormat("HH:mm", Locale.CHINA);
// Toast.makeText(MineInformationActivity.this, format.format(date), Toast.LENGTH_SHORT).show();
editTime.setText(format.format(date));
}
});
mTimePickerView.show();
}
});
//自定义模式选择器
editMode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
OptionsPickerView mOptionsPickerView = new OptionsPickerView<>(mContext);
final ArrayList list = new ArrayList<>();
list.add("仅一次");
list.add("每天");
mOptionsPickerView.setTitle("选择模式");
// 设置数据
mOptionsPickerView.setPicker(list);
// 设置选项单位
mOptionsPickerView.setOnOptionsSelectListener(new OptionsPickerView.OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int option1, int option2, int option3) {
String mode = list.get(option1);
// Toast.makeText(MineInformationActivity.this, sex, Toast.LENGTH_SHORT).show();
editMode.setText(mode);
}
});
mOptionsPickerView.show();
}
});
}
}
3、外部调用层 自定义窗口中按钮点击事件监听器
//自定义按钮监听事件
private View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.clock_edit_cancel:
break;
case R.id.clock_edit_save:
String time = clockEditPop.editTime.getText().toString().trim();
String mode = clockEditPop.editMode.getText().toString().trim();
String note = clockEditPop.editNote.getText().toString().trim();
LogUtil.d(TAG,time+"---"+mode+"---"+note);
clockEditPop.dismiss();
break;
}
}
};
4、调用:
clockEditPop = new MineClockEditPop(this,1,onClickListener);
clockEditPop.show();
5、总结
使用这个方法存在的问题是,我在Dialog中使用了TimePickerView 、OptionsPickerView,这俩本质上是个Popupwindow,点击出现时没有出现在Dialog的顶层,而是在其下层,这样用户无法选中时间或者选项了,如果需求中没有用的这俩东西,只是普通的文本输入,用这个方法是没问题的。
关于无法解决POP 窗口在dialog下面的问题,请路过的兄弟帮忙指点下,多谢!
二、通过继承PopupWindow实现
1、编辑界面,如一,不再赘述
2、Popup窗口类,继承自PopupWindow,外部调用层 自定义窗口中按钮点击事件监听器
思路与一中基本一致,只是构造函数处有略微差别。不再赘述。
public class MineClockEditPop extends PopupWindow {
private Context mContext;
private View view;
private Button buttonCancel;
private Button buttonSave;
public EditText editTime;
public EditText editMode;
public EditText editNote;
//构造函数,外部调用时传入自定义的监听器
public MineClockEditPop(final Activity mContext, View.OnClickListener itemsOnClick) {
this.mContext = mContext;
this.view = LayoutInflater.from(mContext).inflate(R.layout.tab_mine_clock_edit, null);
editTime = (EditText) view.findViewById(R.id.clock_edit_time);
editMode = (EditText) view.findViewById(R.id.clock_edit_mode);
editNote = (EditText) view.findViewById(R.id.clock_edit_note);
buttonCancel = (Button) view.findViewById(R.id.clock_edit_cancel);
buttonSave = (Button) view.findViewById(R.id.clock_edit_save);
// 设置按钮监听
buttonCancel.setOnClickListener(itemsOnClick);
buttonSave.setOnClickListener(itemsOnClick);
// 设置外部可点击,即点击pop窗口外pop消失
this.setOutsideTouchable(false);
/* 设置弹出窗口特征 */
// 设置视图
this.setContentView(this.view);
// 设置弹出窗体的宽和高
/*
* 获取窗口对象及参数对象以修改对话框的布局设置, 可以直接调用getWindow(),表示获得这个Activity的Window
* 对象,这样这可以以同样的方式改变这个Activity的属性.
*/
Window dialogWindow = mContext.getWindow();
WindowManager m = mContext.getWindowManager();
Display d = m.getDefaultDisplay(); // 获取屏幕宽、高用
WindowManager.LayoutParams p = dialogWindow.getAttributes(); // 获取对话框当前的参数值
this.setHeight(RelativeLayout.LayoutParams.WRAP_CONTENT);
this.setWidth((int) (d.getWidth() * 0.8));
// 设置聚焦,否则EditText输入法无响应
this.setFocusable(false);
//自定义时间选择器
editTime.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
TimePickerView mTimePickerView = new TimePickerView(view.getContext(), TimePickerView.Type.HOURS_MINS);
// 设置是否循环
mTimePickerView.setCyclic(true);
// 设置滚轮文字大小
mTimePickerView.setTitle("选择时间");
mTimePickerView.setTextSize(TimePickerView.TextSize.SMALL);
// 设置时间可选范围(结合 setTime 方法使用,必须在)
// Calendar calendar = Calendar.getInstance();
// mTimePickerView.setRange(calendar.get(Calendar.YEAR) - 100, calendar.get(Calendar.YEAR));
// 设置选中时间
mTimePickerView.setTime(new Date());
mTimePickerView.setOnTimeSelectListener(new TimePickerView.OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date) {
SimpleDateFormat format = new SimpleDateFormat("HH:mm", Locale.CHINA);
// Toast.makeText(MineInformationActivity.this, format.format(date), Toast.LENGTH_SHORT).show();
editTime.setText(format.format(date));
}
});
mTimePickerView.show();
}
});
//自定义模式选择器
editMode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
OptionsPickerView mOptionsPickerView = new OptionsPickerView<>(view.getContext());
final ArrayList list = new ArrayList<>();
list.add("仅一次");
list.add("每天");
mOptionsPickerView.setTitle("选择模式");
// 设置数据
mOptionsPickerView.setPicker(list);
// 设置选项单位
mOptionsPickerView.setOnOptionsSelectListener(new OptionsPickerView.OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int option1, int option2, int option3) {
String mode = list.get(option1);
// Toast.makeText(MineInformationActivity.this, sex, Toast.LENGTH_SHORT).show();
editMode.setText(mode);
}
});
mOptionsPickerView.show();
}
});
editNote.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//设置Popupwindow的setFocusable 为true,可以输入文本
//MineClockEditPop.this.setFocusable(false);
//手动弹出输入法
InputMethodManager inputMethodManager = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(0,InputMethodManager.HIDE_NOT_ALWAYS);
// editNote.setFocusableInTouchMode(true);
// editNote.setFocusable(true);
// editNote.requestFocus();
}
});
}
}
3、调用:
//调用
clockEditPop = new MineClockEditPop(this,onClickListener);
clockEditPop.showAtLocation(findViewById(R.id.tab_mine_clock), Gravity.CENTER, 0, 0);
4、总结
1)使用这个方法存在的问题是,我想让自定义的Popup窗口不穿透,即点击窗口外时,这个Popup窗口不消失(弹出实现选择器和选项选择器的需求),这时要设置的两个属性,关于为什么要设置第二个可以看下第一个属性起作用的条件,如下截图,那就是setFocusable 为false。
// 设置外部可点击,即点击pop窗口外pop消失
this.setOutsideTouchable(false);
// 设置聚焦,否则EditText输入法无响应
this.setFocusable(false);
2)第一个问题解决了, setFocusable(false) 却带来了第二个问题,我的文本框没法输入了,输入法也不出现,因为没有聚焦。我尝试在文本点击时手动弹出输入法,在点击事件匿名类中调用上层类的方法,修改setFocusable属性,但是都不起作用,最后还是没有解决,这个水火不容的大BUG。
关于无法解决自定义Popup不穿透及EditText输入文本矛盾的问题,请路过的兄弟指点,多谢!
三、最终变相解决方法
基于Pop的方案的输入法问题实在没法解决,又把目标转向了基于Dialog的方案,这个方案的瑕疵在在于从Dialog弹出时间选择器的界面层级问题,Dialog都是显示在最顶层的,所以把TimerPickerView换成了TimerPickerDialog,最终完美解决,选项选择器换成combox了,就不再给出代码了。
Calendar calendar = Calendar.getInstance();
//create a datePickerDialog and then shoe it on your screen
new TimePickerDialog(mContext,
new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
DecimalFormat df = new DecimalFormat("00");
df.setRoundingMode(RoundingMode.HALF_DOWN);
editTime.setText(df.format(hourOfDay)+":"+df.format(minute));
}
}
, calendar.get(Calendar.HOUR_OF_DAY)
, calendar.get(Calendar.MINUTE)
, true).show();