【问题标题】:How to know when the RecyclerView has finished laying down the items?如何知道 RecyclerView 何时完成放置项目?
【发布时间】:2015-08-04 12:30:54
【问题描述】:

我有一个RecyclerView,它位于CardView 中。 CardView 的高度为 500dp,但如果 RecyclerView 较小,我想缩短此高度。 所以我想知道当RecyclerView第一次完成放置它的项目时是否有任何监听器被调用,从而可以将RecyclerView的高度设置为CardView的高度(如果更小500dp)。

【问题讨论】:

标签: android android-cardview android-recyclerview


【解决方案1】:

我还需要在我的回收器视图完成所有元素的膨胀后执行代码。我尝试在我的适配器中签入onBindViewHolder,如果该位置是最后一个,然后通知观察者。但此时,回收站视图仍未完全填充。

由于RecyclerView 实现ViewGroupthis anwser 非常有帮助。您只需将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.
                recyclerView
                    .getViewTreeObserver()
                    .removeOnGlobalLayoutListener(this);
            }
        });

【讨论】:

  • 这个方法会被多次调用,而不仅仅是在它完成的时候。它对我不起作用
  • @Juancho 如果在onGlobalLayout 上完成后删除监听器,它会起作用吗?
  • @NeonWarge 是的,它可以在第一次通话中删除监听器,谢谢!
  • 你到底在暗示什么,这东西完全挂了我的手机,屏幕开始出现故障。我不得不重新启动手机并立即删除应用程序
  • 对于这个建议遇到问题的每个人,andrino 实际上是正确的。作为 onGlobalLayout() 的最后一行,您需要删除 Listener:recyclerView.getViewTreeObserver().removeOnGlobalLayoutListerner(this);这一行将确保它只被调用一次并且不会挂起设备。
【解决方案2】:

@andrino anwser 的工作修改。

正如@Juancho 在上面的评论中指出的那样。该方法被多次调用。在这种情况下,我们希望它只被触发一次。

使用实例创建自定义侦听器,例如

private RecyclerViewReadyCallback recyclerViewReadyCallback;

public interface RecyclerViewReadyCallback {
    void onLayoutReady();
}

然后在你的RecyclerView上设置OnGlobalLayoutListener

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

【讨论】:

  • 应该在什么时候初始化“recyclerViewReadyCallback”并实现监听?
  • 我会在 RecyclerView 上设置 OnGlobalLayoutListener 之前这样做,所以我确信如果 recycler 调用 onGlobalLayout() 我的监听器已准备好处理操作
【解决方案3】:

如果您使用 Kotlin,那么有一个更紧凑的解决方案。 来自here的样本。
此布局侦听器通常用于在测量视图后执行某些操作,因此您通常需要等到宽度和高度大于 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:
myRecycler.afterMeasured {
    // do the scroll (you can use the RecyclerView functions and properties directly)
    // ...
}    

【讨论】:

  • 你链接的文章已经更新——如果你使用的是android-ktx库,你可以简单地做recyclerView.doOnNextLayout { ... }
  • 我认为您仍然需要 OnGlobalLayoutListener 因为您正在观察 RecyclerView 的子视图的变化。并且监听器触发了视图的树变化,而使用 OnLayoutChangeListener 的 doOnNextLayout 只观察视图本身的变化。
  • 我尝试使用 doOnNextLayout{},但由于某种原因,它的调用时间比要求的早。但是,当您需要在定位 Activity 的所有元素后调用操作时,它就派上用场了。 stackoverflow.com/a/64853255/8313316
【解决方案4】:

我发现知道何时完成放置项目的最佳方法是使用 LinearLayoutManager。

例如:

private RecyclerView recyclerView;

...

recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false){
    @Override
    public void onLayoutCompleted(RecyclerView.State state) {
        super.onLayoutCompleted(state);
        // TODO 
    }
);

...

