public class

TimePicker

extends Picker

 java.lang.Object

↳FrameLayout

androidx.leanback.widget.picker.Picker

↳androidx.leanback.widget.picker.TimePicker

Gradle dependencies

compile group: 'androidx.leanback', name: 'leanback', version: '1.2.0-alpha02'

  • groupId: androidx.leanback
  • artifactId: leanback
  • version: 1.2.0-alpha02

Artifact androidx.leanback:leanback:1.2.0-alpha02 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.leanback:leanback com.android.support:leanback-v17

Androidx class mapping:

androidx.leanback.widget.picker.TimePicker android.support.v17.leanback.widget.picker.TimePicker

Overview

TimePicker is a direct subclass of Picker.

This class is a widget for selecting time and displays it according to the formatting for the current system locale. The time can be selected by hour, minute, and AM/PM picker columns. The AM/PM mode is determined by either explicitly setting the current mode through TimePicker.setIs24Hour(boolean) or the widget attribute is24HourFormat (true for 24-hour mode, false for 12-hour mode). Otherwise, TimePicker retrieves the mode based on the current context. In 24-hour mode, TimePicker displays only the hour and minute columns.

This widget can show the current time as the initial value if useCurrentTime is set to true. Each individual time picker field can be set at any time by calling TimePicker.setHour(int), TimePicker.setMinute(int) using 24-hour time format. The time format can also be changed at any time by calling TimePicker.setIs24Hour(boolean), and the AM/PM picker column will be activated or deactivated accordingly.

Summary

Constructors
publicTimePicker(Context context, AttributeSet attrs)

Constructor called when inflating a TimePicker widget.

publicTimePicker(Context context, AttributeSet attrs, int defStyleAttr)

Constructor called when inflating a TimePicker widget.

Methods
public intgetHour()

Returns the currently selected hour using 24-hour time.

public intgetMinute()

Returns the currently selected minute.

public booleanis24Hour()

public booleanisPm()

Only meaningful for a 12-hour time.

public voidonColumnValueChanged(int columnIndex, int newValue)

Classes extending Picker can override this function to supply the behavior when a list has been scrolled.

public voidsetHour(int hour)

Sets the currently selected hour using a 24-hour time.

public voidsetIs24Hour(boolean is24Hour)

Sets whether this widget displays a 24-hour mode or a 12-hour mode with an AM/PM picker.

public voidsetMinute(int minute)

Sets the currently selected minute.

from PickeraddOnValueChangedListener, dispatchKeyEvent, getActivatedVisibleItemCount, getColumnAt, getColumnsCount, getPickerItemHeightPixels, getPickerItemLayoutId, getPickerItemTextViewId, getSelectedColumn, getSeparator, getSeparators, getVisibleItemCount, onRequestFocusInDescendants, removeOnValueChangedListener, requestChildFocus, setActivated, setActivatedVisibleItemCount, setColumnAt, setColumns, setColumnValue, setPickerItemLayoutId, setPickerItemTextViewId, setSelectedColumn, setSeparator, setSeparators, setVisibleItemCount
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public TimePicker(Context context, AttributeSet attrs)

Constructor called when inflating a TimePicker widget. This version uses a default style of 0, so the only attribute values applied are those in the Context's Theme and the given AttributeSet.

Parameters:

context: the context this TimePicker widget is associated with through which we can access the current theme attributes and resources
attrs: the attributes of the XML tag that is inflating the TimePicker widget

public TimePicker(Context context, AttributeSet attrs, int defStyleAttr)

Constructor called when inflating a TimePicker widget.

Parameters:

context: the context this TimePicker widget is associated with through which we can access the current theme attributes and resources
attrs: the attributes of the XML tag that is inflating the TimePicker widget
defStyleAttr: An attribute in the current theme that contains a reference to a style resource that supplies default values for the widget. Can be 0 to not look for defaults.

Methods

public void setHour(int hour)

Sets the currently selected hour using a 24-hour time.

Parameters:

hour: the hour to set, in the range (0-23)

See also: TimePicker.getHour()

public int getHour()

