Skip to content

Commit d5934ee

Browse files
imhappihunterstich
authored andcommitted
[Lists] Support skipping the open swipe state so clients can go straight to the primary action
PiperOrigin-RevId: 843871144
1 parent 75f0a4e commit d5934ee

File tree

10 files changed

+126
-50
lines changed

10 files changed

+126
-50
lines changed

catalog/java/io/material/catalog/listitem/res/layout/cat_list_item_swipeable_viewholder.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
android:clickable="true"
2929
android:focusable="true"
3030
android:layout_height="wrap_content"
31-
android:layout_width="match_parent"
32-
app:swipeToPrimaryActionEnabled="true">
31+
android:layout_width="match_parent">
3332
<LinearLayout
3433
android:gravity="center_vertical"
3534
android:layout_height="wrap_content"
@@ -73,7 +72,8 @@
7372

7473
<com.google.android.material.listitem.ListItemRevealLayout
7574
android:layout_height="match_parent"
76-
android:layout_width="wrap_content">
75+
android:layout_width="wrap_content"
76+
app:primaryActionSwipeMode="indirect">
7777
<com.google.android.material.button.MaterialButton
7878
style="?attr/materialIconButtonFilledTonalStyle"
7979
android:id="@+id/cat_list_action_add_button"

docs/components/List.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -313,19 +313,19 @@ dismissed.
313313

314314
#### ListItemCardView attributes
315315

316-
Element | Attribute | Related methods | Default value
317-
----------------------------------- | --------------------------------- | -------------------------------------------------------------------- | -------------
318-
**Color** | `app:cardBackgroundColor` | `setCardBackgroundColor`<br/>`getCardBackgroundColor` | `@color/transparent` (standard style)</br>`?attr/colorSurfaceBright` (segmented style) </br> `?attr/colorSecondaryContainer` (selected)
319-
**Shape** | `app:shapeAppearance` | `setShapeAppearanceModel`<br/>`getShapeAppearanceModel` | `?attr/listItemShapeAppearanceSingle` </br> `?attr/listItemShapeAppearanceFirst` </br> `?attr/listItemShapeAppearanceMiddle` </br> `?attr/listItemShapeAppearanceLast`
320-
**Ripple color** | `app:rippleColor` | `setRippleColor`<br/>`setRippleColorResource`<br/>`getRippleColor` | `?attr/colorOnSurface` at 10% opacity (8% when hovered)
321-
**Swipe enabled** | `app:swipeEnabled` | `setSwipeEnabled`<br/>`isSwipeEnabled` | `true`
322-
**Swipe to primary action enabled** | `app:swipeToPrimaryActionEnabled` | `setSwipeToPrimaryActionEnabled`<br/>`isSwipeToPrimaryActionEnabled` | `false`
316+
Element | Attribute | Related methods | Default value
317+
-------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------- | -------------
318+
**Color** | `app:cardBackgroundColor` | `setCardBackgroundColor`<br/>`getCardBackgroundColor` | `@color/transparent` (standard style)</br>`?attr/colorSurfaceBright` (segmented style) </br> `?attr/colorSecondaryContainer` (selected)
319+
**Shape** | `app:shapeAppearance` | `setShapeAppearanceModel`<br/>`getShapeAppearanceModel` | `?attr/listItemShapeAppearanceSingle` </br> `?attr/listItemShapeAppearanceFirst` </br> `?attr/listItemShapeAppearanceMiddle` </br> `?attr/listItemShapeAppearanceLast`
320+
**Ripple color** | `app:rippleColor` | `setRippleColor`<br/>`setRippleColorResource`<br/>`getRippleColor` | `?attr/colorOnSurface` at 10% opacity (8% when hovered)
321+
**Swipe enabled** | `app:swipeEnabled` | `setSwipeEnabled`<br/>`isSwipeEnabled` | `true`
323322

324323
#### ListItemRevealLayout attributes
325324

