Android上的GridView滚动问题

8
这里有一些我可能忽略了的非常简单的东西,但是我遇到了以下问题(这篇文章比较长,但是我想提供尽可能多的信息 :))。
我在我的Android应用程序中有一个网格视图,每个单元格都包含自定义视图:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  >
    <GridView
    android:id = "@+id/photosGridView"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" 
    android:clickable="true" 
    android:drawSelectorOnTop="true" 
    android:focusable="true" 
    android:focusableInTouchMode="true" 
    android:numColumns="6" 
    android:columnWidth="90dp"
    android:verticalSpacing="5dp"
    android:horizontalSpacing="5dp"
    android:stretchMode="columnWidth"     
    >
</GridView>

</RelativeLayout>

每个单元格都是

<?xml version="1.0" encoding="utf-8"?>
<com.myapp.widgets.ImageThumbView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background = "@android:color/transparent"
  android:paddingLeft = "1dip"
  android:paddingRight = "1dip"
  android:paddingTop = "2dip"
  android:paddingBottom = "2dip"
  >
<ImageView
    android:id="@+id/thumbImage"
    android:layout_width="fill_parent"
    android:layout_height = "fill_parent"
    android:src="@drawable/icon_small"
    /> 

  <RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height = "fill_parent"
    android:background = "@android:color/transparent"
   >

  <ImageView
   android:id="@+id/iconRight"
   android:layout_width="40px"
   android:layout_height = "40px"  
   android:src="@drawable/album_check"
   android:visibility="gone"
   android:layout_alignParentTop = "true"
   android:layout_alignParentRight = "true"     
   />

  <ImageView
   android:id="@+id/iconLeft"
   android:src="@drawable/album_check"
   android:visibility="gone"
   android:layout_width = "40px"
   android:layout_height="40px"
   android:layout_alignParentTop = "true"
   android:layout_alignParentLeft = "true"     
   /> 
</RelativeLayout>

</com.myapp.widgets.ImageThumbView>

我的适配器长这样:

public class ImageAdapter extends BaseAdapter {
    private List<String> mPictures = null;

    public ImageAdapter(List<String> pictures) {
         mPictures = pictures;
    }

    public int getCount() {
          return mPictures != null ? mPictures.size() : 0;
    }
    public Object getItem(int position) {
          return mPictures != null ?  mPictures.get(position) : null;
    }
    public long getItemId(int position) {
          return mPictures != null ? position : -1;
    }
    @Override
    public View getView(int position,View convertView,ViewGroup parent) 
    {
        ImageThumbView i = null;
        try
            {               
            Thread.sleep(100);

            if (convertView == null) 
            {
                String path = mPictures.get(position);
                Log.d(((Integer)position).toString(), path);
                i = addSingleView(_li, path);
                TextView idx = (TextView) i.findViewById(R.id.caption);
                if (idx != null)
                    idx.setText(((Integer)position).toString());
            }
            else 
            {
                Log.d(((Integer)position).toString(), "ALREADY NOT NULL");
                i = (ImageThumbView) convertView;
                                    // These 2 lines were added only in desperate attempt to get it working, but it makes no difference
                String path = mPictures.get(position);
                i.updateView(path);
            }
        }
        catch(InterruptedException ie)
        {
            ie.printStackTrace();
        }
    return i;
        }
}

最初,它能正常工作,即显示前18个图像和下一行的几个像素。但是当我开始滚动GridView时,图像开始随机出现,即在最后一个图像之后,我看到了几个来自开头等等。出于好奇,我尝试了几个类似于这个样例: http://androidsamples.blogspot.com/2009/06/how-to-display-thumbnails-of-images.html,结果也是一样的。

那么,我做错了什么?为什么GridView会显示更多的项?为什么项目出现在错误的位置?

敬礼, Alex


请查看此链接:https://dev59.com/1Wgu5IYBdhLWcg3wj3r5。 - vineeth
5个回答

21

实际上这里的问题是你在if或else内设置了"convertView"的内容。或者你应该始终在实例化视图后,如果它为空,则在返回视图之前仅设置内容。

因此,你可以确信视图的内容总是正确的,使用位置更新而不是虚假的回收视图。

因此,通常情况下应该按照以下步骤操作:
(基于Android Guide 此处的教程)

public View getView(int position, View convertView, ViewGroup parent) {
    ImageView imageView;
    if (convertView == null) { 
        imageView = new ImageView(mContext);
        //just creating the view if not already present
    } else {
        imageView = (ImageView) convertView;
        //re-using if already here
    }

    //here is the tricky part : set the content of the view out of the if, else
    //just before returning the view
    imageView.setImageResource(mThumbIds[position]);
    return imageView;
}

1
我之前也遇到了同样的问题,但是一直不明白原因。谢谢你让我理解了这个问题,现在我通过每次刷新GridView时将convertview == null来使我的代码正常工作。谢谢! - varun bhardwaj

