22

我正在亚马逊 Fire TV 上开发。

因为它是一个电视应用程序(无触摸),所以我需要在行布局内的焦点能够导航。

我有一个非常简单Recyclerview的图像、文本和可聚焦的。当我向上或向下按时,它会滚动并正确填充,但我注意到当我导航的速度超过滚动可以跟上时,它会创建新的视图持有者(屏幕外)并滞后 UI。

我创建了一个带有创建编号的活动。当我慢慢滚动时,最高的创造数是 10。但当我快速滚动时,我会在一秒钟内获得创造数 60 的卡片。这会导致巨大的延迟,并且应用程序会丢掉很多帧。我的方法完全错误吗?

使用下面的代码来测试一下。

/**
 * 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

<?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(不属于我的网址)

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

微妙的焦点

    <?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#9000

glass_focus#0000

4

5 回答 5

20

尝试增加池中回收视图的最大数量:

recyclerView.getRecycledViewPool().setMaxRecycledViews(50);

50 是一个任意数字,您可以尝试更高或更低,看看会发生什么。

RecyclerView 试图避免重用具有瞬态的视图,因此如果视图很快失效或正在动画,它们可能不会立即被重用。

同样,如果您有许多较小的视图,您最终可能会在屏幕上看到比默认池大小可以处理的更多(在类似网格的布局中更常见)。

于 2015-04-28T01:07:08.807 回答
3

毕加索阻止了你,但建议的建立你自己的机制的解决方案不是要走的路。

Picasso 在过去一年左右已经落后,而今天有更好的替代品,例如Google GlideFacebook Fresco,它们专门发布了更新以更好地与 RecyclerView 配合使用,并且在许多应用中被证明在加载、缓存和存储方面更快、更高效可在线获得的测试,例如:

我希望这有帮助。祝你好运。

于 2015-04-27T06:33:04.573 回答
2

正如评论者所指出的,毕加索的未决回复可能会让你望而却步。如果是这种情况,您可以通过扩展ImageView和覆盖以下方法来解决它。我认为值得一试。

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

更新:

事实证明这不是正确的方法,任何想要取消请求的人都应该在onViewRecycled()回调中这样做,如下面的评论中指出的那样。

于 2015-04-21T15:55:52.487 回答
1

通过深入挖掘Sam Judd的答案,我强制回收者通过在其适配器中实现以下内容来回收视图。

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

正如你在这里看到的:

如果由此适配器创建的 ViewHolder 由于其瞬态而无法回收,则由 RecyclerView 调用。收到此回调后,Adapter 可以清除影响 View 瞬态状态的动画并返回 true,以便可以回收 View。请记住,相关视图已从 RecyclerView 中删除。

在某些情况下,回收视图是可以接受的,尽管它具有瞬态。大多数情况下,当 View 反弹到新位置时,瞬态将在 onBindViewHolder(ViewHolder, int) 调用中被清除。为此,RecyclerView 将决定权留给了 Adapter,并使用该方法的返回值来决定 View 是否应该被回收。

请注意,当所有动画都由 RecyclerView.ItemAnimator 创建时,您永远不应收到此回调,因为 RecyclerView 将这些视图保留为子视图,直到它们的动画完成。当项视图的子项创建可能不容易使用 RecyclerView.ItemAnimator 实现的动画时,此回调很有用。

您永远不应该通过调用 holder.itemView.setHasTransientState(false); 来解决此问题;除非你之前调用了 holder.itemView.setHasTransientState(true);。每个 View.setHasTransientState(true) 调用必须匹配一个 View.setHasTransientState(false) 调用,否则,View 的状态可能会变得不一致。您应该始终更喜欢结束或取消触发瞬态的动画,而不是手动处理它。

于 2018-04-21T08:08:49.327 回答
-2

对于其他想要快速破解的人,请执行此操作。这将延迟选择,直到它被膨胀和选择。我不知道如何,但它有效。只需在 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;
    }
}
于 2015-05-20T21:27:53.967 回答