如何知道RecyclerView完成放置的时间?


74

我在RecyclerView里面有个CardView。的CardView高度为500dp,但是如果RecyclerView较小,我想缩短此高度。因此,我想知道RecyclerView第一次完成放置项时是否会调用任何侦听器,从而可以将RecyclerView的高度设置为CardView的高度(如果小于500dp)。


1
找到了什么?我也很需要
Magnas '16


还需要解决此问题吗?
AndroidStorm

请查看答案
Zain

Answers:


73

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

作为RecyclerView的实现ViewGroup此答案非常有用。您只需要向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.
                recyclerView
                    .getViewTreeObserver()
                    .removeOnGlobalLayoutListener(this);
            }
        });

25
不仅在完成后,还会多次调用此方法。它对我不起作用
Juan Saravia 2015年

9
@Juancho如果在完成后删除监听器,是否可以正常工作onGlobalLayout
霓虹灯Warge

8
@NeonWarge是的,它可以在第一个呼叫中删除监听器,谢谢!
胡安·萨拉维亚

1
您的建议是什么,这东西完全挂在我的手机上,屏幕开始出现故障。.我不得不重新启动手机,并立即删除该应用程序
Danish Ansari

2
对于面临此建议问题的每个人,andrino实际上是正确的。作为onGlobalLayout()的最后一行,您将需要删除侦听器:recyclerView.getViewTreeObserver()。removeOnGlobalLayoutListerner(this); 此行将确保仅调用一次,并且不会挂起设备。
拉姆·耶尔

39

@andrino anwser的工作修改。

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

用实例创建自定义监听器

private RecyclerViewReadyCallback recyclerViewReadyCallback;

public interface RecyclerViewReadyCallback {
    void onLayoutReady();
}

然后设置OnGlobalLayoutListenerRecyclerView

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”并实现侦听器?
Bisonfan95

我会在RecyclerView上设置OnGlobalLayoutListener之前先这样做,所以我确定如果recycler调用onGlobalLayout(),我的侦听器就可以处理行动了
Phatee P

17

如果使用Kotlin,则有更紧凑的解决方案。从这里取样。
此布局侦听器通常用于在测量View后执行某些操作,因此您通常需要等到width和height大于0为止
。侦听器的所有特定功能和属性。

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

12
您链接到的文章已更新-如果您使用的是android-ktx库,则只需做一下即可recyclerView.doOnNextLayout { ... }
詹姆斯(James)

1
我认为您仍然需要OnGlobalLayoutListener,因为您正在观察RecyclerView的子视图的更改。并触发了视图树的侦听器更改,而使用OnLayoutChangeListener的doOnNextLayout仅观察视图本身的更改。
Arst

我尝试使用doOnNextLayout {},但是由于某种原因,它的调用早于要求的时间。但是,当您需要在定位Activity的所有元素之后调用一个动作时,它就派上用场了。stackoverflow.com/a/64853255/8313316
Gregory

8

我改善了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状态)

完整布局计算完成后调用。布局计算可能会onLayoutChildren(Recycler, State) 因动画或布局测量而包含多个调用,但将仅包含一个onLayoutCompleted(State)调用。该方法将在调用结束时被layout(int, int, int, int)调用。

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


您的mLayoutManager是什么?
Yonko Kilasi

@YonkoKilasi mLayoutManager是的实例NotifyingLinearLayoutManager
苏菲安

5

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


2
此技术非常适合通过动态自动调整滚动到recyclerview GridLayoutManager的当前选定视图。设置适配器后: recyclerView.setAdapter(adapter); 我把: recyclerView.post(new Runnable(){@Override public void run(){recyclerView.scrollToPosition(currentSelected);}}); 而且,它的运行非常出色(currentSelected是我保存位置信息的地方)!PavelGP不错!
Trasd

3

我尝试了这个,对我有用。这是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
}

您已将其及时发布,因为我今天正在寻找它:-]
t3chb0t

这与上面的答案相同:stackoverflow.com/a/52492441/8313316
Gregory

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

有人!

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


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

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

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


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

0

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

...

-1

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


它确实为我工作。这样做有什么弊端?性能?
不可能

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

-2
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。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.