Подтвердить что ты не робот

В адаптере gridview getView (position == 0) вызывается слишком много раз для измерения макета, когда setImageBitmap() в загрузчике

У меня есть GridView для отображения некоторых значков.

До того, как я прочитал этот "Эффективно отображая растровые изображения" с сайта разработчика Android, я расшифровывал растровое изображение из локального пути непосредственно в getView() адаптера, вот так:

public View getView(int position, View convertView, ViewGroup parent) {
      ...
      ImageView icon = ...... (from getTag() of convertView)
      icon.setImageBitmap(BitmapUtil.decode(iconPath));
      ...
}

этот способ отлично работает, я назвал его [Direct Mode], журнал вывода для метода getView() должен быть:

getView(0)   // measure kid layout.
getView(0)
getView(1)
getView(2)
...
getView(n)       // when scrolling gridview.
getView(n+1)
...
getView(n+3)    // scrolling again.
getView(n+4)
...

то я пытаюсь изменить код на [Режим загрузчика], упомянутый в статье Эффективное отображение битмапов, как показано ниже:

public View getView(int position, View convertView, ViewGroup parent) {
      ...
      ImageView icon = ...... (from getTag() of convertView)
      loadIcon(icon, iconPath);
      ...
}

в loadIcon():

...
final CacheImageLoader loader = new CacheImageLoader(getActivity(), imageView, imageUrl, savePath);
final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), placeHolderBitmap, loader);
imageView.setImageDrawable(asyncDrawable);

в загрузчике загрузчика:

@Override
public void onLoadComplete(Loader<Bitmap> arg0, Bitmap arg1) {
    ...
    ImageView imageView = imageViewReference.get();
    if (result != null && imageView != null) {
        imageView.setImageBitmap(result);
    }
}

В принципе, это то же самое, что и код тренировки, на самом деле этот способ отлично работает. Тем не менее, я нашел что-то другое, в этом режиме метод getView() в адаптере был вызван слишком много раз, однако они повторяют вызов этого метода всегда с параметром "position" == 0, это означает, что что-то вызывает g etView(0, X, X) повторно,

getView(0)     // measure kid layout.
getView(0)
getView(1)
getView(2)
...    
getView(0)     // loader completed then imageView.setImageBitmap(result); 
getView(0)     // same as above
getView(0)
getView(0)
...
getView(n)     // when scrolling gridview.
getView(n+1)
getView(n+2)
getView(0)     // loader completed then imageView.setImageBitmap(result); 
getView(0)     // same as above
getView(0)
...
getView(n+3)   // scrolling again.
getView(n+4)
getView(0)     // loader completed then imageView.setImageBitmap(result); 
getView(0)     // same as above
getView(0)

Это не хорошо, потому что я использую загрузчик в getView(). Я проверил исходный код и обнаружил, что они изначально вызывают imageView.setImageBitmap(result) в методе loader onLoadComplete и в ImageView:

 /**
 * Sets a drawable as the content of this ImageView.
 * 
 * @param drawable The drawable to set
 */
public void setImageDrawable(Drawable drawable) {
        ...

        int oldWidth = mDrawableWidth;
        int oldHeight = mDrawableHeight;

        updateDrawable(drawable);

        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }
}

здесь requestLayout() - это метод просмотра и всегда выполняется в режиме [Direct Mode] или [Loader Mode], в View.class:

public void requestLayout() {
    mPrivateFlags |= FORCE_LAYOUT;
    mPrivateFlags |= INVALIDATED;

    if (mLayoutParams != null) {
        mLayoutParams.onResolveLayoutDirection(getResolvedLayoutDirection());
    }

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
}

однако разница: в [Прямой режим], mParent.requestLayout() вызывается один раз, но в [Режим загрузчика], каждый раз, когда я вызываю imageView.setImageBitmap(result);, mParent.requestLayout() будет вызывается mParent.isLayoutRequested() return false, а mParent.requestLayout(); приведет к тому, что GridView измеряет макет ребенка, вызывая obtainView() для первого малыша, а затем вызывает getView(0, X, X):

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
    final int count = mItemCount;
    if (count > 0) {
        final View child = obtainView(0, mIsScrap);
    ...

Итак, мой вопрос : почему mParent.isLayoutRequested() return false, если я использую [режим загрузчика]? или это обычный случай?

4b9b3361

Ответ 1

isLayoutRequested находится там, чтобы сказать вам, что макет уже ожидает этого View. То есть, после вызова requestLayout, isLayoutRequested вернет значение true до завершения следующего макета. Единственная причина для этой проверки в requestLayout заключается в том, чтобы избежать неоднократного вызова requestLayout в родительском, если он все равно собирается делать макет. isLayoutRequested - это красная селедка здесь: это не причина повторного вызова onMeasure.

Корневая проблема заключается в том, что ImageView запрашивает новый макет всякий раз, когда вы меняете его. Это необходимо по двум причинам: -

  • Размер ImageView может зависеть от размера drawable, если adjustViewBounds установлен. Это может, в свою очередь, влиять на размеры других видов, в зависимости от компоновки: ImageView сам не имеет достаточной информации, чтобы знать.
  • ImageView.onMeasure отвечает за определение того, насколько масштабируемый должен быть изменен в соответствии с границами ImageView в соответствии с режимом масштабирования. Если новый drawable не такой же размер, как и предыдущий, то ImageView должен снова измеряться, чтобы пересчитать требуемое масштабирование.

Вы можете устранить проблему слишком большого количества загрузчиков, сохранив локальный кеш Bitmap, возвращенный загрузчиками. Кэш может иметь все Bitmap, если вы знаете, что их не так много, или только n самых последних из них. В getView сначала проверьте, существует ли Bitmap для этого элемента в кеше, и если да, верните ImageView, уже установленный для этого Bitmap. Только если это не в кеше, вам нужно использовать загрузчик.

Будьте осторожны: если базовые данные могут измениться, теперь вам нужно сделать недействительным кеш в то же время, что и вызов invalidate в GridView или уведомление через ContentResolver. Я использовал код доморощенного для этого в своем приложении, и он работает хорошо для меня, но хорошие люди на Square имеют библиотеку с открытым исходным кодом под названием Picasso сделайте всю тяжелую работу, если хотите.

Ответ 2

Это нормальное поведение, андроид может вызвать getView для одного и того же положения несколько раз. Это на разработчике, чтобы получить/установить эскиз в getView только тогда, когда это необходимо (т.е. Если эскиз не был установлен, либо был изменен путь к миниатюре). В других случаях просто возвращаем convertView, который мы получаем как параметр в getView.

Ответ 3

У меня была та же проблема. Сетка всегда измеряет своего первого ребенка, даже если я нахожусь на 30 позиции.
Я просто обойду весь код getView, добавив эту проверку в topView вверху:

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    // Patch for multiple getView for position 0
    if(convertView!=null && position==0 && viewGrid.getFirstVisiblePosition()>1) return convertView;

Это не останавливает вызов getView, но, по крайней мере, тексты, изображения и изменения макета не запускаются.

Ответ 4

попробуйте настроить макет xml в любом месте, которое ссылается на высоту, потому что андроид будет делать измерение, чтобы рисовать каждый раз и снова перерисовывает ячейку. Попробуйте использовать в listview match_parent и по ячейке в строке ровно высоту. извините, мой плохой английский.