Android中LayoutAnimation的分析(二)

本文系转载文章,阅读原文可读性会更好些,原文链接:https://mp.weixin.qq.com/s/y5QgD47JXJMlhBHqxR6sAA

ps:源码是基于 android api 27 来分析的

这篇文章我们来继Android中LayoutAnimation的分析(一)写,在Android中LayoutAnimation的分析(一)这篇文章中,我们主要列举了 demo 进行演示,现在我们往下分析;在上一篇文章中,都用了 LayoutAnimationController 和 GridLayoutAnimationController 的 xml 方式进行动画;其实我们上一篇文章用到的 xml 方式进行动画最终是通过 AnimationUtils 完成动画文件加载任务,我们用到的是 RecyclerView 和 GridView 作为演示动画,RecyclerView 和 GridView 都继承于 ViewGroup,我们看一下 ViewGroup 解析有关属性的方法 initFromAttributes;

private void initFromAttributes(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {

case R.styleable.ViewGroup_layoutAnimation:
int id = a.getResourceId(attr, -1);
if (id > 0) {
//1、
setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
}
break;

}
}

}

看注释1,只要放在布局文件的 ViewGroup 添加了 layoutAnimation 属性,那么 xml 方式进行动画最终是通过 AnimationUtils 完成动画文件加载任务的,如果我们以上一篇文章中 LayoutAnimationController 的 xml 方式进行动画为例的话,注释1 中的 id 就是 R.anim.layout_animation_fall_down ;我们跟踪注释1 的代码,看看如何解析 xml 方式进行动画的,看 AnimationUtils 的 loadLayoutAnimation(Context context, @AnimRes int id) 方法;

public static LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id)
throws Resources.NotFoundException {

try {
//2、
parser = context.getResources().getAnimation(id);

        //3、
        return createLayoutAnimationFromXml(context, parser);
    } catch (XmlPullParserException ex) {
        ......
    } catch (IOException ex) {
        ......
    } finally {
        ......
    }

}

注释2 中根据 id 创建一个 XmlResourceParser 类型的对象;注释3 中将 XmlResourceParser 类型的对象作为参数,然后调用 AnimationUtils 的 createLayoutAnimationFromXml(Context c,XmlPullParser parser) 方法;

private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
XmlPullParser parser) throws XmlPullParserException, IOException {

    return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser));

}

AnimationUtils 的 createLayoutAnimationFromXml(Context c,XmlPullParser parser) 方法又调用了 AnimationUtils 的 createLayoutAnimationFromXml(Context c,XmlPullParser parser, AttributeSet attrs) 方法;

private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {

    LayoutAnimationController controller = null;

    int type;
    int depth = parser.getDepth();

    while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
            && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        String name = parser.getName();
        
        //4、
        if ("layoutAnimation".equals(name)) {
            controller = new LayoutAnimationController(c, attrs);
            
            //5、
        } else if ("gridLayoutAnimation".equals(name)) {
            controller = new GridLayoutAnimationController(c, attrs);
        } else {
            throw new RuntimeException("Unknown layout animation name: " + name);
        }
    }

    return controller;

}

name 只能是 ViewGroup 的 layoutAnimation 属性引用的 xml 文件里的 layoutAnimation 标签或者 gridLayoutAnimation 标签(就好比Android中LayoutAnimation的分析(一)这篇文章中LayoutAnimationController xml 进行动画的 layout_animation_fall_down.xml 文件里的 layoutAnimation 标签和 GridLayoutAnimationController xml 进行动画的 layout_animation_grid.xml 文件里的 gridLayoutAnimation 标签),否则用其他标签就会报错;注释4 中,如果是 layoutAnimation 标签就创建一个 LayoutAnimationController 对象;注释5 中,如果是 gridLayoutAnimation 标签就创建一个 GridLayoutAnimationController 对象;我们往下看注释4 中的 LayoutAnimationController(Context context, AttributeSet attrs) 构造方法;

public LayoutAnimationController(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation);

    Animation.Description d = Animation.Description.parseValue(
            a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay));

    //6、
    mDelay = d.value;

    //7、
    mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL);
    ......

}

看注释6 和注释7,该构造方法实现了从 ViewGroup 的 layoutAnimation 属性引用的 xml 文件(比如Android中LayoutAnimation的分析(一)这篇文章中的 LayoutAnimationController xml 方式进行动画的 layout_animation_fall_down.xml 文件)中的 layoutAnimation 标签解析出 delay 和 animationOrder 属性。

我们回上面注释5 的代码,也就是 GridLayoutAnimationController 的实例化,也顺便调用 GridLayoutAnimationController(Context context, AttributeSet attrs) 方法;

