如何为Android ListView的行添加或删除动画效果

217
在iOS中,有一种非常简单且强大的方法可以动画地添加和删除UITableView行,这里是来自YouTube视频的剪辑,展示了默认动画。注意周围的行如何折叠到被删除的行上。此动画可以帮助用户跟踪列表中的更改及其位置。
由于我一直在Android上开发,我发现没有相应的功能可以在TableView中动画单独的行。在我的适配器上调用notifyDataSetChanged()会使ListView立即更新其内容以显示新信息。我想在数据更改时显示一个简单的行推入或滑出的动画,但我找不到任何文档化的方法来实现这一点。看起来LayoutAnimationController可能是解决问题的关键,但当我在ListView上设置一个LayoutAnimationController(类似于ApiDemo的LayoutAnimation2)并在列表已显示后从我的适配器中删除元素时,这些元素立即消失而没有得到动画处理。
我还尝试了像以下代码一样的方法来动画删除单个项目:
@Override
protected void onListItemClick(ListView l, View v, final int position, long id) {
    Animation animation = new ScaleAnimation(1, 1, 1, 0);
    animation.setDuration(100);
    getListView().getChildAt(position).startAnimation(animation);
    l.postDelayed(new Runnable() {
        public void run() {
            mStringList.remove(position);
            mAdapter.notifyDataSetChanged();
        }
    }, 100);
}

然而,动画行周围的行在调用notifyDataSetChanged()之前不会改变位置,直到它们跳到新位置时才会更新。看起来ListView在元素放置后没有更新其布局。

虽然我也曾考虑过编写自己的ListView实现/分支,但这似乎不应该那么困难。

谢谢!


有人找到了答案吗?请分享一下。 - AndroidGecko
@Alex:你做过那个像YouTube视频(像iPhone一样)的动画吗?如果你有任何Android的演示或链接,请给我,我已经尝试在XML文件中使用animateLayoutChanges,但它并不完全像iPhone。 - Jayesh
@Jayesh,不幸的是,我不再从事Android开发工作。我没有测试过2012年后撰写的任何此问题的答案。 - Alex Pretzlav
14个回答

127
Animation anim = AnimationUtils.loadAnimation(
                     GoTransitApp.this, android.R.anim.slide_out_right
                 );
anim.setDuration(500);
listView.getChildAt(index).startAnimation(anim );

new Handler().postDelayed(new Runnable() {

    public void run() {

        FavouritesManager.getInstance().remove(
            FavouritesManager.getInstance().getTripManagerAtIndex(index)
        );
        populateList();
        adapter.notifyDataSetChanged();

    }

}, anim.getDuration());

如果想要进行自上向下的动画,请使用:

<set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate android:fromYDelta="20%p" android:toYDelta="-20"
            android:duration="@android:integer/config_mediumAnimTime"/>
        <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="@android:integer/config_mediumAnimTime" />
</set>

71
最好使用 anim.setAnimationListener(new AnimationListener() { ... }) 而不是使用 Handler。 - Oleg Vaskevich
5
由于几个原因,建议使用 @MrAppleBR 的方法。首先,这样可以避免创建一个不必要的“Handler”。其次,通过已经使用内置的回调函数来实现,可以使代码更简洁。第三,您不知道在每个平台上“Handler”和“Animation”的实现方式如何工作,也许可能导致延迟运行的代码在动画完成之前执行。 - Oleg Vaskevich
8
然而,在动画行周围的行不会改变位置,直到调用notifyDataSetChanged()时才会跳到它们的新位置。这个问题在你的解决方案中仍然存在。 - Boris Rusev
5
FavouritesManager类和populateList()方法是什么? - Jayesh
1
删除动画对我来说不起作用。如果ListView滚动到索引0,它可以正常工作。但是,如果我将其滚动到列表的任意一部分并删除一个项目,则动画将在不同的列表项上发生。 - Andrew
显示剩余6条评论

16

1
我认为这是在需要简单动画时的简单方法,但您也可以使用RecyclerView.setItemAnimator()方法自定义动画。 - qatz
2
最后一步是我所缺少的关键:稳定的ID。谢谢! - Amir Uval
这是一个很好的示例项目,能够很好地演示API。 - Mr-IDE

10

请看谷歌的解决方案。这里仅介绍删除方法。

ListViewRemovalAnimation项目代码和视频演示

它需要Android 4.1+ (API 16),但我们现在已经是2014年之后了。


3
在将新行插入到 ListView 后,我只需将 ListView 滚动到新位置即可。
ListView.smoothScrollToPosition(position);

3

我想到了一种不需要操作列表视图的方法来实现它。不幸的是,常规的Android动画似乎可以操作行的内容,但无法真正缩小视图。因此,首先考虑使用以下处理程序:

private Handler handler = new Handler() {
@Override
public void handleMessage(Message message) {
    Bundle bundle = message.getData();

    View view = listView.getChildAt(bundle.getInt("viewPosition") - 
        listView.getFirstVisiblePosition());

    int heightToSet;
    if(!bundle.containsKey("viewHeight")) {
        Rect rect = new Rect();
        view.getDrawingRect(rect);
        heightToSet = rect.height() - 1;
    } else {
        heightToSet = bundle.getInt("viewHeight");
    }

    setViewHeight(view, heightToSet);

    if(heightToSet == 1)
        return;

    Message nextMessage = obtainMessage();
    bundle.putInt("viewHeight", (heightToSet - 5 > 0) ? heightToSet - 5 : 1);
    nextMessage.setData(bundle);
    sendMessage(nextMessage);
}

将此集合添加到您的列表适配器中:
private Collection<Integer> disabledViews = new ArrayList<Integer>();

并添加
public boolean isEnabled(int position) {
   return !disabledViews.contains(position);
}

接下来,在您想要隐藏行的任何位置添加以下内容:

Message message = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putInt("viewPosition", listView.getPositionForView(view));
message.setData(bundle);
handler.sendMessage(message);    
disabledViews.add(listView.getPositionForView(view));

就是这样!您可以通过改变每次缩小高度的像素数来改变动画速度。虽然不是非常复杂,但它确实有效!


3

由于ListViews进行了高度优化,我认为这是不可能实现的。您是否尝试过通过代码创建自己的“ListView”(即通过从XML中填充行并将它们附加到LinearLayout来完成)并对其进行动画处理?


6
为了增加动画效果重新实现列表视图是没有意义的。 - Macarse
我当然可以只使用LinearLayout,但是对于非常大的数据集,LinearLayout的性能将变得极差。ListView具有许多智能优化,围绕视图回收,使其能够处理大型数据集(iOS的UITableView也具有相同的优化)。编写自己的视图回收器属于重新实现ListView的范畴:( - Alex Pretzlav
我知道这没有意义 - 但是如果你只处理一些少量的行,拥有漂亮的进出动画可能是值得尝试的。 - whlk

3

您是否考虑过向右动画扫描?您可以尝试在列表项顶部绘制逐渐变大的白色条形,然后将其从列表中移除。其他单元格仍会突然移动到位置,但这总比没有好。


3

在更改列表之前调用listView.scheduleLayoutAnimation();


2
如我在我的网站上解释的那样,我的方法是通过getdrawingcache创建位图。有两个位图,并将下面的位图动画化以创建移动效果。
请参阅以下代码:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener()
    {
        public void onItemClick(AdapterView<?> parent, View rowView, int positon, long id)
        {
            listView.setDrawingCacheEnabled(true);
            //listView.buildDrawingCache(true);
            bitmap = listView.getDrawingCache();
            myBitmap1 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), rowView.getBottom());
            myBitmap2 = Bitmap.createBitmap(bitmap, 0, rowView.getBottom(), bitmap.getWidth(), bitmap.getHeight() - myBitmap1.getHeight());
            listView.setDrawingCacheEnabled(false);
            imgView1.setBackgroundDrawable(new BitmapDrawable(getResources(), myBitmap1));
            imgView2.setBackgroundDrawable(new BitmapDrawable(getResources(), myBitmap2));
            imgView1.setVisibility(View.VISIBLE);
            imgView2.setVisibility(View.VISIBLE);
            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            lp.setMargins(0, rowView.getBottom(), 0, 0);
            imgView2.setLayoutParams(lp);
            TranslateAnimation transanim = new TranslateAnimation(0, 0, 0, -rowView.getHeight());
            transanim.setDuration(400);
            transanim.setAnimationListener(new Animation.AnimationListener()
            {
                public void onAnimationStart(Animation animation)
                {
                }

                public void onAnimationRepeat(Animation animation)
                {
                }

                public void onAnimationEnd(Animation animation)
                {
                    imgView1.setVisibility(View.GONE);
                    imgView2.setVisibility(View.GONE);
                }
            });
            array.remove(positon);
            adapter.notifyDataSetChanged();
            imgView2.startAnimation(transanim);
        }
    });

理解图像请查看此链接。谢谢。

2

我没有尝试过,但看起来animateLayoutChanges应该可以满足您的需求。我在ImageSwitcher类中看到它,我认为在ViewSwitcher类中也有吧?


嘿,谢谢,我会研究一下的。不幸的是,看起来animateLayoutChanges是从API Level 11即Honeycomb开始新推出的,也就是说目前还不能在任何手机上使用它 :( - Alex Pretzlav

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