简体   繁体   English

使用DPAD快速滚动时,RecyclerView onCreateViewHolder过度调用

[英]RecyclerView onCreateViewHolder called excessively when scrolling fast with DPAD

I'm developing on Amazon Fire TV. 我正在亚马逊Fire TV上开发。

Because it's a TV app(No touch), I need focusables inside row's layout to be able to navigate around. 因为它是一个电视应用程序(没有触摸),我需要在行的布局内的焦点能够导航。

I have a really simple Recyclerview with image, text, and a focusable. 我有一个非常简单的Recyclerview图像,文本和可聚焦。 When I press up or down, it all scrolls and stuff correctly, but I noticed that when I navigate faster than scroll can keep up, it creates new viewholders (Off screen) and lags up the UI. 当我向上或向下按压时,它都会正确滚动和填充,但是我注意到当我导航速度快于滚动可以跟上时,它会创建新的视图(关闭屏幕)并延迟UI。

I have created an activity with Creation numbers on it. 我创建了一个包含Creation数字的活动。 When I scroll slowly, the highest creation # is 10. But when I scroll fast, I get cards with creation number 60 in a second. 当我慢慢滚动时,最高的创作#是10.但是当我快速滚动时,我会在一秒内获得创作号为60的卡片。 This causes an enormous lag and the application drops a lot of frames. 这会导致巨大的延迟,并且应用程序会丢失大量帧。 Is my approach totally wrong? 我的做法完全错了吗?

Use the code below to test this out. 使用下面的代码来测试它。

/**
 * Created by sylversphere on 15-04-15.
 */
public class LandfillActivity extends Activity{

private Context context;

private static int ticketNumber;
private static int getTicket(){
    ticketNumber ++;
    return ticketNumber;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    context = this;
    setContentView(R.layout.landfill_activity);
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    GridLayoutManager glm = new GridLayoutManager(context, 2);
    recyclerView.setLayoutManager(glm);
    SickAdapter sickAdapter = new SickAdapter();
    recyclerView.setAdapter(sickAdapter);
}

public class SickViewHolder extends RecyclerView.ViewHolder{
    TextView ticketDisplayer;
    public ImageView imageView;
    public SickViewHolder(View itemView) {
        super(itemView);
        ticketDisplayer = (TextView) itemView.findViewById(R.id.ticketDisplayer);
        imageView = (ImageView) itemView.findViewById(R.id.imageView);

        itemView.findViewById(R.id.focus_glass).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                context.startActivity(new Intent(context, LouisVuittonActivity.class));
            }
        });
    }
    public void setTicket(int value){
        ticketDisplayer.setText(""+value);
    }
}

public class SickAdapter extends RecyclerView.Adapter<SickViewHolder>{

    @Override
    public SickViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        SickViewHolder svh = new SickViewHolder(getLayoutInflater().inflate(R.layout.one_row_element, null));
        svh.setTicket(getTicket());
        return svh;
    }

    @Override
    public void onBindViewHolder(SickViewHolder holder, int position) {
        String[] image_url_array = getResources().getStringArray(R.array.test_image_urls);
        Picasso.with(context).load(image_url_array[position % image_url_array.length] ).fit().centerCrop().into(holder.imageView);
    }

    @Override
    public int getItemCount() {
        return 100000;
    }
}
}

one_row_element.xml one_row_element.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            android:scaleType="centerCrop"
            android:src="@mipmap/sick_view_row_bg" />
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left|center_vertical"
            android:layout_marginLeft="15dp"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/virusTextView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Creation #"
                android:textColor="#fff"
                android:textSize="40sp" />
            <TextView
                android:id="@+id/ticketDisplayer"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="1"
                android:textColor="#fff"
                android:textSize="40sp" />
        </LinearLayout>
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/focus_glass"
            android:background="@drawable/subtle_focus_glass"
            android:focusable="true"
            android:focusableInTouchMode="true"/>
    </FrameLayout>
</FrameLayout>