public GridLayoutAnimationController(Context context, AttributeSet attrs) {
super(context, attrs);

    TypedArray a = context.obtainStyledAttributes(attrs,
            com.android.internal.R.styleable.GridLayoutAnimation);

    Animation.Description d = Animation.Description.parseValue(
            a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay));
    
    //8、
    mColumnDelay = d.value;
    d = Animation.Description.parseValue(
            a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay));
    
    //9、
    mRowDelay = d.value;
    //noinspection PointlessBitwiseExpression
    
    //10、
    mDirection = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_direction,
            DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM);
    
    //11、
    mDirectionPriority = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_directionPriority,
            PRIORITY_NONE);

    a.recycle();

}

看注释8、注释9、注释10 和 注释11,该构造方法实现了从 ViewGroup 的 layoutAnimation 属性引用的 xml 文件(比如Android中LayoutAnimation的分析(一)这篇文章中的 GridLayoutAnimationController xml 方式进行动画的 layout_animation_grid.xml 文件)中的 gridLayoutAnimation 标签解析出 columnDelay、 rowDelay、 direction(这个属性在 demo 演示中我们没有添加) 和 directionPriority 属性。

好了,这里我们就有一个疑问了,RecyclerView 也可以使用网格布局啊,是否可以使用 gridLayoutAnimation 动画,好,我们先举个例子演示一下;

(1)在 app 目录下的 build.gradle 里添加 RecyclerView 的依赖:

implementation “com.android.support:recyclerview-v7:26+”

(2)新建一个 Activity,名叫 GridLayoutAnimationActivity :

public class GridLayoutAnimationActivity extends AppCompatActivity {
RecyclerView mRv;
GridAdapter mGrideAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_grid_layout_animation);
mRv = findViewById(R.id.rv);
List list = new ArrayList();
for (int i = 0; i < 100;i++) {
list.add(“item–”+i);
}
mGrideAdapter = new GridAdapter(this,list);
GridLayoutManager gm = new GridLayoutManager(this,3);
mRv.setLayoutManager(gm);
mRv.setAdapter(mGrideAdapter);
}
}

(3)GridLayoutAnimationActivity 对应的布局文件 activity_grid_layout_animation.xml :

<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.RecyclerView
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:id="@+id/rv"
android:layout_width=“match_parent”
android:layout_height=“200dp”
android:layoutAnimation="@anim/layout_animation_grid"
tools:context=".GridLayoutAnimationActivity">
</android.support.v7.widget.RecyclerView>

(4)在 res 目录下新建一个 anim 文件夹,在 anim 目录下新建一个 layoutAnimation 属性引用的 xml 文件 layout_animation_grid.xml :

(5)在 anim 文件夹下面新建一个 animation 属性引用的 xml 文件 slide_in_left.xml :

<?xml version="1.0" encoding="utf-8"?>

(6)RecyclerView 对应的 Adapter 实现类 GridAdapter :

public class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> {
private Context mContext;
private List mList;

public GridAdapter(Context context, List<String> list) {
    mContext = context;
    mList = list;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View item = LayoutInflater.from(mContext).inflate(R.layout.item_grid, parent, false);
    ViewHolder viewHolder = new ViewHolder(item);
    return viewHolder;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

}

@Override
public int getItemCount() {
    return mList.size();
}

class ViewHolder extends RecyclerView.ViewHolder {

    public ViewHolder(View itemView) {
        super(itemView);
    }
}

}

(7)GridAdapter 对应的 item 布局文件 item_grid.xml :

<?xml version="1.0" encoding="utf-8"?>


程序运行后,发现出现如下报错:

java.lang.ClassCastException: android.view.animation.LayoutAnimationController A n i m a t i o n P a r a m e t e r s c a n n o t b e c a s t t o a n d r o i d . v i e w . a n i m a t i o n . G r i d L a y o u t A n i m a t i o n C o n t r o l l e r AnimationParameters cannot be cast to android.view.animation.GridLayoutAnimationController AnimationParameterscannotbecasttoandroid.view.animation.GridLayoutAnimationControllerAnimationParameters
at android.view.animation.GridLayoutAnimationController.getDelayForView(GridLayoutAnimationController.java:299)
at android.view.animation.LayoutAnimationController.getAnimationForView(LayoutAnimationController.java:323)
at android.view.ViewGroup.bindLayoutAnimation(ViewGroup.java:5295)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4109)
at android.view.View.draw(View.java:20486)
at android.support.v7.widget.RecyclerView.draw(RecyclerView.java:3987)
at android.view.View.updateDisplayListIfDirty(View.java:19294)
at android.view.View.draw(View.java:20211)
at android.view.ViewGroup.drawChild(ViewGroup.java:4394)

从报错信息来看,它说 LayoutAnimationController.AnimationParameters 不能转换成 GridLayoutAnimationController.AnimationParameters,还提示了是 GridLayoutAnimationController 的 getDelayForView 方法的第299行代码,我们来看这个方法;

@Override
protected long getDelayForView(View view) {
    ViewGroup.LayoutParams lp = view.getLayoutParams();
    
    //12、
    AnimationParameters params = (AnimationParameters) lp.layoutAnimationParameters;
    ......
    return (long) (normalizedDelay * totalDelay);
}

