当前位置:网站首页>Swiperefreshlayout+recyclerview failed to pull down troubleshooting

Swiperefreshlayout+recyclerview failed to pull down troubleshooting

2022-06-25 08:39:00 Shen Ye

background

stay Android In development ,SwipeRefreshLayout+RecyclerView Of UI The pattern helps developers easily provide the ability of pull-down refresh , But in previous development , I met a SwipeRefreshLayout Problems that cannot be pulled down .

The specific problem is , I need to go through RecyclerView Show a series of content , The displayed content is distributed according to the server , For example, you need to show Banner, Recommendation list, etc . Later, during the second phase of development , This interface needs to add a small feature, The server may not issue Banner Related properties , But because of the previous Adapter There is some logic in , As a result, if it is not issued Banner Data words ,RecyclerView Will still show Banner Corresponding ViewHolder, And this ViewHolder Empty to display . To solve this problem , I am here Adapter A new... Has been added to the ViewType And corresponding ViewHolderEmptyViewHolder. This ViewHolder Corresponding Layout That's true :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="0dp">
</RelativeLayout>

But this will lead to the problems mentioned in this article ,SwipeRefreshLayout Unable to pull down refresh , Simply speaking , Is that when RecyclerView Of the 0 Height View The height of 0 when ,SwipeRefreshLayout Unable to pull down refresh .

analysis

Observe SwipeRefreshLayout Unable to pull down ,RecyclerView When you pull down, something appears RippleEffect, It is obvious that it was originally required by SwipeRefreshLayout Handle the pull-down gesture , By RecyclerView The consumption . As for this gesture RecyclerView Reasons for consumption , according to Android Of View The principle of event distribution , A reasonable guess is SwipeRefreshLayout There is a problem with the distribution or interception of this event .

dispatchTouchEvent

SwipeRefreshLayout Inherited from ViewGroup, And does not rewrite itself dispatchTouchEvent, So the problem is probably not here .

onInterceptTouchEvent

onInterceptTouchEvent This method is responsible for intercepting events . The basic implementation of this method is :

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  // Omitted code 
​
  if (!isEnabled() || mReturningToStart || canChildScrollUp()
      || mRefreshing || mNestedScrollInProgress) {
    // Fail fast if we're not in a state where a swipe is possible
    return false;
  }
  // Omitted code 
  return true;
}

according to bug What happened and the code , This if The judgment is very suspicious , The probability is that there is a lot of judgment true, Lead to the whole method return false, The incident was not stopped . therefore canChildScrollUp() This judgment is very suspicious . according to SwipeRefreshLayout The law of operation , You can guess when RecyclerView When it can slide ,canChildScrollUp() by true,SwipeRefreshLayout Do not block gestures ,RecyclerView Can slide up and down normally ; When RecyclerView In slide to 0 individual item, And continue to pull down ,canChildScrollUp() return false, The incident was SwipeRefreshLayout Intercept .

canChildScrollUp() The source code of the method is as follows :

    /**
     * @return Whether it is possible for the child view of this layout to
     *         scroll up. Override this if the child view is a custom view.
     */
    public boolean canChildScrollUp() {
        if (mChildScrollUpCallback != null) {
            return mChildScrollUpCallback.canChildScrollUp(this, mTarget);
        }
        if (mTarget instanceof ListView) {
            return ListViewCompat.canScrollList((ListView) mTarget, -1);
        }
        return mTarget.canScrollVertically(-1);
    }

This note is very interesting , Remind us if SwipeRefreshLayout The son of View It's a custom View, This method has to be rewritten , tell SwipeRefreshLayout Son View Can I slide . ( I didn't know this before , So the level of the source code will be improved )

Code logic , There are three branches here , Just look at the code , I don't know which branch to take , Forget it , direct debug Well .

Debug

