本文系转载文章,阅读原文可读性会更好些,原文链接:https://mp.weixin.qq.com/s/mfF_lRGZoIdaMRwJK6h5yg
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>
程序运行没有报错,效果如下所示:
这篇文章写到这里先告一段落了,但是还没完,下一篇会继续分析它。