我的RecyclerView
位于CardView
内。 CardView
的高度为500dp,但如果RecyclerView
较小,我想缩短此高度。
所以我想知道在RecyclerView
第一次完成项目放置时是否有任何监听器被调用,从而可以将RecyclerView
的高度设置为{{ 1}}'的高度(如果小于500dp)。
答案 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();
立即了解上面的代码:
TimerTask-它将运行并检查线程 (每500毫秒)正在运行或完成。
Runnable-runnable就像一个方法,在这里您已编写了在该线程中需要执行的代码。因此,将从此可运行对象调用我们的回收器视图。
线程-使用此线程将调用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”。