android recylerview 瀑布流显示异常终极解决方案

1.正常使用瀑布流流程

定义StaggeredGridLayoutManager

fullyStaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
        fullyStaggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
        recyclerView.setLayoutManager(fullyStaggeredGridLayoutManager);
        newTopicAdapter = new NewTopicAdapter(NewTopicDetailActivity.this, datas);
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        recyclerView.setAdapter(newTopicAdapter);

2.如果发现布局显示异常

可以使用网上的一个自定义的StaggeredGridLayoutManager

package com.unice.longqihair.manager;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import com.unice.longqihair.BuildConfig;

import java.lang.reflect.Field;

/**
 * @author Created by yyj on 2018/8/8
 * @descride 解决Scrollview中嵌套RecyclerView实现瀑布流时无法显示的问题,同时修复了子View显示时底部多出空白区域的问题
 */
public class FullyStaggeredGridLayoutManager extends StaggeredGridLayoutManager {
  private static boolean canMakeInsetsDirty = true;
  private static Field insetsDirtyField = null;

  private static final int CHILD_WIDTH = 0;
  private static final int CHILD_HEIGHT = 1;
  private static final int DEFAULT_CHILD_SIZE = 100;
  private int spanCount = 0;

  private final int[] childDimensions = new int[2];
  private int[] childColumnDimensions;

  private int childSize = DEFAULT_CHILD_SIZE;
  private boolean hasChildSize;
  private final Rect tmpRect = new Rect();

