【问题标题】:Android TimePicker AM/PM button not invoking onTimeChangedAndroid TimePicker AM/PM 按钮未调用 onTimeChanged
【发布时间】:2012-01-28 17:32:08
【问题描述】:

我在我的应用程序中实现 TimePicker 时遇到一些问题,它允许用户在插入数据库记录之前更改它的时间。

问题在于,当按下 AM/PM 按钮时,不会调用 onTimeChanged(View, int, int) 方法。但是,每当我更改 TimePicker 的小时或分钟值时,都会调用 onTimeChanged()。

场景:

  • 用户只需点击 AM/PM 按钮:AM/PM 更新
  • 用户点击小时/分钟:时间已更新
  • 用户单击 AM/PM 按钮,然后更改小时/分钟:时间和 AM/PM 已更新

我认为应该能够单击 AM/PM 按钮来更新时间,而不必在 am/pm 按钮之后更改时间值,我错了吗?

我已经整理了一个小型测试项目来复制它,这是代码:

活动

public class TestActivity extends Activity implements OnTimeChangedListener {

    private Calendar mCalendar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.timepicker);

        mCalendar = Calendar.getInstance();

        TimePicker tp = (TimePicker)findViewById(R.id.timepicker);
        tp.setIs24HourView(false);
        tp.setOnTimeChangedListener(this);
    }

    @Override
    public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
        Log.d("TAG", "In onTimeChanged");
        mCalendar.set(mCalendar.get(Calendar.YEAR),
                      mCalendar.get(Calendar.MONTH),
                      mCalendar.get(Calendar.DAY_OF_MONTH),
                      hourOfDay,
                      minute);

        setCalendarTime();
    }

    private void setCalendarTime() {
        Date date = mCalendar.getTime();

        if (date != null) {
            SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yy '@' h:mm a");
            String dateTime = formatter.format(date);

            Toast.makeText(this, dateTime, Toast.LENGTH_LONG).show();
        }
    }
}

timepicker.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:fillViewport="true">
    <TimePicker android:id="@+id/timepicker"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dip"
                android:layout_marginRight="5dip"/>
</LinearLayout>

