测试环境
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()