TextInputTimePickerView.java revision f3cda89def7386db5d74fa76a36a911710e02f1c
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package android.widget; 18 19import android.content.Context; 20import android.os.LocaleList; 21import android.text.Editable; 22import android.text.InputFilter; 23import android.text.TextWatcher; 24import android.util.AttributeSet; 25import android.util.MathUtils; 26import android.view.View; 27 28import com.android.internal.R; 29 30/** 31 * View to show text input based time picker with hour and minute fields and an optional AM/PM 32 * spinner. 33 * 34 * @hide 35 */ 36public class TextInputTimePickerView extends RelativeLayout { 37 public static final int HOURS = 0; 38 public static final int MINUTES = 1; 39 public static final int AMPM = 2; 40 41 private static final int AM = 0; 42 private static final int PM = 1; 43 44 private final EditText mHourEditText; 45 private final EditText mMinuteEditText; 46 private final TextView mInputSeparatorView; 47 private final Spinner mAmPmSpinner; 48 private final TextView mErrorLabel; 49 private final TextView mHourLabel; 50 private final TextView mMinuteLabel; 51 52 private boolean mIs24Hour; 53 private boolean mHourFormatStartsAtZero; 54 private OnValueTypedListener mListener; 55 56 private boolean mErrorShowing; 57 58 interface OnValueTypedListener { 59 void onValueChanged(int inputType, int newValue); 60 } 61 62 public TextInputTimePickerView(Context context) { 63 this(context, null); 64 } 65 66 public TextInputTimePickerView(Context context, AttributeSet attrs) { 67 this(context, attrs, 0); 68 } 69 70 public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle) { 71 this(context, attrs, defStyle, 0); 72 } 73 74 public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle, 75 int defStyleRes) { 76 super(context, attrs, defStyle, defStyleRes); 77 78 inflate(context, R.layout.time_picker_text_input_material, this); 79 80 mHourEditText = findViewById(R.id.input_hour); 81 mMinuteEditText = findViewById(R.id.input_minute); 82 mInputSeparatorView = findViewById(R.id.input_separator); 83 mErrorLabel = findViewById(R.id.label_error); 84 mHourLabel = findViewById(R.id.label_hour); 85 mMinuteLabel = findViewById(R.id.label_minute); 86 87 mHourEditText.addTextChangedListener(new TextWatcher() { 88 @Override 89 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} 90 91 @Override 92 public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} 93 94 @Override 95 public void afterTextChanged(Editable editable) { 96 parseAndSetHourInternal(editable.toString()); 97 } 98 }); 99 100 mMinuteEditText.addTextChangedListener(new TextWatcher() { 101 @Override 102 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} 103 104 @Override 105 public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} 106 107 @Override 108 public void afterTextChanged(Editable editable) { 109 parseAndSetMinuteInternal(editable.toString()); 110 } 111 }); 112 113 mAmPmSpinner = findViewById(R.id.am_pm_spinner); 114 final String[] amPmStrings = TimePicker.getAmPmStrings(context); 115 ArrayAdapter<CharSequence> adapter = 116 new ArrayAdapter<CharSequence>(context, R.layout.simple_spinner_dropdown_item); 117 adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[0])); 118 adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[1])); 119 mAmPmSpinner.setAdapter(adapter); 120 mAmPmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 121 @Override 122 public void onItemSelected(AdapterView<?> adapterView, View view, int position, 123 long id) { 124 if (position == 0) { 125 mListener.onValueChanged(AMPM, AM); 126 } else { 127 mListener.onValueChanged(AMPM, PM); 128 } 129 } 130 131 @Override 132 public void onNothingSelected(AdapterView<?> adapterView) {} 133 }); 134 } 135 136 void setListener(OnValueTypedListener listener) { 137 mListener = listener; 138 } 139 140 void setHourFormat(int maxCharLength) { 141 mHourEditText.setFilters(new InputFilter[] { 142 new InputFilter.LengthFilter(maxCharLength)}); 143 mMinuteEditText.setFilters(new InputFilter[] { 144 new InputFilter.LengthFilter(maxCharLength)}); 145 final LocaleList locales = mContext.getResources().getConfiguration().getLocales(); 146 mHourEditText.setImeHintLocales(locales); 147 mMinuteEditText.setImeHintLocales(locales); 148 } 149 150 boolean validateInput() { 151 final boolean inputValid = parseAndSetHourInternal(mHourEditText.getText().toString()) 152 && parseAndSetMinuteInternal(mMinuteEditText.getText().toString()); 153 setError(!inputValid); 154 return inputValid; 155 } 156 157 void updateSeparator(String separatorText) { 158 mInputSeparatorView.setText(separatorText); 159 } 160 161 private void setError(boolean enabled) { 162 mErrorShowing = enabled; 163 164 mErrorLabel.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); 165 mHourLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE); 166 mMinuteLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE); 167 } 168 169 /** 170 * Computes the display value and updates the text of the view. 171 * <p> 172 * This method should be called whenever the current value or display 173 * properties (leading zeroes, max digits) change. 174 */ 175 void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour, 176 boolean hourFormatStartsAtZero) { 177 final String format = "%d"; 178 179 mIs24Hour = is24Hour; 180 mHourFormatStartsAtZero = hourFormatStartsAtZero; 181 182 mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE); 183 184 if (amOrPm == AM) { 185 mAmPmSpinner.setSelection(0); 186 } else { 187 mAmPmSpinner.setSelection(1); 188 } 189 190 mHourEditText.setText(String.format(format, localizedHour)); 191 mMinuteEditText.setText(String.format(format, minute)); 192 193 if (mErrorShowing) { 194 validateInput(); 195 } 196 } 197 198 private boolean parseAndSetHourInternal(String input) { 199 try { 200 final int hour = Integer.parseInt(input); 201 if (!isValidLocalizedHour(hour)) { 202 final int minHour = mHourFormatStartsAtZero ? 0 : 1; 203 final int maxHour = mIs24Hour ? 23 : 11 + minHour; 204 mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour( 205 MathUtils.constrain(hour, minHour, maxHour))); 206 return false; 207 } 208 mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(hour)); 209 return true; 210 } catch (NumberFormatException e) { 211 // Do nothing since we cannot parse the input. 212 return false; 213 } 214 } 215 216 private boolean parseAndSetMinuteInternal(String input) { 217 try { 218 final int minutes = Integer.parseInt(input); 219 if (minutes < 0 || minutes > 59) { 220 mListener.onValueChanged(MINUTES, MathUtils.constrain(minutes, 0, 59)); 221 return false; 222 } 223 mListener.onValueChanged(MINUTES, minutes); 224 return true; 225 } catch (NumberFormatException e) { 226 // Do nothing since we cannot parse the input. 227 return false; 228 } 229 } 230 231 private boolean isValidLocalizedHour(int localizedHour) { 232 final int minHour = mHourFormatStartsAtZero ? 0 : 1; 233 final int maxHour = (mIs24Hour ? 23 : 11) + minHour; 234 return localizedHour >= minHour && localizedHour <= maxHour; 235 } 236 237 private int getHourOfDayFromLocalizedHour(int localizedHour) { 238 int hourOfDay = localizedHour; 239 if (mIs24Hour) { 240 if (!mHourFormatStartsAtZero && localizedHour == 24) { 241 hourOfDay = 0; 242 } 243 } else { 244 if (!mHourFormatStartsAtZero && localizedHour == 12) { 245 hourOfDay = 0; 246 } 247 if (mAmPmSpinner.getSelectedItemPosition() == 1) { 248 hourOfDay += 12; 249 } 250 } 251 return hourOfDay; 252 } 253} 254