【问题标题】:Recyclerview onCreateViewHolder called for every item为每个项目调用 Recyclerview onCreateViewHolder
【发布时间】:2018-02-17 17:08:37
【问题描述】:

我在NestedScrollView 中有一个RecyclerView,它显示了一些异步下载的数据。问题是当项目被初始化时存在明显的滞后。经过一些测试,我发现问题在于每个项目都调用了 onCreateViewHolder,并且需要一些时间来膨胀布局。这是我的适配器:

public class EpisodeAdapter extends RecyclerView.Adapter<EpisodeAdapter.ViewHolder> {

    private static final String TAG = "EpisodeAdapter";

    private static final int NO_POSITION = -1;
    private static final int EXPAND = 1;
    private static final int COLLAPSE = 2;

    private SparseArray<Episode> episodes;
    private OnItemClickListener<Episode> downloadClickListener;
    private OnItemClickListener<Episode> playClickListener;

    private RecyclerView recyclerView;
    private final EpisodeAnimator episodeAnimator;
    private final Transition expandCollapse;

    private int expandedPosition = NO_POSITION;

    public EpisodeAdapter() {
        episodes = new SparseArray<>();
        episodeAnimator = new EpisodeAnimator();
        expandCollapse = new AutoTransition();
    }

    //Called when first loading items
    public void swapEpisodes(SparseArray<Episode> newEpisodes){
        final int previousSize = episodes.size();
        episodes = newEpisodes;
        expandedPosition = NO_POSITION;
        Log.e(TAG, "Swap called");
        if(previousSize == 0) {
            notifyItemRangeInserted(0, episodes.size());
        }
        else {
            notifyItemRangeChanged(0, Math.max(previousSize, episodes.size()));
        }
    }

    //Called when downloading other information, this seems to work fine without delay
    public void setEpisodesDetails(final List<TmdbEpisode> episodeList){
        for (TmdbEpisode episode : episodeList){
            final int position = episodes.indexOfKey(episode.getNumber());
            notifyItemChanged(position, episode);
        }
    }

    @Override
    public EpisodeAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Log.e(TAG, "Start createViewHolder");
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_episode, parent, false);
        ViewHolder viewHolder = new ViewHolder(view);

        viewHolder.downloadButton.setOnClickListener(v -> {
            if(downloadClickListener != null)
                downloadClickListener.onItemClick(v, episodes.valueAt(viewHolder.getAdapterPosition()));
        });

        viewHolder.playButton.setOnClickListener(v -> {
            if(playClickListener != null)
                playClickListener.onItemClick(v, episodes.valueAt(viewHolder.getAdapterPosition()));
        });

        viewHolder.itemView.setOnClickListener(v -> {
            final int position = viewHolder.getAdapterPosition();
            if(position == NO_POSITION) return;

            TransitionManager.beginDelayedTransition(recyclerView, expandCollapse);
            episodeAnimator.setAnimateMoves(false);

            //Collapse any currently expanded items
            if(expandedPosition != NO_POSITION){
                notifyItemChanged(expandedPosition, COLLAPSE);
            }

            //Expand clicked item
            if(expandedPosition != position){
                expandedPosition = position;
                notifyItemChanged(position, EXPAND);
            }
            else {
                expandedPosition = NO_POSITION;
            }
        });

        Log.e(TAG, "Finish createViewHolder");
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(EpisodeAdapter.ViewHolder holder, int itemPosition) {
        Log.e(TAG, "Start");
        holder.number.setText(String.valueOf(episodes.keyAt(itemPosition)));
        holder.details.setVisibility(View.GONE);
        holder.itemView.setActivated(false);
        Log.e(TAG, "Finish");
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
        Log.e(TAG, "Start payloads");
        if(payloads.contains(EXPAND) || payloads.contains(COLLAPSE)){
            setExpanded(holder, position == expandedPosition);
        }
        else if(!payloads.isEmpty() && payloads.get(0) instanceof TmdbEpisode){
                TmdbEpisode episode = (TmdbEpisode) payloads.get(0);
                holder.title.setText(episode.getName());
                holder.details.setText(episode.getOverview());
        }
        else {
            onBindViewHolder(holder, position);
        }
        Log.e(TAG, "Finish payloads");
    }

    private void setExpanded(ViewHolder holder, boolean isExpanded) {
        holder.itemView.setActivated(isExpanded);
        holder.details.setVisibility((isExpanded) ? View.VISIBLE : View.GONE);
    }


    public void setPlayClickListener(OnItemClickListener<Episode> onItemClickListener){
        playClickListener = onItemClickListener;
    }

    public void setDownloadClickListener(OnItemClickListener<Episode> onItemClickListener){
        downloadClickListener = onItemClickListener;
    }

    @Override
    public int getItemCount() {
        return episodes.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        View itemView;
        TextView number;
        FadeTextSwitcher title;
        ImageButton downloadButton;
        FloatingActionButton playButton;
        TextView details;

        ViewHolder(View itemView) {
            super(itemView);
            Log.e(TAG, "Start constructor");
            this.itemView = itemView;
            number = itemView.findViewById(R.id.number);
            title = itemView.findViewById(R.id.title);
            downloadButton = itemView.findViewById(R.id.download_button);
            playButton = itemView.findViewById(R.id.play_button);
            details = itemView.findViewById(R.id.details);
            Log.e(TAG, "Finish constructor");
        }
    }

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    this.recyclerView = recyclerView;
    this.recyclerView.setItemAnimator(episodeAnimator);

    expandCollapse.setDuration(recyclerView.getContext().getResources().getInteger(R.integer.episode_expand_collapse_duration));
    expandCollapse.setInterpolator(AnimationUtils.loadInterpolator(this.recyclerView.getContext(), android.R.interpolator.fast_out_slow_in));
    expandCollapse.addListener(new Transition.TransitionListener() {
        @Override
        public void onTransitionStart(android.transition.Transition transition) {
           EpisodeAdapter.this.recyclerView.setOnTouchListener((v, event) -> true);
        }

        @Override
        public void onTransitionEnd(android.transition.Transition transition) {
           episodeAnimator.setAnimateMoves(true);
           EpisodeAdapter.this.recyclerView.setOnTouchListener(null);
        }

        @Override
        public void onTransitionCancel(android.transition.Transition transition) {}

        @Override
        public void onTransitionPause(android.transition.Transition transition) {}

        @Override
        public void onTransitionResume(android.transition.Transition transition) {}
    });
}