【讨论】:

  • 在以上所有方法中,这是最适合我的解决方案。 Kotlin 方式如下 val linearLayoutManager = object : LinearLayoutManager(baseContext, VERTICAL, false) { override fun onLayoutCompleted(state: RecyclerView.State?) { super.onLayoutCompleted(state) //TODO: } }
  • 确实,最直接的方法,谢谢!
  • 这个方法被多次调用。
【解决方案5】:

我改进了the answer of android developer 来解决这个问题。这是一段 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(),它只被调用一次,就像docs say

void onLayoutCompleted(RecyclerView.State 状态)

在完整的布局计算完成后调用。布局 计算可能包括多个onLayoutChildren(Recycler, State) 由于动画或布局测量而调用,但它只包括 一个onLayoutCompleted(State) 电话。该方法将在 layout(int, int, int, int) 通话结束。

这是 LayoutManager 进行一些清理的好地方,例如 待定滚动位置、保存状态等。

【讨论】:

  • 你的 mLayoutManager 是什么?
  • @YonkoKilasi mLayoutManager 是NotifyingLinearLayoutManager 的一个实例。
  • isLastItemCompletelyVisible() 的意义何在?它在您的示例中未使用
  • @DennisVA 这只是一种方便的方法。您可以删除它。
【解决方案6】:

我试过这个,它对我有用。这是 Kotlin 扩展

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

然后调用它

myRecyclerView.runWhenReady {
    // Your action
}

【讨论】:

【解决方案7】:

同样,在弹出列表/网格项目后,您可以使用RecyclerView.post() 方法运行您的代码。在我的情况下,这已经足够了。

【讨论】:

  • 此技术非常适合滚动到具有动态自动调整功能的 recyclerview GridLayoutManager 的当前选定视图。设置适配器后: recyclerView.setAdapter(adapter); 我输入: recyclerView.post(new Runnable() { @Override public void run() { recyclerView.scrollToPosition(currentSelected); } }); 而且,它工作得非常好(currentSelected 是我保存位置信息的地方)! PavelGP 不错的收获。
【解决方案8】:

一旦触发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
    }
}

某人!

(当然,这是受到@andrino 和@Phatee 答案的启发)

