изображения постоянно меняются при использовании переработанного вида в пользовательском адаптере - Android - PullRequest
0 голосов
/ 22 мая 2018

У меня есть следующий код для моего пользовательского адаптера, в котором каждая строка состоит из имени и картинки, взятой из хранилища Firebase:

import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageException;
import com.google.firebase.storage.StorageReference;
import com.squareup.picasso.Picasso;

import java.util.List;

public class CustomAdapter extends BaseAdapter {

    Context context;
    List<RowItem> rowItems;


    CustomAdapter(Context context, List<RowItem> rowItems) {
        this.context = context;
        this.rowItems = rowItems;
    }

    @Override
    public int getCount() {
        return rowItems.size();
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    /* private view holder class */
    private class ViewHolder {
        ImageView profile_pic;
        TextView member_name;

    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;
        final RowItem row_pos = rowItems.get(position);

        final StorageReference storageReference = FirebaseStorage.getInstance().getReference().child(row_pos.getFirebaseUserUid()+".jpg");




        //if (convertView == null) {
        LayoutInflater mInflater = LayoutInflater.from(context);
        convertView = mInflater.inflate(R.layout.list_item, parent, false);


        holder = new ViewHolder();

        holder.member_name = convertView
                .findViewById(R.id.member_name);
        holder.profile_pic = convertView
                .findViewById(R.id.profile_pic);





        //convertView.setTag(holder);


        /*} else {
        holder = (ViewHolder) convertView.getTag();

        }*/

        holder.member_name.setText(row_pos.getName());



        storageReference.getDownloadUrl().addOnSuccessListener(new 
OnSuccessListener<Uri>() {
            @Override
            public void onSuccess(Uri uri) {





 Glide.with(context).load(uri).apply(RequestOptions.circleCropTransform()).into(holder.profile_pic);
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
            int errorCode = ((StorageException) exception).getErrorCode();
            if (errorCode == StorageException.ERROR_OBJECT_NOT_FOUND) {
                Picasso.get()
                        .load(R.drawable.user)
                        .resize(70,70)
                        .into(holder.profile_pic);
            }
        }
    });


    return convertView;
    }

}

Теперь, если я запустлю его какэто будет загружать каждую новую строку, и, таким образом, прокрутка будет не такой гладкой, как хотелось бы.Чтобы преодолеть это, многие посты предлагали добавить следующее (в основном то, что помечено как комментарии в приведенном выше коде):

if (convertView == null){
    /.../
    convertView.setTag(holder);
}

} else {
      holder = (ViewHolder) convertView.getTag();
}

Но когда я это делаю, в представлении profilePic элемента списка он отображаетдовольно быстро много изображений одно за другим.

Может ли кто-нибудь помочь в обнаружении проблемы и предложении каких-либо решений?

Вот видео с ошибкой: https://vimeo.com/271555362

1 Ответ

0 голосов
/ 24 мая 2018

Я думаю, что проблема в том, что указано Eselfar.Когда вы запрашиваете URI для firebase, это должно быть сделано в асинхронном потоке.Это означает, что в то время как URL итерации n получен, адаптер продолжает выполнять итерации в вашем списке.

Эти 2 потока выполняются одновременно, поэтому переменная владельца постоянно переназначается, вероятно, до тех пор, пока итерация не достигнет своего последнего видимого элемента(просмотр).

Это приводит к такому сценарию:

for each view {
   request image url       (for example 2 seconds)
   assign holder variable  (for example 0.0002 seconds)
}

в то время, как извлекается первый URL-адрес изображения (2 секунды), адаптер завершил итерацию.Таким образом, переменная-держатель теперь имеет то же значение, которое является последним значением всей итерации.Через 2 секунды для первого URL-адреса изображения (и т. Д. Для оставшегося URL-адреса изображения) он входит в метод OnSuccessListener.onSuccess, для него устанавливается Glide изображение в переменную holder.profile_pic.Но эта переменная, как было сказано ранее, теперь принадлежит к последней выполненной итерации.

Итак, ваша проблема в том, что вы смешиваете синхронизацию и асинхронный подход.

Возможное решение - сначала получить все изображенияURL, затем удалите эту асинхронную задачу (storageReference.getDownloadUrl()) и сделайте все вещи в одном UIThread.

Я думаю, что самое простое решение (но, вероятно, не самое лучшее) - это создать собственный OnSuccessListener и собственный OnFailureListenerнапример:

public class MySuccessListener extends OnSuccessListener {


          Holder holder;

            public MySuccessListener(Holder holder) {
            this.holder = holder;
            super();
          }

            @Override
            public void onSuccess(Uri uri) {
                Glide.with(context).load(uri).apply(RequestOptions.circleCropTransform()).into(holder.profile_pic);
          }
}

public class MyFailureListener extends OnFailureListener {


          Holder holder;

            public MyFailureListener(Holder holder) {
            this.holder = holder;
            super();
          }

            @Override
            public void onFailure(@NonNull Exception exception)
                int errorCode = ((StorageException) exception).getErrorCode();
                 if (errorCode == StorageException.ERROR_OBJECT_NOT_FOUND) {
                    Picasso.get()
                        .load(R.drawable.user)
                        .resize(70,70)
                        .into(holder.profile_pic);
                 }
          }
}

Обратите внимание, что вы передаете текущий держатель в конструкторы слушателей, поэтому, когда асинхронная задача заканчивается, вы можете выбрать правильный держатель.Затем вы можете обновить свой код следующим образом:

storageReference.getDownloadUrl().addOnSuccessListener(new MyFailureListener<Uri>(holder))
                            .addOnFailureListener(new MyFailureListener(holder));

Теперь я не могу его протестировать, и в IDE мой код может быть неточным, но в основном вы должны понимать предложенный способ.

Обратите внимание, чтоВы используете Пикассо и Glide.Эти две библиотеки делают одно и то же, я предлагаю вам использовать одну из них и удалить ненужную зависимость.

...