How to know when the RecyclerView has finished laying down the items?

后端 未结 12 1419
野的像风
野的像风 2020-11-29 20:06

I have a RecyclerView that is inside a CardView. The CardView has a height of 500dp, but I want to shorten this height if the Re

相关标签:
12条回答
  • 2020-11-29 20:50

    I improved the answer of android developer to fix this problem. It's a Kotlin code but should be simple to understand even if you know only Java.

    I wrote a subclass of LinearLayoutManager which lets you listen to the onLayoutCompleted() event:

    /**
     * This class calls [mCallback] (instance of [OnLayoutCompleteCallback]) when all layout
     * calculations are complete, e.g. following a call to
     * [RecyclerView.Adapter.notifyDataSetChanged()] (or related methods).
     *
     * In a paginated listing, we will decide if load more needs to be called in the said callback.
     */
    class NotifyingLinearLayoutManager(context: Context) : LinearLayoutManager(context, VERTICAL, false) {
        var mCallback: OnLayoutCompleteCallback? = null
    
        override fun onLayoutCompleted(state: RecyclerView.State?) {
            super.onLayoutCompleted(state)
            mCallback?.onLayoutComplete()
        }
    
        fun isLastItemCompletelyVisible() = findLastCompletelyVisibleItemPosition() == itemCount - 1
    
        interface OnLayoutCompleteCallback {
            fun onLayoutComplete()
        }
    }
    

    Now I set the mCallback like below:

    
    mLayoutManager.mCallback = object : NotifyingLinearLayoutManager.OnLayoutCompleteCallback {
        override fun onLayoutComplete() {
            // here we know that the view has been updated.
            // now you can execute your code here
        }
    }
    
    

    Note: what is different from the linked answer is that I use onLayoutComplete() which is only invoked once, as the docs say:

    void onLayoutCompleted (RecyclerView.State state)

    Called after a full layout calculation is finished. The layout calculation may include multiple onLayoutChildren(Recycler, State) calls due to animations or layout measurement but it will include only one onLayoutCompleted(State) call. This method will be called at the end of layout(int, int, int, int) call.

    This is a good place for the LayoutManager to do some cleanup like pending scroll position, saved state etc.

    0 讨论(0)
  • 2020-11-29 20:54

    I tried this and it worked for me. Here is the Kotlin extension

    fun RecyclerView.runWhenReady(action: () -> Unit) {
        val globalLayoutListener = object: ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                action()
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
        viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
    }
    

    then call it

    myRecyclerView.runWhenReady {
        // Your action
    }
    
    0 讨论(0)
  • 2020-11-29 20:55

    I also needed to execute code after my recycler view finished inflating all elements. I tried checking in onBindViewHolder in my Adapter, if the position was the last, and then notified the observer. But at that point, the recycler view still was not fully populated.

    As RecyclerView implements ViewGroup, this anwser was very helpful. You simply need to add an OnGlobalLayoutListener to the recyclerView:

    View recyclerView = findViewById(R.id.myView);
    recyclerView
        .getViewTreeObserver()
        .addOnGlobalLayoutListener(
            new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    // At this point the layout is complete and the
                    // dimensions of recyclerView and any child views 
                    // are known.
                    recyclerView
                        .getViewTreeObserver()
                        .removeOnGlobalLayoutListener(this);
                }
            });
    
    0 讨论(0)
  • 2020-11-29 20:56
    // Another way
    
    // Get the values
    Maybe<List<itemClass>> getItemClass(){
        return /*    */
    }
    
    // Create a listener 
    void getAll(DisposableMaybeObserver<List<itemClass>> dmo) {
        getItemClass().subscribeOn(Schedulers.computation())
                      .observeOn(AndroidSchedulers.mainThread())
                      .subscribe(dmo);
    }
    
    // In the code where you want to track the end of loading in recyclerView:
    
    DisposableMaybeObserver<List<itemClass>> mSubscriber = new DisposableMaybeObserver<List<itemClass>>() {
            @Override
            public void onSuccess(List<itemClass> item_list) {
                adapter.setWords(item_list);
                adapter.notifyDataSetChanged();
                Log.d("RECYCLER", "DONE");
            }
    
            @Override
            public void onError(Throwable e) {
                Log.d("RECYCLER", "ERROR " + e.getMessage());
            }
    
            @Override
            public void onComplete() {
                Log.d("RECYCLER", "COMPLETE");
            }
        };
    
    void getAll(mSubscriber);
    
    
    //and
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        mSubscriber.dispose();
        Log.d("RECYCLER","onDestroy");
    }
    
    0 讨论(0)
  • 2020-11-29 20:57
    recyclerView.getChildAt(recyclerView.getChildCount() - 1).postDelayed(new Runnable() {
            @Override
            public void run() {
                //do something
            }
    }, 300);
    

    RecyclerView only lays down specific number of items at a time, we can get the number by calling getChildCount(). Next, we need to get the last item by calling getChildAt (int index). The index is getChildCount() - 1.

    I'm inspired by this person answer and I can't find his post again. He said it's important to use postDelayed() instead of regular post() if you want to do something to the last item. I think it's to avoid NullPointerException. 300 is delayed time in ms. You can change it to 50 like that person did.

    0 讨论(0)
  • 2020-11-29 20:59

    If you are using the android-ktx library and if you need to perform an action after positioning all elements of the Activity, you can use this method:

    // define 'afterMeasured' Activity listener:
    fun Activity.afterMeasured(f: () -> Unit) {
        window.decorView.findViewById<View>(android.R.id.content).doOnNextLayout {
            f()
        }
    }
    
    // in Activity:
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(...)
    
        afterMeasured {
            // do something here
        }    
    }
    
    0 讨论(0)
提交回复
热议问题