Returns the currently selected hour using 24-hour time.

Returns:

the currently selected hour in the range (0-23)

See also: TimePicker.setHour(int)

public void setMinute(int minute)

Sets the currently selected minute.

Parameters:

minute: the minute to set, in the range (0-59)

See also: TimePicker.getMinute()

public int getMinute()

Returns the currently selected minute.

Returns:

the currently selected minute, in the range (0-59)

See also: TimePicker.setMinute(int)

public void setIs24Hour(boolean is24Hour)

Sets whether this widget displays a 24-hour mode or a 12-hour mode with an AM/PM picker.

Parameters:

is24Hour: true to display in 24-hour mode, false ti display in 12-hour mode with AM/PM.

See also: TimePicker.is24Hour()

public boolean is24Hour()

Returns:

true if this widget displays time in 24-hour mode, false otherwise.

See also: TimePicker.setIs24Hour(boolean)

public boolean isPm()

Only meaningful for a 12-hour time.

Returns:

true if the currently selected time is in PM, false if the currently selected time in in AM.

public void onColumnValueChanged(int columnIndex, int newValue)

Classes extending Picker can override this function to supply the behavior when a list has been scrolled. Subclass may call Picker.setColumnValue(int, int, boolean) and or Picker.setColumnAt(int, PickerColumn). Subclass should not directly call PickerColumn.setCurrentValue(int) which does not update internal state or notify listeners.

Parameters:

columnIndex: index of which column was changed.
newValue: A new value desired to be set on the column.

Source

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.leanback.widget.picker;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.IntRange;
import androidx.core.view.ViewCompat;
import androidx.leanback.R;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

/**
 * {@link TimePicker} is a direct subclass of {@link Picker}.
 * <p>
 * This class is a widget for selecting time and displays it according to the formatting for the
 * current system locale. The time can be selected by hour, minute, and AM/PM picker columns.
 * The AM/PM mode is determined by either explicitly setting the current mode through
 * {@link #setIs24Hour(boolean)} or the widget attribute {@code is24HourFormat} (true for 24-hour
 * mode, false for 12-hour mode). Otherwise, TimePicker retrieves the mode based on the current
 * context. In 24-hour mode, TimePicker displays only the hour and minute columns.
 * <p>
 * This widget can show the current time as the initial value if {@code useCurrentTime} is set to
 * true. Each individual time picker field can be set at any time by calling {@link #setHour(int)},
 * {@link #setMinute(int)} using 24-hour time format. The time format can also be changed at any
 * time by calling {@link #setIs24Hour(boolean)}, and the AM/PM picker column will be activated or
 * deactivated accordingly.
 *
 * {@link R.attr#is24HourFormat}
 * {@link R.attr#useCurrentTime}
 */
public class TimePicker extends Picker {

    static final String TAG = "TimePicker";

    private static final int AM_INDEX = 0;
    private static final int PM_INDEX = 1;

    private static final int HOURS_IN_HALF_DAY = 12;
    PickerColumn mHourColumn;
    PickerColumn mMinuteColumn;
    PickerColumn mAmPmColumn;
    int mColHourIndex;
    int mColMinuteIndex;
    int mColAmPmIndex;

    private final PickerUtility.TimeConstant mConstant;

    private boolean mIs24hFormat;

    private int mCurrentHour;
    private int mCurrentMinute;
    private int mCurrentAmPmIndex;

    private String mTimePickerFormat;

