Подтвердить что ты не робот

Как узнать, когда RecyclerView завершил установку элементов?

У меня есть RecyclerView который находится внутри CardView. Высота CardView составляет 500 dp, но я хочу сократить эту высоту, если RecyclerView меньше. Поэтому мне интересно, есть ли какой-нибудь слушатель, который вызывается, когда RecyclerView впервые завершил установку своих элементов, позволяя установить высоту RecyclerView CardView высоте CardView (если она меньше 500 dp).

4b9b3361

Ответ 1

Я также должен был выполнить код после того, как мое представление переработчика закончило раздувать все элементы. Я попытался проверить onBindViewHolder в моем адаптере, если позиция была последней, а затем уведомил наблюдателя. Но на тот момент точка зрения переработчика еще не была полностью заполнена.

Поскольку RecyclerView реализует ViewGroup, этот ответ был очень полезным. Вам просто нужно добавить OnGlobalLayoutListener в 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.
                            //Remove listener after changed RecyclerView height to prevent infinite loop
                            recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        }
                    });

Ответ 2

Рабочая модификация @andrino anwser.

Как отметил @Juancho в комментарии выше. Этот метод вызывается несколько раз. В этом случае мы хотим, чтобы он был запущен только один раз.

Создать пользовательский слушатель с экземпляром, например

private RecyclerViewReadyCallback recyclerViewReadyCallback;

public interface RecyclerViewReadyCallback {
    void onLayoutReady();
}

Затем установите OnGlobalLayoutListener на свой RecyclerView

recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (recyclerViewReadyCallback != null) {
                    recyclerViewReadyCallback.onLayoutReady();
                }
                recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });

после этого вам нужно только реализовать пользовательский слушатель с вашим кодом

recyclerViewReadyCallback = new RecyclerViewReadyCallback() {
             @Override
             public void onLayoutReady() {
                 //
                 //here comes your code that will be executed after all items are laid down
                 //
             }
};

Ответ 3

Если вы используете Kotlin, то есть более компактное решение. Образец отсюда.
Этот слушатель макета обычно используется для выполнения чего-либо после измерения View, поэтому вам обычно нужно подождать, пока ширина и высота не станут больше 0.
... он может использоваться любым объектом, который расширяет View, а также может иметь доступ ко всем его конкретным функциям и свойствам из слушателя.

// define 'afterMeasured' layout listener:
inline fun <T: View> T.afterMeasured(crossinline f: T.() -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (measuredWidth > 0 && measuredHeight > 0) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                f()
            }
        }
    })
}

// using 'afterMeasured' handler:
recycler.afterMeasured {
    // do the scroll (you can use the RecyclerView functions and properties directly)
    // ...
}    

Ответ 4

Я пытаюсь удалить OnGlobalLayoutListener после его запуска, но это вызывает IllegalStateException. Поскольку мне нужно прокрутить мой recyclerView ко второму элементу, то я должен был проверить, есть ли у него уже дети, и если это первый раз, это правда, только тогда я прокручиваю:

public class MyActivity extends BaseActivity implements BalanceView {
    ...
    private boolean firstTime = true;
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        ViewTreeObserver vto = myRecyclerView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (myRecyclerView.getChildCount() > 0 && MyActivity.this.firstTime){
                    MyActivity.this.firstTime = false;
                    scrollToSecondPosition();
                }
            }
        });
    }
    ...
    private void scrollToSecondPosition() {
        // do the scroll
    }
}

HTH кто-то!

(Конечно, это было вдохновлено на ответы @andrino и @Phatee)

Ответ 5

Также в тех же случаях вы можете использовать метод RecyclerView.post() для запуска вашего кода после появления элементов списка/сетки. В моих случаях этого было достаточно.

Ответ 6

Я улучшил ответ разработчика Android, чтобы решить эту проблему. Это код Kotlin, но он должен быть прост для понимания, даже если вы знаете только Java.

Я написал подкласс LinearLayoutManager, который позволяет прослушивать событие onLayoutCompleted():

/**
 * 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()
    }
}

Теперь я установил mCallback, как показано ниже:


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

Примечание. отличается от связанного ответа тем, что я использую onLayoutComplete(), который вызывается только один раз, как говорят в документации:

void onLayoutCompleted (RecyclerView.State state)

Вызывается после завершения полного расчета макета. Расположение Расчет может включать в себя несколько onLayoutChildren(Recycler, State) вызовы из-за анимации или измерения макета, но будут включать только один вызов onLayoutCompleted(State). Этот метод будет вызван на конец вызова layout(int, int, int, int).

Это хорошее место для LayoutManager, чтобы сделать некоторые очистки, как ожидающая прокрутка позиция, сохраненное состояние и т.д.

Ответ 7

// 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");
}

Ответ 8

recyclerView.getChildAt(recyclerView.getChildCount() - 1).postDelayed(new Runnable() {
        @Override
        public void run() {
            //do something
        }
}, 300);

RecyclerView устанавливает только определенное количество элементов за раз, мы можем получить номер, вызвав getChildCount(). Далее нам нужно получить последний элемент, вызвав getChildAt (int index). Индекс getChildCount() - 1.

Этот ответ меня вдохновил, и я не могу найти его пост снова. Он сказал, что важно использовать postDelayed() вместо обычной post(), если вы хотите что-то сделать с последним элементом. Я думаю это чтобы избежать NullPointerException. 300 - задержанное время в мс. Вы можете изменить его на 50, как сделал этот человек.

Ответ 9

Что сработало для меня, так это добавить слушателя после установки адаптера RecyclerView.

ServerRequest serverRequest = new ServerRequest(this);
serverRequest.fetchAnswersInBackground(question_id, new AnswersDataCallBack()
{
     @Override
     public void done(ArrayList<AnswerObject> returnedListOfAnswers)
     {
         mAdapter = new ForumAnswerRecyclerViewAdapter(returnedListOfAnswers, ForumAnswerActivity.this);
         recyclerView.setAdapter(mAdapter);
         recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
         {
             @Override
             public void onGlobalLayout()
             {
                 progressDialog.dismiss();
             }
         });
     }
 });

Это отклоняет "progressDialog" после глобального состояния макета или видимости представлений в дереве просмотра.