2 * Copyright (C) 2017 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.widget;
19 import android.content.Context;
20 import android.text.Editable;
21 import android.text.InputFilter;
22 import android.text.TextWatcher;
23 import android.util.AttributeSet;
24 import android.util.MathUtils;
25 import android.view.View;
27 import com.android.internal.R;
30 * View to show text input based time picker with hour and minute fields and an optional AM/PM
35 public class TextInputTimePickerView extends RelativeLayout {
36 public static final int HOURS = 0;
37 public static final int MINUTES = 1;
38 public static final int AMPM = 2;
40 private static final int AM = 0;
41 private static final int PM = 1;
43 private final EditText mHourEditText;
44 private final EditText mMinuteEditText;
45 private final TextView mInputSeparatorView;
46 private final Spinner mAmPmSpinner;
47 private final TextView mErrorLabel;
48 private final TextView mHourLabel;
49 private final TextView mMinuteLabel;
51 private boolean mIs24Hour;
52 private boolean mHourFormatStartsAtZero;
53 private OnValueTypedListener mListener;
55 private boolean mErrorShowing;
57 interface OnValueTypedListener {
58 void onValueChanged(int inputType, int newValue);
61 public TextInputTimePickerView(Context context) {
65 public TextInputTimePickerView(Context context, AttributeSet attrs) {
66 this(context, attrs, 0);
69 public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle) {
70 this(context, attrs, defStyle, 0);
73 public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle,
75 super(context, attrs, defStyle, defStyleRes);
77 inflate(context, R.layout.time_picker_text_input_material, this);
79 mHourEditText = findViewById(R.id.input_hour);
80 mMinuteEditText = findViewById(R.id.input_minute);
81 mInputSeparatorView = findViewById(R.id.input_separator);
82 mErrorLabel = findViewById(R.id.label_error);
83 mHourLabel = findViewById(R.id.label_hour);
84 mMinuteLabel = findViewById(R.id.label_minute);
86 mHourEditText.addTextChangedListener(new TextWatcher() {
88 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
91 public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
94 public void afterTextChanged(Editable editable) {
95 parseAndSetHourInternal(editable.toString());
99 mMinuteEditText.addTextChangedListener(new TextWatcher() {
101 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
104 public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
107 public void afterTextChanged(Editable editable) {
108 parseAndSetMinuteInternal(editable.toString());
112 mAmPmSpinner = findViewById(R.id.am_pm_spinner);
113 final String[] amPmStrings = TimePicker.getAmPmStrings(context);
114 ArrayAdapter<CharSequence> adapter =
115 new ArrayAdapter<CharSequence>(context, R.layout.simple_spinner_dropdown_item);
116 adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[0]));
117 adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[1]));
118 mAmPmSpinner.setAdapter(adapter);
119 mAmPmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
121 public void onItemSelected(AdapterView<?> adapterView, View view, int position,
124 mListener.onValueChanged(AMPM, AM);
126 mListener.onValueChanged(AMPM, PM);
131 public void onNothingSelected(AdapterView<?> adapterView) {}
135 void setListener(OnValueTypedListener listener) {
136 mListener = listener;
139 void setHourFormat(int maxCharLength) {
140 mHourEditText.setFilters(new InputFilter[] {
141 new InputFilter.LengthFilter(maxCharLength)});
142 mMinuteEditText.setFilters(new InputFilter[] {
143 new InputFilter.LengthFilter(maxCharLength)});
146 boolean validateInput() {
147 final boolean inputValid = parseAndSetHourInternal(mHourEditText.getText().toString())
148 && parseAndSetMinuteInternal(mMinuteEditText.getText().toString());
149 setError(!inputValid);
153 void updateSeparator(String separatorText) {
154 mInputSeparatorView.setText(separatorText);
157 private void setError(boolean enabled) {
158 mErrorShowing = enabled;
160 mErrorLabel.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
161 mHourLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
162 mMinuteLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
166 * Computes the display value and updates the text of the view.
168 * This method should be called whenever the current value or display
169 * properties (leading zeroes, max digits) change.
171 void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour,
172 boolean hourFormatStartsAtZero) {
173 final String format = "%d";
175 mIs24Hour = is24Hour;
176 mHourFormatStartsAtZero = hourFormatStartsAtZero;
178 mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE);
181 mAmPmSpinner.setSelection(0);
183 mAmPmSpinner.setSelection(1);
186 mHourEditText.setText(String.format(format, localizedHour));
187 mMinuteEditText.setText(String.format(format, minute));
194 private boolean parseAndSetHourInternal(String input) {
196 final int hour = Integer.parseInt(input);
197 if (!isValidLocalizedHour(hour)) {
198 final int minHour = mHourFormatStartsAtZero ? 0 : 1;
199 final int maxHour = mIs24Hour ? 23 : 11 + minHour;
200 mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(
201 MathUtils.constrain(hour, minHour, maxHour)));
204 mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(hour));
206 } catch (NumberFormatException e) {
207 // Do nothing since we cannot parse the input.
212 private boolean parseAndSetMinuteInternal(String input) {
214 final int minutes = Integer.parseInt(input);
215 if (minutes < 0 || minutes > 59) {
216 mListener.onValueChanged(MINUTES, MathUtils.constrain(minutes, 0, 59));
219 mListener.onValueChanged(MINUTES, minutes);
221 } catch (NumberFormatException e) {
222 // Do nothing since we cannot parse the input.
227 private boolean isValidLocalizedHour(int localizedHour) {
228 final int minHour = mHourFormatStartsAtZero ? 0 : 1;
229 final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
230 return localizedHour >= minHour && localizedHour <= maxHour;
233 private int getHourOfDayFromLocalizedHour(int localizedHour) {
234 int hourOfDay = localizedHour;
236 if (!mHourFormatStartsAtZero && localizedHour == 24) {
240 if (!mHourFormatStartsAtZero && localizedHour == 12) {
243 if (mAmPmSpinner.getSelectedItemPosition() == 1) {