23

我有一个GridView用于显示一些图标的。

在我从 Android 开发人员网站上阅读此高效显示位图之前,我直接在getView()适配器的本地路径中解码位图,如下所示:

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

这种方式无论如何都很好,我称之为[直接模式],getView()方法的输出日志应该是:

getView(0)   // measure kid's 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);

在 Loader 的监听器中:

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

基本上,它与训练代码相同,实际上,这种方式也可以正常工作。但是,我发现了一些不同的地方,在这种模式下getView(),适配器中的方法被调用了太多次,但是,这些对这个方法的重复调用总是使用“位置”参数== 0,这意味着etView(0, X, X)重复调用 g。

getView(0)     // measure kid's 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()是 View 的方法,总是在 View.class 中的 [Direct Mode] 或 [Loader Mode] 中执行:

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()返回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);
    ...

所以,我的问题是:如果我使用 [loader mode]为什么要mParent.isLayoutRequested()返回?false还是只是正常情况?

4

4 回答 4

7

isLayoutRequested只是在那里告诉您布局是否已经为此挂起View。也就是说, afterrequestLayout被调用,isLayoutRequested将返回 true 直到下一个布局传递完成。此签入的唯一原因requestLayout是避免requestLayout在父级无论如何都要进行布局时重复调用它。isLayoutRequested这里是一个红鲱鱼:这不是onMeasure被反复调用的原因。

根本问题是ImageView每当您更改其可绘制对象时都会请求新布局。这是必要的,原因有两个:-

  1. adjustViewBounds如果已设置,ImageView 的大小可能取决于可绘制对象的大小。这可能反过来影响其他视图的大小,具体取决于布局:ImageView它本身没有足够的信息来了解。
  2. ImageView.onMeasure负责ImageView根据缩放模式计算可绘制对象的大小必须调整多少以适应 的边界。如果新的drawable与旧的drawable大小不同,则ImageView 必须再次测量以重新计算所需的缩放比例。

Bitmap您只能通过保留加载程序返回的 s的本地缓存来解决加载程序过多的问题。Bitmap如果您知道没有那么多,或者只有n 个最近使用的缓存,缓存可能包含所有s。在您的getView中,首先检查Bitmap缓存中是否存在该项目的 ,如果存在,则返回一个ImageView已设置的Bitmap。只有当它不在缓存中时,您才需要使用加载器。

注意:如果底层数据可以更改,您现在需要确保在调用或通知invalidate的同时使缓存无效。我在我的应用程序中使用了一些自制代码来实现这一点,它对我来说效果很好,但是 Square 的好人有一个名为Picasso的开源库,如果你愿意,可以为你完成所有艰苦的工作。GridViewContentResolver

于 2013-08-09T11:32:26.013 回答
1

这是正常的行为,android 可以多次调用同一位置的 getView。开发人员仅在需要时才能在 getView 中获取/设置缩略图(即,如果未设置缩略图或缩略图路径发生更改)。在其他情况下,只需返回 convertView,我们将其作为 getView 中的参数。

于 2013-01-02T16:14:55.277 回答
1

我有同样的问题。Grid 总是在测量它的第一个孩子,即使我在 30 的位置。
我只是通过在 getView 顶部添加此检查来绕过整个 getView 代码:

@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,但至少不会运行文本、图像和布局更改。

于 2015-11-11T12:55:05.260 回答
0

尝试在任何引用高度的地方调整您的 xml 布局,因为 android 每次都会进行测量并重新绘制单元格。尝试在 listview match_parent 和行上的单元格上使用一个精确的高度。对不起我的英语不好。

于 2013-05-25T20:43:52.057 回答