在ViewHolder模式中使用动画适配器

10

在我的适配器中使用动画时出现了问题。

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

        if (convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(context);
            convertView = inflater.inflate(resource, parent, false);
            holder = new ViewHolder();

            holder.newRoomView = (TextView) convertView.findViewById(R.id.newRoom);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        Room item = items.get(position);

        // animate new rooms
        if (item.isNewRoom()) {
            AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0.0f);
            alphaAnim.setDuration(1500);
            alphaAnim.setAnimationListener(new AnimationListener() {
                public void onAnimationEnd(Animation animation) {
                    holder.newRoomView.setVisibility(View.INVISIBLE);
                }

                @Override
                public void onAnimationStart(Animation animation) {}

                @Override
                public void onAnimationRepeat(Animation animation) {}
            });
            holder.newRoomView.startAnimation(alphaAnim);
        }

        // ...

        return convertView;
    }

当在适配器之外添加新房间并调用notifyDataSetChanged时,新房间会正确地进行动画处理,但是当onAnimationEnd被调用时,另一个(不是新房间)会被隐藏。

有没有什么办法可以让正确的房间不被隐藏?


为什么不尝试使用“RecyclerView”呢? - Amrut Bidri
请发布您的项目布局。 - SilentKnight
6个回答

3

由于你没有在getView()方法中声明holder变量,我只能假设你已经将其声明为类的实例变量。这就是你的问题所在。当动画完成时,变量holder保存了对完全不同项目的引用。

你需要在getView()方法内使用一个被声明为final的局部变量。我不知道你是否需要在getView()方法外使用这个holder变量,但如果需要,可以这样做:

    // animate new rooms
    if (item.isNewRoom()) {
        final ViewHolder holderCopy = holder; // make a copy
        AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0.0f);
        alphaAnim.setDuration(1500);
        alphaAnim.setAnimationListener(new AnimationListener() {
            public void onAnimationEnd(Animation animation) {
                holderCopy.newRoomView.setVisibility(View.INVISIBLE);
            }

            @Override
            public void onAnimationStart(Animation animation) {}

            @Override
            public void onAnimationRepeat(Animation animation) {}
        });
        holder.newRoomView.startAnimation(alphaAnim);
    }

当然,如果动画的持续时间太长以至于在此期间视图已被回收,则此方法无效。

1
    if (item.isNewRoom()) {

        // store view reference first
        final View newRoomView = holder.newRoomView;
        ...
        alphaAnim.setAnimationListener(new AnimationListener() {
            public void onAnimationEnd(Animation animation) {

                // hide exactly this view when animation ends
                newRoomView.setVisibility(View.INVISIBLE);
            }
        ...
    }

0

这可能是由于ListView的回收机制引起的。尝试给动画视图打上标记,并在onAnimationEnd中使用findViewByTag来检索它。例如:

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

    if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(context);
        convertView = inflater.inflate(resource, parent, false);
        holder = new ViewHolder();

        holder.newRoomView = (TextView) convertView.findViewById(R.id.newRoom);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    Room item = items.get(position);

    // animate new rooms
    if (item.isNewRoom()) {
        AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0.0f);
        alphaAnim.setDuration(1500);
        alphaAnim.setAnimationListener(new AnimationListener() {
            public void onAnimationEnd(Animation animation) {
                View view = convertView.findViewWithTag(position);
                if (view != null) {
                     view.setVisibility(View.INVISIBLE);
                } 

            }

            @Override
            public void onAnimationStart(Animation animation) {}

            @Override
            public void onAnimationRepeat(Animation animation) {}
        });
        holder.newRoomView.startAnimation(alphaAnim);
        holder.newRoomView.setTag(position);
    }

    // ...

    return convertView;
}

0
记住,像这样的情况你需要始终有一个else语句。否则,使用相同视图持有者的任何其他内容也将变得不可见。
if (item.isNewRoom()) {
        AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0.0f);
        alphaAnim.setDuration(1500);
        alphaAnim.setAnimationListener(new AnimationListener() {
            public void onAnimationEnd(Animation animation) {
                holder.newRoomView.setVisibility(View.INVISIBLE);
            }

            @Override
            public void onAnimationStart(Animation animation) {}

            @Override
            public void onAnimationRepeat(Animation animation) {}
        });
        holder.newRoomView.startAnimation(alphaAnim);
    }else{
        holder.newRoomView.setVisibility(View.VISIBLE);
    }

0

好的。我之前遇到过这个问题,这很可能是一个Android的bug:

您正在使用内部对象来设置动画监听器:

 alphaAnim.setAnimationListener(new AnimationListener(){/*bla bla bla*/});

将上面的代码更改为以下内容:
AnimationListener myListener = new AnimationListener(){/*bla bla bla*/};
alphaAnim.setAnimationListener(myListener);

这个解决方案可能有点疯狂,但在几个类似的情况下它救了我的命。


这个答案没有用。你建议的代码和你要替换的代码完全相同。你仍然在创建一个匿名类,就像以前一样。编译器将把这两个代码片段编译成完全相同的东西。 - David Wasser
@David Wasser:这在过去对我起作用了。你能百分之百确定代码在Android上完全相同吗? - Behnam
是的,完全一样。我无法相信这解决了你过去可能遇到的任何问题。 - David Wasser
我和楼主遇到了完全相同的问题。但由于您是一位经验更丰富的程序员,我将在回到电脑后立即删除我的答案。 - Behnam

0

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