    /**
     * Constructor called when inflating a TimePicker widget. This version uses a default style of
     * 0, so the only attribute values applied are those in the Context's Theme and the given
     * AttributeSet.
     *
     * @param context the context this TimePicker widget is associated with through which we can
     *                access the current theme attributes and resources
     * @param attrs the attributes of the XML tag that is inflating the TimePicker widget
     */
    public TimePicker(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.timePickerStyle);
    }

    /**
     * Constructor called when inflating a TimePicker widget.
     *
     * @param context the context this TimePicker widget is associated with through which we can
     *                access the current theme attributes and resources
     * @param attrs the attributes of the XML tag that is inflating the TimePicker widget
     * @param defStyleAttr An attribute in the current theme that contains a reference to a style
     *                     resource that supplies default values for the widget. Can be 0 to not
     *                     look for defaults.
     */
    @SuppressLint("CustomViewStyleable")
    public TimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mConstant = PickerUtility.getTimeConstantInstance(Locale.getDefault(),
                context.getResources());

        final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
                R.styleable.lbTimePicker);
        ViewCompat.saveAttributeDataForStyleable(
                this, context, R.styleable.lbTimePicker, attrs, attributesArray, 0, 0);
        boolean useCurrentTime;

        try {
            mIs24hFormat = attributesArray.getBoolean(R.styleable.lbTimePicker_is24HourFormat,
                    DateFormat.is24HourFormat(context));
            useCurrentTime = attributesArray.getBoolean(
                    R.styleable.lbTimePicker_useCurrentTime,
                    true);
        } finally {
            attributesArray.recycle();
        }

        // The following 2 methods must be called after setting mIs24hFormat since this attribute is
        // used to extract the time format string.
        updateColumns();
        updateColumnsRange();

        if (useCurrentTime) {
            Calendar currentDate = PickerUtility.getCalendarForLocale(null,
                    mConstant.locale);
            setHour(currentDate.get(Calendar.HOUR_OF_DAY));
            setMinute(currentDate.get(Calendar.MINUTE));
            setAmPmValue();
        }
    }

    private static void updateMin(PickerColumn column, int value) {
        if (value != column.getMinValue()) {
            column.setMinValue(value);
        }
    }

    private static void updateMax(PickerColumn column, int value) {
        if (value != column.getMaxValue()) {
            column.setMaxValue(value);
        }
    }

    /**
     * @return The best localized representation of time for the current locale
     */
    String getBestHourMinutePattern() {
        final String hourPattern;
        if (PickerUtility.SUPPORTS_BEST_DATE_TIME_PATTERN) {
            hourPattern = DateFormat.getBestDateTimePattern(mConstant.locale, mIs24hFormat ? "Hma"
                    : "hma");
        } else {
            // Using short style to avoid picking extra fields e.g. time zone in the returned time
            // format.
            final java.text.DateFormat dateFormat =
                    SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT, mConstant.locale);
            if (dateFormat instanceof SimpleDateFormat) {
                String defaultPattern = ((SimpleDateFormat) dateFormat).toPattern();
                defaultPattern = defaultPattern.replace("s", "");
                if (mIs24hFormat) {
                    defaultPattern = defaultPattern.replace('h', 'H').replace("a", "");
                }
                hourPattern = defaultPattern;
            } else {
                hourPattern = mIs24hFormat ? "H:mma" : "h:mma";
            }
        }
        return TextUtils.isEmpty(hourPattern) ? "h:mma" : hourPattern;
    }

    /**
     * Extracts the separators used to separate time fields (including before the first and after
     * the last time field). The separators can vary based on the individual locale and 12 or
     * 24 hour time format, defined in the Unicode CLDR and cannot be supposed to be ":".
     *
     * See http://unicode.org/cldr/trac/browser/trunk/common/main
     *
     * For example, for english in 12 hour format
     * (time pattern of "h:mm a"), this will return {"", ":", "", ""}, where the first separator
     * indicates nothing needs to be displayed to the left of the hour field, ":" needs to be
     * displayed to the right of hour field, and so forth.
     *
     * @return The ArrayList of separators to populate between the actual time fields in the
     * TimePicker.
     */
    List<CharSequence> extractSeparators() {
        // Obtain the time format string per the current locale (e.g. h:mm a)
        String hmaPattern = getBestHourMinutePattern();

        List<CharSequence> separators = new ArrayList<>();
        StringBuilder sb = new StringBuilder();
        char lastChar = '\0';
        // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
        final char[] timeFormats = {'H', 'h', 'K', 'k', 'm', 'M', 'a'};
        boolean processingQuote = false;
        for (int i = 0; i < hmaPattern.length(); i++) {
            char c = hmaPattern.charAt(i);
            if (c == ' ') {
                continue;
            }
            if (c == '\'') {
                if (!processingQuote) {
                    sb.setLength(0);
                    processingQuote = true;
                } else {
                    processingQuote = false;
                }
                continue;
            }
            if (processingQuote) {
                sb.append(c);
            } else {
                if (isAnyOf(c, timeFormats)) {
                    if (c != lastChar) {
                        separators.add(sb.toString());
                        sb.setLength(0);
                    }
                } else {
                    sb.append(c);
                }
            }
            lastChar = c;
        }
        separators.add(sb.toString());
        return separators;
    }

    private static boolean isAnyOf(char c, char[] any) {
        for (int i = 0; i < any.length; i++) {
            if (c == any[i]) {
                return true;
            }
        }
        return false;
    }

    /**
     *
     * @return the time picker format string based on the current system locale and the layout
     *         direction
     */
    private String extractTimeFields() {
        // Obtain the time format string per the current locale (e.g. h:mm a)
        String hmaPattern = getBestHourMinutePattern();

        boolean isRTL = TextUtils.getLayoutDirectionFromLocale(mConstant.locale) == View
                .LAYOUT_DIRECTION_RTL;
        boolean isAmPmAtEnd = (hmaPattern.indexOf('a') >= 0)
                ? (hmaPattern.indexOf("a") > hmaPattern.indexOf("m")) : true;
        // Hour will always appear to the left of minutes regardless of layout direction.
        String timePickerFormat = isRTL ? "mh" : "hm";

        if (is24Hour()) {
            return timePickerFormat;
        } else {
            return isAmPmAtEnd ? (timePickerFormat + "a") : ("a" + timePickerFormat);
        }
    }

    private void updateColumns() {
        String timePickerFormat = getBestHourMinutePattern();
        if (TextUtils.equals(timePickerFormat, mTimePickerFormat)) {
            return;
        }
        mTimePickerFormat = timePickerFormat;

        String timeFieldsPattern = extractTimeFields();
        List<CharSequence> separators = extractSeparators();
        if (separators.size() != (timeFieldsPattern.length() + 1)) {
            throw new IllegalStateException("Separators size: " + separators.size() + " must equal"
                    + " the size of timeFieldsPattern: " + timeFieldsPattern.length() + " + 1");
        }
        setSeparators(separators);
        timeFieldsPattern = timeFieldsPattern.toUpperCase(mConstant.locale);

        mHourColumn = mMinuteColumn = mAmPmColumn = null;
        mColHourIndex = mColMinuteIndex = mColAmPmIndex = -1;

        ArrayList<PickerColumn> columns = new ArrayList<>(3);
        for (int i = 0; i < timeFieldsPattern.length(); i++) {
            switch (timeFieldsPattern.charAt(i)) {
                case 'H':
                    columns.add(mHourColumn = new PickerColumn());
                    mHourColumn.setStaticLabels(mConstant.hours24);
                    mColHourIndex = i;
                    break;
                case 'M':
                    columns.add(mMinuteColumn = new PickerColumn());
                    mMinuteColumn.setStaticLabels(mConstant.minutes);
                    mColMinuteIndex = i;
                    break;
                case 'A':
                    columns.add(mAmPmColumn = new PickerColumn());
                    mAmPmColumn.setStaticLabels(mConstant.ampm);
                    mColAmPmIndex = i;
                    updateMin(mAmPmColumn, 0);
                    updateMax(mAmPmColumn, 1);
                    break;
                default:
                    throw new IllegalArgumentException("Invalid time picker format.");
            }
        }
        setColumns(columns);
    }

    private void updateColumnsRange() {
        // updateHourColumn(false);
        updateMin(mHourColumn, mIs24hFormat ? 0 : 1);
        updateMax(mHourColumn, mIs24hFormat ? 23 : 12);

        updateMin(mMinuteColumn, 0);
        updateMax(mMinuteColumn, 59);

        if (mAmPmColumn != null) {
            updateMin(mAmPmColumn, 0);
            updateMax(mAmPmColumn, 1);
        }
    }

    /**
     * Updates the value of AM/PM column for a 12 hour time format. The correct value should already
     * be calculated before this method is called by calling setHour.
     */
    private void setAmPmValue() {
        if (!is24Hour()) {
            setColumnValue(mColAmPmIndex, mCurrentAmPmIndex, false);
        }
    }

    /**
     * Sets the currently selected hour using a 24-hour time.
     *
     * @param hour the hour to set, in the range (0-23)
     * @see #getHour()
     */
    public void setHour(@IntRange(from = 0, to = 23) int hour) {
        if (hour < 0 || hour > 23) {
            throw new IllegalArgumentException("hour: " + hour + " is not in [0-23] range in");
        }
        mCurrentHour = hour;
        if (!is24Hour()) {
            if (mCurrentHour >= HOURS_IN_HALF_DAY) {
                mCurrentAmPmIndex = PM_INDEX;
                if (mCurrentHour > HOURS_IN_HALF_DAY) {
                    mCurrentHour -= HOURS_IN_HALF_DAY;
                }
            } else {
                mCurrentAmPmIndex = AM_INDEX;
                if (mCurrentHour == 0) {
                    mCurrentHour = HOURS_IN_HALF_DAY;
                }
            }
            setAmPmValue();
        }
        setColumnValue(mColHourIndex, mCurrentHour, false);
    }

    /**
     * Returns the currently selected hour using 24-hour time.
     *
     * @return the currently selected hour in the range (0-23)
     * @see #setHour(int)
     */
    public int getHour() {
        if (mIs24hFormat) {
            return mCurrentHour;
        }
        if (mCurrentAmPmIndex == AM_INDEX) {
            return mCurrentHour % HOURS_IN_HALF_DAY;
        }
        return (mCurrentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
    }

    /**
     * Sets the currently selected minute.
     *
     * @param minute the minute to set, in the range (0-59)
     * @see #getMinute()
     */
    public void setMinute(@IntRange(from = 0, to = 59) int minute) {
        if (minute < 0 || minute > 59) {
            throw new IllegalArgumentException("minute: " + minute + " is not in [0-59] range.");
        }
        mCurrentMinute = minute;
        setColumnValue(mColMinuteIndex, mCurrentMinute, false);
    }

    /**
     * Returns the currently selected minute.
     *
     * @return the currently selected minute, in the range (0-59)
     * @see #setMinute(int)
     */
    public int getMinute() {
        return mCurrentMinute;
    }

    /**
     * Sets whether this widget displays a 24-hour mode or a 12-hour mode with an AM/PM picker.
     *
     * @param is24Hour {@code true} to display in 24-hour mode,
     *                 {@code false} ti display in 12-hour mode with AM/PM.
     * @see #is24Hour()
     */
    public void setIs24Hour(boolean is24Hour) {
        if (mIs24hFormat == is24Hour) {
            return;
        }
        // the ordering of these statements is important
        int currentHour = getHour();
        int currentMinute = getMinute();
        mIs24hFormat = is24Hour;
        updateColumns();
        updateColumnsRange();

        setHour(currentHour);
        setMinute(currentMinute);
        setAmPmValue();
    }

    /**
     * @return {@code true} if this widget displays time in 24-hour mode,
     *         {@code false} otherwise.
     *
     * @see #setIs24Hour(boolean)
     */
    public boolean is24Hour() {
        return mIs24hFormat;
    }

    /**
     * Only meaningful for a 12-hour time.
     *
     * @return {@code true} if the currently selected time is in PM,
     *         {@code false} if the currently selected time in in AM.
     */
    public boolean isPm() {
        return (mCurrentAmPmIndex == PM_INDEX);
    }

    @Override
    public void onColumnValueChanged(int columnIndex, int newValue) {
        if (columnIndex == mColHourIndex) {
            mCurrentHour = newValue;
        } else if (columnIndex == mColMinuteIndex) {
            mCurrentMinute = newValue;
        } else if (columnIndex == mColAmPmIndex) {
            mCurrentAmPmIndex = newValue;
        } else {
            throw new IllegalArgumentException("Invalid column index.");
        }
    }
}