В адаптере 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
, если я использую [режим загрузчика]? или это обычный случай?
Ответы
Ответ 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 и по ячейке в строке ровно высоту.
извините, мой плохой английский.