乐趣区

关于java:Android中LayoutAnimation的分析二

PS:本文系转载文章,浏览原文可读性会更好,文章开端有原文链接

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<String> list = new ArrayList<String>();
    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:

<gridLayoutAnimation xmlns:android=”http://schemas.android.com/apk/res/android”

android:rowDelay="0%"
android:columnDelay="60%"
android:directionPriority="column"
android:animation="@anim/slide_in_left"/>

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

<?xml version=”1.0″ encoding=”utf-8″?>
<set xmlns:android=”http://schemas.android.com/apk/res/android” android:duration=”1000″>

<translate android:fromXDelta="-50%p" android:toXDelta="0"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0" />

</set>

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

public class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> {

private Context mContext;
private List<String> 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″?>
<TextView

xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:text="hellow"
android:layout_height="wrap_content">

</TextView>

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

java.lang.ClassCastException: android.view.animation.LayoutAnimationController$AnimationParameters cannot be cast to android.view.animation.GridLayoutAnimationController$AnimationParameters

    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>

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

这篇文章写到这里先告一段落了,然而还没完,下一篇会持续剖析它。

退出移动版