我有一个包含13个项目的列表(虽然可以添加或删除项目),位置0-12。当首次显示包含RecyclerView的片段时,用户只能看到位置0到7(位置7只有一半可见)。在我的适配器中Log
每次视图持有者被绑定/绑定(如果语法适用于此,则为idk)并记录其位置。
适配器
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Log.d(TAG, "onBindViewHolder() position: " + position);
...
}
从我的Log
我看到0-7的位置受到约束:
我有一个selectAll()
方法,可以通过适配器位置获取每个ViewHolder
。如果返回的holder
不是null
,我会使用返回的holder
更新视图以显示其已被选中。如果返回的持有者是null
,我会调用selectOnBind()
一个标记该位置的视图的方法更新,以显示它在绑定时被选中,而不是实时,因为它当前未显示:
public void selectAll() {
for (int i = 0; i < numberOfItemsInList; i++) {
MyAdapter.ViewHolder holder = (MyAdapter.ViewHolder)
mRecyclerView.findViewHolderForAdapterPosition(i);
Log.d(TAG, "holder at position " + i + " is " + holder);
if (holder != null) {
select(holder);
} else {
selectOnBind(i);
}
}
}
在这种方法中,我Log
holder
及其位置:
所以到目前为止一切看起来都很正常。我们有0-7的位置显示,并且根据Log
这些是位置限制。当我在不更改可见视图(滚动)的情况下点击selectAll()
时,我看到定义了0-7位置,8-12位置为null
。到目前为止一切都很好。
这里有趣的地方。如果在调用selectAll()
之后我向下滚动列表位置8和9,则不会显示它们已被选中。
检查Log
时,我发现它是因为它们从未被绑定,即使它们被报告为null
:
更令人困惑的是,每次都不会发生这种情况。如果我首先启动应用程序并测试它可能会有效。但事后似乎没有失败。我猜它与被回收的观点有关,但即便如此,它们也不必被束缚?
编辑(6-29-16)
在AndroidStudio更新后,我似乎无法重现该错误。它按照我的预期工作,绑定空视图。如果这个问题重新出现,我将回到这篇文章。
答案 0 :(得分:15)
发生这种情况是因为:
getChildAt
将无效,并将为该位置返回null)onBind
不会被调用)致电recyclerView.setItemViewCacheSize(0)
将解决此“问题”。
由于默认值为2(private static final int DEFAULT_CACHE_SIZE = 2;
中的RecyclerView.Recycler
),因此您将始终获得2个不会调用onBind
但未添加到回收站的视图
答案 1 :(得分:6)
在您的情况下,位置8和9的视图未被回收,它们将从窗口分离并将再次附加。对于这些分离视图onBindViewHolder
未调用,仅调用onViewAttachedToWindow
。如果您在适配器中覆盖这些功能,您可以看到我在说什么。
@Override
public void onViewRecycled(ViewHolder vh){
Log.wtf(TAG,"onViewRecycled "+vh);
}
@Override
public void onViewDetachedFromWindow(ViewHolder viewHolder){
Log.wtf(TAG,"onViewDetachedFromWindow "+viewHolder);
}
现在为了解决您的问题,您需要跟踪应该回收但已分离的视图,然后执行您的部分过程
@Override
public void onViewAttachedToWindow(ViewHolder viewHolder){
Log.wtf(TAG,"onViewAttachedToWindow "+viewHolder);
}
答案 2 :(得分:2)
Pedro Oliveira和Zartha的回答非常适合理解这个问题,但我没有看到任何我满意的解决方案。
我相信你有2个不错的选择取决于你正在做什么:
选项1
如果您想要onBindViewHolder()
调用屏幕外视图,无论它是否已缓存/分离,您都可以执行以下操作:
RecyclerView.ViewHolder view_holder = recycler_view.findViewHolderForAdapterPosition( some_position );
if ( view_holder != null )
{
//manipulate the attached view
}
else //view is either non-existant or detached waiting to be reattached
notifyItemChanged( some_position );
这个想法是,如果视图被缓存/分离,那么notifyItemChanged()
将告诉适配器视图无效,这将导致调用onBindViewHolder()
。
选项2
如果您只想执行部分更改(而不是onBindViewHolder()
内的所有内容),那么在onBindViewHolder( ViewHolder view_holder, int position )
内,您需要将position
存储在view_holder
中,并在onViewAttachedToWindow( ViewHolder view_holder )
中执行您想要的更改。
我建议使用选项1以简化,除非你的onBindViewHolder()
正在做一些密集的事情,比如弄乱Bitmaps。
答案 3 :(得分:0)
我认为在recyclelerview中玩视图并不是一个好主意。我总是使用的方法只是为RecyclerView引入一个标志到模型。假设你的模型就像 -
class MyModel{
String name;
int age;
}
如果您正在跟踪视图是否被选中,则向模型引入一个布尔值。现在它看起来像 -
class MyModel{
String name;
int age;
boolean isSelected;
}
现在,您的复选框将根据新标志isSelected(在onBindViewHolder()中)进行选择/取消选中。在视图上的每个选择上都会将相应模型选定值的值更改为true,而在未选中时将其更改为false。在你的情况下,只需运行一个循环将所有模型的isSelected值更改为true,然后调用notifyDataSetChanged()
。
例如,假设您的列表是
ArrayList<MyModel> recyclerList;
private void selectAll(){
for(MyModel myModel:recyclerList)
myModel.isSelected = true;
notifyDataSetChanged();
}
我的建议,在使用recyclerView或ListView时尽量少尝试使用视图。
所以在你的情况下 -
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.clickableView.setTag(position);
holder.selectableView.setTag(position);
holder.checkedView.setChecked(recyclerList.get(position).isSelected);
Log.d(TAG, "onBindViewHolder() position: " + position);
...
}
@Override
public void onClick(View view){
int position = (int)view.getTag();
recyclerList.get(position).isSelected = !recyclerList.get(position).isSelected;
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int position = (int)buttonView.getTag();
recyclerList.get(position).isSelected = isChecked;
}
希望它能为您提供帮助,如果您需要进一步解释,请与我们联系。)
答案 4 :(得分:0)
所以我认为你的问题在下面由@Pedro Oliveira回答。 RecycleView的主要意义,即他在任何时候都使用特殊算法来缓存ViewHolder。所以接下来onBindViewHolder(...)可能不起作用,例如。如果视图是静态的,或其他东西。
关于您的问题,您认为将RecycleView用于动态更改的视图。 不要做它!因为RecycleView使视图无效并具有缓存系统,所以你会遇到很多问题。
使用LinkedListView执行此任务!
答案 5 :(得分:0)
当列表中有大量项目时,您已传递给recyclerview adapter
,则不会遇到onBindViewHolder()
在滚动时不执行的问题。
但是如果列表中的项目较少(我检查过列表大小5),则可能会遇到此问题。
更好的解决方案是检查list size
。
请在下面找到示例代码。
private void setupAdapter(){
if (list.size() <= 10){
recycler.setItemViewCacheSize(0);
}
recycler.setAdapter(adapter);
recycler.setLayoutManager(linearLayoutManager);
}