adopt debug It can be seen that the final branch is taken , there mTarget It's the son View, namely RecyclerView, But think about it later Debug RecyclerView#canScrollVertically() when ` You will find that the number of lines of code does not correspond to . This is because the domestic manufacturers will be Android Customize the system source code , Recommended here Android Studio Self contained Simulator , Use the original operating system Debug.

Start simulator , restart Debug.

/**
 * Check if this view can be scrolled vertically in a certain direction.
 *
 * @param direction Negative to check scrolling up, positive to check scrolling down.
 * @return true if this view can be scrolled in the specified direction, false otherwise.
 */
public boolean canScrollVertically(int direction) {
    final int offset = computeVerticalScrollOffset();
    final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
    if (range == 0) return false;
    if (direction < 0) {
        return offset > 0;
    } else {
        return offset < range - 1;
    }
}

The point is that final int offset = computeVerticalScrollOffset();, here offset Returned a greater than 0 Value , This causes the entire method to return true.

Let's see it again computeVerticalScrollOffset() What happened? .

/**
 * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
 * This value is used to compute the length of the thumb within the scrollbar's track. </p>
 *
 * <p>The range is expressed in arbitrary units that must be the same as the units used by
 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
 *
 * <p>Default implementation returns 0.</p>
 *
 * <p>If you want to support scroll bars, override
 * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your
 * LayoutManager.</p>
 *
 * @return The vertical offset of the scrollbar's thumb
 * @see RecyclerView.LayoutManager#computeVerticalScrollOffset
 * (RecyclerView.State)
 */
@Override
public int computeVerticalScrollOffset() {
    if (mLayout == null) {
        return 0;
    }
    return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
}

Here we give the calculation of the vertical offset to layoutManager To do , We're going to use LinearLayoutManager, Continue to look at the source code . After a few jumps , Arrived ScrollbarHelper#computeScrollOffset(), The core code is as follows :

/**
 * @param startChild View closest to start of the list. (top or left)
 * @param endChild   View closest to end of the list (bottom or right)
 */
static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation,
        View startChild, View endChild, RecyclerView.LayoutManager lm,
        boolean smoothScrollbarEnabled, boolean reverseLayout) {
  // Omitted code ...
 
    final int minPosition = //.... Visible page 0 individual item
    final int maxPosition = //... The last visible item
    final int itemsBefore = //... Visible page 0 individual item In front of item The number of 
      //
    final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild)
            - orientation.getDecoratedStart(startChild));// Last visible item The bottom split line of the to 0 A visible item Top style line 
    final int itemRange = Math.abs(lm.getPosition(startChild)
            - lm.getPosition(endChild)) + 1;
  // so item The number of 
    final float avgSizePerRow = (float) laidOutArea / itemRange;
  // Every item The average height of 
​
    return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
            - orientation.getDecoratedStart(startChild)));// Calculate how far you can slide 
}

The general logic here is as follows , First, find the first and last... Visible on the screen item Of position, Already these two item Distance between , Then calculate each item The average height of , forecast recyclerview The sliding distance . for instance , Currently visible 3 individual item,position And height respectively 0->100px,1->200px,2->300p, So here itemRange Namely 2-0+1=3, The average height is 600/3=200px,itemBefore by 0, So the final result is that you can slide 0*200px = 0px. Change the scene , The current courseware is 3 individual item,position And height respectively 1->100px,2->200px,3->300p, here itemBefore for 1, Therefore, the calculation result is 200px.

So in this scenario , The first 0 individual item The height is 0 invisible , The first 0 A visible item Of position by 1, therefore itemBefore by 1, The result must be greater than 0.

So go back to the top canScrollVertically() Method , The return value is true, It can lead to swipeRefreshLayout Do not block gestures , Therefore, the pull-down refresh cannot be triggered .

terms of settlement

Set up EmptyViewHolder The height of 1px That's all right. . In this way, No 0 individual item It can be seen ,itemBefore = 0, The return value is 0,SwipeRefreshLayout You can intercept the event .

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="1px">
</RelativeLayout>

原网站

版权声明
本文为[Shen Ye]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/176/202206250708122982.html