Fix TalkBack content descriptions for MaterialDatePicker and improve scrolling logic

PiperOrigin-RevId: 261849633
This commit is contained in:
ldjesper 2019-08-06 02:45:23 -04:00 committed by Daniel Nizri
parent 3da7ddc314
commit fc195cfe10
8 changed files with 112 additions and 36 deletions

View File

@ -74,6 +74,31 @@ class DateStrings {
}
}
static String getMonthDayOfWeekDay(long timeInMillis) {
if (VERSION.SDK_INT >= VERSION_CODES.N) {
DateFormat df =
DateFormat.getInstanceForSkeleton(DateFormat.ABBR_MONTH_WEEKDAY_DAY, Locale.getDefault());
return df.format(new Date(timeInMillis));
} else {
java.text.DateFormat df =
java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL, Locale.getDefault());
return df.format(new Date(timeInMillis));
}
}
static String getYearMonthDayOfWeekDay(long timeInMillis) {
if (VERSION.SDK_INT >= VERSION_CODES.N) {
DateFormat df =
DateFormat.getInstanceForSkeleton(
DateFormat.YEAR_ABBR_MONTH_WEEKDAY_DAY, Locale.getDefault());
return df.format(new Date(timeInMillis));
} else {
java.text.DateFormat df =
java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL, Locale.getDefault());
return df.format(new Date(timeInMillis));
}
}
static String getDateString(long timeInMillis) {
return getDateString(timeInMillis, null);
}

View File

@ -43,6 +43,7 @@ class DaysOfWeekAdapter extends BaseAdapter {
private final int firstDayOfWeek;
/** Style value from Calendar.NARROW_FORMAT unavailable before 1.8 */
private static final int NARROW_FORMAT = 4;
private static final int CALENDAR_DAY_STYLE =
VERSION.SDK_INT >= VERSION_CODES.O ? NARROW_FORMAT : Calendar.SHORT;
@ -84,6 +85,10 @@ class DaysOfWeekAdapter extends BaseAdapter {
calendar.set(Calendar.DAY_OF_WEEK, positionToDayOfWeek(position));
dayOfWeek.setText(
calendar.getDisplayName(Calendar.DAY_OF_WEEK, CALENDAR_DAY_STYLE, Locale.getDefault()));
dayOfWeek.setContentDescription(
String.format(
parent.getContext().getString(R.string.mtrl_picker_day_of_week_column_header),
calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault())));
return dayOfWeek;
}

View File