【讨论】:

    【解决方案9】:

    这是另一种方式:

    您可以在线程中加载您的回收站视图。像这样

    首先,创建一个 TimerTask

    void threadAliveChecker(final Thread thread){
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    if(!thread.isAlive()){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // stop your progressbar here
                            }
                        });
                    }
                }
            },500,500);
        }
    

    其次,创建一个runnable

    Runnable myRunnable = new Runnable() {
                            @Override
                            public void run() {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        // load recycler view from here
                                        // you can also toast here
                                    }
                                });
                            }
                        };
    
    

    三、创建线程

    Thread myThread = new Thread(myRunnable);
    threadAliveChecker();
    // start showing progress bar according to your need (call a method)
    myThread.start();
    

    现在了解上面的代码:

    1. TimerTask - 它将运行并检查线程 (每 500 毫秒)正在运行或完成。

    2. Runnable - runnable 就像一个方法,在这里您已经编写了需要在该线程中完成的代码。所以我们的回收视图将从这个runnable中被调用。

    3. Thread - Runnable 将使用此线程调用。所以我们已经启动了这个线程,当 recyclerView 加载(可运行代码加载)时,这个线程将完成(不会存在于编程语言中)。

    所以我们的计时器正在检查线程是否处于活动状态,当 thread.isAlive 为 false 时,我们将删除进度条。

    【讨论】:

      【解决方案10】:

      如果你使用的是android-ktx库,如果你需要在定位完Activity的所有元素后执行一个动作,你可以使用这个方法:

      // 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
          }    
      }
      

      【讨论】:

        【解决方案11】:

        您可以使用这种方法

          if ((adapterPosition + 1) == mHistoryResponse.size) {
            Log.d("debug", "process done")
          }
        

        用加1获取adapterPosition并检查您的数据类大小,如果大小相同,则该过程实际上完成了。

        【讨论】:

          【解决方案12】:

          对于那些没有使用 Kotlin 并且还在苦苦挣扎的人,我快速浏览了一下他们实现的 doOnNextLayout(crossinline action: (view: T) -> Unit) 解决方案,非常简单。

          如果您不使用自定义 RecyclerView (CustomRecyclerView extends RecyclerView),您可能需要重新考虑它,因为这会带来很多您将来可能想要添加的好处(平滑滚动到位置、垂直分隔线等) ..)

          CustomRecyclerView.class 内部

          public void doOnNextLayout(Consumer<List<View>> onChange) {
                  addOnLayoutChangeListener(
                          new OnLayoutChangeListener() {
                              @Override
                              public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                                  onChange.accept(getChildren());
                                  removeOnLayoutChangeListener(this);
                              }
                          }
                  );
              }
          

          getChildren() 方法正在构建一个大小为 getChildCount(); 的列表;每次迭代都有一个 add(getChild(i)) 。

          现在...

          关于代码的一个重要方面是:removeOnLayoutChangeListener(this);

          这意味着开发人员要求您在每次向适配器提交列表之前执行此操作。

          理论上,我们只能在创建 RecyclerView 时将监听器放置一次(IMO 会更便宜/更好)+ 因为我们正在检索视图,所以我们可以使用 DataBindingUtils 检索它们各自的绑定。并通过其 DataBind 获取适配器在 onBind 上提供的任何数据。

          要做到这一点,它需要更多的代码。

          首先适配器需要知道它们所在的 Fragment,或者 RecyclerView::setAdapter 需要提供一个 ViewLifeCyclerOwner,第三个更简单的选择是为适配器提供一个 onViewDestroy() 方法,并在 Fragment 的 onDestroyView 上执行它() 方法。

          @Override
              public void onDestroyView() {
                  super.onDestroyView();
                  adater.onViewDestroyed();
              }
          

          通过覆盖 onAttachedToRecyclerView,我们可以将它们附加为观察者。

          private final List<Runnable> submitter = new ArrayList<>();
          
          @Override
          public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
              super.onAttachedToRecyclerView(recyclerView);
              if (recyclerView instanceof CustomRecyclerView) {
                  submitter.add(((CustomRecyclerView) recyclerView)::onSubmit);
              }
          }
          

          CustomRecyclerView 端的 onSubmit 方法将提供一个布尔值,告诉 recyclerView 是否正在提交列表。

          private boolean submitting;
          
          public void doOnNextLayout(Consumer<List<View>> onChange) {
              addOnLayoutChangeListener(
                      (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                          if (submitting) {
                              onChange.accept(getChildren());
                              submitting = false;
                          }
                      }
              );
          }
          
          public void onSubmit() {
              submitting = true;
          }
          

          每个 Runnable 都会在列表提交的那一刻被执行:

          对于 ListAdapter,有 2 个可能的入口点:

          private void notifyRVs() {
              for (Runnable r:submitter
                   ) {
                  r.run();
              }
          }
          
          @Override
          public void submitList(@Nullable List<X> list, @Nullable Runnable commitCallback) {
              notifyRVs();
              super.submitList(list, commitCallback);
          }
          
          @Override
          public void submitList(@Nullable List<X> list) {
              notifyRVs();
              super.submitList(list);
          }
          

          现在为了防止内存泄漏,我们必须清除 ViewDestroyed() 上的 Runnables 列表 在适配器内部...

          public void onViewDestroyed() {
                  submitter.clear();
              }
          

          现在因为方法的功能发生了变化,我们应该重命名它,并将 Consumer 与 LayoutChangeListener() 解耦

          private Consumer<List<View>> onChange = views -> {};
          
          public void setOnListSubmitted(Consumer<List<View>> onChange) {
              this.onChange = onChange;
          }
          
          public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
              super(context, attrs);
              //Read attributes
              setOnListSubmissionListener();
          }
          
          private void setOnListSubmissionListener() {
              addOnLayoutChangeListener(
                      (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                          if (submitting) {
                              onChange.accept(getChildren());
                              submitting = false;
                          }
                      }
              );
          }
          

          【讨论】:

            【解决方案13】:

            我就是这样做的

            recyclerView.setLayoutManager(new LinearLayoutManager(this){
                        @Override
                        public void onLayoutCompleted(RecyclerView.State state) {
                            super.onLayoutCompleted(state);
            
                            //code to run after loading recyclerview
                            new GuideView.Builder(MainActivity.this)
                                    .setTargetView(targetView)
                                    .setGravity(Gravity.auto)
                                    .setDismissType(DismissType.outside)
                                    .setContentTextSize(18)
                                    .build()
                                    .show();
            
                        }
                    });
            

            我希望这会对你有所帮助。

            【讨论】:

              【解决方案14】:

              对我有用的是在设置 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”。

              【讨论】:

              • 它确实对我有用。这样做有什么坏处?性能?
              【解决方案15】:
              // 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");
              }
              

              【讨论】:

                【解决方案16】:
                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。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-01-04
                  • 2013-02-01
                  • 2011-04-05
                  • 2021-04-25
                  • 2020-02-13
                  相关资源
                  最近更新 更多