mirror of
https://github.com/material-components/material-components-android.git
synced 2026-01-09 07:11:07 +08:00
[Lists] Support skipping the open swipe state so clients can go straight to the primary action
PiperOrigin-RevId: 843871144
This commit is contained in:
parent
75f0a4e812
commit
d5934ee5ba
@ -28,8 +28,7 @@
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
app:swipeToPrimaryActionEnabled="true">
|
||||
android:layout_width="match_parent">
|
||||
<LinearLayout
|
||||
android:gravity="center_vertical"
|
||||
android:layout_height="wrap_content"
|
||||
@ -73,7 +72,8 @@
|
||||
|
||||
<com.google.android.material.listitem.ListItemRevealLayout
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content">
|
||||
android:layout_width="wrap_content"
|
||||
app:primaryActionSwipeMode="indirect">
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
android:id="@+id/cat_list_action_add_button"
|
||||
|
||||
@ -313,19 +313,19 @@ dismissed.
|
||||
|
||||
#### ListItemCardView attributes
|
||||
|
||||
Element | Attribute | Related methods | Default value
|
||||
----------------------------------- | --------------------------------- | -------------------------------------------------------------------- | -------------
|
||||
**Color** | `app:cardBackgroundColor` | `setCardBackgroundColor`<br/>`getCardBackgroundColor` | `@color/transparent` (standard style)</br>`?attr/colorSurfaceBright` (segmented style) </br> `?attr/colorSecondaryContainer` (selected)
|
||||
**Shape** | `app:shapeAppearance` | `setShapeAppearanceModel`<br/>`getShapeAppearanceModel` | `?attr/listItemShapeAppearanceSingle` </br> `?attr/listItemShapeAppearanceFirst` </br> `?attr/listItemShapeAppearanceMiddle` </br> `?attr/listItemShapeAppearanceLast`
|
||||
**Ripple color** | `app:rippleColor` | `setRippleColor`<br/>`setRippleColorResource`<br/>`getRippleColor` | `?attr/colorOnSurface` at 10% opacity (8% when hovered)
|
||||
**Swipe enabled** | `app:swipeEnabled` | `setSwipeEnabled`<br/>`isSwipeEnabled` | `true`
|
||||
**Swipe to primary action enabled** | `app:swipeToPrimaryActionEnabled` | `setSwipeToPrimaryActionEnabled`<br/>`isSwipeToPrimaryActionEnabled` | `false`
|
||||
Element | Attribute | Related methods | Default value
|
||||
-------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------- | -------------
|
||||
**Color** | `app:cardBackgroundColor` | `setCardBackgroundColor`<br/>`getCardBackgroundColor` | `@color/transparent` (standard style)</br>`?attr/colorSurfaceBright` (segmented style) </br> `?attr/colorSecondaryContainer` (selected)
|
||||
**Shape** | `app:shapeAppearance` | `setShapeAppearanceModel`<br/>`getShapeAppearanceModel` | `?attr/listItemShapeAppearanceSingle` </br> `?attr/listItemShapeAppearanceFirst` </br> `?attr/listItemShapeAppearanceMiddle` </br> `?attr/listItemShapeAppearanceLast`
|
||||
**Ripple color** | `app:rippleColor` | `setRippleColor`<br/>`setRippleColorResource`<br/>`getRippleColor` | `?attr/colorOnSurface` at 10% opacity (8% when hovered)
|
||||
**Swipe enabled** | `app:swipeEnabled` | `setSwipeEnabled`<br/>`isSwipeEnabled` | `true`
|
||||
|
||||
#### ListItemRevealLayout attributes
|
||||
|
||||
Element | Attribute | Related methods | Default value
|
||||
------------------ | --------------------------- | ----------------------------------------- | -------------
|
||||
**Min child size** | `app:minRevealedChildWidth` | `setMinChildWidth`<br/>`getMinChildWidth` | `6dp`
|
||||
Element | Attribute | Related methods | Default value
|
||||
----------------------------- | ---------------------------- | ----------------------------------------------------------- | -------------
|
||||
**Min child size** | `app:minRevealedChildWidth` | `setMinChildWidth`<br/>`getMinChildWidth` | `6dp`
|
||||
**Primary Action Swipe Mode** | `app:primaryActionSwipeMode` | `setPrimaryActionSwipeMode`<br/>`getPrimaryActionSwipeMode` | `disabled`
|
||||
|
||||
### Accessibility
|
||||
|
||||
|
||||
@ -62,7 +62,6 @@ public class ListItemCardView extends MaterialCardView implements SwipeableListI
|
||||
private boolean isSwiped = false;
|
||||
|
||||
private final int swipeMaxOvershoot;
|
||||
private boolean swipeToPrimaryActionEnabled;
|
||||
private boolean swipeEnabled;
|
||||
@NonNull private final LinkedHashSet<SwipeCallback> swipeCallbacks = new LinkedHashSet<>();
|
||||
|
||||
@ -89,7 +88,6 @@ public class ListItemCardView extends MaterialCardView implements SwipeableListI
|
||||
TintTypedArray attributes =
|
||||
ThemeEnforcement.obtainTintedStyledAttributes(
|
||||
context, attrs, R.styleable.ListItemCardView, defStyleAttr, defStyleRes);
|
||||
swipeToPrimaryActionEnabled = attributes.getBoolean(R.styleable.ListItemCardView_swipeToPrimaryActionEnabled, false);
|
||||
swipeEnabled = attributes.getBoolean(R.styleable.ListItemCardView_swipeEnabled, true);
|
||||
attributes.recycle();
|
||||
}
|
||||
@ -102,6 +100,7 @@ public class ListItemCardView extends MaterialCardView implements SwipeableListI
|
||||
/**
|
||||
* Whether or not to enabling swiping when there is a sibling {@link RevealableListItem}.
|
||||
*/
|
||||
@Override
|
||||
public void setSwipeEnabled(boolean swipeEnabled) {
|
||||
this.swipeEnabled = swipeEnabled;
|
||||
}
|
||||
@ -111,23 +110,6 @@ public class ListItemCardView extends MaterialCardView implements SwipeableListI
|
||||
return swipeEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not to enable the swipe to action. This enables the ListItemCardView to be
|
||||
* swiped fully out of its parent {@link ListItemLayout}, in order to trigger an action.
|
||||
*
|
||||
* <p>Users should add a {@link SwipeCallback} via {@link #addSwipeCallback} to listen for swipe
|
||||
* state changes and trigger an action.
|
||||
*/
|
||||
public void setSwipeToPrimaryActionEnabled(boolean swipeToPrimaryActionEnabled) {
|
||||
this.swipeToPrimaryActionEnabled = swipeToPrimaryActionEnabled;
|
||||
}
|
||||
|
||||
/** Returns whether or not the swipe to action is enabled. */
|
||||
@Override
|
||||
public boolean isSwipeToPrimaryActionEnabled() {
|
||||
return swipeToPrimaryActionEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
@ -300,7 +300,8 @@ public class ListItemLayout extends FrameLayout {
|
||||
SwipeableListItem swipeableItem = (SwipeableListItem) contentView;
|
||||
|
||||
int maxSwipeDistance;
|
||||
if (swipeableItem.isSwipeToPrimaryActionEnabled()) {
|
||||
if (revealableItem.getPrimaryActionSwipeMode()
|
||||
!= RevealableListItem.PRIMARY_ACTION_SWIPE_DISABLED) {
|
||||
MarginLayoutParams contentViewLp = (MarginLayoutParams) contentView.getLayoutParams();
|
||||
maxSwipeDistance = contentView.getMeasuredWidth() + contentViewLp.getMarginEnd();
|
||||
} else {
|
||||
@ -353,10 +354,14 @@ public class ListItemLayout extends FrameLayout {
|
||||
}
|
||||
|
||||
private int calculateTargetSwipeState(float xvel, View swipeView) {
|
||||
if (swipeToRevealLayout == null) {
|
||||
return STATE_CLOSED;
|
||||
}
|
||||
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
|
||||
xvel *= -1;
|
||||
}
|
||||
if (!((SwipeableListItem) swipeView).isSwipeToPrimaryActionEnabled()) {
|
||||
if (((RevealableListItem) swipeToRevealLayout).getPrimaryActionSwipeMode()
|
||||
== RevealableListItem.PRIMARY_ACTION_SWIPE_DISABLED) {
|
||||
if (xvel > DEFAULT_SIGNIFICANT_VEL_THRESHOLD) { // A fast fling to the right
|
||||
return STATE_CLOSED;
|
||||
}
|
||||
@ -371,11 +376,18 @@ public class ListItemLayout extends FrameLayout {
|
||||
}
|
||||
|
||||
// Swipe to action is supported
|
||||
boolean swipeToPrimaryActionDirect =
|
||||
((RevealableListItem) swipeToRevealLayout).getPrimaryActionSwipeMode()
|
||||
== RevealableListItem.PRIMARY_ACTION_SWIPE_DIRECT;
|
||||
if (xvel > DEFAULT_SIGNIFICANT_VEL_THRESHOLD) { // A fast fling to the right
|
||||
return lastStableSwipeState == STATE_SWIPE_PRIMARY_ACTION ? STATE_OPEN : STATE_CLOSED;
|
||||
return lastStableSwipeState == STATE_SWIPE_PRIMARY_ACTION
|
||||
? (swipeToPrimaryActionDirect ? STATE_CLOSED : STATE_OPEN)
|
||||
: STATE_CLOSED;
|
||||
}
|
||||
if (xvel < -DEFAULT_SIGNIFICANT_VEL_THRESHOLD) { // A fast fling to the left
|
||||
return lastStableSwipeState == STATE_CLOSED ? STATE_OPEN : STATE_SWIPE_PRIMARY_ACTION;
|
||||
return lastStableSwipeState == STATE_CLOSED
|
||||
? (swipeToPrimaryActionDirect ? STATE_SWIPE_PRIMARY_ACTION : STATE_OPEN)
|
||||
: STATE_SWIPE_PRIMARY_ACTION;
|
||||
}
|
||||
|
||||
// Settle to the closest point if velocity is not significant
|
||||
@ -385,7 +397,7 @@ public class ListItemLayout extends FrameLayout {
|
||||
}
|
||||
if (Math.abs(swipeView.getLeft() - getSwipeRevealViewRevealedOffset())
|
||||
< Math.abs(swipeView.getLeft() - getSwipeViewClosedOffset())) {
|
||||
return STATE_OPEN;
|
||||
return swipeToPrimaryActionDirect ? STATE_SWIPE_PRIMARY_ACTION : STATE_OPEN;
|
||||
}
|
||||
return STATE_CLOSED;
|
||||
}
|
||||
@ -533,7 +545,11 @@ public class ListItemLayout extends FrameLayout {
|
||||
((SwipeableListItem) contentView).onSwipe(revealViewOffset);
|
||||
|
||||
int fullSwipedOffset = getSwipeToActionOffset();
|
||||
int fadeOutThreshold = (fullSwipedOffset + getSwipeRevealViewRevealedOffset()) / 2;
|
||||
int fadeOutThreshold =
|
||||
getSwipeRevealViewRevealedOffset() == getSwipeToActionOffset()
|
||||
? (fullSwipedOffset + getSwipeViewClosedOffset()) / 2
|
||||
: (fullSwipedOffset + getSwipeRevealViewRevealedOffset()) / 2;
|
||||
|
||||
float contentViewAlpha =
|
||||
AnimationUtils.lerp(
|
||||
/* startValue= */ 1f,
|
||||
@ -573,9 +589,10 @@ public class ListItemLayout extends FrameLayout {
|
||||
}
|
||||
// If swipe to action is not supported but the swipe state to be set in
|
||||
// STATE_SWIPE_PRIMARY_ACTION, we do nothing.
|
||||
if (!(contentView instanceof SwipeableListItem)
|
||||
if (!(swipeToRevealLayout instanceof RevealableListItem)
|
||||
|| (swipeState == STATE_SWIPE_PRIMARY_ACTION
|
||||
&& !((SwipeableListItem) contentView).isSwipeToPrimaryActionEnabled())) {
|
||||
&& ((RevealableListItem) swipeToRevealLayout).getPrimaryActionSwipeMode()
|
||||
== RevealableListItem.PRIMARY_ACTION_SWIPE_DISABLED)) {
|
||||
return;
|
||||
}
|
||||
this.swipeState = swipeState;
|
||||
|
||||
@ -59,6 +59,9 @@ public class ListItemRevealLayout extends ViewGroup implements RevealableListIte
|
||||
private int originalWidthMeasureSpec = UNSET;
|
||||
private int originalHeightMeasureSpec = UNSET;
|
||||
|
||||
@PrimaryActionSwipeMode
|
||||
private int primaryActionSwipeMode;
|
||||
|
||||
public ListItemRevealLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@ -86,6 +89,9 @@ public class ListItemRevealLayout extends ViewGroup implements RevealableListIte
|
||||
attributes.getDimensionPixelSize(
|
||||
R.styleable.ListItemRevealLayout_minChildWidth,
|
||||
getResources().getDimensionPixelSize(R.dimen.m3_list_reveal_min_child_width));
|
||||
primaryActionSwipeMode = attributes.getInt(
|
||||
R.styleable.ListItemRevealLayout_primaryActionSwipeMode,
|
||||
PRIMARY_ACTION_SWIPE_DISABLED);
|
||||
attributes.recycle();
|
||||
}
|
||||
|
||||
@ -120,7 +126,8 @@ public class ListItemRevealLayout extends ViewGroup implements RevealableListIte
|
||||
} else if (childCount == 0) {
|
||||
// If there's no children, just set to desired width without doing anything.
|
||||
setMeasuredDimension(revealedWidth, intrinsicHeight);
|
||||
} else if (revealedWidth > intrinsicWidth + overswipeAllowance
|
||||
} else if (primaryActionSwipeMode != PRIMARY_ACTION_SWIPE_DISABLED
|
||||
&& revealedWidth > intrinsicWidth + overswipeAllowance
|
||||
&& fullRevealableWidth > intrinsicWidth) {
|
||||
measureByGrowingPrimarySwipeAction(fullRevealableWidth);
|
||||
} else {
|
||||
@ -447,4 +454,22 @@ public class ListItemRevealLayout extends ViewGroup implements RevealableListIte
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the swipe-to-primary-action behavior of this RevealableListItem when swiping with a
|
||||
* sibling {@link SwipeableListItem}.
|
||||
*
|
||||
* <p>Use {@link SwipeableListItem#onSwipeStateChanged(int)} to listen for when the primary
|
||||
* action is triggered to initiate the action.
|
||||
*/
|
||||
@Override
|
||||
public void setPrimaryActionSwipeMode(@PrimaryActionSwipeMode int primaryActionSwipeMode) {
|
||||
this.primaryActionSwipeMode = primaryActionSwipeMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@PrimaryActionSwipeMode
|
||||
public int getPrimaryActionSwipeMode() {
|
||||
return primaryActionSwipeMode;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,11 +15,46 @@
|
||||
*/
|
||||
package com.google.android.material.listitem;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Px;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/** Interface for the part of a ListItem that is able to be revealed when swiped. */
|
||||
public interface RevealableListItem {
|
||||
|
||||
/** Disable the primary action. */
|
||||
int PRIMARY_ACTION_SWIPE_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* When swiping with a sibling {@link SwipeableListItem}, allow swiping to intermediary states
|
||||
* before the primary action.
|
||||
*/
|
||||
int PRIMARY_ACTION_SWIPE_INDIRECT = 1;
|
||||
|
||||
/**
|
||||
* When swiping with a sibling {@link SwipeableListItem}, swipe directly to the primary action.
|
||||
*/
|
||||
int PRIMARY_ACTION_SWIPE_DIRECT = 2;
|
||||
|
||||
/**
|
||||
* Mode which defines the behavior when swiping to reveal the primary action of the
|
||||
* RevealableListItem.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@IntDef({
|
||||
PRIMARY_ACTION_SWIPE_DISABLED,
|
||||
PRIMARY_ACTION_SWIPE_INDIRECT,
|
||||
PRIMARY_ACTION_SWIPE_DIRECT,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface PrimaryActionSwipeMode {}
|
||||
|
||||
/**
|
||||
* Sets the revealed width of RevealableListItem, in pixels.
|
||||
*/
|
||||
@ -30,4 +65,17 @@ public interface RevealableListItem {
|
||||
* has not yet been measured.
|
||||
*/
|
||||
@Px int getIntrinsicWidth();
|
||||
|
||||
/**
|
||||
* Returns the {@link PrimaryActionSwipeMode} for the RevealableListItem that defines the swipe
|
||||
* to primary action behavior when swiping with a sibling {@link SwipeableListItem}.
|
||||
*/
|
||||
@PrimaryActionSwipeMode
|
||||
int getPrimaryActionSwipeMode();
|
||||
|
||||
/**
|
||||
* Sets the {@link PrimaryActionSwipeMode} for the RevealableListItem that defines the swipe
|
||||
* to primary action behavior when swiping with a sibling {@link SwipeableListItem}.
|
||||
*/
|
||||
void setPrimaryActionSwipeMode(@PrimaryActionSwipeMode int swipeToPrimaryActionMode);
|
||||
}
|
||||
|
||||
@ -93,9 +93,6 @@ public interface SwipeableListItem {
|
||||
*/
|
||||
boolean isSwipeEnabled();
|
||||
|
||||
/**
|
||||
* Whether or not to allow the SwipeableListItem can be fully swiped to trigger the primary
|
||||
* action.
|
||||
*/
|
||||
boolean isSwipeToPrimaryActionEnabled();
|
||||
/** Sets whether or not to allow the SwipeableListItem to be swiped. */
|
||||
void setSwipeEnabled(boolean swipeEnabled);
|
||||
}
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
|
||||
<public name="state_swiped" type="attr" />
|
||||
<public name="minChildWidth" type="attr"/>
|
||||
<public name="swipeToPrimaryActionEnabled" type="attr"/>
|
||||
<public name="swipeEnabled" type="attr"/>
|
||||
<public name="primaryActionSwipeMode" type="attr"/>
|
||||
|
||||
<public name="Widget.Material3.ListItemLayout" type="style"/>
|
||||
<public name="ThemeOverlay.Material3.ListItemLayout.Segmented" type="style"/>
|
||||
|
||||
@ -25,8 +25,6 @@
|
||||
<attr name="listItemRevealLayoutStyle" format="reference"/>
|
||||
|
||||
<declare-styleable name="ListItemCardView">
|
||||
<!-- Whether or not to enable the swipe to action. -->
|
||||
<attr name="swipeToPrimaryActionEnabled" format="boolean"/>
|
||||
<!-- Whether or not to enable swiping, if a sibling RevealableListItem exists. -->
|
||||
<attr name="swipeEnabled" format="boolean"/>
|
||||
</declare-styleable>
|
||||
@ -39,6 +37,15 @@
|
||||
<declare-styleable name="ListItemRevealLayout">
|
||||
<!-- Minimum width any children are measured as. -->
|
||||
<attr name="minChildWidth" format="dimension" />
|
||||
<!-- Defines the behavior when swiping to reveal the primary action of the ListItemRevealLayout. -->
|
||||
<attr name="primaryActionSwipeMode">
|
||||
<!-- The primary action is disabled. -->
|
||||
<enum name="disabled" value="0"/>
|
||||
<!-- Swiping reveals intermediary states before fully revealing the primary action. -->
|
||||
<enum name="indirect" value="1"/>
|
||||
<!-- Swiping directly reveals the primary action without stopping at intermediary states. -->
|
||||
<enum name="direct" value="2"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<!-- Shape appearance of a single item in the list. -->
|
||||
|
||||
@ -34,7 +34,6 @@
|
||||
<item name="contentPaddingBottom">10dp</item>
|
||||
<item name="contentPaddingLeft">16dp</item>
|
||||
<item name="contentPaddingRight">16dp</item>
|
||||
<item name="swipeToPrimaryActionEnabled">false</item>
|
||||
<item name="swipeEnabled">true</item>
|
||||
</style>
|
||||
|
||||
@ -46,6 +45,7 @@
|
||||
<item name="enforceMaterialTheme">true</item>
|
||||
<item name="materialThemeOverlay">@style/ThemeOverlay.Material3.ListItemRevealLayout</item>
|
||||
<item name="minChildWidth">@dimen/m3_list_reveal_min_child_width</item>
|
||||
<item name="primaryActionSwipeMode">disabled</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Material3Expressive.ListItemRevealLayout" parent="Widget.Material3.ListItemRevealLayout">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user