test_image_urls.xml (Urls not owned by me) test_image_urls.xml(不属于我的网址)

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="test_image_urls"
    formatted="false">
    <item>http://farm4.static.flickr.com/3175/2737866473_7958dc8760.jpg</item>
    <item>http://farm4.static.flickr.com/3276/2875184020_9944005d0d.jpg</item>
    <item>http://farm3.static.flickr.com/2531/4094333885_e8462a8338.jpg</item>
    <item>http://farm4.static.flickr.com/3289/2809605169_8efe2b8f27.jpg</item>
    <item>http://2.bp.blogspot.com/_SrRTF97Kbfo/SUqT9y-qTVI/AAAAAAAABmg/saRXhruwS6M/s400/bARADEI.jpg</item>
    <item>http://fortunaweb.com.ar/wp-content/uploads/2009/10/Caroline-Atkinson-FMI.jpg</item>
    <item>http://farm4.static.flickr.com/3488/4051378654_238ca94313.jpg</item>
    <item>http://farm4.static.flickr.com/3368/3198142470_6eb0be5f32.jpg</item>
    <item>http://www.powercai.net/Photo/UploadPhotos/200503/20050307172201492.jpg</item>
    <item>http://www.web07.cn/uploads/Photo/c101122/12Z3Y54RZ-22027.jpg</item>
    <item>http://www.mitravel.com.tw/html/asia/2011/Palau-4/index_clip_image002_0000.jpg</item>
    <item>http://news.xinhuanet.com/mil/2007-05/19/xinsrc_36205041914150623191153.jpg</item>
    <item>http://ib.berkeley.edu/labs/koehl/images/hannah.jpg</item>
    <item>http://down.tutu001.com/d/file/20110307/ef7937c2b70bfc2da539eea9df_560.jpg</item>
    <item>http://farm3.static.flickr.com/2278/2300491905_5272f77e56.jpg</item>
    <item>http://www.pic35.com/uploads/allimg/100526/1-100526224U1.jpg</item>
    <item>http://img.99118.com/Big2/1024768/20101211/1700013.jpg</item>
    <item>http://farm1.static.flickr.com/45/139488995_bd06578562.jpg</item>
</string-array>
</resources>

subtle_focus subtle_focus

    <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true" android:drawable="@color/glass_focus"/>
    <item android:drawable="@color/glass_normal"/>
</selector>

glass_normal is #9000 glass_normal#9000

glass_focus is #0000 glass_focus#0000

Try increasing the maximum number of recycled views in the pool: 尝试增加池中回收视图最大数量

recyclerView.getRecycledViewPool().setMaxRecycledViews(50);

50 is an arbitrary number, you can try higher or lower and see what happens. 50是一个任意数字,你可以尝试更高或更低,看看会发生什么。

RecyclerView tries to avoid re-using views that have transient state , so if views are invalidated quickly or are animating, they may not be re-used right away. RecyclerView尝试避免重新使用具有瞬态的视图,因此如果视图快速无效或动画,则可能无法立即重用它们。

Similarly if you have many smaller views, you may end up with more on screen than the default pool size can handle (more common with grid like layouts). 类似地,如果您有许多较小的视图,您可能会在屏幕上显示比默认池大小可以处理的更多(更常见的网格布局)。

Picasso is holding you up but the suggested solution to build your own mechanism is not the way to go. 毕加索正在阻止你,但是建立自己机制的建议解决方案并非如此。

Picasso has fallen behind in the last year or so and today there are far better alternatives in the form of Google Glide and Facebook Fresco which specifically released updates to work better with RecyclerView and have proved faster and more efficient in loading, caching and storing in many tests which are available online such as: 毕加索在过去一年左右落后了,今天有更好的替代方案, 谷歌GlideFacebook Fresco ,专门发布更新,以更好地与RecyclerView合作,并证明更快,更有效的加载,缓存和存储在许多可在线获得的测试,例如:

I hope that helped. 我希望有所帮助。 Good luck. 祝好运。

As the commenters pointed out pending responses from Picasso might be holding you up. 正如评论者指出,毕加索的待决回应可能会阻碍你。 If that is the case, you can solve it by extending ImageView and overriding the following method. 如果是这种情况,您可以通过扩展ImageView并覆盖以下方法来解决它。 I think it is worth trying. 我觉得值得尝试。

@Override
protected void onDetachedFromWindow() {
    Picasso.with(context).cancelRequest(this);
    super.onDetachedFromWindow();
}

Update: 更新:

Turns out this is not the correct way, anyone wanting to cancel requests should do so in the onViewRecycled() callback as pointed out in the comments below. 事实证明这不是正确的方法,任何想要取消请求的人都应该在onViewRecycled()回调中这样做,如下面的评论中所指出的。

By digging deeper in Sam Judd 's answer I forced recycler to recycle the views by implementing the followings in its adapter. 通过深入挖掘Sam Judd的回答,我强迫回收者通过在其适配器中实现以下内容来回收视图。

@Override
public boolean onFailedToRecycleView(@NonNull VH holder) 
      return true;
}

