安卓学习笔记---ScrollView中嵌套recycleView 出现的不显示,显示不全,终极解决方案

来源:互联网 发布:易语言制作下载器源码 编辑:程序博客网 时间:2024/05/22 09:44

转载博客地址:

http://www.cnblogs.com/woaixingxing/p/6098726.html

同时也可查看这一篇博客:

http://www.th7.cn/Program/Android/201705/1175541.shtml


ScrollView中嵌套recycleView 出现的不显示,显示不全,终极解决方案

   最近公司项目中用到了ScrollView去嵌套recycleView, 最开始我天真的把recycleView直接放入scrollView中,结果可想而知,什么都不显示,瞬间懵逼,我心想应该是和嵌套ListView差不多吧,看来需要重写recycleView中onMeasure()方法,

像这样:

 @Override    protected void onMeasure(int widthSpec, int heightSpec) {        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);        super.onMeasure(widthSpec, expandSpec);    }

结果:运行还是什么都没有。

 

   没办法上网搜索解决方案,一搜看来网友很多跟我有一样的需求,网上也有大神提供的解决方案

例如这样: 

复制代码
 <RelativeLayout     android:layout_width="match_parent"     android:layout_height="wrap_content"     android:descendantFocusability="blocksDescendants">       <android.support.v7.widget.RecyclerView          android:id="@+id/menuRv"          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:layout_marginLeft="@dimen/margin_16"          android:layout_marginRight="@dimen/margin_16"/> </RelativeLayout>
复制代码

我的结果: 运行还是原来一样没有任何反应.

注意: 你们可以试试,说是解决在Android6.0显示不全的问题,如果成功了那祝贺你们,如果没有成功的话就跟着我往下看。

 

例如这样: 重写LinearLayoutManager,来计算每个item进行显示

复制代码
public class FullyLinearLayoutManager extends LinearLayoutManager {    private static final String TAG = FullyLinearLayoutManager.class.getSimpleName();    public FullyLinearLayoutManager(Context context) {        super(context);    }    public FullyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {        super(context, orientation, reverseLayout);    }    private int[] mMeasuredDimension = new int[2];    @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);        Log.i(TAG, "onMeasure called. \nwidthMode " + widthMode + " \nheightMode " + heightSpec + " \nwidthSize "                + widthSize + " \nheightSize " + heightSize + " \ngetItemCount() " + getItemCount());        int width = 0;        int height = 0;        for (int i = 0; i < getItemCount(); i++) {            measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension);            if (getOrientation() == HORIZONTAL) {                width = width + mMeasuredDimension[0];                if (i == 0) {                    height = mMeasuredDimension[1];                }            } else {                height = height + mMeasuredDimension[1];                if (i == 0) {                    width = mMeasuredDimension[0];                }            }        }        switch (widthMode) {        case View.MeasureSpec.EXACTLY:            width = widthSize;        case View.MeasureSpec.AT_MOST:        case View.MeasureSpec.UNSPECIFIED:        }        switch (heightMode) {        case View.MeasureSpec.EXACTLY:            height = heightSize;        case View.MeasureSpec.AT_MOST:        case View.MeasureSpec.UNSPECIFIED:        }        setMeasuredDimension(width, height);    }    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec,            int[] measuredDimension) {        try {            View view = recycler.getViewForPosition(0);// fix                                                        // 动态添加时报IndexOutOfBoundsException            if (view != null) {                RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();                int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(),                        p.width);                int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(),                        p.height);                view.measure(childWidthSpec, childHeightSpec);                measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;                measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;                recycler.recycleView(view);            }        } catch (Exception e) {            e.printStackTrace();        } finally {        }    }}
复制代码

结果:运行成功 好开心,但是发现显示不完全,item越多这个就无法显示完全,不是最后一个被挡了,就是明明已经是最后一个item了,可是滚动条还没有结束,再继续往下滚,下面没有数据而是空了一大片白。没办法这是算失败了。

注意: 你们可以试试,说是可以解决item不显示问题和显示不全问题,结果我是失败了,所以你们如果用这个方法成功了那么恭喜,如果没有请看接下来。

 

  国内目前大多数都是上面几种解决方案,很不幸我逐一试了都不行,最后一个最有希望结果还是失败了,没办法,只能国外找了终于在国外的找到了解决方法,不知道你们是不是用了这个,反正我是用了这个了,结果成功了,很满意。

接下来进入正题:

      1.废话不多说先上个xml看看

复制代码
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#F2F2F2"    android:orientation="vertical" >    <include layout="@layout/include_header_view" />    <ScrollView        android:layout_width="match_parent"        android:layout_height="match_parent"        android:fillViewport="true"         >        <LinearLayout            android:id="@+id/li_show_recyclerView"            android:layout_width="fill_parent"            android:layout_height="wrap_content"            android:orientation="vertical" >            <TextView                android:id="@+id/tvHeaderStormTitle"                android:layout_width="fill_parent"                android:layout_height="wrap_content"                android:layout_margin="1dp"                android:background="@color/white"                android:gravity="center_vertical"                android:minHeight="40dp"                android:paddingLeft="15dp"                android:paddingRight="10dp"                android:textColor="#808080"                android:textSize="16sp" />            <TextView                android:id="@+id/tvHeaderStormEditContent"                android:layout_width="fill_parent"                android:layout_height="wrap_content"                android:layout_margin="1dp"                android:background="@color/white"                android:padding="5dp"                android:textColor="#404040"                android:textSize="17sp" />                        <android.support.v7.widget.RecyclerView                android:id="@+id/id_recyclerview"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:layout_marginTop="10dp" />        </LinearLayout>    </ScrollView></LinearLayout>
复制代码

看完xml后是不是很失望发现和你们的并没有什么不一样 ,但我既然贴了代码就要说明白,这里1.最好为ScrollView加上android:fillViewport="true"这个属性。 2.最好将RecyclerView的高度属性 设成android:layout_height="wrap_content",这2点是建议,

我不知道这两点会不会影响接下来的事,但最好这样设置吧,我没有试过你们可以试试。

      2.接下来看java代码(这里只是截取了用到recycleView的代码)

复制代码
FullyLinearLayoutManager mLayoutManager = new FullyLinearLayoutManager(this);
@Override
public void initView() { mReplyList = new ArrayList<BeanHeaderStorm>(); mRecyclerView = (RecyclerView) findViewById(R.id.id_recyclerview);// 设置item动画 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); if (null == mRecyclerAdapter) {       //设置布局样式
       mRecyclerView.setLayoutManager(mLayoutManager);      
//设置分割线 HeaderStormItemDiratcion diraction = new HeaderStormItemDiratcion(1); mRecyclerView.addItemDecoration(diraction);        //设置适配器 mRecyclerAdapter = new AdapterHeaderStorm(ActivityHeaderStormShow.this, mReplyList);       //设置item点击事件 mRecyclerAdapter.setOnItemClickLitener(this); mRecyclerView.setAdapter(mRecyclerAdapter); } else {      //刷新,这里的适配器是我自己单独写的别直接复制这段代码 mRecyclerAdapter.notifyView(mReplyList); } }
复制代码

这里看完是不是发现好像还是没有什么区别,的确是没有区别,但注意里面红色代码标注的地方,没错用了名字和前面重写LinearLayoutManager用的名字一样,但内部是不一样的,接下来就来看看这里面到底改变了什么

  完全重新重写的FullyLinearLayoutManager如下:

复制代码
import java.lang.reflect.Field;import com.loopj.android.http.BuildConfig;import android.content.Context;import android.graphics.Rect;import android.support.v4.view.ViewCompat;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.util.Log;import android.view.View;/** * 重写 LinearLayoutManager 为了ScrollView可以显示RecyclerView 垂直布局 *  * @author M.Z */public class FullyLinearLayoutManager extends LinearLayoutManager {    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 final int[] childDimensions = new int[2];    private final RecyclerView view;    private int childSize = DEFAULT_CHILD_SIZE;    private boolean hasChildSize;    private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;    private final Rect tmpRect = new Rect();    public FullyLinearLayoutManager(Context context) {        super(context);        this.view = null;    }    public FullyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {        super(context, orientation, reverseLayout);        this.view = null;    }    public FullyLinearLayoutManager(RecyclerView view) {        super(view.getContext());        this.view = view;        this.overScrollMode = ViewCompat.getOverScrollMode(view);    }    public FullyLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {        super(view.getContext(), orientation, reverseLayout);        this.view = view;        this.overScrollMode = ViewCompat.getOverScrollMode(view);    }    public void setOverScrollMode(int overScrollMode) {        if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)            throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);        if (this.view == null) throw new IllegalStateException("view == null");        this.overScrollMode = overScrollMode;        ViewCompat.setOverScrollMode(view, overScrollMode);    }    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();        // 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);                    }                }                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;                }            }        }        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);        if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {            final boolean fit = (vertical && (!hasHeightSize || height < heightSize))                    || (!vertical && (!hasWidthSize || width < widthSize));            ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);        }    }    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");        }    }}
复制代码

忽然一看,我的天是不是改变太大了,对没错,这就是终极的解决方案,最上面的国内的重写代码思路是对的,但还是处理的不够全面,而这个应该是一个外国的大牛写的,写的很详细全面,我拿来试了试,果然很完美的解决了我的问题.


阅读全文
0 0
原创粉丝点击