просмотр галереи Android "заикается" с адаптером загрузки отложенного изображения - PullRequest
5 голосов
/ 22 апреля 2011

Я хотел бы создать адаптер отложенной загрузки для использования с виджетом Gallery.

То есть getView() немедленно возвращает ImageView, и позже какой-то другой механизм будет асинхронно вызывать свой метод setImageBitmap(). Я сделал это, создав "ленивый" ImageView, который расширяет ImageView.

public class GalleryImageView extends ImageView {

    // ... other stuff here ...

    public void setImage(final Looper looper, final int position) {

    final Uri uri = looper.get(position);
    final String path = looper.sharePath(position);

    new Thread(new Runnable() {

        @Override
        public void run() {
            GalleryBitmap gbmp = new GalleryBitmap(context, uri, path);
            final Bitmap bmp = gbmp.getBitmap(); // all the work is here
            handler.post(new Runnable() {

                @Override
                public void run() {
                    if (GalleryImageView.this.getTag().equals(uri)) {
                        setImageBitmap(bmp);
                    }
                }
            });
        }
    }).start();
}

}

Когда я медленно прокручиваю в Gallery, центральное изображение продолжает появляться в центре. Трудно объяснить, но это действительно раздражает. Я также попробовал тот же подход для адаптера блесны, и он прекрасно работает там.

Есть идеи?

Ответы [ 3 ]

12 голосов
/ 04 мая 2011

Решение состоит в том, чтобы внедрить более интеллектуальный метод получения миниатюр - это бессмысленная выборка миниатюр, пока пользователь просматривает список. По сути, вы хотите что-то подобное, реализованное в приложении Полки Романа Гая.

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

  • Установите изображение только в том случае, если оно существует в кеше в памяти из вашего getView. Установите флажок, указывающий, было ли установлено изображение или требуется ли загрузка. Вы также можете сохранить память в кэш-памяти на SD-карте и во внутренней памяти, и, если в данный момент не выполняется сброс, показать версию с низким разрешением (inSampleSize, установленную на 16 или 8), которая будет видна при простой прокрутке - версия с высоким разрешением будет загружаться, когда пользователь отпускает и устанавливает изображение.
  • Добавьте OnItemSelectedListener (и обязательно вызовите setCallbackDuringFling(false) при инициализации), который загружает новые миниатюры для всех видимых элементов, для которых требуется загрузка , только если палец пользователя поднят (вы можете использовать getFirstVisiblePosition и getLastVisiblePosition, чтобы найти диапазон видимых представлений)
  • Также, когда пользователь поднимает палец, проверьте: 1. изменилась ли выбранная позиция с тех пор, как пользователь опустил палец, и если да, 2. была ли начата загрузка из-за вашего OnItemSelectedListener - если это не было тогда инициировать один. Это нужно для того, чтобы уловить случай, когда не происходит бросание, и, таким образом, OnItemSelected никогда ничего не делает, потому что в этой ситуации он всегда вызывается пальцем вниз. Я бы использовал обработчик, чтобы отложить запуск загрузки на время анимации вашей галереи (обязательно очищайте все отложенные сообщения, отправленные этому обработчику, всякий раз, когда вызывается onItemSelected или когда вы получаете событие ACTION_DOWN.
  • После загрузки изображения проверьте, запрашивали ли это изображение какие-либо видимые виды, и обновите эти представления

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

Это означает, что параметр convertView, передаваемый вашему методу getView, будет чаще всего отличаться от нуля, то есть вы будете раздувать много просмотров (что дорого) - см. Мой ответ на замена для галереи с View переработкой существует? для некоторых намеков на это. (PS: с тех пор я изменил этот код - я бы использовал другую корзину для фаз компоновки и фаз прокрутки, на месте фазы макета и получал бы представления в корзине макета в соответствии с их положением, и НЕ вызывал getView, если представление, которое вы получаете из корзины, не является нулевым, так как оно будет точно таким же видом, а также очистите корзину после макета - это сделает вещи немного более быстрыми)

PS: Также будьте очень осторожны с тем, что вы делаете в OnItemSelected - а именно, если это не в местах, упомянутых выше, то старайтесь делать как можно меньше. Например, я устанавливал некоторый текст в TextView над моей Галереей в OnItemSelected. Перемещение этого вызова в те же точки, в которых я обновлял миниатюры, дало заметную разницу.

12 голосов
/ 21 июля 2011

У меня есть для вас ответ!

Когда какой-либо из setImage... методов вызывается на ImageView во внутренней системе, запрашивается проход макета, например, setImageBitmap(), как указано выше, определяется как

public void setImageBitmap(Bitmap bm) {
    setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}

который звонит

public void setImageDrawable(Drawable drawable) {
    if (mDrawable != drawable) {
        mResource = 0;
        mUri = null;
        updateDrawable(drawable);
        requestLayout(); //layout requested here!
        invalidate();
    }
}

, который имеет эффект «привязки» галереи к центру изображения, которое в данный момент находится ближе всего к центру галереи.

Что я сделал, чтобы предотвратить это, так это просмотр, который загружается в Галерею, имеет явную высоту и ширину (в dip с) и использует подкласс ImageView, который игнорирует запросы макета. Это работает, поскольку у галереи все еще есть проход макета изначально, но не стоит делать это каждый раз, когда изображение в галерее изменяется, что, я думаю, должно было бы произойти, если бы у представлений галереи были установлены ширина и высота WRAP_CONTENT, что мы не Обратите внимание, что, поскольку invalidate() все еще вызывается в setImageDrawable(), изображение все равно будет отображаться при установке.

Мой очень простой ImageView подкласс ниже!

/**
 * This class is useful when loading images (say via a url or file cache) into
 * ImageView that are contained in dynamic views (Gallerys and ListViews for
 * example) The width and height should be set explicitly instead of using
 * wrap_content as any wrapping of content will not be triggered by the image
 * drawable or bitmap being set (which is normal behaviour for an ImageView)
 * 
 */
public class ImageViewNoLayoutRefresh extends ImageView
{
    public ImageViewNoLayoutRefresh(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }

    public ImageViewNoLayoutRefresh(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    public ImageViewNoLayoutRefresh(Context context)
    {
        super(context);
    }

    @Override
    public void requestLayout()
    {
        // do nothing - for this to work well this image view should have its dims
        // set explicitly
    }
}

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

2 голосов
/ 04 мая 2011

Это может быть ошибка в методе onLayout Галереи. Проверьте http://code.google.com/p/android/issues/detail?id=16171 для возможного обходного пути.

...