326-
Element | Attribute | Related methods | Default value
327-
------------------ | --------------------------- | ----------------------------------------- | -------------
328-
**Min child size** | `app:minRevealedChildWidth` | `setMinChildWidth`<br/>`getMinChildWidth` | `6dp`
325+
Element | Attribute | Related methods | Default value
326+
----------------------------- | ---------------------------- | ----------------------------------------------------------- | -------------
327+
**Min child size** | `app:minRevealedChildWidth` | `setMinChildWidth`<br/>`getMinChildWidth` | `6dp`
328+
**Primary Action Swipe Mode** | `app:primaryActionSwipeMode` | `setPrimaryActionSwipeMode`<br/>`getPrimaryActionSwipeMode` | `disabled`
329329

330330
### Accessibility
331331

lib/java/com/google/android/material/listitem/ListItemCardView.java

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ public abstract static class SwipeCallback {
6262
private boolean isSwiped = false;
6363

6464
private final int swipeMaxOvershoot;
65-
private boolean swipeToPrimaryActionEnabled;
6665
private boolean swipeEnabled;
6766
@NonNull private final LinkedHashSet<SwipeCallback> swipeCallbacks = new LinkedHashSet<>();
6867

@@ -89,7 +88,6 @@ public ListItemCardView(Context context, AttributeSet attrs, int defStyleAttr, @
8988
TintTypedArray attributes =
9089
ThemeEnforcement.obtainTintedStyledAttributes(
9190
context, attrs, R.styleable.ListItemCardView, defStyleAttr, defStyleRes);
92-
swipeToPrimaryActionEnabled = attributes.getBoolean(R.styleable.ListItemCardView_swipeToPrimaryActionEnabled, false);
9391
swipeEnabled = attributes.getBoolean(R.styleable.ListItemCardView_swipeEnabled, true);
9492
attributes.recycle();
9593
}
@@ -102,6 +100,7 @@ public int getSwipeMaxOvershoot() {
102100
/**
103101
* Whether or not to enabling swiping when there is a sibling {@link RevealableListItem}.
104102
*/
103+
@Override
105104
public void setSwipeEnabled(boolean swipeEnabled) {
106105
this.swipeEnabled = swipeEnabled;
107106
}
@@ -111,23 +110,6 @@ public boolean isSwipeEnabled() {
111110
return swipeEnabled;
112111
}
113112

114-
/**
115-
* Set whether or not to enable the swipe to action. This enables the ListItemCardView to be
116-
* swiped fully out of its parent {@link ListItemLayout}, in order to trigger an action.
117-
*
118-
* <p>Users should add a {@link SwipeCallback} via {@link #addSwipeCallback} to listen for swipe
119-
* state changes and trigger an action.
120-
*/
121-
public void setSwipeToPrimaryActionEnabled(boolean swipeToPrimaryActionEnabled) {
122-
this.swipeToPrimaryActionEnabled = swipeToPrimaryActionEnabled;
123-
}
124-
125-
/** Returns whether or not the swipe to action is enabled. */
126-
@Override
127-
public boolean isSwipeToPrimaryActionEnabled() {
128-
return swipeToPrimaryActionEnabled;
129-
}
130-
131113
@Override
132114
protected int[] onCreateDrawableState(int extraSpace) {
133115
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);

lib/java/com/google/android/material/listitem/ListItemLayout.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,8 @@ public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
300300
SwipeableListItem swipeableItem = (SwipeableListItem) contentView;
301301

302302
int maxSwipeDistance;
303-
if (swipeableItem.isSwipeToPrimaryActionEnabled()) {
303+
if (revealableItem.getPrimaryActionSwipeMode()
304+
!= RevealableListItem.PRIMARY_ACTION_SWIPE_DISABLED) {
304305
MarginLayoutParams contentViewLp = (MarginLayoutParams) contentView.getLayoutParams();
305306
maxSwipeDistance = contentView.getMeasuredWidth() + contentViewLp.getMarginEnd();
306307
} else {
@@ -353,10 +354,14 @@ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel)
353354
}
354355

355356
private int calculateTargetSwipeState(float xvel, View swipeView) {
357+
if (swipeToRevealLayout == null) {
358+
return STATE_CLOSED;
359+
}
356360
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
357361
xvel *= -1;
358362
}
359-
if (!((SwipeableListItem) swipeView).isSwipeToPrimaryActionEnabled()) {
363+
if (((RevealableListItem) swipeToRevealLayout).getPrimaryActionSwipeMode()
364+
== RevealableListItem.PRIMARY_ACTION_SWIPE_DISABLED) {
360365
if (xvel > DEFAULT_SIGNIFICANT_VEL_THRESHOLD) { // A fast fling to the right
361366
return STATE_CLOSED;
362367
}
@@ -371,11 +376,18 @@ private int calculateTargetSwipeState(float xvel, View swipeView) {
371376
}
372377

373378
// Swipe to action is supported
379+
boolean swipeToPrimaryActionDirect =
380+
((RevealableListItem) swipeToRevealLayout).getPrimaryActionSwipeMode()
381+
== RevealableListItem.PRIMARY_ACTION_SWIPE_DIRECT;
374382
if (xvel > DEFAULT_SIGNIFICANT_VEL_THRESHOLD) { // A fast fling to the right
375-
return lastStableSwipeState == STATE_SWIPE_PRIMARY_ACTION ? STATE_OPEN : STATE_CLOSED;
383+
return lastStableSwipeState == STATE_SWIPE_PRIMARY_ACTION
384+
? (swipeToPrimaryActionDirect ? STATE_CLOSED : STATE_OPEN)
385+
: STATE_CLOSED;
376386
}
377387
if (xvel < -DEFAULT_SIGNIFICANT_VEL_THRESHOLD) { // A fast fling to the left
378-
return lastStableSwipeState == STATE_CLOSED ? STATE_OPEN : STATE_SWIPE_PRIMARY_ACTION;
388+
return lastStableSwipeState == STATE_CLOSED
389+
? (swipeToPrimaryActionDirect ? STATE_SWIPE_PRIMARY_ACTION : STATE_OPEN)
390+
: STATE_SWIPE_PRIMARY_ACTION;
379391
}
380392

381393
// Settle to the closest point if velocity is not significant
@@ -385,7 +397,7 @@ private int calculateTargetSwipeState(float xvel, View swipeView) {
385397
}
386398
if (Math.abs(swipeView.getLeft() - getSwipeRevealViewRevealedOffset())
387399
< Math.abs(swipeView.getLeft() - getSwipeViewClosedOffset())) {
388-
return STATE_OPEN;
400+
return swipeToPrimaryActionDirect ? STATE_SWIPE_PRIMARY_ACTION : STATE_OPEN;
389401
}
390402
return STATE_CLOSED;
391403
}
@@ -533,7 +545,11 @@ private void updateSwipeProgress(int left) {
533545
((SwipeableListItem) contentView).onSwipe(revealViewOffset);
534546

535547
int fullSwipedOffset = getSwipeToActionOffset();
536-
int fadeOutThreshold = (fullSwipedOffset + getSwipeRevealViewRevealedOffset()) / 2;
548+
int fadeOutThreshold =
549+
getSwipeRevealViewRevealedOffset() == getSwipeToActionOffset()
550+
? (fullSwipedOffset + getSwipeViewClosedOffset()) / 2
551+
: (fullSwipedOffset + getSwipeRevealViewRevealedOffset()) / 2;
552+
537553
float contentViewAlpha =
538554
AnimationUtils.lerp(
539555
/* startValue= */ 1f,
@@ -573,9 +589,10 @@ private void setSwipeStateInternal(@SwipeState int swipeState) {
573589
}
574590
// If swipe to action is not supported but the swipe state to be set in
575591
// STATE_SWIPE_PRIMARY_ACTION, we do nothing.
576-
if (!(contentView instanceof SwipeableListItem)
592+
if (!(swipeToRevealLayout instanceof RevealableListItem)
577593
|| (swipeState == STATE_SWIPE_PRIMARY_ACTION
578-
&& !((SwipeableListItem) contentView).isSwipeToPrimaryActionEnabled())) {
594+
&& ((RevealableListItem) swipeToRevealLayout).getPrimaryActionSwipeMode()
595+
== RevealableListItem.PRIMARY_ACTION_SWIPE_DISABLED)) {
579596
return;
580597
}
581598
this.swipeState = swipeState;

lib/java/com/google/android/material/listitem/ListItemRevealLayout.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ public class ListItemRevealLayout extends ViewGroup implements RevealableListIte
5959
private int originalWidthMeasureSpec = UNSET;
6060
private int originalHeightMeasureSpec = UNSET;
6161

62+
@PrimaryActionSwipeMode
63+
private int primaryActionSwipeMode;
64+
6265
public ListItemRevealLayout(Context context) {
6366
this(context, null);
6467
}
@@ -86,6 +89,9 @@ public ListItemRevealLayout(
8689
attributes.getDimensionPixelSize(
8790
R.styleable.ListItemRevealLayout_minChildWidth,
8891
getResources().getDimensionPixelSize(R.dimen.m3_list_reveal_min_child_width));
92+
primaryActionSwipeMode = attributes.getInt(
93+
R.styleable.ListItemRevealLayout_primaryActionSwipeMode,
94+
PRIMARY_ACTION_SWIPE_DISABLED);
8995
attributes.recycle();
9096
}
9197

@@ -120,7 +126,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
120126
} else if (childCount == 0) {
121127
// If there's no children, just set to desired width without doing anything.
122128
setMeasuredDimension(revealedWidth, intrinsicHeight);
123-
} else if (revealedWidth > intrinsicWidth + overswipeAllowance
129+
} else if (primaryActionSwipeMode != PRIMARY_ACTION_SWIPE_DISABLED
130+
&& revealedWidth > intrinsicWidth + overswipeAllowance
124131
&& fullRevealableWidth > intrinsicWidth) {
125132
measureByGrowingPrimarySwipeAction(fullRevealableWidth);
126133
} else {
@@ -447,4 +454,22 @@ private Integer findLastVisibleChildIndex() {
447454
}
448455
return null;
449456
}
457+
458+
/**
459+
* Sets the swipe-to-primary-action behavior of this RevealableListItem when swiping with a
460+
* sibling {@link SwipeableListItem}.
461+
*
462+
* <p>Use {@link SwipeableListItem#onSwipeStateChanged(int)} to listen for when the primary
463+
* action is triggered to initiate the action.
464+
*/
465+
@Override
466+
public void setPrimaryActionSwipeMode(@PrimaryActionSwipeMode int primaryActionSwipeMode) {
467+
this.primaryActionSwipeMode = primaryActionSwipeMode;
468+
}
469+
470+
@Override
471+
@PrimaryActionSwipeMode
472+
public int getPrimaryActionSwipeMode() {
473+
return primaryActionSwipeMode;
474+
}
450475
}

lib/java/com/google/android/material/listitem/RevealableListItem.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,46 @@
1515
*/
1616
package com.google.android.material.listitem;
1717

18+
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
19+
20+
import androidx.annotation.IntDef;
1821
import androidx.annotation.Px;
22+
import androidx.annotation.RestrictTo;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.RetentionPolicy;
1925

2026
/** Interface for the part of a ListItem that is able to be revealed when swiped. */
2127
public interface RevealableListItem {
2228

29+
/** Disable the primary action. */
30+
int PRIMARY_ACTION_SWIPE_DISABLED = 0;
31+
32+
/**
33+
* When swiping with a sibling {@link SwipeableListItem}, allow swiping to intermediary states
34+
* before the primary action.
35+
*/
36+
int PRIMARY_ACTION_SWIPE_INDIRECT = 1;
37+
38+
/**
39+
* When swiping with a sibling {@link SwipeableListItem}, swipe directly to the primary action.
40+
*/
41+
int PRIMARY_ACTION_SWIPE_DIRECT = 2;
42+
43+
/**
44+
* Mode which defines the behavior when swiping to reveal the primary action of the
45+
* RevealableListItem.
46+
*
47+
* @hide
48+
*/
49+
@RestrictTo(LIBRARY_GROUP)
50+
@IntDef({
51+
PRIMARY_ACTION_SWIPE_DISABLED,
52+
PRIMARY_ACTION_SWIPE_INDIRECT,
53+
PRIMARY_ACTION_SWIPE_DIRECT,
54+
})
55+
@Retention(RetentionPolicy.SOURCE)
56+
@interface PrimaryActionSwipeMode {}
57+
2358
/**
2459
* Sets the revealed width of RevealableListItem, in pixels.
2560
*/
@@ -30,4 +65,17 @@ public interface RevealableListItem {
3065
* has not yet been measured.
3166
*/
3267
@Px int getIntrinsicWidth();
68+
69+
/**
70+
* Returns the {@link PrimaryActionSwipeMode} for the RevealableListItem that defines the swipe
71+
* to primary action behavior when swiping with a sibling {@link SwipeableListItem}.
72+
*/
73+
@PrimaryActionSwipeMode
74+
int getPrimaryActionSwipeMode();
75+
76+
/**
77+
* Sets the {@link PrimaryActionSwipeMode} for the RevealableListItem that defines the swipe
78+
* to primary action behavior when swiping with a sibling {@link SwipeableListItem}.
79+
*/
80+
void setPrimaryActionSwipeMode(@PrimaryActionSwipeMode int swipeToPrimaryActionMode);
3381
}

lib/java/com/google/android/material/listitem/SwipeableListItem.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,6 @@ public interface SwipeableListItem {
9393
*/
9494
boolean isSwipeEnabled();
9595

96-
/**
97-
* Whether or not to allow the SwipeableListItem can be fully swiped to trigger the primary
98-
* action.
99-
*/
100-
boolean isSwipeToPrimaryActionEnabled();
96+
/** Sets whether or not to allow the SwipeableListItem to be swiped. */
97+
void setSwipeEnabled(boolean swipeEnabled);
10198
}

lib/java/com/google/android/material/listitem/res-public/values/public.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
<public name="state_swiped" type="attr" />
2323
<public name="minChildWidth" type="attr"/>
24-
<public name="swipeToPrimaryActionEnabled" type="attr"/>
2524
<public name="swipeEnabled" type="attr"/>
25+
<public name="primaryActionSwipeMode" type="attr"/>
2626

2727
<public name="Widget.Material3.ListItemLayout" type="style"/>
2828
<public name="ThemeOverlay.Material3.ListItemLayout.Segmented" type="style"/>

lib/java/com/google/android/material/listitem/res/values/attrs.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
<attr name="listItemRevealLayoutStyle" format="reference"/>
2626

2727
<declare-styleable name="ListItemCardView">
28-
<!-- Whether or not to enable the swipe to action. -->
29-
<attr name="swipeToPrimaryActionEnabled" format="boolean"/>
3028
<!-- Whether or not to enable swiping, if a sibling RevealableListItem exists. -->
3129
<attr name="swipeEnabled" format="boolean"/>
3230
</declare-styleable>
@@ -39,6 +37,15 @@
3937
<declare-styleable name="ListItemRevealLayout">
4038
<!-- Minimum width any children are measured as. -->
4139
<attr name="minChildWidth" format="dimension" />
40+
<!-- Defines the behavior when swiping to reveal the primary action of the ListItemRevealLayout. -->
41+
<attr name="primaryActionSwipeMode">
42+
<!-- The primary action is disabled. -->
43+
<enum name="disabled" value="0"/>
44+
<!-- Swiping reveals intermediary states before fully revealing the primary action. -->
45+
<enum name="indirect" value="1"/>
46+
<!-- Swiping directly reveals the primary action without stopping at intermediary states. -->
47+
<enum name="direct" value="2"/>
48+
</attr>
4249
</declare-styleable>
4350

4451
<!-- Shape appearance of a single item in the list. -->

lib/java/com/google/android/material/listitem/res/values/styles.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
<item name="contentPaddingBottom">10dp</item>
3535
<item name="contentPaddingLeft">16dp</item>
3636
<item name="contentPaddingRight">16dp</item>
37-
<item name="swipeToPrimaryActionEnabled">false</item>
3837
<item name="swipeEnabled">true</item>
3938
</style>
4039

@@ -46,6 +45,7 @@
4645
<item name="enforceMaterialTheme">true</item>
4746
<item name="materialThemeOverlay">@style/ThemeOverlay.Material3.ListItemRevealLayout</item>
4847
<item name="minChildWidth">@dimen/m3_list_reveal_min_child_width</item>
48+
<item name="primaryActionSwipeMode">disabled</item>
4949
</style>
5050

5151
<style name="Widget.Material3Expressive.ListItemRevealLayout" parent="Widget.Material3.ListItemRevealLayout">

0 commit comments

Comments
 (0)