我在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"
。
答案 0 :(得分:7)
我在
中有一个RecyclerView
NestedScrollView
我猜你的<RecyclerView>
标签的高度定义为wrap_content
。如果是,则表示您为数据集中的每个项目夸大了布局资源(并创建了ViewHolder
对象);可能有数以千计的布局通胀和对象创建。
RecyclerView
的回收行为仅在recyclerview的高度小于显示其子项所需的高度时才有效。回收者视图创建一个小的两位数ViewHolder
个实例是正常的(通常你可以在屏幕上看到很多项目,加上一些只能在屏幕外优化视图),但是这个取决于您的recyclerview的大小受屏幕尺寸的限制(即您使用match_parent
或固定尺寸)。
对于RecyclerView
内wrap_content
身高NestedScrollView
的情况,用户无法查看所有项目在一次,但Android框架只知道你有一个足够大的recyclerview来保存你的数据集中的每一个项目,因此它必须为每个项目创建一个查看器。
您必须找到一种方法来重新设计您的布局层次结构,以便您可以为RecyclerView
使用一些有限的高度。