hooked82
hooked82

Reputation: 6376

Android TimePicker AM/PM button not invoking onTimeChanged

I'm having some issues implementing a TimePicker in my application that allows the user to change the time of a database record prior to inserting it.

The problem is that when the AM/PM button is pressed, the onTimeChanged(View, int, int) method isn't invoked. Whenever I change either the hour or minute value of the TimePicker, onTimeChanged() is called, however.

Scenarios:

Am I wrong in thinking that the AM/PM button should be able to be clicked to update the time without having to also change a time value after the am/pm button?

I've put together a small test project to replicate this and here's the code:

Activity

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>

Upvotes: 15

Views: 9041

Answers (11)

J-Jamet
J-Jamet

Reputation: 857

With API 25 Nougat, code of Velval / Hisham is the only one that works but not in landscape mode. I changed the code and it works fine :) :

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

Upvotes: 1

tam.teixeira
tam.teixeira

Reputation: 863

I've also faced the same problem using API 21, and the problem hasn't been solved yet.

I replaced the TimePicker view for a TimePickerDialog and it worked.

The following code sets a TextView with the time picked from the TimePickerDialog. You can replace the tv.setText() with any logic :

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

Upvotes: 0

Hisham Bakr
Hisham Bakr

Reputation: 559

Velval solution is the only solution that works and compatiple with different Android vesrions. I tried it on api 17 and 23. but it has an issue that it prevent original on click listener on Android 6. so here is what worked with me. to use touch instead of click:

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.
        }


    }
}

Upvotes: 5

velval
velval

Reputation: 3312

Had the same issue when creating the TimePicker in code and none of the provided solutions worked for me. Below was what I ended up doing. Tested on API levels 18, 21 and 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.
}

Upvotes: 5

nww04
nww04

Reputation: 1857

I would like to share my solution as well since the other workarounds posted here doesn't work for me. But I am able to figure it out. This line:

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

Doesn't seem to return the picker wheel for AM_PM.

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

Neither this as well, since this one returns a TextView. It seems it seems it is the label designated on the picker.

But using the latter, getting the 4th child element returns me the picker wheel for AM_PM. Here is what I got so far:

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

Now I am able to detect changes in AM_PM. I hope this help some other people who can't retrieve it via getChildAt(2) or getChildAt(3) as described by other answers.

Also take note that this is the case for the class TimePicker, I haven't tried this yet on a TimePickerDialog so I am not sure for that one. I am testing at min sdk 8 targeting api 22.

HTH

Upvotes: 0

Vivek
Vivek

Reputation: 13238

I have tested the code. Yes, you are right, TimePicker AM/PM button not invoking onTimeChanged method which it should.

Actually, Its a bug. It has been reported to google. You can find it here
http://code.google.com/p/android/issues/detail?id=18982

Please vote, comment & Star the bug report to raise its priority and to get it fixed by development team as soon as possible.

Upvotes: 33

user3186511
user3186511

Reputation: 63

i make this method thats return true if the TimePicker select AM and false if the TimePicker select PM. note: the suppresLint is for Api 8 don't work .getvalue() statement

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

}

Upvotes: 0

Anonsage
Anonsage

Reputation: 8320

Here's a quick project that I threw together that supports Android 4.0+. Basically, it shows a TextView that is always in sync with the TimePicker, regardless of which vertical spinner is being manipulated by the user.

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

Upvotes: 0

user123321
user123321

Reputation: 12783

this link shows that onTimeChanged(); is getting called which triggers the event dispatcher.

If you're not getting the events you need (even though it appears to be sending) you have have to

  • extend the default TimerPicker,

  • override mAmPmButton.setOnClickListener,

  • and include your version in the view.


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

Upvotes: 3

rtbsoft
rtbsoft

Reputation: 61

I found this answer, but it didn't work for me, so I thought I would update.

In 3.2 (and above?), the time picker does not have a button for am/pm. Instead it has a NumberPicker. The following code worked for me. The last bit is there because I needed to limit the time selected to no earlier than the 'min' date and no greater than the 'max' date, so I had to check the date selected in the matching DatePicker control:

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

Upvotes: 6

ram
ram

Reputation: 3507

Please use the below code it is working fine for me.

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

            }
        });
    }

Upvotes: 3

Related Questions