Skip to content

Commit 11fafd9

Browse files
Material Design Teamhunterstich
authored andcommitted
[Card][A11y] Adds keyboard drag and drop support to the card demo fragment
This change introduces an `OnKeyboardDragListener` interface to handle keyboard-driven drag interactions. The `CardViewHolder` now listens for keyboard events (Enter/DPAD) to toggle and perform card movements, while the `CardAdapter` manages the state of the currently keyboard-dragged item and performs the swaps. The `ItemTouchHelperCallback` is updated to integrate with the new keyboard drag functionality. PiperOrigin-RevId: 807222398
1 parent fb26da6 commit 11fafd9

File tree

1 file changed

+116
-25
lines changed

1 file changed

+116
-25
lines changed

catalog/java/io/material/catalog/card/CardListDemoFragment.java

Lines changed: 116 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
2525
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
2626
import androidx.recyclerview.widget.ItemTouchHelper;
27+
import android.view.KeyEvent;
2728
import android.view.LayoutInflater;
2829
import android.view.MotionEvent;
2930
import android.view.View;
@@ -93,10 +94,10 @@ public void onInitializeAccessibilityNodeInfo(View host,
9394
public boolean performAccessibilityAction(View host, int action, Bundle args) {
9495
int fromPosition = recyclerView.getChildLayoutPosition(host);
9596
if (action == R.id.move_card_down_action) {
96-
swapCards(fromPosition, fromPosition + 1, cardAdapter);
97+
cardAdapter.swapCards(fromPosition, fromPosition + 1);
9798
return true;
9899
} else if (action == R.id.move_card_up_action) {
99-
swapCards(fromPosition, fromPosition - 1, cardAdapter);
100+
cardAdapter.swapCards(fromPosition, fromPosition - 1);
100101
return true;
101102
}
102103

@@ -118,10 +119,13 @@ private static int[] generateCardNumbers() {
118119
return cardNumbers;
119120
}
120121

121-
private static class CardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
122+
private static class CardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
123+
implements OnKeyboardDragListener {
122124

123125
private final int[] cardNumbers;
124126

127+
@Nullable private ViewHolder draggedViewHolder;
128+
125129
private ItemTouchHelper itemTouchHelper;
126130

127131
private CardAdapter(int[] cardNumbers) {
@@ -133,12 +137,12 @@ private CardAdapter(int[] cardNumbers) {
133137
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
134138
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
135139
View view = inflater.inflate(R.layout.cat_card_list_item, parent, /* attachToRoot= */ false);
136-
return new CardViewHolder(view);
140+
return new CardViewHolder(view, this);
137141
}
138142

139143
@Override
140144
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
141-
((CardViewHolder) viewHolder).bind(cardNumbers[position], itemTouchHelper);
145+
((CardViewHolder) viewHolder).bind(cardNumbers[position]);
142146
}
143147

144148
@Override
@@ -150,27 +154,113 @@ private void setItemTouchHelper(ItemTouchHelper itemTouchHelper) {
150154
this.itemTouchHelper = itemTouchHelper;
151155
}
152156

153-
private static class CardViewHolder extends RecyclerView.ViewHolder {
157+
void swapCards(int fromPosition, int toPosition) {
158+
if (fromPosition < 0
159+
|| fromPosition >= cardNumbers.length
160+
|| toPosition < 0
161+
|| toPosition >= cardNumbers.length) {
162+
return;
163+
}
164+
165+
int fromNumber = cardNumbers[fromPosition];
166+
cardNumbers[fromPosition] = cardNumbers[toPosition];
167+
cardNumbers[toPosition] = fromNumber;
168+
notifyItemMoved(fromPosition, toPosition);
169+
}
170+
171+
void cancelDrag() {
172+
if (draggedViewHolder != null) {
173+
((MaterialCardView) draggedViewHolder.itemView).setDragged(false);
174+
draggedViewHolder = null;
175+
}
176+
}
177+
178+
@Override
179+
public void onDragStarted(@NonNull ViewHolder viewHolder) {
180+
itemTouchHelper.startDrag(viewHolder);
181+
}
182+
183+
@Override
184+
public void onKeyboardDragToggle(@NonNull ViewHolder viewHolder) {
185+
boolean isCurrentlyDragged = draggedViewHolder == viewHolder;
186+
cancelDrag();
187+
if (!isCurrentlyDragged) {
188+
draggedViewHolder = viewHolder;
189+
((MaterialCardView) viewHolder.itemView).setDragged(true);
190+
}
191+
}
192+
193+
@Override
194+
public boolean onKeyboardMoved(int keyCode) {
195+
if (draggedViewHolder == null) {
196+
return false;
197+
}
198+
199+
int fromPosition = draggedViewHolder.getBindingAdapterPosition();
200+
if (fromPosition == RecyclerView.NO_POSITION) {
201+
return false;
202+
}
203+
204+
switch (keyCode) {
205+
case KeyEvent.KEYCODE_DPAD_UP:
206+
swapCards(fromPosition, fromPosition - 1);
207+
return true;
208+
case KeyEvent.KEYCODE_DPAD_DOWN:
209+
swapCards(fromPosition, fromPosition + 1);
210+
return true;
211+
default:
212+
return false;
213+
}
214+
}
215+
216+
private static class CardViewHolder extends RecyclerView.ViewHolder {
154217

155218
private final TextView titleView;
156-
private final View dragHandleView;
157219

158-
private CardViewHolder(View itemView) {
220+
private CardViewHolder(View itemView, OnKeyboardDragListener listener) {
159221
super(itemView);
160-
titleView = itemView.findViewById(R.id.cat_card_list_item_title);
161-
dragHandleView = itemView.findViewById(R.id.cat_card_list_item_drag_handle);
162-
}
163222

164-
private void bind(int cardNumber, final ItemTouchHelper itemTouchHelper) {
165-
titleView.setText(String.format(Locale.getDefault(), "Card #%02d", cardNumber));
223+
MaterialCardView cardView = (MaterialCardView) itemView;
224+
cardView.setFocusable(true);
225+
cardView.setOnKeyListener(
226+
(v, keyCode, event) -> {
227+
if (event.getAction() != KeyEvent.ACTION_DOWN) {
228+
return false;
229+
}
230+
231+
switch (keyCode) {
232+
case KeyEvent.KEYCODE_ENTER:
233+
case KeyEvent.KEYCODE_DPAD_CENTER:
234+
listener.onKeyboardDragToggle(this);
235+
return true;
236+
case KeyEvent.KEYCODE_DPAD_UP:
237+
case KeyEvent.KEYCODE_DPAD_DOWN:
238+
return listener.onKeyboardMoved(keyCode);
239+
default:
240+
return false;
241+
}
242+
});
243+
244+
View dragHandleView = itemView.findViewById(R.id.cat_card_list_item_drag_handle);
166245
dragHandleView.setOnTouchListener(
167246
(v, event) -> {
168-
if (event.getAction() == MotionEvent.ACTION_DOWN) {
169-
itemTouchHelper.startDrag(CardViewHolder.this);
170-
return true;
247+
switch (event.getAction()) {
248+
case MotionEvent.ACTION_DOWN:
249+
listener.onDragStarted(this);
250+
return true;
251+
case MotionEvent.ACTION_UP:
252+
v.performClick();
253+
break;
254+
default: // fall out
171255
}
172256
return false;
173257
});
258+
259+
titleView = itemView.findViewById(R.id.cat_card_list_item_title);
260+
}
261+
262+
private void bind(int cardNumber) {
263+
titleView.setText(String.format(Locale.getDefault(), "Card #%02d", cardNumber));
174264
}
175265
}
176266
}
@@ -199,10 +289,9 @@ public boolean onMove(
199289
@NonNull RecyclerView recyclerView,
200290
@NonNull ViewHolder viewHolder,
201291
@NonNull ViewHolder target) {
202-
int fromPosition = viewHolder.getAdapterPosition();
203-
int toPosition = target.getAdapterPosition();
204-
205-
swapCards(fromPosition, toPosition, cardAdapter);
292+
int fromPosition = viewHolder.getBindingAdapterPosition();
293+
int toPosition = target.getBindingAdapterPosition();
294+
cardAdapter.swapCards(fromPosition, toPosition);
206295
return true;
207296
}
208297

@@ -214,6 +303,7 @@ public void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState)
214303
super.onSelectedChanged(viewHolder, actionState);
215304

216305
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
306+
cardAdapter.cancelDrag();
217307
dragCardView = (MaterialCardView) viewHolder.itemView;
218308
dragCardView.setDragged(true);
219309
} else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE && dragCardView != null) {
@@ -223,10 +313,11 @@ public void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState)
223313
}
224314
}
225315

226-
private static void swapCards(int fromPosition, int toPosition, CardAdapter cardAdapter) {
227-
int fromNumber = cardAdapter.cardNumbers[fromPosition];
228-
cardAdapter.cardNumbers[fromPosition] = cardAdapter.cardNumbers[toPosition];
229-
cardAdapter.cardNumbers[toPosition] = fromNumber;
230-
cardAdapter.notifyItemMoved(fromPosition, toPosition);
316+
private interface OnKeyboardDragListener {
317+
void onDragStarted(@NonNull ViewHolder viewHolder);
318+
319+
void onKeyboardDragToggle(@NonNull ViewHolder viewHolder);
320+
321+
boolean onKeyboardMoved(int keyCode);
231322
}
232323
}

0 commit comments

Comments
 (0)