As you can see here : 正如你在这里看到的:

Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled due to its transient state. 如果由此适配器创建的ViewHolder由于其瞬态状态而无法回收,则由RecyclerView调用。 Upon receiving this callback, Adapter can clear the animation(s) that effect the View's transient state and return true so that the View can be recycled. 收到此回调后,适配器可以清除影响View瞬态的动画并返回true,以便可以回收View。 Keep in mind that the View in question is already removed from the RecyclerView. 请记住,有问题的视图已从RecyclerView中删除。

In some cases, it is acceptable to recycle a View although it has transient state. 在某些情况下,虽然它具有瞬态,但可以回收View。 Most of the time, this is a case where the transient state will be cleared in onBindViewHolder(ViewHolder, int) call when View is rebound to a new position. 大多数情况下,当View反弹到新位置时,会在onBindViewHolder(ViewHolder,int)调用中清除瞬态。 For this reason, RecyclerView leaves the decision to the Adapter and uses the return value of this method to decide whether the View should be recycled or not. 出于这个原因,RecyclerView将决定留给适配器并使用此方法的返回值来决定是否应该回收View。

Note that when all animations are created by RecyclerView.ItemAnimator, you should never receive this callback because RecyclerView keeps those Views as children until their animations are complete. 请注意,当所有动画由RecyclerView.ItemAnimator创建时,您永远不应该收到此回调,因为RecyclerView会将这些视图保留为子视图,直到其动画完成为止。 This callback is useful when children of the item views create animations which may not be easy to implement using an RecyclerView.ItemAnimator. 当项目视图的子项创建使用RecyclerView.ItemAnimator可能不容易实现的动画时,此回调很有用。

You should never fix this issue by calling holder.itemView.setHasTransientState(false); 你永远不应该通过调用holder.itemView.setHasTransientState(false)来解决这个问题。 unless you've previously called holder.itemView.setHasTransientState(true);. 除非您之前调用了holder.itemView.setHasTransientState(true);. Each View.setHasTransientState(true) call must be matched by a View.setHasTransientState(false) call, otherwise, the state of the View may become inconsistent. 每个View.setHasTransientState(true)调用必须与View.setHasTransientState(false)调用匹配,否则,View的状态可能会变得不一致。 You should always prefer to end or cancel animations that are triggering the transient state instead of handling it manually. 您应该始终更喜欢结束或取消触发瞬态的动画,而不是手动处理它。

For anyone else looking for a quick hack, Do this. 对于其他寻找快速黑客的人,请这样做。 This will delay selection until it's been inflated and selected. 这将延迟选择,直到它被充气和选择。 I don't know how but it works. 我不知道它是如何工作的。 Just return null on onFocusSearchFailed. 只需在onFocusSearchFailed上返回null。

 /**
 * Created by sylversphere on 15-04-22.
 */
public class SomeGridLayoutManager extends GridLayoutManager{

    private final Context context;

    public SomeGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
        this.context = context;
    }

    public SomeGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
        this.context = context;
    }

    @Override
    public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) {
        return null;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 RecyclerView onCreateViewHolder未被调用 - RecyclerView onCreateViewHolder not being called RecyclerView中没有调用适配器的onCreateViewHolder和onBindViewHolder方法? - Adapter onCreateViewHolder and onBindViewHolder methods are not getting called in RecyclerView? RecyclerView index=-1 快速滚动到底部时 - RecyclerView index=-1 when scrolling fast to the bottom 尽管回收了所有视图,还是调用了Recyclerview onCreateViewHolder? - Recyclerview onCreateViewHolder being called despite all views being recycled? RecyclerView onCreateViewHolder 方法未调用 - RecyclerView onCreateViewHolder method not calling Recyclerview 不调用 onCreateViewHolder - Recyclerview not call onCreateViewHolder RecyclerView 不调用 onCreateViewHolder 或 onBindView - RecyclerView not calling onCreateViewHolder or onBindView FirebaseRecyclerAdapter &lt;&gt;不会调用OnCreateViewHolder - OnCreateViewHolder is not being called for the FirebaseRecyclerAdapter<> RecyclerView onCreateViewHolder位置背景颜色 - RecyclerView onCreateViewHolder position background Color 是否有用于 RecyclerView 的 OnScrollListener 在滚动时连续调用,而不仅仅是在状态更改或滚动开始时? - Is there a OnScrollListener for RecyclerView which is called continuously while Scrolling and not only when the State is changed or scrolling begins?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM