mirror of
https://github.com/material-components/material-components-android.git
synced 2026-01-18 02:51:50 +08:00
Before the CL we set AM/PM selection in the listeners no matter the target view (AM/PM) is being checked or being unchecked. So the order of calling listeners of AM and PM can "revert" the selection, if the callback for being unchecked is called later. This logic is apparently wrong - we should select AM/PM when they are being unchecked anyways. Resolves https://github.com/material-components/material-components-android/pull/2791 Resolves https://github.com/material-components/material-components-android/issues/2788 GIT_ORIGIN_REV_ID=018e053394f44d6334827fc3f95406c2937ae582 PiperOrigin-RevId: 458027987
285 lines
9.9 KiB
Java
285 lines
9.9 KiB
Java
/*
|
|
* Copyright (C) 2020 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 com.google.android.material.timepicker;
|
|
|
|
import com.google.android.material.R;
|
|
|
|
import static android.view.View.GONE;
|
|
import static com.google.android.material.timepicker.TimeFormat.CLOCK_12H;
|
|
import static java.util.Calendar.AM;
|
|
import static java.util.Calendar.HOUR;
|
|
import static java.util.Calendar.MINUTE;
|
|
import static java.util.Calendar.PM;
|
|
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Build.VERSION;
|
|
import android.os.Build.VERSION_CODES;
|
|
import androidx.appcompat.content.res.AppCompatResources;
|
|
import android.text.Editable;
|
|
import android.text.TextUtils;
|
|
import android.text.TextWatcher;
|
|
import android.view.View;
|
|
import android.view.View.OnClickListener;
|
|
import android.widget.EditText;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
import androidx.annotation.ColorInt;
|
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
|
import com.google.android.material.button.MaterialButtonToggleGroup;
|
|
import com.google.android.material.color.MaterialColors;
|
|
import com.google.android.material.internal.TextWatcherAdapter;
|
|
import com.google.android.material.internal.ViewUtils;
|
|
import com.google.android.material.timepicker.TimePickerView.OnSelectionChange;
|
|
import java.lang.reflect.Field;
|
|
import java.util.Locale;
|
|
|
|
class TimePickerTextInputPresenter implements OnSelectionChange, TimePickerPresenter {
|
|
|
|
private final LinearLayout timePickerView;
|
|
private final TimeModel time;
|
|
private final TextWatcher minuteTextWatcher =
|
|
new TextWatcherAdapter() {
|
|
@Override
|
|
public void afterTextChanged(Editable s) {
|
|
try {
|
|
if (TextUtils.isEmpty(s)) {
|
|
time.setMinute(0);
|
|
return;
|
|
}
|
|
int minute = Integer.parseInt(s.toString());
|
|
time.setMinute(minute);
|
|
} catch (NumberFormatException ok) {
|
|
// ignore invalid input
|
|
}
|
|
}
|
|
};
|
|
|
|
private final TextWatcher hourTextWatcher =
|
|
new TextWatcherAdapter() {
|
|
@Override
|
|
public void afterTextChanged(Editable s) {
|
|
try {
|
|
if (TextUtils.isEmpty(s)) {
|
|
time.setHour(0);
|
|
return;
|
|
}
|
|
int hour = Integer.parseInt(s.toString());
|
|
time.setHour(hour);
|
|
} catch (NumberFormatException ok) {
|
|
// ignore invalid input
|
|
}
|
|
}
|
|
};
|
|
private final ChipTextInputComboView minuteTextInput;
|
|
private final ChipTextInputComboView hourTextInput;
|
|
private final TimePickerTextInputKeyController controller;
|
|
private final EditText hourEditText;
|
|
private final EditText minuteEditText;
|
|
private MaterialButtonToggleGroup toggle;
|
|
|
|
public TimePickerTextInputPresenter(final LinearLayout timePickerView, final TimeModel time) {
|
|
this.timePickerView = timePickerView;
|
|
this.time = time;
|
|
Resources res = timePickerView.getResources();
|
|
minuteTextInput = timePickerView.findViewById(R.id.material_minute_text_input);
|
|
hourTextInput = timePickerView.findViewById(R.id.material_hour_text_input);
|
|
TextView minuteLabel = minuteTextInput.findViewById(R.id.material_label);
|
|
TextView hourLabel = hourTextInput.findViewById(R.id.material_label);
|
|
|
|
minuteLabel.setText(res.getString(R.string.material_timepicker_minute));
|
|
hourLabel.setText(res.getString(R.string.material_timepicker_hour));
|
|
minuteTextInput.setTag(R.id.selection_type, MINUTE);
|
|
hourTextInput.setTag(R.id.selection_type, HOUR);
|
|
|
|
if (time.format == CLOCK_12H) {
|
|
setupPeriodToggle();
|
|
}
|
|
|
|
OnClickListener onClickListener =
|
|
new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
onSelectionChanged((int) v.getTag(R.id.selection_type));
|
|
}
|
|
};
|
|
|
|
hourTextInput.setOnClickListener(onClickListener);
|
|
minuteTextInput.setOnClickListener(onClickListener);
|
|
hourTextInput.addInputFilter(time.getHourInputValidator());
|
|
minuteTextInput.addInputFilter(time.getMinuteInputValidator());
|
|
|
|
hourEditText = hourTextInput.getTextInput().getEditText();
|
|
minuteEditText = minuteTextInput.getTextInput().getEditText();
|
|
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
|
|
// Our XML drawable is not colored for pre-lollipop, set color programmatically.
|
|
int primaryColor = MaterialColors.getColor(timePickerView, R.attr.colorPrimary);
|
|
setCursorDrawableColor(hourEditText, primaryColor);
|
|
setCursorDrawableColor(minuteEditText, primaryColor);
|
|
}
|
|
|
|
controller = new TimePickerTextInputKeyController(hourTextInput, minuteTextInput, time);
|
|
hourTextInput.setChipDelegate(
|
|
new ClickActionDelegate(timePickerView.getContext(), R.string.material_hour_selection) {
|
|
@Override
|
|
public void onInitializeAccessibilityNodeInfo(
|
|
View host, AccessibilityNodeInfoCompat info) {
|
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
|
info.setContentDescription(
|
|
host.getResources()
|
|
.getString(
|
|
R.string.material_hour_suffix, String.valueOf(time.getHourForDisplay())));
|
|
}
|
|
});
|
|
minuteTextInput.setChipDelegate(
|
|
new ClickActionDelegate(timePickerView.getContext(), R.string.material_minute_selection) {
|
|
@Override
|
|
public void onInitializeAccessibilityNodeInfo(
|
|
View host, AccessibilityNodeInfoCompat info) {
|
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
|
info.setContentDescription(
|
|
host.getResources()
|
|
.getString(R.string.material_minute_suffix, String.valueOf(time.minute)));
|
|
}
|
|
});
|
|
|
|
initialize();
|
|
}
|
|
|
|
@Override
|
|
public void initialize() {
|
|
addTextWatchers();
|
|
setTime(time);
|
|
controller.bind();
|
|
}
|
|
|
|
private void addTextWatchers() {
|
|
hourEditText.addTextChangedListener(hourTextWatcher);
|
|
minuteEditText.addTextChangedListener(minuteTextWatcher);
|
|
}
|
|
|
|
private void removeTextWatchers() {
|
|
hourEditText.removeTextChangedListener(hourTextWatcher);
|
|
minuteEditText.removeTextChangedListener(minuteTextWatcher);
|
|
}
|
|
|
|
private void setTime(TimeModel time) {
|
|
removeTextWatchers();
|
|
Locale current = timePickerView.getResources().getConfiguration().locale;
|
|
String minuteFormatted = String.format(current, "%02d", time.minute);
|
|
String hourFormatted = String.format(current, "%02d", time.getHourForDisplay());
|
|
minuteTextInput.setText(minuteFormatted);
|
|
hourTextInput.setText(hourFormatted);
|
|
addTextWatchers();
|
|
updateSelection();
|
|
}
|
|
|
|
private void setupPeriodToggle() {
|
|
toggle = timePickerView.findViewById(R.id.material_clock_period_toggle);
|
|
|
|
toggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
|
|
if (!isChecked) {
|
|
return;
|
|
}
|
|
|
|
int period = checkedId == R.id.material_clock_period_pm_button ? PM : AM;
|
|
time.setPeriod(period);
|
|
});
|
|
|
|
toggle.setVisibility(View.VISIBLE);
|
|
updateSelection();
|
|
}
|
|
|
|
private void updateSelection() {
|
|
if (toggle == null) {
|
|
return;
|
|
}
|
|
|
|
toggle.check(
|
|
time.period == AM
|
|
? R.id.material_clock_period_am_button
|
|
: R.id.material_clock_period_pm_button);
|
|
}
|
|
|
|
@Override
|
|
public void onSelectionChanged(int selection) {
|
|
time.selection = selection;
|
|
minuteTextInput.setChecked(selection == MINUTE);
|
|
hourTextInput.setChecked(selection == HOUR);
|
|
updateSelection();
|
|
}
|
|
|
|
@Override
|
|
public void show() {
|
|
timePickerView.setVisibility(View.VISIBLE);
|
|
onSelectionChanged(time.selection);
|
|
}
|
|
|
|
@Override
|
|
public void hide() {
|
|
View currentFocus = timePickerView.getFocusedChild();
|
|
if (currentFocus != null) {
|
|
ViewUtils.hideKeyboard(currentFocus);
|
|
}
|
|
|
|
timePickerView.setVisibility(GONE);
|
|
}
|
|
|
|
@Override
|
|
public void invalidate() {
|
|
setTime(time);
|
|
}
|
|
|
|
/*
|
|
* android:textColorDrawable doesn't have an app compat version to be able to use theme attributes
|
|
* for colors. We have to apply a color filter manually. This method is only meant to be used
|
|
* before API 21.
|
|
*/
|
|
private static void setCursorDrawableColor(EditText view, @ColorInt int color) {
|
|
try {
|
|
Context context = view.getContext();
|
|
Field cursorDrawableResField = TextView.class.getDeclaredField("mCursorDrawableRes");
|
|
cursorDrawableResField.setAccessible(true);
|
|
int cursorDrawableResId = cursorDrawableResField.getInt(view);
|
|
Field editorField = TextView.class.getDeclaredField("mEditor");
|
|
editorField.setAccessible(true);
|
|
Object editor = editorField.get(view);
|
|
Class<?> clazz = editor.getClass();
|
|
Field cursorDrawableField = clazz.getDeclaredField("mCursorDrawable");
|
|
cursorDrawableField.setAccessible(true);
|
|
Drawable drawable = AppCompatResources.getDrawable(context, cursorDrawableResId);
|
|
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
|
Drawable[] drawables = {drawable, drawable};
|
|
cursorDrawableField.set(editor, drawables);
|
|
} catch (Throwable ignored) {
|
|
// ignore use the drawable default color (black).
|
|
}
|
|
}
|
|
|
|
public void resetChecked() {
|
|
minuteTextInput.setChecked(time.selection == MINUTE);
|
|
hourTextInput.setChecked(time.selection == HOUR);
|
|
}
|
|
|
|
public void clearCheck() {
|
|
minuteTextInput.setChecked(false);
|
|
hourTextInput.setChecked(false);
|
|
}
|
|
}
|