【问题讨论】:

    标签: android timepicker


    【解决方案1】:

    在代码中创建 TimePicker 时遇到了同样的问题,并且提供的解决方案都不适合我。下面是我最终做的事情。在 API 级别 18、21 和 23 上测试

    final TimePicker tp = new TimePicker(getContext());
    tp.setOnTimeChangedListener(this);
    try {
        ViewGroup amPmView;
        ViewGroup v1 = (ViewGroup)tp.getChildAt(0);
        ViewGroup v2 = (ViewGroup)v1.getChildAt(0);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            ViewGroup v3 = (ViewGroup)v2.getChildAt(0);
            amPmView = (ViewGroup)v3.getChildAt(3);
        } else {
            amPmView = (ViewGroup)v2.getChildAt(3);
        }
        View.OnClickListener listener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tp.setCurrentHour((tp.getCurrentHour() + 12) % 24);
            }
        };
        View am = amPmView.getChildAt(0);
        View pm = amPmView.getChildAt(1);
        am.setOnClickListener(listener);
        pm.setOnClickListener(listener);
    } catch (Exception e) {
        // DO nothing... just ignore the workaround if this fails.
    }
    

    【讨论】:

    • 这个 sn-p 设法将侦听器置于 AM/PM 上,但现在 AM/PM 无法正常工作,因为您用侦听器替换了默认侦听器。请指教
    • 我用 OnTouch 替换了 onClickListener 以不覆盖原来的点击监听器
    • 嗨@Hisham,这个解决方法的整个想法是修复默认侦听器,因为它有Vivek指出的错误。默认侦听器不会调用 onTimeChanged 回调,因此您需要将其替换为自定义侦听器并调用 tp.setCurrentHour((tp.getCurrentHour() + 12) % 24);以确保在正确的时间触发回调。
    • 这个答案应该已经被接受并且应该已经收到了 100 个声望点的赏金。我对此进行了测试,它在所有 Android 版本上都能正常运行。请注意,在 Android N+ 中,此错误已得到修复。
    • 多么出色的解决方案!这个 Android 错误浪费了我 2 个小时的调试时间:/ 这应该是公认的答案!这个解决方案完全有效!
    【解决方案2】:

    我已经测试了代码。是的,你是对的,TimePicker AM/PM 按钮没有调用它应该调用的 onTimeChanged 方法。

    实际上,这是一个错误。已向谷歌报告。你可以在这里找到它
    http://code.google.com/p/android/issues/detail?id=18982

    请对错误报告进行投票、评论和星标,以提高其优先级并尽快由开发团队修复。

    【讨论】:

    • 谷歌总是这样做
    • 难以置信这个问题是如何仍然没有解决的。
    • 此跟踪器的最新更新表明他们不会修复它,因为它是“过时的”。 ....好吧,我们应该使用更新的版本吗?
    【解决方案3】:

    Velval 解决方案是唯一适用于并兼容不同 Android 版本的解决方案。我在 api 17 和 23 上尝试过。但它有一个问题是它会阻止 Android 6 上的点击侦听器上的原始内容。所以这对我有用。使用触摸而不是点击:

    public class MyTimePicker extends TimePicker {
    
        private OnTimeChangedListener onTimeChangedListener;
    
        public MyTimePicker(Context context) {
            super(context);
            //  init();
        }
    
        public MyTimePicker(Context context, AttributeSet attrs) {
            super(context, attrs);
            //init();
        }
    
        public MyTimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            // init();
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public MyTimePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            // Stop ScrollView from getting involved once you interact with the View
            if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
                ViewParent p = getParent();
                if (p != null)
                    p.requestDisallowInterceptTouchEvent(true);
            }
            return false;
        }
    
        @Override
        public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
            super.setOnTimeChangedListener(onTimeChangedListener);
            this.onTimeChangedListener = onTimeChangedListener;
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            init();
        }
    
        private void init() {
    
            try {
                ViewGroup amPmView;
                ViewGroup v1 = (ViewGroup) getChildAt(0);
                ViewGroup v2 = (ViewGroup) v1.getChildAt(0);
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                    ViewGroup v3 = (ViewGroup) v2.getChildAt(0);
                    amPmView = (ViewGroup) v3.getChildAt(3);
                } else {
                    amPmView = (ViewGroup) v2.getChildAt(3);
                }
                View.OnTouchListener listener = new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                            onTimeChangedListener.onTimeChanged(MyTimePicker.this, getCurrentHour(), getCurrentMinute());
                        } else {
                            int hour = getCurrentHour();
                            if (hour >= 12) {
                                hour -= 12;
                            } else {
                                hour += 12;
                            }
                            onTimeChangedListener.onTimeChanged(MyTimePicker.this, hour, getCurrentMinute());
                        }
    
                        return false;
                    }
    
                };
                View am = amPmView.getChildAt(0);
                View pm = amPmView.getChildAt(1);
    
                am.setOnTouchListener(listener);
                pm.setOnTouchListener(listener);
            } catch (Exception e) {
                // DO nothing... just ignore the workaround if this fails.
            }
    
    
        }
    }
    

    【讨论】:

    • 这是一个很好的解决方法,而谷歌解决了这个问题;这可能不会很快。
    【解决方案4】:

    使用 API 25 Nougat,Velval / Hisham 的代码是唯一可以工作但不能在横向模式下工作的代码。 我更改了代码,它工作正常:):

    public class AMPMTimePicker extends TimePicker {
    
    private static final String TAG = "AMPMTimePicker";
    private OnTimeChangedListener onTimeChangedListener;
    
    public AMPMTimePicker(Context context) {
        super(context);
    }
    
    public AMPMTimePicker(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public AMPMTimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public AMPMTimePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // Stop ScrollView from getting involved once you interact with the View
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            ViewParent p = getParent();
            if (p != null)
                p.requestDisallowInterceptTouchEvent(true);
        }
        return false;
    }
    
    @Override
    public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
        super.setOnTimeChangedListener(onTimeChangedListener);
        this.onTimeChangedListener = onTimeChangedListener;
    }
    
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        init();
    }
    
    @SuppressWarnings("deprecation")
    private void init() {
        try {
            ViewGroup amPmView;
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    
                // LinearLayout (LOLLIPOP)
                // GridLayout (M-LANDSCAPE)
                // LinearLayout (M-PORTRAIT)
                ViewGroup v1 = (ViewGroup) getChildAt(0);
    
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
    
                    // FrameLayout (LOLLIPOP-LANDSCAPE)
                    // FrameLayout - id:time_header (LOLLIPOP-PORTRAIT)
                    ViewGroup v2 = (ViewGroup) v1.getChildAt(0);
    
                    // FrameLayout - id:TimeHeader (LOLLIPOP-LANDSCAPE)
                    // LinearLayout (LOLLIPOP-PORTRAIT)
                    ViewGroup v3 = (ViewGroup) v2.getChildAt(0);
    
                    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
                        ViewGroup v4 = (ViewGroup) v3.getChildAt(0); // LinearLayout (LOLLIPOP)
                        amPmView = (ViewGroup) v4.getChildAt(3); // LinearLayout - id:ampm_layout (LOLLIPOP)
                    } else { // PORTRAIT
                        amPmView = (ViewGroup) v3.getChildAt(3); // LinearLayout - id:ampm_layout (LOLLIPOP)
                    }
                } else { // M and after
                    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
                        ViewGroup v2 = (ViewGroup) v1.getChildAt(1); // RelativeLayout (M)
                        amPmView = (ViewGroup) v2.getChildAt(1); // LinearLayout - id:ampm_layout (M)
                    } else {
                        ViewGroup v2 = (ViewGroup) v1.getChildAt(0); // RelativeLayout - id:time_header (M)
                        amPmView = (ViewGroup) v2.getChildAt(3); // LinearLayout - id:ampm_layout (M)
                    }
                }
    
                View am = amPmView.getChildAt(0); // AppCompatCheckedTextView - id:am_label
                View pm = amPmView.getChildAt(1); // AppCompatCheckedTextView - id:pm_label
    
                View.OnTouchListener listener = new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        int hour;
                        int minute;
                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                            hour = getCurrentHour();
                            minute = getCurrentMinute();
                        } else {
                            hour = getHour();
                            minute = getMinute();
                        }
                        hour = (hour >= 12) ? hour - 12 : hour + 12;
                        onTimeChangedListener.onTimeChanged(AMPMTimePicker.this, hour, minute);
                        return false;
                    }
                };
                am.setOnTouchListener(listener);
                pm.setOnTouchListener(listener);
            }
        } catch (Exception e) {
            Log.e(TAG, "TimePicker is not defined for this Android version : " + e.getMessage());
        }
    }
    }
    

    【讨论】:

      【解决方案5】:

      我找到了这个答案,但它对我不起作用,所以我想我会更新。

      在 3.2(及更高版本?)中,时间选择器没有上午/下午的按钮。相反,它有一个 NumberPicker。以下代码对我有用。最后一点是因为我需要将选择的时间限制为不早于“最小”日期且不大于“最大”日期,所以我必须检查在匹配的 DatePicker 控件中选择的日期:

              NumberPicker amPmView  = (NumberPicker)((ViewGroup)tp.getChildAt(0)).getChildAt(3);
              amPmView.setOnValueChangedListener(new OnValueChangeListener() { 
                  @Override
                  public void onValueChange(NumberPicker arg0, int arg1, int arg2) {
                      if(arg0.getValue()== 1){ 
                          if (tp.getCurrentHour() < 12)
                              tp.setCurrentHour(tp.getCurrentHour() + 12); 
                      } 
                      else{ 
                          if (tp.getCurrentHour() >= 12) 
                              tp.setCurrentHour(tp.getCurrentHour() - 12); 
                      } 
      
                      int year = dp.getYear();
                      int month = dp.getMonth();
                      int dayOfMonth = dp.getDayOfMonth();
                      int hourOfDay = tp.getCurrentHour();
                      int minute = tp.getCurrentMinute();
      
                      if (year == min.get(Calendar.YEAR) && month == min.get(Calendar.MONTH) && dayOfMonth == min.get(Calendar.DAY_OF_MONTH)){
                          if ((hourOfDay < min.get(Calendar.HOUR_OF_DAY))||
                              (hourOfDay == min.get(Calendar.HOUR_OF_DAY) && (minute < min.get(Calendar.MINUTE)))){
                              tp.setCurrentHour(min.get(Calendar.HOUR_OF_DAY));
                              tp.setCurrentMinute(min.get(Calendar.MINUTE));
                          }
                      }else if (year == max.get(Calendar.YEAR) && month == max.get(Calendar.MONTH) && dayOfMonth == max.get(Calendar.DAY_OF_MONTH)){
                          if ((hourOfDay > max.get(Calendar.HOUR_OF_DAY))||
                              (hourOfDay == max.get(Calendar.HOUR_OF_DAY) && (minute > max.get(Calendar.MINUTE)))){
                              tp.setCurrentHour(max.get(Calendar.HOUR_OF_DAY));
                              tp.setCurrentMinute(max.get(Calendar.MINUTE));
                          }
                      }
                  } 
              }); 
      

      【讨论】:

        【解决方案6】:

        请使用下面的代码,它对我来说很好。

        final TimePicker tp=(TimePicker)findViewById(R.id.timePicker1);
            View amPmView  = ((ViewGroup)tp.getChildAt(0)).getChildAt(2);
            if(amPmView instanceof Button)
            {
                amPmView.setOnClickListener(new OnClickListener() {
        
                    @Override
                    public void onClick(View v) {
                        Log.d("OnClickListener", "OnClickListener called");
                        if(v instanceof Button)
                        {
                            if(((Button) v).getText().equals("AM"))
                            {
                                ((Button) v).setText("PM");
                                 if (tp.getCurrentHour() < 12) {
                                     tp.setCurrentHour(tp.getCurrentHour() + 12);
                                    }  
        
                            }
                            else{
                                ((Button) v).setText("AM");
                                 if (tp.getCurrentHour() >= 12) {
                                     tp.setCurrentHour(tp.getCurrentHour() - 12);
                                    }
                            }
                        }
        
                    }
                });
            }
        

        【讨论】:

        • 谢谢,这很好用。我注释掉了 v.setText("AM/PM") 行,它仍然得到适当的更新。我不确定这是否是 b/c tp.setCurrentHour() 最终触发 timeChanged 事件。
        • 我对这种方法有疑问,instanceof 检查失败。
        【解决方案7】:

        this 链接显示 onTimeChanged(); 正在被调用,这会触发事件调度程序。

        如果你没有收到你需要的事件(即使它看起来正在发送),你必须

        • 扩展默认的TimerPicker,

        • 覆盖 mAmPmButton.setOnClickListener,

        • 并在视图中包含您的版本。


        mAmPmButton.setOnClickListener(new OnClickListener() {
                public void onClick(View v) {
                    requestFocus();
                    if (mIsAm) {
        
                        // Currently AM switching to PM
                        if (mCurrentHour < 12) {
                            mCurrentHour += 12;
                        }                
                    } else {
        
                        // Currently PM switching to AM
                        if (mCurrentHour >= 12) {
                            mCurrentHour -= 12;
                        }
                    }
                    mIsAm = !mIsAm;
                    mAmPmButton.setText(mIsAm ? mAmText : mPmText);
                    onTimeChanged();
                }
            });
        

        【讨论】:

        • 是的,我在源代码中看到了。我什至将源代码拉下来并将 TimePicker/NumberPicker 重新编译为本地类,它可以按预期工作。这样做的问题是我不想为这些提取源并拥有自己的自定义源。我宁愿弄清楚为什么默认设置在我的应用程序中没有按预期工作?
        • 我认为如果不执行这些选项之一,您将无法使其正常工作。 sdk 中包含的 TimerPicker 显然存在问题。
        【解决方案8】:

        这是我整理的一个支持 Android 4.0+ 的快速项目。基本上,它显示一个始终与 TimePicker 同步的 TextView,无论用户正在操纵哪个垂直微调器。

        https://gist.github.com/danialgoodwin/5694256

        【讨论】:

          【解决方案9】:

          如果 TimePicker 选择 AM,我使这个方法返回 true,如果 TimePicker 选择 PM,则返回 false。注意:suppresLint 适用于 Api 8 don't work .getvalue() 语句

          Saludos!! :)

          @SuppressLint("NewApi")
          private boolean isAM(TimePicker timePicker){
              NumberPicker numberPickerAmPm  = (NumberPicker)((ViewGroup) timePicker.getChildAt(0)).getChildAt(3);
              if(numberPickerAmPm.getValue()==0){
                  //sendToast("es am");
                  return true;
              }else{
                 //sendToast("es pm");
                  return false;
              }
          
          }
          

          【讨论】:

            【解决方案10】:

            我也想分享我的解决方案,因为此处发布的其他解决方法对我不起作用。但我能够弄清楚。这一行:

                View amPmView  = ((ViewGroup)tp.getChildAt(0)).getChildAt(2);
            

            似乎没有返回 AM_PM 的选择轮。

                NumberPicker amPmView  = (NumberPicker ((ViewGroup)tp.getChildAt(0)).getChildAt(3);
            

            也不是这个,因为这个返回一个TextView。似乎它似乎是选择器上指定的标签。

            但是使用后者,获取第 4 个子元素会返回 AM_PM 的选择轮。这是我到目前为止得到的:

                NumberPicker amPmView = (NumberPicker)((ViewGroup) mTimePicker.getChildAt(0)).getChildAt(4);
                amPmView.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() 
                {
                    @Override
                    public void onValueChange(NumberPicker picker, int oldVal, int newVal) 
                    {
                        Log.i(NoteApplication.TAG, "AM_PM selected...");
                    }
                });
            

            现在我能够检测到 AM_PM 的变化。我希望这可以帮助其他无法通过getChildAt(2)getChildAt(3) 检索它的人,如其他答案所述。

            还要注意 TimePicker 类就是这种情况,我还没有在 TimePickerDialog 上尝试过这个,所以我不确定那个。我正在针对 api 22 的 min sdk 8 进行测试。

            HTH

            【讨论】:

              【解决方案11】:

              我在使用 API 21 时也遇到过同样的问题,但问题还没有解决。

              我用 TimePickerDialog 替换了 TimePicker 视图,它起作用了。

              以下代码使用从 TimePickerDialog 中选取的时间设置一个 TextView。您可以用任何逻辑替换 tv.setText() :

              @Override
              protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.activity_main);
                  final TextView tv = (TextView)findViewById(R.id.textView);
              
                  Calendar calendar = Calendar.getInstance();
                  int hour = calendar.get(Calendar.HOUR_OF_DAY);
                  int minute = calendar.get(Calendar.MINUTE);
                  TimePickerDialog timePickerDialog = new TimePickerDialog(this,
                          new TimePickerDialog.OnTimeSetListener() {
                              @Override
                              public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                                  tv.setText(String.format(Locale.getDefault(),"Hour: %d, Minute: %d ",hourOfDay,minute));
                              }
                  }, hour, minute, true);
                  timePickerDialog.show();
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2022-01-19
                • 1970-01-01
                • 2015-12-19
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多