mirror of
https://github.com/material-components/material-components-android.git
synced 2026-02-20 08:39:55 +08:00
Fix TalkBack content descriptions for MaterialDatePicker and improve scrolling logic
PiperOrigin-RevId: 261849633
This commit is contained in:
parent
3da7ddc314
commit
fc195cfe10
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user