注释12 就是 getDelayForView 方法的第299行代码,params 就是 GridLayoutAnimationController.AnimationParameters 类型的对象,而 lp.layoutAnimationParameters是 LayoutAnimationController.Ani-mationParameters 类型的对象,所以转换异常;我们看一下 lp.layoutAnimationParameters 初始化是在什么地方,我们看一下 ViewGroup 的 dispatchDraw(Canvas canvas) 方法;

@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) {

                //13、
                final ViewGroup.LayoutParams params = child.getLayoutParams();
                
                //14、
                attachLayoutAnimationParameters(child, params, i, childrenCount);
                bindLayoutAnimation(child);
            }
        }
        ......
    }
    ......
}

注释13 表示 ViewGroup 的子元素获取到相应的布局参数;注释14 是把子元素的布局参数传入 ViewGroup 的 attachLayoutAnimationParameters(View child,LayoutParams params, int index, int count) 方法中,我们看一下 attachLayoutAnimationParameters(View child,LayoutParams params, int index, int count) 方法;

protected void attachLayoutAnimationParameters(View child,
ViewGroup.LayoutParams params, int index, int count) {
LayoutAnimationController.AnimationParameters animationParams =
params.layoutAnimationParameters;
if (animationParams == null) {
animationParams = new LayoutAnimationController.AnimationParameters();

        //15、
        params.layoutAnimationParameters = animationParams;
    }

    animationParams.count = count;
    animationParams.index = index;

}

看注释15,ViewGroup 子元素的 params 中的 layoutAnimationParameters 是一个 LayoutAnimationController.Ani-mationParameters 类型的对象,也就是说 ViewGroup 的子类如果没有重写 attachLayoutAnimationParameters(View child,LayoutParams params, int index, int count) 方法,那么 ViewGroup 子元素的 params 中的 layoutAnimationParameters 就会默认被赋值一个 Layout-AnimationController.AnimationParameters 类型的对象。

我们查看 RecyclerView 的源码发现并没有重写 attachLayoutAnimationParameters(View child,LayoutParams params, int index, int count) 方法;在上一篇文章中,我们用 GridView 来进行 GridLayoutAnimation 的动画,我们查看 GridView 的源码,发现 Grid-View 重写了 attachLayoutAnimationParameters(View child,Layout-Params params, int index, int count) 方法;

@Override
protected void attachLayoutAnimationParameters(View child,
                                               ViewGroup.LayoutParams params, int index, int count) {

    GridLayoutAnimationController.AnimationParameters animationParams =
            (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;

    if (animationParams == null) {
        animationParams = new GridLayoutAnimationController.AnimationParameters();
        
        //16、
        params.layoutAnimationParameters = animationParams;
    }
    ......
}

看注释16,animationParams 是一个 GridLayoutAnimationControl-ler.AnimationParameters 类型的对象,GridView 之所以能进行 GridLayoutAnimation 的动画,是因为重写了 attachLayoutAnimation-Parameters(View child,Layout-Params params, int index, int count) 方法并创建了 GridLayoutAnimationControl-ler.AnimationParameters 类型的对象;那如果我们要 RecyclerView 进行 GridLayoutAnimation 的动画,那么就要重写 attachLayoutAnimation-Parameters(View child,Layout-Params params, int index, int count) 方法;

(1)我自定义一个 RecyclerView(包名com.example.layoutanimationdemo.layoutanimationdemo) :

public class MyRecyclerView extends RecyclerView {
public MyRecyclerView(Context context) {
super(context);
}

public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

@Override
protected void attachLayoutAnimationParameters(View child,
                                               ViewGroup.LayoutParams params, int index, int count) {
    GridLayoutAnimationController.AnimationParameters animationParams =
            (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;
    if (animationParams == null) {
        animationParams = new GridLayoutAnimationController.AnimationParameters();
        params.layoutAnimationParameters = animationParams;
    }
    animationParams.count = count;
    animationParams.index = index;
}

}

(2)拿这篇文章的上面报错的 demo 稍微改一下,只需要改 activity_grid_layout_animation.xml 文件就好了,其他文件不用改 :

<?xml version="1.0" encoding="utf-8"?>

<com.example.layoutanimationdemo.layoutanimationdemo.MyRecyclerView
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:id="@+id/rv"
android:layout_width=“match_parent”
android:layout_height=“200dp”
android:layoutAnimation="@anim/layout_animation_grid"
tools:context=".GridLayoutAnimationActivity">
</com.example.layoutanimationdemo.layoutanimationdemo.MyRecyclerView>

程序运行没有报错,效果如下所示:

这篇文章写到这里先告一段落了,但是还没完,下一篇会继续分析它。

上一篇:C++第12周项目2——分段函数


下一篇:Android中自定义ViewGroup,移动混合开发