marianomartin a6935dc73d [TimePicker] Added haptic feedback
PiperOrigin-RevId: 319287390
2020-07-06 11:09:40 -04:00

192 lines
6.5 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 static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static android.view.HapticFeedbackConstants.VIRTUAL_KEY;
import static android.view.View.GONE;
import static com.google.android.material.timepicker.TimeFormat.CLOCK_12H;
import static com.google.android.material.timepicker.TimeFormat.CLOCK_24H;
import static java.util.Calendar.MINUTE;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.View;
import com.google.android.material.timepicker.ClockHandView.OnActionUpListener;
import com.google.android.material.timepicker.ClockHandView.OnRotateListener;
import com.google.android.material.timepicker.TimePickerControls.ActiveSelection;
import com.google.android.material.timepicker.TimePickerView.OnPeriodChangeListener;
import com.google.android.material.timepicker.TimePickerView.OnSelectionChange;
import java.util.Calendar;
class TimePickerClockPresenter
implements OnRotateListener,
OnSelectionChange,
OnPeriodChangeListener,
OnActionUpListener,
TimePickerPresenter {
private static final String[] HOUR_CLOCK_VALUES =
{"12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"};
private static final String[] HOUR_CLOCK_24_VALUES =
{"00", "2", "4", "6", "8", "10", "12", "14", "16", "18", "20", "22"};
private static final String[] MINUTE_CLOCK_VALUES =
{"00", "5", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55"};
private static final int DEGREES_PER_HOUR = 30;
private static final int DEGREES_PER_MINUTE = 6;
private TimePickerView timePickerView;
private TimeModel time;
private float minuteRotation;
private float hourRotation;
private boolean broadcasting = false;
public TimePickerClockPresenter(TimePickerView timePickerView, TimeModel time) {
this.timePickerView = timePickerView;
this.time = time;
initialize();
}
@Override
public void initialize() {
if (time.format == CLOCK_12H) {
timePickerView.showToggle();
}
timePickerView.addOnRotateListener(this);
timePickerView.setOnSelectionChangeListener(this);
timePickerView.setOnPeriodChangeListener(this);
timePickerView.setOnActionUpListener(this);
invalidate();
}
@Override
public void invalidate() {
int hourForDisplay = time.getHourForDisplay();
hourRotation = hourForDisplay * getDegreesPerHour();
minuteRotation = time.minute * DEGREES_PER_MINUTE;
setSelection(time.selection, false);
updateTime();
}
@Override
public void show() {
timePickerView.setVisibility(View.VISIBLE);
}
@Override
public void hide() {
timePickerView.setVisibility(GONE);
}
private String[] getHourClockValues() {
return time.format == CLOCK_24H ? HOUR_CLOCK_24_VALUES : HOUR_CLOCK_VALUES;
}
private int getDegreesPerHour() {
return time.format == CLOCK_24H ? DEGREES_PER_HOUR / 2 : DEGREES_PER_HOUR;
}
@Override
public void onRotate(float rotation, boolean animating) {
if (broadcasting) {
return;
}
int prevHour = time.hour;
int prevMinute = time.minute;
int rotationInt = Math.round(rotation);
if (time.selection == MINUTE) {
int minuteOffset = DEGREES_PER_MINUTE / 2;
time.setMinute((rotationInt + minuteOffset) / DEGREES_PER_MINUTE);
minuteRotation = (float) Math.floor(time.minute * DEGREES_PER_MINUTE);
} else {
int hourOffset = getDegreesPerHour() / 2;
time.setHour((rotationInt + hourOffset) / getDegreesPerHour());
hourRotation = time.getHourForDisplay() * getDegreesPerHour();
}
// Do not update the display during an animation
if (!animating) {
updateTime();
performHapticFeedback(prevHour, prevMinute);
}
}
private void performHapticFeedback(int prevHour, int prevMinute) {
if (time.minute != prevMinute || time.hour != prevHour) {
int feedbackKey = VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP ? CLOCK_TICK : VIRTUAL_KEY;
timePickerView.performHapticFeedback(feedbackKey);
}
}
@Override
public void onSelectionChanged(int selection) {
setSelection(selection, true);
}
@Override
public void onPeriodChange(int period) {
time.setPeriod(period);
}
void setSelection(@ActiveSelection int selection, boolean animate) {
boolean isMinute = selection == MINUTE;
// Don't animate hours since we are going to auto switch to the minute selection.
timePickerView.setAnimateOnTouchUp(isMinute);
time.selection = selection;
timePickerView.setValues(isMinute ? MINUTE_CLOCK_VALUES : getHourClockValues());
timePickerView.setHandRotation(isMinute ? minuteRotation : hourRotation, animate);
timePickerView.setActiveSelection(selection);
}
@Override
public void onActionUp(float rotation, boolean moveInEventStream) {
broadcasting = true;
int prevMinute = time.minute;
int prevHour = time.hour;
if (time.selection == Calendar.HOUR) {
// Current rotation might be half way to an exact hour position.
// Snap to the closest hour before animating to the position the minute selection is on.
timePickerView.setHandRotation(hourRotation, /* animate= */ false);
// Automatically move to minutes once the user finishes choosing the hour.
setSelection(MINUTE, /* animate= */ true);
} else {
int rotationInt = Math.round(rotation);
if (!moveInEventStream) {
// snap minute to 5 minute increment if there was only a touch down/up.
int newRotation = (rotationInt + 15) / 30;
time.setMinute(newRotation * 5);
minuteRotation = time.minute * DEGREES_PER_MINUTE;
}
timePickerView.setHandRotation(minuteRotation, /* animate= */ moveInEventStream);
}
broadcasting = false;
updateTime();
performHapticFeedback(prevHour, prevMinute);
}
private void updateTime() {
timePickerView.updateTime(time.period, time.getHourForDisplay(), time.minute);
}
}