如何在适配器类中避免多次调用findViewById()?

3
我正在尝试通过在Adapter类中尽可能少地调用findViewById()来尽可能加快应用程序的速度。
我听说过一种“方法”,可以创建一个包含View实例和整数值的HashMap<T>,并将其与适配器的ViewHolder内部类一起使用。正如您所看到的,我对此了解甚少,并没有机会询问更多关于这个“方法”的问题。
我已经在互联网上搜索了解决方案,但要么是我使用了错误的关键字进行搜索,要么是我没有将解决方案识别为解决方案。
有人能指导我正确的方法吗?也许有人可以写一个小样例来完成这个过程?
编辑1
我的当前ListView已经使用了ViewHolder模式,所以我没有任何问题。之所以提出这个问题,是因为我听说使用HashMap模式比使用ViewHolder模式可以获得更快的速度。我知道这是模糊的描述,但这就是我来这里问的原因。
分享代码不会有任何帮助,因为它只是一个使用ViewHolder模式的常规适配器,就像您分享的链接中的适配器一样。没有任何实验或使适配器变慢的东西。
再说一遍,提出这个问题的主要原因是想找到比ViewHolder更快的模式。

4
如果你已经在使用 ViewHolder,并且 ViewHolder 被正确编写,那么你已经最小化了 findViewById() 的调用次数。 - CommonsWare
请发布一些代码,以便我们可以看到您已经拥有的内容。 - tyczj
你可能已经想好了,但是你有没有考虑退一步,实际测量一下使用当前的findViewById()是否存在性能问题?你可能会花费很多时间进行这种优化,最终可能会发现你几乎没有获得任何东西,反而使你的代码变得丑陋 :) - dimsuz
@CommonsWare 是的,它使用 ViewHolder,我以为那就是了。但后来我听说有一种“更好的方法”,可以将ID映射到 HashMap 中,然后通过迭代来访问它,因为 HashMapfindViewById() 快得多。所以我正在努力理解这种更好的方法中使用 HashMap 的概念。 - sandalone
1
“我听说使用HashMap模式比使用ViewHolder模式可以获得更快的速度”——请引用相关资料。可能编写代码时这种方法会更快,因为您不需要烦恼创建自己的ViewHolder。但是,否则您声称访问HashMap中的对象比访问对象上的字段更快,我非常怀疑这是否正确。 - CommonsWare
2个回答

1
减少调用findViewById的方法是通过使用ViewHolder来“回收”视图。其背后的思想是在需要时保留已经充气的视图并重复使用它们 - 由于它们已经有了ViewHolder,因此您不需要进行findViewById调用。
正如@nhaarman在他的答案的最后一部分所描述的那样,对于listview的做法是在getView中重复使用convertView(如果它不为空),并使用ViewHolder中的引用视图来更新它们。
如@nhaarman提供的first link所述,一种创建ViewHolder并将其存储在View中的方法是通过setTag。然后,在getView函数中,如果convertView不为null,则调用getTag以获取ViewHolder。
另外,您应该了解一下RecyclerView,这是一个强制实施ViewHolder模式的列表视图。

1
你应该使用ViewHolder模式,当涉及到Adapters时,最容易实现的方式是使用RecyclerView。在线示例代码可在此处获得,GitHub示例可直接通过Android Studio克隆,或者通过Github进行克隆。HashMap解决方案可以工作,但为什么要产生HashMap查找开销,当你可以简单地持有对象的引用?此外,RecyclerView本身处理不同的View类型,因此当你拥有与数据行不同的部分标题行时,你不必自己编写解决方案。

===============================================

根据我提供的示例代码,下面会更详细地介绍RecyclerView。请将您的CustomAdapter扩展为RecyclerView
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {

创建一个自定义的ViewHolder类。如果将其嵌入到CustomAdapter中,它几乎总是应该是一个静态类。
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final TextView textView;

        public ViewHolder(View v) {
            super(v);
            // Define click listener for the ViewHolder's View.
            v.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d(TAG, "Element " + getPosition() + " clicked.");
                }
            });
            textView = (TextView) v.findViewById(R.id.textView);

请注意,ViewHolder 在构造函数中查找视图,因为ViewHolder实例与View实例绑定。在onBind回调中更新的任何UI元素都应该有ViewHolder的引用。当您在CustomAdapter中包含以下代码时,RecyclerViewViewHolder对象与View关联起来。
// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    // Create a new view.
    View v = LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.text_row_item, viewGroup, false);

    return new ViewHolder(v);
}

请注意,onCreateViewHolder 回调函数带有一个 viewType 参数。目前的代码始终返回相同类型的自定义 ViewHolder,但是可以根据类型提供不同的 ViewHolder 类(这是支持支持不同视图的行的方式)。当视图绑定到数据集时,以下代码是如何更新您的 UI 的。
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
    Log.d(TAG, "Element " + position + " set.");

    // Get element from your dataset at this position and replace the contents of the view
    // with that element
    viewHolder.getTextView().setText(mDataSet[position]);
}

使用这些组件,RecyclerView 会按照类型创建和重复利用您的 ViewsViewHolders,从而减少视图层次结构查找,可能会使您的应用程序更加流畅。

当我使用“RecyclerView”时,我正在做同样的事情。您所说的“...为什么不只是缓存引用”是什么意思? - sandalone
更新了我的回答,使其更加具体。 - PaulR
谢谢。已经在做完全相同的事情了 :)。我猜像CommonsWare说的那样,没有更快的方式。我们有什么理由怀疑他呢: )。 - sandalone
如果你遵循这种模式,那么每个视图创建都会有1个findViewById()调用,只要你不阻止视图回收,你就应该将优化工作集中在其他方面。 - PaulR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接