Recyclerview onCreateViewHolder调用了每个项目

时间:2018-02-17 17:08:38

标签: android android-recyclerview android-adapter recycler-adapter

我在RecyclerView中有一个NestedScrollView,显示异步下载的某些数据。问题是当项目被启动时存在显着的滞后。经过一些测试后,我发现问题是每个项目都调用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将被调用一次。

我还在recyclerview中设置了nestedScrollingEnabled="false"

1 个答案:

答案 0 :(得分:7)

  

我在RecyclerView

中有一个NestedScrollView

我猜你的<RecyclerView>标签的高度定义为wrap_content。如果是,则表示您为数据集中的每个项目夸大了布局资源(并创建了ViewHolder对象);可能有数以千计的布局通胀和对象创建。

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

对于RecyclerViewwrap_content身高NestedScrollView的情况,用户无法查看所有项目在一次,但Android框架只知道你有一个足够大的recyclerview来保存你的数据集中的每一个项目,因此它必须为每个项目创建一个查看器。

您必须找到一种方法来重新设计您的布局层次结构,以便您可以为RecyclerView使用一些有限的高度。