15

I am facing two problems:

  1. The scroll listener won't work
  2. The RecyclerView never recycles any views when it's attached to a NestedScrollView. It acts like a linear layout inside the ScrollView. It uses a lot of memory and creates lags.

I am attaching a youtube player fragment on top of the recycler view since I can't put a fragment inside the recycler view. In my code you can see there is a frame layout.

My layout looks like this:

    <android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nestedScroll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ffffff"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <FrameLayout               
                android:layout_width="match_parent"
                android:layout_height="240dp"
                android:layout_alignParentTop="true"/>





        </LinearLayout>


        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/youtube_layout"
            android:visibility="visible"/>




    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

I want to use scroll listener to load more items so I have tried the following:

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    Log.i(TAG, "onScrolled: ");
    // bail out if scrolling upward or already loading data
    if (dy < 0 || dataLoading.isDataLoading()) return;

    final int visibleItemCount = recyclerView.getChildCount();
    final int totalItemCount = layoutManager.getItemCount();
    final int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();

    if ((totalItemCount - visibleItemCount) <= (firstVisibleItem )) {
        onLoadMore();
    }


}

But layoutManager.findFirstVisibleItemPosition()==0 so it's not working, and more over onScrolled never called twice since I set recyclerview.setNestedScrollingEnabled(false)

so I have tried in onBindView like this

   public void onBindViewHolder(RecyclerView.ViewHolder customViewHolder, int i) {
    Log.w("d","inside bind view");

    if(i>=getItemCount()-1  &&   !datamanager.isDataLoading()){
        datamanager.loadmoreData();
    }

but the recylerview binds the all the view at once time before I even start scrolling, so this method also does not work.

Cody Gray
  • 222,280
  • 47
  • 466
  • 543
Asthme
  • 4,663
  • 5
  • 44
  • 64

3 Answers3

12

The recycler view never recycle any views when its attached to nested scroll view,its acts as set on linear layout inside scroll view.its create a huge memory and lags the screen.

Exactly. You can't put a RecyclerView inside another scrolling view.

It will not work, because the wrapping view needs to know the total height of the RecyclerView and the RecyclerView can only know its total height by measuring and layouting all of its items.

How to fix this?

Don't put a RecyclerView inside another scrolling view.

If you need a header or footer you will have to add it to the RecyclerView and let the RecyclerView take care of displaying it. Get rid of your ScrollView and move your header inside of the RecyclerView.

Technically it is possible to also load fragments inside a RecyclerView, but I have to admit getting this to work properly is a bit tricky.

There are also a lot of libraries that facilitate the process, my personal favorite being Epoxy made by AirBnb, but there's also Groupie, and a lot of others.

David Medenjak
  • 31,688
  • 14
  • 100
  • 129
  • i have loaded the fragments, but recycler view does not allow the fragment state. Its crashing randomly. – Asthme Aug 06 '17 at 15:06
0

I have handled this kind of situation. There is a recyclerview. Recycler view has viewpager. The viewpager has fragments and then there is a recyclerview inside the fragment.

You need to hack the touches to make it scroll perfectly.

private boolean interceptTouch = true;
    private static final int MIN_DISTANCE = 200;
    private float downX = 0, downY = 0, moveX = 0, moveY = 0, upX = 0, upY;

    public TouchInterceptRecyclerView(Context context) {
        super(context);
    }

    public TouchInterceptRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (getRvTop() == 0) {
            onTouchEvent(ev);
        }
        return interceptTouch;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                moveX = event.getX();
                moveY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                upX = event.getX();
                upY = event.getY();
                break;
        }
        float deltaX = upX - downX;

        if (getViewTop() == 0 && moveY > downY && Math.abs(moveY - downY) > MIN_DISTANCE) {  // moving down
            interceptTouch = true;
        }else if (Math.abs(deltaX) > MIN_DISTANCE) {
            if (upX > downX) {    // Left to Right swipe action
                interceptTouch = false;
            }else {    // Right to left swipe action
                interceptTouch = false;
            }
        } else {  // screen tap
            interceptTouch = false;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
    }

    @Override
    public void onScrolled(int dx, int dy) {
        if (getViewTop() <= 0) {
            interceptTouch = false;            // dispatch the touch to child when reached top
        } else {
            interceptTouch = true;           // do not dispatch the touch event
        }
        super.onScrolled(dx, dy);
    }

    public int getViewTop() {
        int top = 0;
        View v = this.getChildAt(1);
        if (v == null) {
            v = this.getChildAt(0);
        }
        if (v != null && v instanceof LinearLayout) {
            top = v.getTop();
        }
        return top;
    }

    public int getRvTop() {
        int top = -1;
        View v = this.getChildAt(1);
        if (v == null) {
            v = this.getChildAt(0);
        }
        if (v != null && v instanceof LinearLayout) {

            ViewPager vp = (ViewPager) v.findViewById(R.id.single_tribe_pager);
            if (vp != null) {
                int currentPos = vp.getCurrentItem();
                RecyclerView rv = (RecyclerView) vp.findViewWithTag("recyclerview" + currentPos);
                if (rv != null) {
                    View v1 = rv.getChildAt(0);
                    if (v1 != null && v1 instanceof CardView) {
                        top = v1.getTop() - ResourceUtils.getDimensionPixelOffset(R.dimen.padding_20);   // deducting 20 as we have give top margin as 20 to the top layout
                    }
                }
            }
        }
        return top;
    }
