很长一段时间以来,我在 recyclerview 上一遍又一遍地遇到同样的问题。
简短版本:当 RV 快速滚动并且需要在短时间内进行大量计算时,它有时会“跳过”代码行。
我在这个 10 秒的视频中录制了这种行为:
https://www.youtube.com/watch?v=SVAvY6X5yr0。
注意我是如何向下滚动到歌曲文件“Supreme”的。据说是 Billy Talent 出品,但事实并非如此。它实际上是罗比威廉姆斯的作品。RV 确实知道这一点。当我向下滚动直到视图离开屏幕然后向上滚动时,它似乎已经“纠正”了自己。这首歌现在由罗比威廉姆斯演唱。此外,它左侧不正确的专辑封面现在也已修复并显示标准图像,因为我的手机上从来没有专辑封面。
当滚动变得非常激烈时,结果会越来越糟。有时项目会翻倍甚至三倍,而当点击正确的歌曲时正在播放。
同样有趣的是,RV 选择了正确的歌曲名称:“Supreme”,但却弄乱了专辑封面和艺术家姓名。
另一件值得注意的事情:每张专辑封面都应该被裁剪成同一个圆圈。然而,“至尊”的不正确专辑封面艺术被简单地忽略并保留为矩形形式。再说一遍:就好像 RV 只是“跳过”代码行一样。
好吧,说起来:这基本上就是整个事情:
从 OnCreate() 调用的 Init 函数:
private void InitRecView()
{
ImageView lnBg = (ImageView)FindViewById(Resource.Id.background_recView);
mAdapter = new PhotoAlbumAdapter(GetSortedListWithAllSongs(), this, dbSeekObj, seekObj, mAudioManager, this, lnBg);
// Get our RecyclerView layout:
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
// Plug the adapter into the RecyclerView:
mRecyclerView.SetAdapter(mAdapter);
mRecyclerView.SetItemViewCacheSize(50);
mRecyclerView.DrawingCacheEnabled = true;
mRecyclerView.DrawingCacheQuality = DrawingCacheQuality.High;
mLayoutManager = new PreCachingLayoutManager(this);
mLayoutManager.ItemPrefetchEnabled = true;
mRecyclerView.SetLayoutManager(mLayoutManager);
}
PhotoViewHolder:
public class PhotoViewHolder : RecyclerView.ViewHolder
{
public ImageView Image { get; private set; }
public TextView SongName { get; private set; }
public LinearLayout lnContainer { get; private set; }
public TextView AristName { get; private set; }
public ImageButton CoverArt { get; private set; }
public Drawable dr { get; set; }
public PhotoViewHolder(View itemView, List<MP3objectSmall> mp3Obj, Activity_Player act, MediaMetadataRetriever reader, DataBase db, List<SeekObj> seekObj, AudioManager audioManager, ImageView lnBg, Context ctx) : base(itemView)
{
// Locate and cache view references:
SongName = itemView.FindViewById<TextView>(Resource.Id.textView);
AristName = itemView.FindViewById<TextView>(Resource.Id.textView2);
lnContainer = itemView.FindViewById<LinearLayout>(Resource.Id.linlay_cardview);
CoverArt = itemView.FindViewById<ImageButton>(Resource.Id.musical_note);
this.mp3Obj = mp3Obj;
this.act = act;
this.reader = reader;
this.db = db;
this.seekObj = seekObj;
this.ctx = ctx;
this.audioManager = audioManager;
this.lnBg = lnBg;
lnContainer.Click += delegate
{
int pos = AdapterPosition;
ClickEvent(pos, AristName.Text, SongName.Text, CoverArt, lnBg);
};
}
private void ClickEvent(int position, string artist, string song, ImageView CoverArt, ImageView lnBackground)
{
if (PhotoAlbumAdapter.NewSongUri != null)
{
PhotoAlbumAdapter.OldSongUri = PhotoAlbumAdapter.NewSongUri;
}
PhotoAlbumAdapter.NewSongUri = Android.Net.Uri.Parse(mp3Obj[position].Mp3Uri);
if (!FirstStart) // dont save the very first uri, only get it for playing
{
ObjectToBeSaved = WriteMetaDataToFileList(PhotoAlbumAdapter.OldSongUri.ToString());
}
Activity_Player.CurrentSongObject = WriteMetaDataToFileList(PhotoAlbumAdapter.NewSongUri.ToString());
Activity_Player.txt_CurrentSong.Text = song;
Activity_Player.txt_CurrentArtist.Text = artist;
PlayMusic(PhotoAlbumAdapter.NewSongUri);
FirstStart = false;
Activity_Player.SetBackgroundToHeader(dr, lnBg, Activity_Player.btn_Settings, ctx);
}
还有可能是最重要的类 PhotoAlbumAdapter:
public class PhotoAlbumAdapter : RecyclerView.Adapter
{
public List<MP3objectSmall> mp3Obj;
Context ctx;
Activity_Player act;
MediaMetadataRetriever reader;
DataBase db;
List<SeekObj> seekObj;
Typeface tf;
AudioManager audioManager;
ImageView lnBg;
LinearLayout ln;
public static Android.Net.Uri NewSongUri = null;
public static Android.Net.Uri OldSongUri = null;
public PhotoAlbumAdapter(List<MP3objectSmall> mp3Obj, Context ctx, DataBase db, List<SeekObj> seekObj, AudioManager audioManager, Activity_Player act, ImageView lnBg)
{
this.lnBg = lnBg;
this.mp3Obj = mp3Obj;
this.ctx = ctx;
this.db = db;
this.seekObj = seekObj;
this.act = act;
this.audioManager = audioManager;
reader = new MediaMetadataRetriever();
tf = Typeface.CreateFromAsset(ctx.Assets, "Baiti.ttf");
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.CardView, parent, false);
PhotoViewHolder vh = new PhotoViewHolder(itemView, mp3Obj, act, reader, db, seekObj, audioManager, lnBg, ctx);
ln = itemView.FindViewById<LinearLayout>(Resource.Id.linlay_album_art);
ln.LayoutParameters.Height = ctx.Resources.DisplayMetrics.HeightPixels / 10;
ln.RequestLayout();
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
PhotoViewHolder vh = holder as PhotoViewHolder;
SetContent(vh, position);
}
public override void OnViewRecycled(Java.Lang.Object holder)
{
base.OnViewRecycled(holder);
}
private async void SetContent(PhotoViewHolder vh, int position) {
await SetContentAsync(vh, position);
}
private async Task SetContentAsync(PhotoViewHolder vh, int position)
{
string SongName = "";
string ArtistName = "";
Bitmap bitmap = null;
byte[] data = null;
RequestOptions requestOptions = null;
try
{
reader.SetDataSource(mp3Obj[position].Mp3Uri);
}
catch { }
await Task.Run(() => // cause problems with the reload
{
SongName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle);
ArtistName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist);
data = reader.GetEmbeddedPicture();
if (data != null)
{
// try
// {
bitmap = BitmapFactory.DecodeByteArray(data, 0, data.Length);
requestOptions = new RequestOptions();
requestOptions.InvokeDiskCacheStrategy(DiskCacheStrategy.None);
// requestOptions.SkipMemoryCache(true);
requestOptions.CircleCrop();
requestOptions.CenterInside();
requestOptions.FitCenter();
requestOptions.OptionalCircleCrop();
ConvertBitmapToBackground(bitmap, vh, data); // Set As Backgorund, blurry and black ( just sets the variable)
// }
// catch { }
}
});
((Activity)ctx).RunOnUiThread(() =>
{
vh.SongName.SetTypeface(tf, TypefaceStyle.Normal);
vh.AristName.SetTypeface(tf, TypefaceStyle.Normal);
vh.SongName.Text = SongName;
vh.AristName.Text = ArtistName;
// try
// {
if (data != null)
{
Glide
.With(ctx)
.Load(data)
.Apply(requestOptions)
.Into(vh.CoverArt);
}
else // because recycler items inherit their shit and if it is altered it just shows views were there shouldnt be any ...
{
vh.CoverArt.SetImageResource(Resource.Drawable.btn_musicalnote);
vh.dr = null;
}
// }
// catch { }
});
}
public override int ItemCount
{
get
{
if (mp3Obj != null)
{
return mp3Obj.Count();
}
else
return 0;
}
}
}
}
在 SetContentAsync() 中,RV 决定将什么项目放在哪里。这是我清楚地说明它应该做什么的地方,这是我认为代码行被跳过的地方。
我真的,真的需要帮助。我认为我做的一切都是正确的 - 谁能解释这种奇怪的行为?
非常感谢!
我会说异步处理可能是罪魁祸首。有时,您可能会在较早启动的迭代之前完成一次迭代,这实质上允许较早的迭代覆盖后来的“正确”值。
为了解决这个问题,我会在启动异步操作之前在每个持有者中放置一个 UUID 标志,并且只有在异步操作完成后标志匹配时才允许更新视图。这将确保“陈旧”操作无法更新 UI。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句