2121import static com .google .android .material .listitem .SwipeableListItem .STATE_DRAGGING ;
2222import static com .google .android .material .listitem .SwipeableListItem .STATE_OPEN ;
2323import static com .google .android .material .listitem .SwipeableListItem .STATE_SETTLING ;
24+ import static com .google .android .material .listitem .SwipeableListItem .STATE_SWIPE_PRIMARY_ACTION ;
2425import static com .google .android .material .theme .overlay .MaterialThemeOverlay .wrap ;
2526import static java .lang .Math .max ;
2627import static java .lang .Math .min ;
@@ -68,9 +69,6 @@ public class ListItemLayout extends FrameLayout {
6869 private static final int [] SINGLE_STATE_SET = {android .R .attr .state_single };
6970 private static final int SETTLING_DURATION = 350 ;
7071 private static final int DEFAULT_SIGNIFICANT_VEL_THRESHOLD = 500 ;
71- // The overshoot that the user can swipe the reveal view by before it settles
72- // back to the closest stable swipe state.
73- private final int swipeMaxOvershoot ;
7472
7573 @ Nullable private int [] positionState ;
7674
@@ -84,7 +82,8 @@ public class ListItemLayout extends FrameLayout {
8482 @ Nullable private View swipeToRevealLayout ;
8583 private boolean originalClipToPadding ;
8684
87- private int swipeState = STATE_CLOSED ;
85+ @ SwipeState private int swipeState = STATE_CLOSED ;
86+ @ StableSwipeState private int lastStableSwipeState = STATE_CLOSED ;
8887 private final StateSettlingTracker stateSettlingTracker = new StateSettlingTracker ();
8988
9089 // Cubic bezier curve approximating a spring with damping = 0.6 and stiffness = 800
@@ -124,14 +123,14 @@ public ListItemLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
124123 }
125124
126125 public ListItemLayout (@ NonNull Context context , @ Nullable AttributeSet attrs , int defStyleAttr ) {
127- this (context , attrs , defStyleAttr , R .attr . listItemLayoutStyle );
126+ this (context , attrs , defStyleAttr , R .style . Widget_Material3_ListItemLayout );
128127 }
129128
130129 public ListItemLayout (
131130 @ NonNull Context context , @ Nullable AttributeSet attrs , int defStyleAttr , int defStyleRes ) {
132131 super (wrap (context , attrs , defStyleAttr , defStyleRes ), attrs , defStyleAttr );
132+ // Ensure we are using the correctly themed context rather than the context that was passed in.
133133 context = getContext ();
134- swipeMaxOvershoot = getResources ().getDimensionPixelSize (R .dimen .m3_list_max_swipe_overshoot );
135134 }
136135
137136 @ Override
@@ -215,8 +214,6 @@ private void ensureContentViewIfRevealLayoutExists() {
215214 @ Override
216215 public boolean onTouchEvent (MotionEvent ev ) {
217216 if (ensureSwipeToRevealSetupIfNeeded ()) {
218- // TODO - b/447218120: Check that at least one child is a ListItemRevealLayout and the other
219- // is List content.
220217 // Process the event regardless of the event type.
221218 viewDragHelper .processTouchEvent (ev );
222219 gestureDetector .onTouchEvent (ev );
@@ -272,26 +269,46 @@ public boolean tryCaptureView(@NonNull View child, int pointerId) {
272269
273270 @ Override
274271 public int clampViewPositionHorizontal (@ NonNull View child , int left , int dx ) {
272+ if (!(contentView instanceof SwipeableListItem
273+ && swipeToRevealLayout instanceof RevealableListItem )) {
274+ return 0 ;
275+ }
275276 // TODO:b/443153708 - Support RTL
276- LayoutParams lp = (LayoutParams ) swipeToRevealLayout .getLayoutParams ();
277+ MarginLayoutParams revealViewLp =
278+ (LayoutParams ) swipeToRevealLayout .getLayoutParams ();
279+ MarginLayoutParams contentViewLp = (LayoutParams ) contentView .getLayoutParams ();
280+ int leftPositionClamp =
281+ ((SwipeableListItem ) contentView ).isSwipeToPrimaryActionEnabled ()
282+ ? originalContentViewLeft
283+ - contentView .getMeasuredWidth ()
284+ - contentViewLp .rightMargin
285+ : // left margin is accounted for in originalContentViewLeft
286+ originalContentViewLeft
287+ - ((RevealableListItem ) swipeToRevealLayout ).getIntrinsicWidth ()
288+ - revealViewLp .leftMargin
289+ - revealViewLp .rightMargin ;
277290 return max (
278291 min (left , originalContentViewLeft ),
279- originalContentViewLeft
280- - ((RevealableListItem ) swipeToRevealLayout ).getIntrinsicWidth ()
281- - lp .leftMargin
282- - lp .rightMargin
283- - swipeMaxOvershoot );
292+ leftPositionClamp - ((SwipeableListItem ) contentView ).getSwipeMaxOvershoot ());
284293 }
285294
286295 @ Override
287296 public int getViewHorizontalDragRange (@ NonNull View child ) {
288- return ((RevealableListItem ) swipeToRevealLayout ).getIntrinsicWidth ()
289- + swipeMaxOvershoot ;
297+ if (contentView instanceof SwipeableListItem
298+ && swipeToRevealLayout instanceof RevealableListItem ) {
299+ return ((RevealableListItem ) swipeToRevealLayout ).getIntrinsicWidth ()
300+ + ((SwipeableListItem ) contentView ).getSwipeMaxOvershoot ();
301+ }
302+ return 0 ;
290303 }
291304
292305 @ Override
293306 public void onViewPositionChanged (
294307 @ NonNull View changedView , int left , int top , int dx , int dy ) {
308+ if (!(contentView instanceof SwipeableListItem
309+ && swipeToRevealLayout instanceof RevealableListItem )) {
310+ return ;
311+ }
295312 super .onViewPositionChanged (changedView , left , top , dx , dy );
296313 // TODO:b/443153708 - Support RTL
297314 revealViewOffset = left - originalContentViewLeft ;
@@ -314,19 +331,46 @@ public void onViewPositionChanged(
314331
315332 @ Override
316333 public void onViewReleased (@ NonNull View releasedChild , float xvel , float yvel ) {
317- startSettling (contentView , calculateTargetSwipeState (xvel , releasedChild ));
334+ if (contentView instanceof SwipeableListItem
335+ && swipeToRevealLayout instanceof RevealableListItem ) {
336+ startSettling (contentView , calculateTargetSwipeState (xvel , releasedChild ));
337+ }
318338 }
319339
320340 private int calculateTargetSwipeState (float xvel , View swipeView ) {
341+ if (!((SwipeableListItem ) swipeView ).isSwipeToPrimaryActionEnabled ()) {
342+ if (xvel > DEFAULT_SIGNIFICANT_VEL_THRESHOLD ) { // A fast fling to the right
343+ return STATE_CLOSED ;
344+ }
345+ if (xvel < -DEFAULT_SIGNIFICANT_VEL_THRESHOLD ) { // A fast fling to the left
346+ return STATE_OPEN ;
347+ }
348+ // Settle to the closest point if velocity is not significant
349+ return Math .abs (swipeView .getLeft () - getSwipeRevealViewRevealedOffset ())
350+ < Math .abs (swipeView .getLeft () - getSwipeViewClosedOffset ())
351+ ? STATE_OPEN
352+ : STATE_CLOSED ;
353+ }
354+
355+ // Swipe to action is supported
321356 if (xvel > DEFAULT_SIGNIFICANT_VEL_THRESHOLD ) { // A fast fling to the right
322- return STATE_CLOSED ;
357+ return lastStableSwipeState == STATE_SWIPE_PRIMARY_ACTION
358+ ? STATE_OPEN
359+ : STATE_CLOSED ;
323360 }
324361 if (xvel < -DEFAULT_SIGNIFICANT_VEL_THRESHOLD ) { // A fast fling to the left
325- return STATE_OPEN ;
362+ return lastStableSwipeState == STATE_CLOSED
363+ ? STATE_OPEN
364+ : STATE_SWIPE_PRIMARY_ACTION ;
365+ }
366+
367+ // Settle to the closest point if velocity is not significant
368+ if (Math .abs (swipeView .getLeft () - getSwipeToActionOffset ())
369+ < Math .abs (swipeView .getLeft () - getSwipeRevealViewRevealedOffset ())) {
370+ return STATE_SWIPE_PRIMARY_ACTION ;
326371 }
327372 if (Math .abs (swipeView .getLeft () - getSwipeRevealViewRevealedOffset ())
328373 < Math .abs (swipeView .getLeft () - getSwipeViewClosedOffset ())) {
329- // Settle to the closest point if velocity is not significant
330374 return STATE_OPEN ;
331375 }
332376 return STATE_CLOSED ;
@@ -378,6 +422,17 @@ private int getSwipeViewClosedOffset() {
378422 return originalContentViewLeft ;
379423 }
380424
425+ private int getSwipeToActionOffset () {
426+ if (contentView == null ) {
427+ return 0 ;
428+ }
429+ LayoutParams lp = (LayoutParams ) contentView .getLayoutParams ();
430+ return originalContentViewLeft
431+ - contentView .getMeasuredWidth ()
432+ - lp .leftMargin
433+ - lp .rightMargin ;
434+ }
435+
381436 private int getOffsetForSwipeState (@ StableSwipeState int swipeState ) {
382437 if (swipeToRevealLayout == null ) {
383438 throw new IllegalArgumentException (
@@ -388,6 +443,8 @@ private int getOffsetForSwipeState(@StableSwipeState int swipeState) {
388443 return getSwipeViewClosedOffset ();
389444 case STATE_OPEN :
390445 return getSwipeRevealViewRevealedOffset ();
446+ case STATE_SWIPE_PRIMARY_ACTION :
447+ return getSwipeToActionOffset ();
391448 default :
392449 throw new IllegalArgumentException ("Invalid state to get swipe offset: " + swipeState );
393450 }
@@ -418,7 +475,19 @@ private void startSettling(View contentView, @StableSwipeState int targetSwipeSt
418475 }
419476
420477 private void setSwipeStateInternal (@ SwipeState int swipeState ) {
478+ // If swipe to action is not supported but the swipe state to be set in
479+ // STATE_SWIPE_PRIMARY_ACTION, we do nothing.
480+ if (swipeState == STATE_SWIPE_PRIMARY_ACTION
481+ && !(contentView instanceof SwipeableListItem
482+ && ((SwipeableListItem ) contentView ).isSwipeToPrimaryActionEnabled ())) {
483+ return ;
484+ }
421485 this .swipeState = swipeState ;
486+ if (swipeState == STATE_CLOSED
487+ || swipeState == STATE_OPEN
488+ || swipeState == STATE_SWIPE_PRIMARY_ACTION ) {
489+ this .lastStableSwipeState = swipeState ;
490+ }
422491 }
423492
424493 @ Override
0 commit comments