1818import com .google .android .material .R ;
1919
2020import static com .google .android .material .theme .overlay .MaterialThemeOverlay .wrap ;
21+ import static java .lang .Math .max ;
22+ import static java .lang .Math .min ;
2123
2224import android .content .Context ;
2325import android .util .AttributeSet ;
3032import androidx .annotation .NonNull ;
3133import androidx .annotation .Nullable ;
3234import androidx .customview .widget .ViewDragHelper ;
33- import java .lang .ref .WeakReference ;
3435
3536/**
3637 * A container layout for a List item.
@@ -61,7 +62,13 @@ public class ListItemLayout extends FrameLayout {
6162
6263 @ Nullable private ViewDragHelper viewDragHelper ;
6364 @ Nullable private GestureDetector gestureDetector ;
64- private WeakReference <ListItemRevealLayout > swipeToRevealLayoutRef ;
65+
66+ private int revealViewOffset ;
67+ private int originalContentViewLeft ;
68+
69+ private View contentView ;
70+ @ Nullable private View swipeToRevealLayout ;
71+ private boolean originalClipToPadding ;
6572
6673 public ListItemLayout (@ NonNull Context context ) {
6774 this (context , null );
@@ -115,32 +122,54 @@ public void updateAppearance(int position, int itemCount) {
115122 @ Override
116123 public void addView (View child , int index , ViewGroup .LayoutParams params ) {
117124 super .addView (child , index , params );
118- if (swipeToRevealLayoutRef != null
119- && swipeToRevealLayoutRef .get () != null
120- && child instanceof ListItemRevealLayout ) {
125+ if (swipeToRevealLayout != null && child instanceof ListItemRevealLayout ) {
121126 throw new UnsupportedOperationException (
122127 "Only one ListItemRevealLayout is supported in a ListItemLayout." );
123- } else if (child instanceof ListItemRevealLayout ) {
124- swipeToRevealLayoutRef = new WeakReference <>((ListItemRevealLayout ) child );
128+ } else if (child instanceof RevealableListItem ) {
129+ swipeToRevealLayout = child ;
130+ originalClipToPadding = getClipToPadding ();
131+ setClipToPadding (false );
132+ // Start the reveal view at a desired width of 0
133+ ((RevealableListItem ) child ).setRevealedWidth (0 );
134+ // Make sure reveal view has lower elevation
135+ child .setElevation (getElevation () - 1 );
125136 }
126137 }
127138
128139 @ Override
129140 public void onViewRemoved (View child ) {
130141 super .onViewRemoved (child );
131- if (child instanceof ListItemRevealLayout ) {
142+ if (child == swipeToRevealLayout ) {
132143 viewDragHelper = null ;
133144 gestureDetector = null ;
134- swipeToRevealLayoutRef = null ;
145+ swipeToRevealLayout = null ;
146+ setClipToPadding (originalClipToPadding );
147+ }
148+ }
149+
150+ private void ensureContentViewIfRevealLayoutExists () {
151+ if (contentView != null || swipeToRevealLayout == null ) {
152+ return ;
153+ }
154+
155+ int childCount = getChildCount ();
156+ for (int i = 0 ; i < childCount ; i ++) {
157+ if (getChildAt (i ) instanceof SwipeableListItem ) {
158+ if (contentView != null ) {
159+ throw new UnsupportedOperationException (
160+ "Only one SwipeableListItem view is allowed in a ListItemLayout." );
161+ }
162+ contentView = getChildAt (i );
163+ }
135164 }
136165 }
137166
138167 @ Override
139168 public boolean onTouchEvent (MotionEvent ev ) {
140- if (ensureSwipeToRevealSetupIfNeeded () && viewDragHelper != null && gestureDetector != null ) {
169+ if (ensureSwipeToRevealSetupIfNeeded ()) {
141170 // TODO - b/447218120: Check that at least one child is a ListItemRevealLayout and the other
142171 // is List content.
143- // Process the event
172+ // Process the event regardless of the event type.
144173 viewDragHelper .processTouchEvent (ev );
145174 gestureDetector .onTouchEvent (ev );
146175
@@ -176,7 +205,7 @@ public boolean onInterceptTouchEvent(MotionEvent ev) {
176205 * variables are initialized if true.
177206 */
178207 private boolean ensureSwipeToRevealSetupIfNeeded () {
179- if (swipeToRevealLayoutRef == null || swipeToRevealLayoutRef . get () == null ) {
208+ if (swipeToRevealLayout == null ) {
180209 return false ;
181210 }
182211 if (viewDragHelper == null || gestureDetector == null ) {
@@ -186,8 +215,50 @@ private boolean ensureSwipeToRevealSetupIfNeeded() {
186215 new ViewDragHelper .Callback () {
187216 @ Override
188217 public boolean tryCaptureView (@ NonNull View child , int pointerId ) {
218+ if (swipeToRevealLayout != null && contentView != null ) {
219+ viewDragHelper .captureChildView (contentView , pointerId );
220+ }
189221 return false ;
190222 }
223+
224+ @ Override
225+ public int clampViewPositionHorizontal (@ NonNull View child , int left , int dx ) {
226+ // TODO:b/443153708 - Support RTL
227+ LayoutParams lp = (LayoutParams ) swipeToRevealLayout .getLayoutParams ();
228+ return max (
229+ min (left , originalContentViewLeft ),
230+ originalContentViewLeft
231+ - ((RevealableListItem ) swipeToRevealLayout ).getIntrinsicWidth ()
232+ - lp .leftMargin
233+ - lp .rightMargin );
234+ }
235+
236+ @ Override
237+ public int getViewHorizontalDragRange (@ NonNull View child ) {
238+ return ((RevealableListItem ) swipeToRevealLayout ).getIntrinsicWidth ();
239+ }
240+
241+ @ Override
242+ public void onViewPositionChanged (
243+ @ NonNull View changedView , int left , int top , int dx , int dy ) {
244+ super .onViewPositionChanged (changedView , left , top , dx , dy );
245+ // TODO:b/443153708 - Support RTL
246+ revealViewOffset = left - originalContentViewLeft ;
247+
248+ LayoutParams revealViewLp = (LayoutParams ) swipeToRevealLayout .getLayoutParams ();
249+ LayoutParams contentViewLp = (LayoutParams ) contentView .getLayoutParams ();
250+
251+ // Desired width is how much we've displaced the content view minus any margins.
252+ int revealViewDesiredWidth =
253+ max (
254+ 0 ,
255+ originalContentViewLeft
256+ - contentView .getLeft ()
257+ - contentViewLp .rightMargin // only end margin matters here
258+ - revealViewLp .leftMargin
259+ - revealViewLp .rightMargin );
260+ ((RevealableListItem ) swipeToRevealLayout ).setRevealedWidth (revealViewDesiredWidth );
261+ }
191262 });
192263
193264 gestureDetector =
@@ -207,6 +278,29 @@ public boolean onScroll(
207278 }
208279 });
209280 }
281+
282+ ensureContentViewIfRevealLayoutExists ();
283+
210284 return true ;
211285 }
286+
287+ @ Override
288+ protected void onLayout (boolean changed , int left , int top , int right , int bottom ) {
289+ super .onLayout (changed , left , top , right , bottom );
290+ if (contentView != null && swipeToRevealLayout != null ) {
291+ originalContentViewLeft = contentView .getLeft ();
292+ int originalContentViewRight = contentView .getRight ();
293+ contentView .offsetLeftAndRight (revealViewOffset );
294+ // We always lay out swipeToRevealLayout such that the right is aligned to where the original
295+ // content view's right was. Note that if the content view had a right margin, it will
296+ // effectively be passed onto the reveal view.
297+ LayoutParams lp = (LayoutParams ) swipeToRevealLayout .getLayoutParams ();
298+ // TODO:b/443153708 - Support RTL
299+ swipeToRevealLayout .layout (
300+ originalContentViewRight - lp .rightMargin - swipeToRevealLayout .getMeasuredWidth (),
301+ swipeToRevealLayout .getTop (),
302+ originalContentViewRight - lp .rightMargin ,
303+ swipeToRevealLayout .getBottom ());
304+ }
305+ }
212306}
0 commit comments