  public FullyStaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
      int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }

  public FullyStaggeredGridLayoutManager(int spanCount, int orientation) {
    super(spanCount, orientation);
    this.spanCount = spanCount;
  }

  public static int makeUnspecifiedSpec() {
    return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
  }

  @Override
  public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,
                        int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);

    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);

    final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
    final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;

    final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
    final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;

    final int unspecified = makeUnspecifiedSpec();

    if (exactWidth && exactHeight) {
      // in case of exact calculations for both dimensions let's use default "onMeasure" implementation
      super.onMeasure(recycler, state, widthSpec, heightSpec);
      return;
    }

    final boolean vertical = getOrientation() == VERTICAL;

    initChildDimensions(widthSize, heightSize, vertical);

    int width = 0;
    int height = 0;

    // it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
    // happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
    // recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
    // called whiles scrolling)
    recycler.clear();

    final int stateItemCount = state.getItemCount();
    final int adapterItemCount = getItemCount();

    childColumnDimensions = new int[adapterItemCount];
    // adapter always contains actual data while state might contain old data (f.e. data before the animation is
    // done). As we want to measure the view with actual data we must use data from the adapter and not from  the
    // state
    for (int i = 0; i < adapterItemCount; i++) {
      if (vertical) {
        if (!hasChildSize) {
          if (i < stateItemCount) {
            // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
            // we will use previously calculated dimensions
            measureChild(recycler, i, widthSize, unspecified, childDimensions);
          } else {
            logMeasureWarning(i);
          }
        }
        childColumnDimensions[i] = childDimensions[CHILD_HEIGHT];
        //height += childDimensions[CHILD_HEIGHT];
        if (i == 0) {
          width = childDimensions[CHILD_WIDTH];
        }
        if (hasHeightSize && height >= heightSize) {
          break;
        }
      } else {
        if (!hasChildSize) {
          if (i < stateItemCount) {
            // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
            // we will use previously calculated dimensions
            measureChild(recycler, i, unspecified, heightSize, childDimensions);
          } else {
            logMeasureWarning(i);
          }
        }
        width += childDimensions[CHILD_WIDTH];
        if (i == 0) {
          height = childDimensions[CHILD_HEIGHT];
        }
        if (hasWidthSize && width >= widthSize) {
          break;
        }
      }
    }

    int[] maxHeight = new int[spanCount];
    for (int i = 0; i < adapterItemCount; i++) {
      int position = i % spanCount;
      if (i < spanCount) {
        maxHeight[position] += childColumnDimensions[i];
      } else if (position < spanCount) {
        int mixHeight = maxHeight[0];
        int mixPosition = 0;
        for (int j = 0; j < spanCount; j++) {
          if (mixHeight > maxHeight[j]) {
            mixHeight = maxHeight[j];
            mixPosition = j;
          }
        }
        maxHeight[mixPosition] += childColumnDimensions[i];
      }
    }

    for (int i = 0; i < spanCount; i++) {
      for (int j = 0; j < spanCount - i - 1; j++) {
        if (maxHeight[j] < maxHeight[j + 1]) {
          int temp = maxHeight[j];
          maxHeight[j] = maxHeight[j + 1];
          maxHeight[j + 1] = temp;
        }
      }
    }
    height = maxHeight[0];//this is max height

    if (exactWidth) {
      width = widthSize;
    } else {
      width += getPaddingLeft() + getPaddingRight();
      if (hasWidthSize) {
        width = Math.min(width, widthSize);
      }
    }

    if (exactHeight) {
      height = heightSize;
    } else {
      height += getPaddingTop() + getPaddingBottom();
      if (hasHeightSize) {
        height = Math.min(height, heightSize);
      }
    }

    setMeasuredDimension(width, height);
  }

  private void logMeasureWarning(int child) {
    if (BuildConfig.DEBUG) {
      Log.w("LinearLayoutManager", "Can't measure child #"
          + child
          + ", previously used dimensions will be reused."
          + "To remove this message either use #setChildSize() method or don't run RecyclerView animations");
    }
  }

  private void initChildDimensions(int width, int height, boolean vertical) {
    if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
      // already initialized, skipping
      return;
    }
    if (vertical) {
      childDimensions[CHILD_WIDTH] = width;
      childDimensions[CHILD_HEIGHT] = childSize;
    } else {
      childDimensions[CHILD_WIDTH] = childSize;
      childDimensions[CHILD_HEIGHT] = height;
    }
  }

  @Override public void setOrientation(int orientation) {
    // might be called before the constructor of this class is called
    //noinspection ConstantConditions
    if (childDimensions != null) {
      if (getOrientation() != orientation) {
        childDimensions[CHILD_WIDTH] = 0;
        childDimensions[CHILD_HEIGHT] = 0;
      }
    }
    super.setOrientation(orientation);
  }

  public void clearChildSize() {
    hasChildSize = false;
    setChildSize(DEFAULT_CHILD_SIZE);
  }

  public void setChildSize(int childSize) {
    hasChildSize = true;
    if (this.childSize != childSize) {
      this.childSize = childSize;
      requestLayout();
    }
  }

  private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize,
      int heightSize, int[] dimensions) {
    final View child;
    try {
      child = recycler.getViewForPosition(position);
    } catch (IndexOutOfBoundsException e) {
      if (BuildConfig.DEBUG) {
        Log.w("LinearLayoutManager",
            "LinearLayoutManager doesn't work well with animations. Consider switching them off",
            e);
      }
      return;
    }

    final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();

    final int hPadding = getPaddingLeft() + getPaddingRight();
    final int vPadding = getPaddingTop() + getPaddingBottom();

    final int hMargin = p.leftMargin + p.rightMargin;
    final int vMargin = p.topMargin + p.bottomMargin;

    // we must make insets dirty in order calculateItemDecorationsForChild to work
    makeInsetsDirty(p);
    // this method should be called before any getXxxDecorationXxx() methods
    calculateItemDecorationsForChild(child, tmpRect);

    final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
    final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);

    final int childWidthSpec =
        getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width,
            canScrollHorizontally());
    final int childHeightSpec =
        getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height,
            canScrollVertically());

    child.measure(childWidthSpec, childHeightSpec);

    dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
    dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;

    // as view is recycled let's not keep old measured values
    makeInsetsDirty(p);
    recycler.recycleView(child);
  }

  private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
    if (!canMakeInsetsDirty) {
      return;
    }
    try {
      if (insetsDirtyField == null) {
        insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
        insetsDirtyField.setAccessible(true);
      }
      insetsDirtyField.set(p, true);
    } catch (NoSuchFieldException e) {
      onMakeInsertDirtyFailed();
    } catch (IllegalAccessException e) {
      onMakeInsertDirtyFailed();
    }
  }

  private static void onMakeInsertDirtyFailed() {
    canMakeInsetsDirty = false;
    if (BuildConfig.DEBUG) {
      Log.w("LinearLayoutManager",
          "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
    }
  }
}

3.recylerview 添加防止顶部空白

        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                fullyStaggeredGridLayoutManager.invalidateSpanAssignments();//防止第一行到顶部有空白
            }
        });

4.如果自己布局出现嵌套recylerview,外面使用scrollview,有时会出现瀑布流显示不全这种情况有一种解决方案,就是把不同布局放在一个最外层recylerview里面

   @Override
            public int getLayoutViewId(int viewType) {
                if (viewType == 0) {
                    return R.layout.new_topic_detail_first_item;
                } else if (viewType == 1) {
                    return R.layout.new_topic_detail_second_item;
                } else {
                    return 0;
                }

            }

5.在item里添加瀑布流显示,轻松解决:

 else if (itemViewType == 1) {
                    bottom_topic_rlview = holder.getView(R.id.bottom_topic_rlview);
                    initRecyclerview(bottom_topic_rlview, topicDetailInfo.getMore_content());
                }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值