10
答案是视图回收机制。
一般而言,您的getView方法应该始终采用以下形式:
public class ImageAdapter extends BaseAdapter {

    private List<String> mUrls; // put your urls here
    private Map<String, Drawable> mImages; // cache your images here

    public ImageAdapter() {
        ...
        mUrls = new ArrayList<String>();
        mImages = new HashMap<String, Drawable>();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder; // Use the ViewHolder pattern for efficiency

        if (convertView == null) {
            // first time this view has been created so inflate our layout
            convertView = View.inflate(this, R.layout.my_grid_item, null);
            holder = new ViewHolder();
            holder.image = convertView.findViewById(R.id.image);
            holder.text = convertView.findViewById(R.id.text);
            convertView.setTag(holder); // set the View holder
        } else {
           holder = (ViewHolder) convertView.getTag();
        }

        // update the current view - this must be done EVERY
        // time getView is called due to view recycling
        holder.text.setText(Integer.toString(position));

        // check our cache for the downloaded image
        final String url = mUrls.get(position);
        if (mImages.get(url) != null)
            holder.image.setImageDrawable(mImages.get(url));
        else
            loadImage(url, holder.image);

        // return our view
        return convertView;
    }

    public loadImage(final String url, final ImageView image) {
        // load an image (maybe do this using an AsyncTask
        // if you're loading from network
    }

    ...
}

你的 ViewHolder 类应该长成这个样子

public class ViewHolder {
    ImageView thumbImage;
    TextView text;
}

那么你不应该遇到任何问题。另外,我不确定为什么你需要在getView()方法中加入sleep?这会减慢GridView的滚动速度。


好的,我相信是视图回收导致了我的原始代码出现问题。非常感谢! - alexey_gusev
实际上,我说得太早了。提出的解决方案不起作用,因为它与我使用的代码的主要区别在于它设置了预定义的可绘制对象。但至少我现在看到了问题所在。 - alexey_gusev
我已经修改了代码,希望更适合您的用例。如果有改进,请告诉我。 - Joseph Earl
2
你好!我和你的代码一样,但是在滚动时仍然遇到随机图片。这是gridview上已知的bug吗? - iamtheexp01
如果(convertView == null) { 这部分非常重要。避免了我在自定义适配器中遇到的java.lang.outofmemoryerror问题。 - Zafer

3

使用标准的懒加载器获取图像,但永远不要在onPostExecute中更新,因为这会导致图像更新过快,并获得您不想要的随机性。

我甚至使用了以下代码:

if (result != null && !mFlinging && myPosition < 12)
{   
   imageView.setImageBitmap(result);
}

在onPostExecute中更新图片,以便正确显示第一个屏幕上的图片。但是如果您快速滑动图片屏幕,然后再迅速返回第一个屏幕,则会重新获得随机性。因此现在我使用:

mPhotoView.setOnScrollListener(new OnScrollListener() { 



            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {
                // TODO Auto-generated method stub

            }

            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // TODO Auto-generated method stub
                if(scrollState != SCROLL_STATE_IDLE) {
                  mAdapter.setFlinging(true);
                } else {
                  mAdapter.setFlinging(false);   
                }

                int first = view.getFirstVisiblePosition(); 
                int count = view.getChildCount(); 

                if (scrollState == SCROLL_STATE_IDLE || (first + count > mAdapter.getCount()) ) { 
                    mPhotoView.invalidateViews(); 
                }

            } 
        }); 

更新视图。


0

-8

为了避免这个问题,建议在getView()方法中总是返回新创建的对象。

public View getView(int position, View convertView, ViewGroup parent) {
                ImageView       imageView;
                TextView        textView;
                LinearLayout    linearlayout  = new LinearLayout(mContext);
                //if (convertView == null) {
                    imageView       = new ImageView(mContext);
                    textView        = new TextView(mContext);

                    //imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
                    imageView.setAdjustViewBounds(false);
                    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                    imageView.setPadding(4, 8, 8, 8);                   
                    imageView.setBackgroundResource(mThumbIds[position]);

                    textView.setText(mThumbText[position]);                
                    textView.setGravity(0x01);
                    textView.setMaxLines(2);

                    //textView.setPadding(20, 0, 0, 0);
                    linearlayout.addView(imageView,0);
                    linearlayout.addView(textView,1);
                    linearlayout.setPadding(4, 4, 4, 4);
                    linearlayout.setOrientation(LinearLayout.VERTICAL);
                    linearlayout.setGravity(0x01);

               // } else {
                    //linearlayout = (LinearLayout) convertView;
                //}

                return linearlayout;
            }

11
在每个 getView() 中返回一个新的对象是一个非常糟糕的想法。这会导致你的列表在滚动时出现卡顿,并引起过多的垃圾回收。 - Matthew Runo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接