static class EpisodeAnimator extends SlideInItemAnimator {
    private boolean animateMoves = false;

    EpisodeAnimator() {
        super();
    }

    void setAnimateMoves(boolean animateMoves) {
        this.animateMoves = animateMoves;
    }

    @Override
    public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        if (!animateMoves) {
            dispatchMoveFinished(holder);
            return false;
        }
        return super.animateMove(holder, fromX, fromY, toX, toY);
    }
}
}

有没有办法强制对每个项目重复使用相同的ViewHolder?所以onCreateViewHolder会被调用一次。

我还在回收站视图中设置了nestedScrollingEnabled="false"

【问题讨论】:

  • 不要让viewholder静态..

标签: android android-adapter android-recyclerview


【解决方案1】:

我在NestedScrollView 中有一个RecyclerView

我猜你的&lt;RecyclerView&gt; 标签的高度定义为wrap_content。如果是这样,则意味着您正在为数据集中的每个项目膨胀布局资源(并创建ViewHolder 对象);可能有数以千计的布局膨胀和对象创建。

RecyclerView 的回收行为仅在 recyclerview 的高度小于显示其子项所需的高度时才有效。 recyclerview 创建少量两位数的 ViewHolder 实例是正常的(通常是您可以同时在屏幕上看到的许多项目加上一些用于优化屏幕外视图的项目),但这取决于您的recyclerview 的大小受屏幕大小的限制(即您使用的是match_parent 或固定大小)。

如果RecyclerViewwrap_content 高度在NestedScrollView 内,用户将无法查看一次所有项目,但Android框架只知道你有一个足够大的 recyclerview 来容纳数据集中的每个项目,因此它必须为每个项目创建一个 viewholder。

你必须想办法重新设计你的布局层次结构,这样你就可以为你的RecyclerView使用一些有限的高度。

【讨论】:

  • 问题是,即使只有很少的项目,比如 10 个,也会有明显的滞后。所以即使我按照你说的那样设置 recyclerview,我猜也会有滞后。
  • 我已经浏览了整个互联网以获得解释。 最后, 你的答案很到位!特别是,Android 框架只知道你有一个足够大的 RecyclerView /.../ 它必须为每个项目创建一个 viewholder ...现在我们知道了,有没有在NestedScrollView 内启用回收的方法?也许通过在某些滚动回调上调整 RecyclerView 的大小?
  • 实际上,Android docs 说您应该 永远不要将 RecyclerView 或 ListView 添加到滚动视图。这样做会导致糟糕的用户界面性能和糟糕的用户体验。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-05
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-07
相关资源
最近更新 更多