Harsh
  • 569
  • 3
  • 18
-1

Here is how I achieved it, Ive tried stripping down the code to the barest minimum

public class DataAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int VIEW_TYPE_LOADING = 0;
    private static final int VIEW_TYPE_ITEM = 1;

    public boolean isLoading;
    private int visibleThreshold = 5;
    private int lastVisibleItem, totalItemCount;

    public ArrayList<Data> datas;

    private OnLoadMoreListener mOnLoadMoreListener;

    public DataAdapter(RecyclerView mRecyclerView, ArrayList<Data> datas) {
        this.datas = datas;

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();

                if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    if (mOnLoadMoreListener != null) {
                        DataAdapter.this.datas.remove(null);
                        mOnLoadMoreListener.onLoadMore();
                    }
                }
            }
        });
    }
    public static class DataObjectHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        //your views

        public DataObjectHolder(View itemView) {
            super(itemView);
            //initialize your views
        }

        @Override
        public void onClick(View v) {
            if (onItemClickListener != null) onItemClickListener.onItemClick(getAdapterPosition(), v);
        }
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public int getItemViewType(int position) {
        return datas.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ITEM) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_data, parent, false);
            DataObjectHolder dataObjectHolder = new DataObjectHolder(view);
            return dataObjectHolder;
        } else if (viewType == VIEW_TYPE_LOADING) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_loading_item, parent, false);
            return new LoadingViewHolder(view);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder hldr, final int position) {
        if (hldr instanceof DataObjectHolder) {
            final Data data = datas.get(position);
            final DataObjectHolder holder = (DataObjectHolder) hldr;

            //bind your views


        } else if (hldr instanceof LoadingViewHolder) {
            LoadingViewHolder loadingViewHolder = (LoadingViewHolder) hldr;

            if (isLoading) {
                loadingViewHolder.progressBar.setVisibility(View.VISIBLE);
                loadingViewHolder.progressBar.setIndeterminate(true);

                loadingViewHolder.tv_load_more.setVisibility(View.GONE);
                loadingViewHolder.tv_load_more.setOnClickListener(null);
            } else {
                loadingViewHolder.progressBar.setVisibility(View.GONE);

                loadingViewHolder.tv_load_more.setVisibility(View.VISIBLE);
                loadingViewHolder.tv_load_more.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (mOnLoadMoreListener != null) {
                            datas.remove(null);
                            mOnLoadMoreListener.onLoadMore();
                        }
                    }
                });
            }
        }
    }

    @Override
    public int getItemCount() {
        return datas.size();
    }

    public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
        this.mOnLoadMoreListener = mOnLoadMoreListener;
    }

}

The XML for the loadingViewHolder is show below, you basically just inflate to get the holder

<?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="60dp">

    <ProgressBar
        android:id="@+id/progressBar1"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:indeterminate="true"/>

    <TextView
        android:id="@+id/tv_load_more"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:text="Tap to load more"
        android:textSize="14sp"/>

</RelativeLayout>

Hopefully youll find this helpful

Odufuwa Segun
  • 274
  • 1
  • 9
  • i am using fragment on the top of recyclerview. i can't write like this – Asthme Aug 05 '17 at 05:29
  • Oh sorry, I was answering to another question – Odufuwa Segun Aug 05 '17 at 11:43
  • Feel free to delete your answer if you determined that you posted it in error. – Cody Gray Aug 05 '17 at 11:57
  • You know what, yh I didnt post the answer to a wrong question. I solved the part of loading more items into the recycler view as you scroll to the bottom. As others have pointed out, you cant use a fragment inside a recycler view. There always a better way to achieve what youre trying to do – Odufuwa Segun Aug 05 '17 at 21:58