测试环境
Pixel + Android 10 + Android Studio 3.5.3
问题描述
为GridView 添加了 LayoutAnimation 但是, 启动后不执行动画
layout.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <GridView android:id="@+id/gv" android:layout_width="match_parent" android:layout_height="match_parent" android:horizontalSpacing="@dimen/spacing" android:verticalSpacing="@dimen/spacing" android:listSelector="@color/transparent" android:layoutAnimation="@anim/layout_anim" android:numColumns="auto_fit"/> <Button android:id="@+id/add_data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/app_name"/> </RelativeLayout>
layout_anim.xml
<gridLayoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" android:rowDelay="50%" android:columnDelay="40%" android:directionPriority="none" android:direction="top_to_bottom|left_to_right" android:animation="@anim/anim"/>
anim.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:duration="600"> <translate android:fromYDelta="50%p" android:toYDelta="0"/> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" /> </set>
原因分析
先看下java部分的代码:
public class Test extends Activity { private GridAdapter mGrideAdapter; private GridView grid; private List<String> mDatas = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); grid = (GridView) findViewById(R.id.gv); mDatas.addAll(getData()); mGrideAdapter = new GridAdapter(); grid.setAdapter(mGrideAdapter); Button addData = (Button)findViewById(R.id.add_data); addData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addData(); mGrideAdapter.notifyDataSetChanged(); } }); } private List<String> getData() { List<String> data = new ArrayList<String>(); for (int i = 1;i<15;i++){ data.add("DATA "+i); } return data; } public void addData(){ mDatas.addAll(mDatas); //mGrideAdapter.notifyDataSetChanged(); } public class GridAdapter extends BaseAdapter { public View getView(int position, View view, ViewGroup parent) { Item item; if(view != null && view.getTag() != null){ item = (Item)view.getTag(); }else{ item = new Item(); view = getLayoutInflater().inflate(R.layout.item_cover, null, false); item.cover = (ImageView)view.findViewById(R.id.ivCover); item.title = (TextView)view.findViewById(R.id.title); } item.title.setText(mDatas.get(position)); return view; } class Item { ImageView cover; TextView title; } public final int getCount() { return mDatas.size(); } public final Object getItem(int position) { return null; } public final long getItemId(int position) { return position; } } }
上面的测试代码, 在单独执行的过程中, 并没有什么问题.
然而, 在移植到另外一个项目中会出现问题, 而问题的根源在于:
在GridView初始化完成后, LayoutAnimation未执行或执行过程中, 若调用 BaseAdapter.notifyDataSetChanged, LayoutAnimation会立即中止
在出问题的代码中, 在Adapter中, 加入了异步加载图片, 并通过notifyDataSetChanged 更新UI, 而何时调用notifyDataSetChanged, 则由异步加载图片的速度决定, 通常情况下, 会早于LayoutAnimation执行完毕.
一般情况下, LayoutAnimation只执行一次
frameworks/base/core/java/android/view/ViewGroup.java
// When set, dispatchDraw() will run the layout animation and unset the flag private static final int FLAG_RUN_ANIMATION = 0x8; @Override protected void dispatchDraw(Canvas canvas) { //... if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { final boolean buildCache = !isHardwareAccelerated(); for (int i = 0; i < childrenCount; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams(); attachLayoutAnimationParameters(child, params, i, childrenCount); bindLayoutAnimation(child); } } final LayoutAnimationController controller = mLayoutAnimationController; if (controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; } controller.start(); //清除标识, 后续不再执行LayoutAnimation mGroupFlags &= ~FLAG_RUN_ANIMATION; mGroupFlags &= ~FLAG_ANIMATION_DONE; if (mAnimationListener != null) { mAnimationListener.onAnimationStart(controller.getAnimation()); } } //... } /** * Runs the layout animation. Calling this method triggers a relayout of * this view group. */ public void startLayoutAnimation() { if (mLayoutAnimationController != null) { mGroupFlags |= FLAG_RUN_ANIMATION; requestLayout(); } } /** * Schedules the layout animation to be played after the next layout pass * of this view group. This can be used to restart the layout animation * when the content of the view group changes or when the activity is * paused and resumed. */ public void scheduleLayoutAnimation() { mGroupFlags |= FLAG_RUN_ANIMATION; }
若需要重新执行, 可以尝试使用ViewGroup.scheduleLayoutAnimation()
参考
自定义控件三部曲之动画篇(十一)——layoutAnimation与gridLayoutAnimation