@ -68,6 +68,7 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
private static final String GRID_SELECTOR_KEY = "GRID_SELECTOR_KEY";
private static final String CALENDAR_CONSTRAINTS_KEY = "CALENDAR_CONSTRAINTS_KEY";
private static final String CURRENT_MONTH_KEY = "CURRENT_MONTH_KEY";
private static final int SMOOTH_SCROLL_MAX = 3;
@VisibleForTesting
@RestrictTo(Scope.LIBRARY_GROUP)
@ -83,7 +84,6 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
private RecyclerView recyclerView;
private View yearFrame;
private View dayFrame;
private MaterialButton monthDropSelect;
static <T> MaterialCalendar<T> newInstance(
DateSelector<T> dateSelector, int themeResId, CalendarConstraints calendarConstraints) {
@ -140,27 +140,38 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
View root = themedInflater.inflate(layout, viewGroup, false);
GridView daysHeader = root.findViewById(R.id.mtrl_calendar_days_of_week);
ViewCompat.setAccessibilityDelegate(
daysHeader,
new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(
View view, AccessibilityNodeInfoCompat accessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(view, accessibilityNodeInfoCompat);
// Remove announcing row/col info.
accessibilityNodeInfoCompat.setCollectionInfo(null);
}
});
daysHeader.setAdapter(new DaysOfWeekAdapter());
daysHeader.setNumColumns(earliestMonth.daysInWeek);
daysHeader.setEnabled(false);
final RecyclerView monthsPager = root.findViewById(R.id.mtrl_calendar_months);
recyclerView = root.findViewById(R.id.mtrl_calendar_months);
LinearLayoutManager layoutManager =
new LinearLayoutManager(getContext(), orientation, false) {
@Override
protected void calculateExtraLayoutSpace(@NonNull State state, @NonNull int[] ints) {
if (orientation == LinearLayoutManager.HORIZONTAL) {
ints[0] = monthsPager.getWidth();
ints[1] = monthsPager.getWidth();
ints[0] = recyclerView.getWidth();
ints[1] = recyclerView.getWidth();
} else {
ints[0] = monthsPager.getHeight();
ints[1] = monthsPager.getHeight();
ints[0] = recyclerView.getHeight();
ints[1] = recyclerView.getHeight();
}
}
};
monthsPager.setLayoutManager(layoutManager);
monthsPager.setTag(MONTHS_VIEW_GROUP_TAG);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setTag(MONTHS_VIEW_GROUP_TAG);
final MonthsPagerAdapter monthsPagerAdapter =
new MonthsPagerAdapter(
@ -177,14 +188,14 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
listener.onSelectionChanged(dateSelector.getSelection());
}
// TODO(b/134663744): Look into monthsPager.getAdapter().notifyItemRangeChanged();
monthsPager.getAdapter().notifyDataSetChanged();
recyclerView.getAdapter().notifyDataSetChanged();
if (yearSelector != null) {
yearSelector.getAdapter().notifyDataSetChanged();
}
}
}
});
monthsPager.setAdapter(monthsPagerAdapter);
recyclerView.setAdapter(monthsPagerAdapter);
int columns =
themedContext.getResources().getInteger(R.integer.mtrl_calendar_year_selector_span);
@ -202,9 +213,9 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
}
if (!MaterialDatePicker.isFullscreen(themedContext)) {
new LinearSnapHelper().attachToRecyclerView(monthsPager);
new LinearSnapHelper().attachToRecyclerView(recyclerView);
}
monthsPager.scrollToPosition(monthsPagerAdapter.getPosition(current));
recyclerView.scrollToPosition(monthsPagerAdapter.getPosition(current));
return root;
}
@ -275,16 +286,20 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
* CalendarConstraints}.
*/
void setCurrentMonth(Month moveTo) {
setCurrentMonth(moveTo, /* smooth= */ true);
}
void setCurrentMonth(Month moveTo, boolean smooth) {
MonthsPagerAdapter adapter = (MonthsPagerAdapter) recyclerView.getAdapter();
int moveToPosition = adapter.getPosition(moveTo);
int distance = moveToPosition - adapter.getPosition(current);
boolean jump = Math.abs(distance) > SMOOTH_SCROLL_MAX;
boolean isForward = distance > 0;
current = moveTo;
int moveToPosition = ((MonthsPagerAdapter) recyclerView.getAdapter()).getPosition(current);
if (smooth) {
if (jump && isForward) {
recyclerView.scrollToPosition(moveToPosition - SMOOTH_SCROLL_MAX);
recyclerView.smoothScrollToPosition(moveToPosition);
} else if (jump) {
recyclerView.scrollToPosition(moveToPosition + SMOOTH_SCROLL_MAX);
recyclerView.smoothScrollToPosition(moveToPosition);
} else {
recyclerView.scrollToPosition(moveToPosition);
recyclerView.smoothScrollToPosition(moveToPosition);
}
}
@ -334,8 +349,7 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
private void addActionsToMonthNavigation(
final View root, final MonthsPagerAdapter monthsPagerAdapter) {
recyclerView = root.findViewById(R.id.mtrl_calendar_months);
monthDropSelect = root.findViewById(R.id.month_navigation_fragment_toggle);
final MaterialButton monthDropSelect = root.findViewById(R.id.month_navigation_fragment_toggle);
ViewCompat.setAccessibilityDelegate(
monthDropSelect,
new AccessibilityDelegateCompat() {
@ -362,13 +376,11 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
new OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager layoutManager =
(LinearLayoutManager) recyclerView.getLayoutManager();
int currentItem;
if (dx < 0) {
currentItem = layoutManager.findFirstVisibleItemPosition();
currentItem = getLayoutManager().findFirstVisibleItemPosition();
} else {
currentItem = layoutManager.findLastVisibleItemPosition();
currentItem = getLayoutManager().findLastVisibleItemPosition();
}
monthDropSelect.setText(monthsPagerAdapter.getPageTitle(currentItem));
}
@ -398,9 +410,7 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
new OnClickListener() {
@Override
public void onClick(View view) {
int currentItem =
((LinearLayoutManager) recyclerView.getLayoutManager())
.findFirstVisibleItemPosition();
int currentItem = getLayoutManager().findFirstVisibleItemPosition();
if (currentItem + 1 < recyclerView.getAdapter().getItemCount()) {
setCurrentMonth(monthsPagerAdapter.getPageMonth(currentItem + 1));
}
@ -410,13 +420,15 @@ public final class MaterialCalendar<S> extends PickerFragment<S> {
new OnClickListener() {
@Override
public void onClick(View view) {
int currentItem =
((LinearLayoutManager) recyclerView.getLayoutManager())
.findLastVisibleItemPosition();
int currentItem = getLayoutManager().findLastVisibleItemPosition();
if (currentItem - 1 >= 0) {
setCurrentMonth(monthsPagerAdapter.getPageMonth(currentItem - 1));
}
}
});
}
LinearLayoutManager getLayoutManager() {
return (LinearLayoutManager) recyclerView.getLayoutManager();
}
}

View File

@ -21,6 +21,9 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import androidx.core.util.Pair;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
@ -46,6 +49,17 @@ final class MaterialCalendarGridView extends GridView {
setNextFocusLeftId(R.id.cancel_button);
setNextFocusRightId(R.id.confirm_button);
}
ViewCompat.setAccessibilityDelegate(
this,
new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(
View view, AccessibilityNodeInfoCompat accessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(view, accessibilityNodeInfoCompat);
// Stop announcing of row/col information in favor of internationalized day information.
accessibilityNodeInfoCompat.setCollectionInfo(null);
}
});
}
@Override

View File

@ -177,6 +177,8 @@ public class MaterialDatePicker<S> extends DialogFragment {
new LayoutParams(getPaddedPickerWidth(context), getDialogPickerHeight(context)));
}
headerSelectionText = root.findViewById(R.id.mtrl_picker_header_selection_text);
ViewCompat.setAccessibilityLiveRegion(
headerSelectionText, ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
headerToggleButton = root.findViewById(R.id.mtrl_picker_header_toggle);
((TextView) root.findViewById(R.id.mtrl_picker_title_text)).setText(titleTextResId);
initHeaderToggle(context);
@ -267,7 +269,10 @@ public class MaterialDatePicker<S> extends DialogFragment {
}
private void updateHeader() {
headerSelectionText.setText(getHeaderText());
String headerText = getHeaderText();
headerSelectionText.setContentDescription(
String.format(getString(R.string.mtrl_picker_announce_current_selection), headerText));
headerSelectionText.setText(headerText);
}
private void startPickerFragment() {

View File

@ -108,9 +108,16 @@ class MonthAdapter extends BaseAdapter {
day.setVisibility(View.GONE);
day.setEnabled(false);
} else {
int dayNumber = offsetPosition + 1;
// The tag and text uniquely identify the view within the MaterialCalendar for testing
day.setText(String.valueOf(offsetPosition + 1));
day.setTag(month);
day.setText(String.valueOf(dayNumber));
long dayInMillis = month.getDay(dayNumber);
if (month.year == Month.today().year) {
day.setContentDescription(DateStrings.getMonthDayOfWeekDay(dayInMillis));
} else {
day.setContentDescription(DateStrings.getYearMonthDayOfWeekDay(dayInMillis));
}
day.setVisibility(View.VISIBLE);
day.setEnabled(true);
}

View File

@ -59,7 +59,13 @@ class YearGridAdapter extends RecyclerView.Adapter<YearGridAdapter.ViewHolder> {
@Override
public void onBindViewHolder(@NonNull YearGridAdapter.ViewHolder viewHolder, int position) {
int year = getYearForPosition(position);
String navigateYear =
viewHolder
.textView
.getContext()
.getString(R.string.mtrl_picker_navigate_to_year_description);
viewHolder.textView.setText(String.format(Locale.getDefault(), "%d", year));
viewHolder.textView.setContentDescription(String.format(navigateYear, year));
CalendarStyle styles = materialCalendar.getCalendarStyle();
Calendar calendar = Calendar.getInstance();
CalendarItemStyle style = calendar.get(Calendar.YEAR) == year ? styles.todayYear : styles.year;
@ -77,9 +83,8 @@ class YearGridAdapter extends RecyclerView.Adapter<YearGridAdapter.ViewHolder> {
return new OnClickListener() {
@Override
public void onClick(View view) {
Month moveTo =
Month.create(year, materialCalendar.getCalendarConstraints().getOpening().month);
materialCalendar.setCurrentMonth(moveTo, /*smooth= */ false);
Month moveTo = Month.create(year, materialCalendar.getCurrentMonth().month);
materialCalendar.setCurrentMonth(moveTo);
materialCalendar.setSelector(CalendarSelector.DAY);
}
};

View File

@ -43,5 +43,8 @@
<string name="mtrl_picker_a11y_next_month" description="a11y string to indicate this button moves the calendar to the next month [CHAR_LIMIT=NONE]">Move to next month</string>
<string name="mtrl_picker_toggle_to_year_selection" description="a11y string to indicate this button switches the user to choosing a year [CHAR_LIMIT=NONE]">Tap to switch to selecting a year</string>
<string name="mtrl_picker_toggle_to_day_selection" description="a11y string to indicate this button switches the user to choosing a day [CHAR_LIMIT=NONE]">Tap to switch to selecting a day</string>
<string name="mtrl_picker_day_of_week_column_header" description="a11y string to indicate this is a header for a column of days for one day of the week (e.g., Monday) [CHAR_LIMIT=NONE]">Column of Days: %1$s</string>
<string name="mtrl_picker_announce_current_selection" description="a11y string read on selection change to indicate the new selection [CHAR_LIMIT=NONE]">Current Selection: %1$s</string>
<string name="mtrl_picker_navigate_to_year_description" description="a11y string that informs the user that tapping this button will switch the year [CHAR_LIMIT=NONE]">Navigate to year %1$s</string>
</resources>