如何知道RecyclerView何时完成了物品的放置?

时间:2015-05-22 13:03:30

标签: android android-cardview android-recyclerview

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

15 个答案:

答案 0 :(得分:46)

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

RecyclerView实施ViewGroup时,this anwser非常有帮助。您只需要向recyclerView添加OnGlobalLayoutListener

    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's height to prevent infinite loop
                            recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        }
                    });

答案 1 :(得分:28)

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

答案 2 :(得分:6)

我发现知道何时完成放置项目的最佳方法是使用 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 
    }
);

...

答案 3 :(得分:5)

如果使用Kotlin,则有更紧凑的解决方案。 来自here的样本。
此布局侦听器通常用于在测量View后执行某些操作,因此通常需要等到width和height大于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 :(得分:3)

我改进了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()仅被调用一次,如文档所述:

  

void onLayoutCompleted(RecyclerView.State状态)

     

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

     

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

答案 5 :(得分:2)

我一直在努力尝试删除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答案的启发)

答案 6 :(得分:2)

我尝试了这个,对我有用。这是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 :(得分:1)

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

答案 8 :(得分:1)

这是另一种方法:

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

首先,创建一个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 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就像一个方法,在这里您已编写了在该线程中需要执行的代码。因此,将从此可运行对象调用我们的回收器视图。

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

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

答案 9 :(得分:1)

如果您使用的是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
    }    
}

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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。

答案 12 :(得分:0)

您可以使用这种方法

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

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

答案 13 :(得分:0)

对于那些没有使用 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;
                }
            }
    );
}

答案 14 :(得分:-2)

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