3

使用带有扩展 BaseAdapter 的 GridView 我正在加载包含图像和标题的 RelativeLayouts 集合。当用户单击 GridView 中的这些项目之一时,它应该从网格中删除并替换为它后面的项目。

这适用于除第一个项目以外的任何项目(适配器管理的项目的 ArrayList 中的索引 0)。

在我正在测试的设备上,当屏幕完全滚动到网格顶部(3 列和 4 行)并且最后一行被稍微切断时,屏幕能够显示 12 个项目。第 5 行根本没有显示。在这种情况下,如果我单击第一项(将其删除),则所有项都正确填写,但是,第 5 行的第一项(索引 12)返回第一项(索引 0)的视图索引 12 处的项目。

一些有助于解释问题的屏幕:

第1步

点击 Brian McKnight 将删除他,然后 BECK 和其他人将填补。

第2步

BECK 已与其余部分一起填写,现在向下滚动到下一行。

第 3 步

哦哦。贝克也在那儿。我让它部分滚动,所以你可以看到这两个项目都有 BECK 图片。

第4步

向上滚动以使第 12 项(第 5 行)不在视线范围内,然后向下滚动可纠正问题。

网格中任何其他项目的此过程都正常工作,只是项目 0。我做了一些日志记录并发现了一些事情:

  • 对象 0 的 getView(...) 方法被调用了很多。当网格第一次加载时,它被调用了 3 次,而所有其他项只被调用一次。
  • 当项目 0 以外的项目被删除时,位置 0 的 getView(...) 被调用 19 次。一开始是 3 次,然后在调用了其他对象的所有 getView(...) 方法之后再进行 16 次。
  • 当项目 0 被删除时,在您向下滚动到第 5 行(项目 12)之后,将调用 0 的 getView(...) 方法两次。我认为问题出在此处,因为有时如果您可以看到正确的项目在被@位置 0 的项目覆盖之前开始加载。

所以我很确定位置 0 的额外 2 次 getView(...) 调用(在 item @ index 12 出现时调用)是导致此问题的原因。我不知道为什么当时会调用位置 0 的 getView(...) ,以及为什么它会决定覆盖@位置 12 的项目。

我将在下面包含我的适配器的代码,我认为这足以解决问题。

public class PopularArtistsGridAdapter extends BaseAdapter {

    private ArrayList<ArtistsListItem> mArtists = new ArrayList<ArtistsListItem>();
    private LayoutInflater mInflater;
    private Context mContext;

    private int mRemovedIndex = -1;
    private int mRefresher = -1;
    private int mLastVisible = -1;

    public PopularArtistsGridAdapter(Context context) {
        mContext = context;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public void setItems(ArrayList<ArtistsListItem> artists) {
        if(artists == null)
            throw new NullPointerException();

        mArtists = artists;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return mArtists.size();
    }

    public void removeItem(int position, int lastVisible) {
        mLastVisible = lastVisible;

        mRemovedIndex = position;
        mRefresher = mRemovedIndex;

        mArtists.remove(position);
        notifyDataSetChanged();
    }

    public void addItem(int position) {
        ArtistsListItem item = getItem(position);

        mArtists.add(0, item);
        notifyDataSetChanged();
    }

    @Override
    public ArtistsListItem getItem(int position) {
        return mArtists.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    private void iterateRefresher() {
        mRefresher++;

        if(mRefresher > mLastVisible) {
            mRemovedIndex = -1;
            mRefresher = -1;
            mLastVisible = -1;
        }
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {

        final ArtistsListItem item = getItem(position);

        if(convertView == null)
            convertView = mInflater.inflate(R.layout.popular_artist_item, null);

        final TextView tv = (TextView) convertView.findViewById(R.id.pai_stupid_bubble_button);
        final ImageView iv = (ImageView) convertView.findViewById(R.id.pai_image);

        final BitmapDrawable drawable = DrawableManager.fetchBitmapDrawable(mContext, item.getImageURL(), true, false);

        iv.invalidate();
        tv.invalidate();

        if(mRefresher >= 0 && position >= mRefresher) {
            Animation anim = AnimationUtils.loadAnimation(mContext, R.anim.grid_item_fadein);
            anim.setAnimationListener(new AnimationListener() {

                @Override
                public void onAnimationStart(Animation animation) {
                    iv.setImageDrawable(drawable);
                    tv.setText(item.getName());
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                    // TODO Auto-generated method stub

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    // TODO Auto-generated method stub

                }
            });

            anim.setStartOffset(300 + (50 * (position - mRemovedIndex)));

            convertView.startAnimation(anim);
            iterateRefresher();
        }
        else {
            iv.setImageDrawable(drawable);
            tv.setText(item.getName());
        }

        return convertView;
    }

}

复习是我为了让屏幕上的所有可见项目在替换已删除项目时淡入淡出而制作的。如果有人在这里看到任何可疑的东西,或者对为什么会发生这种情况有任何其他想法,我将不胜感激。谢谢!

4

1 回答 1

2

问题是由于您用于淡化视图的动画。您以至少 350 毫秒的偏移量开始动画。这意味着动画在该时间段过去之前不会运行。问题出现在onAnimationStart您的AnimationListener. 它正在引用局部变量并对其进行操作。这就是事情出错的地方。

这有点难以解释,所以请尝试跟随。到第一次onAnimationStart被调用的时候,getView就会被调用很多次。由于项目视图被回收(就是这样convertView),因此在调用它时onAnimationStart,它所引用的视图可能位于不同的位置。

粗略的插图:

// Views being held by the adapter after first pass.
view0 --> onScreenItemtemAtPosition0
view1 --> onScreenItemtemAtPosition1
view2 --> onScreenItemtemAtPosition2

// schedule animation, listener uses view0 reference

// Scrolling occurs, item0 goes offscreen
view0 --> onScreenItemtemAtPosition3 // view0 is now being used in a new position onscreen
view1 --> onScreenItemtemAtPosition1
view2 --> onScreenItemtemAtPosition2

// onAnimationStart triggers and mutates view0
// !!!! Boom !!! view0 is being used for the onscreen item at position 3
// which isn't what you want because the drawable in your case is for item 0

如您所见,这是一个棘手的情况。一种解决方案是让您AnimationListener知道您打算变异的视图的位置,以便它可以与即将变异的视图相协调。

大致:

class MyAnimationListener extends AnimationListener {
    private int positionToMutate;

    public MyAnimationListener(int positionToMutate) {
        this.positionToMutate = positionToMutate;
    }

    @Override
    public void onAnimationStart(Animation animation) {
        Integer viewPosition = (Integer)localConvertView.getTag(); // add this to getView as final View localConvertView
        if (positionToMutate == viewPosition) {
            iv.setImageDrawable(drawable);
            tv.setText(item.getName());
        }
    }
    // rest of the methods
}

并在您的getView方法中使用位置信息更新 convertView 的标签:

final View localConvertView = convertView;
localConvertView.setTag(new Integer(position));

这将确保动画侦听器知道它是否应该改变视图。

于 2013-04-09T05:41:07.807 回答