287 lines
10 KiB
Java
287 lines
10 KiB
Java
/*
|
|
* 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 android.widget;
|
|
|
|
import android.content.Context;
|
|
import android.os.LocaleList;
|
|
import android.text.Editable;
|
|
import android.text.InputFilter;
|
|
import android.text.TextUtils;
|
|
import android.text.TextWatcher;
|
|
import android.util.AttributeSet;
|
|
import android.util.MathUtils;
|
|
import android.view.View;
|
|
import android.view.accessibility.AccessibilityManager;
|
|
|
|
import com.android.internal.R;
|
|
|
|
/**
|
|
* View to show text input based time picker with hour and minute fields and an optional AM/PM
|
|
* spinner.
|
|
*
|
|
* @hide
|
|
*/
|
|
public class TextInputTimePickerView extends RelativeLayout {
|
|
public static final int HOURS = 0;
|
|
public static final int MINUTES = 1;
|
|
public static final int AMPM = 2;
|
|
|
|
private static final int AM = 0;
|
|
private static final int PM = 1;
|
|
|
|
private final EditText mHourEditText;
|
|
private final EditText mMinuteEditText;
|
|
private final TextView mInputSeparatorView;
|
|
private final Spinner mAmPmSpinner;
|
|
private final TextView mErrorLabel;
|
|
private final TextView mHourLabel;
|
|
private final TextView mMinuteLabel;
|
|
|
|
private boolean mIs24Hour;
|
|
private boolean mHourFormatStartsAtZero;
|
|
private OnValueTypedListener mListener;
|
|
|
|
private boolean mErrorShowing;
|
|
private boolean mTimeSet;
|
|
|
|
interface OnValueTypedListener {
|
|
void onValueChanged(int inputType, int newValue);
|
|
}
|
|
|
|
public TextInputTimePickerView(Context context) {
|
|
this(context, null);
|
|
}
|
|
|
|
public TextInputTimePickerView(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle) {
|
|
this(context, attrs, defStyle, 0);
|
|
}
|
|
|
|
public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle,
|
|
int defStyleRes) {
|
|
super(context, attrs, defStyle, defStyleRes);
|
|
|
|
inflate(context, R.layout.time_picker_text_input_material, this);
|
|
|
|
mHourEditText = findViewById(R.id.input_hour);
|
|
mMinuteEditText = findViewById(R.id.input_minute);
|
|
mInputSeparatorView = findViewById(R.id.input_separator);
|
|
mErrorLabel = findViewById(R.id.label_error);
|
|
mHourLabel = findViewById(R.id.label_hour);
|
|
mMinuteLabel = findViewById(R.id.label_minute);
|
|
|
|
mHourEditText.addTextChangedListener(new TextWatcher() {
|
|
@Override
|
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
|
|
|
@Override
|
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
|
|
|
@Override
|
|
public void afterTextChanged(Editable editable) {
|
|
if (parseAndSetHourInternal(editable.toString()) && editable.length() > 1) {
|
|
AccessibilityManager am = (AccessibilityManager) context.getSystemService(
|
|
context.ACCESSIBILITY_SERVICE);
|
|
if (!am.isEnabled()) {
|
|
mMinuteEditText.requestFocus();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
mMinuteEditText.addTextChangedListener(new TextWatcher() {
|
|
@Override
|
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
|
|
|
@Override
|
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
|
|
|
@Override
|
|
public void afterTextChanged(Editable editable) {
|
|
parseAndSetMinuteInternal(editable.toString());
|
|
}
|
|
});
|
|
|
|
mAmPmSpinner = findViewById(R.id.am_pm_spinner);
|
|
final String[] amPmStrings = TimePicker.getAmPmStrings(context);
|
|
ArrayAdapter<CharSequence> adapter =
|
|
new ArrayAdapter<CharSequence>(context, R.layout.simple_spinner_dropdown_item);
|
|
adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[0]));
|
|
adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[1]));
|
|
mAmPmSpinner.setAdapter(adapter);
|
|
mAmPmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
|
@Override
|
|
public void onItemSelected(AdapterView<?> adapterView, View view, int position,
|
|
long id) {
|
|
if (position == 0) {
|
|
mListener.onValueChanged(AMPM, AM);
|
|
} else {
|
|
mListener.onValueChanged(AMPM, PM);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onNothingSelected(AdapterView<?> adapterView) {}
|
|
});
|
|
}
|
|
|
|
void setListener(OnValueTypedListener listener) {
|
|
mListener = listener;
|
|
}
|
|
|
|
void setHourFormat(int maxCharLength) {
|
|
mHourEditText.setFilters(new InputFilter[] {
|
|
new InputFilter.LengthFilter(maxCharLength)});
|
|
mMinuteEditText.setFilters(new InputFilter[] {
|
|
new InputFilter.LengthFilter(maxCharLength)});
|
|
final LocaleList locales = mContext.getResources().getConfiguration().getLocales();
|
|
mHourEditText.setImeHintLocales(locales);
|
|
mMinuteEditText.setImeHintLocales(locales);
|
|
}
|
|
|
|
boolean validateInput() {
|
|
final String hourText = TextUtils.isEmpty(mHourEditText.getText())
|
|
? mHourEditText.getHint().toString()
|
|
: mHourEditText.getText().toString();
|
|
final String minuteText = TextUtils.isEmpty(mMinuteEditText.getText())
|
|
? mMinuteEditText.getHint().toString()
|
|
: mMinuteEditText.getText().toString();
|
|
|
|
final boolean inputValid = parseAndSetHourInternal(hourText)
|
|
&& parseAndSetMinuteInternal(minuteText);
|
|
setError(!inputValid);
|
|
return inputValid;
|
|
}
|
|
|
|
void updateSeparator(String separatorText) {
|
|
mInputSeparatorView.setText(separatorText);
|
|
}
|
|
|
|
private void setError(boolean enabled) {
|
|
mErrorShowing = enabled;
|
|
|
|
mErrorLabel.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
|
|
mHourLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
|
|
mMinuteLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
|
|
}
|
|
|
|
private void setTimeSet(boolean timeSet) {
|
|
mTimeSet = mTimeSet || timeSet;
|
|
}
|
|
|
|
private boolean isTimeSet() {
|
|
return mTimeSet;
|
|
}
|
|
|
|
/**
|
|
* Computes the display value and updates the text of the view.
|
|
* <p>
|
|
* This method should be called whenever the current value or display
|
|
* properties (leading zeroes, max digits) change.
|
|
*/
|
|
void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour,
|
|
boolean hourFormatStartsAtZero) {
|
|
final String hourFormat = "%d";
|
|
final String minuteFormat = "%02d";
|
|
|
|
mIs24Hour = is24Hour;
|
|
mHourFormatStartsAtZero = hourFormatStartsAtZero;
|
|
|
|
mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE);
|
|
|
|
if (amOrPm == AM) {
|
|
mAmPmSpinner.setSelection(0);
|
|
} else {
|
|
mAmPmSpinner.setSelection(1);
|
|
}
|
|
|
|
if (isTimeSet()) {
|
|
mHourEditText.setText(String.format(hourFormat, localizedHour));
|
|
mMinuteEditText.setText(String.format(minuteFormat, minute));
|
|
} else {
|
|
mHourEditText.setHint(String.format(hourFormat, localizedHour));
|
|
mMinuteEditText.setHint(String.format(minuteFormat, minute));
|
|
}
|
|
|
|
|
|
if (mErrorShowing) {
|
|
validateInput();
|
|
}
|
|
}
|
|
|
|
private boolean parseAndSetHourInternal(String input) {
|
|
try {
|
|
final int hour = Integer.parseInt(input);
|
|
if (!isValidLocalizedHour(hour)) {
|
|
final int minHour = mHourFormatStartsAtZero ? 0 : 1;
|
|
final int maxHour = mIs24Hour ? 23 : 11 + minHour;
|
|
mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(
|
|
MathUtils.constrain(hour, minHour, maxHour)));
|
|
return false;
|
|
}
|
|
mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(hour));
|
|
setTimeSet(true);
|
|
return true;
|
|
} catch (NumberFormatException e) {
|
|
// Do nothing since we cannot parse the input.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private boolean parseAndSetMinuteInternal(String input) {
|
|
try {
|
|
final int minutes = Integer.parseInt(input);
|
|
if (minutes < 0 || minutes > 59) {
|
|
mListener.onValueChanged(MINUTES, MathUtils.constrain(minutes, 0, 59));
|
|
return false;
|
|
}
|
|
mListener.onValueChanged(MINUTES, minutes);
|
|
setTimeSet(true);
|
|
return true;
|
|
} catch (NumberFormatException e) {
|
|
// Do nothing since we cannot parse the input.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private boolean isValidLocalizedHour(int localizedHour) {
|
|
final int minHour = mHourFormatStartsAtZero ? 0 : 1;
|
|
final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
|
|
return localizedHour >= minHour && localizedHour <= maxHour;
|
|
}
|
|
|
|
private int getHourOfDayFromLocalizedHour(int localizedHour) {
|
|
int hourOfDay = localizedHour;
|
|
if (mIs24Hour) {
|
|
if (!mHourFormatStartsAtZero && localizedHour == 24) {
|
|
hourOfDay = 0;
|
|
}
|
|
} else {
|
|
if (!mHourFormatStartsAtZero && localizedHour == 12) {
|
|
hourOfDay = 0;
|
|
}
|
|
if (mAmPmSpinner.getSelectedItemPosition() == 1) {
|
|
hourOfDay += 12;
|
|
}
|
|
}
|
|
return hourOfDay;
|
|
}
|
|
}
|