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

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

咱们持续Android中LayoutAnimation的剖析(二)这篇文章剖析,在Android中LayoutAnimation的剖析(二)这篇文章中,咱们还漏剖析了一些内容,那就是属性动画的解析,咱们看 LayoutAnimation-Controller 中 LayoutAnimationController(Context context, AttributeSet attrs) 的构造方法;

public LayoutAnimationController(Context context, AttributeSet attrs) {

    ......    //1、    int resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_animation, 0);    if (resource > 0) {        //2、        setAnimation(context, resource);    }    ......

}

正文1 示意从 layoutAnimation 标签中解析出 animation 属性援用的 xml 文件 id;正文2 示意要将析出 animation 属性援用的 xml 文件 id作为参数调用 setAnimation(Context context, @AnimRes int resourceID) 办法;

public void setAnimation(Context context, @AnimRes int resourceID) {

        //3、    setAnimation(AnimationUtils.loadAnimation(context, resourceID));

}

setAnimation(Context context, @AnimRes int resourceID) 办法又调用了 AnimationUtils 的 loadAnimation(Context context, @AnimRes int id) 办法;

public static Animation loadAnimation(Context context, @AnimRes int id)

        throws Resources.NotFoundException {    XmlResourceParser parser = null;    try {        parser = context.getResources().getAnimation(id);                //4、        return createAnimationFromXml(context, parser);    } catch (XmlPullParserException ex) {        ......    } catch (IOException ex) {        ......    } finally {        ......    }

}

在正文4 中,将 XmlResourceParser 接口作为参数调用 AnimationUtils 的 createAnimationFromXml(Context c, XmlPullParser parser) 办法;

private static Animation createAnimationFromXml(Context c, XmlPullParser parser)

        throws XmlPullParserException, IOException {    return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser));

}

AnimationUtils 的 createAnimationFromXml(Context c, XmlPullParser parser) 办法又调用 AnimationUtils 的 createAnimationFromXml(Context c, XmlPullParser parser,AnimationSet parent, AttributeSet attrs) 办法;

private static Animation createAnimationFromXml(Context c, XmlPullParser parser,

                                                AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {    ......    while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)            && type != XmlPullParser.END_DOCUMENT) {        ......        String  name = parser.getName();        //5、        if (name.equals("set")) {            anim = new AnimationSet(c, attrs);            createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);            //6、        } else if (name.equals("alpha")) {            anim = new AlphaAnimation(c, attrs);        } else if (name.equals("scale")) {            anim = new ScaleAnimation(c, attrs);        }  else if (name.equals("rotate")) {            anim = new RotateAnimation(c, attrs);        }  else if (name.equals("translate")) {            anim = new TranslateAnimation(c, attrs);        } else {            throw new RuntimeException("Unknown animation name: " + parser.getName());        }        ......    }    return anim;}

看正文5,如果是 set 标签,那么就创立 AnimationSet 对象并作为参数递归调用 AnimationUtils 的 createAnimationFromXml(Context c, XmlPullParser parser,AnimationSet parent, AttributeSet attrs) 办法,咱们看一下 AnimationSet(Context context, AttributeSet attrs) 的构造方法;

public AnimationSet(Context context, AttributeSet attrs) {

    ......    if (context.getApplicationInfo().targetSdkVersion >=            Build.VERSION_CODES.ICE_CREAM_SANDWICH) {        //7、        if (a.hasValue(com.android.internal.R.styleable.AnimationSet_duration)) {            mFlags |= PROPERTY_DURATION_MASK;        }        //8、        if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillBefore)) {            mFlags |= PROPERTY_FILL_BEFORE_MASK;        }        //9、        if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillAfter)) {            mFlags |= PROPERTY_FILL_AFTER_MASK;        }        //10、        if (a.hasValue(com.android.internal.R.styleable.AnimationSet_repeatMode)) {            mFlags |= PROPERTY_REPEAT_MODE_MASK;        }        //11、        if (a.hasValue(com.android.internal.R.styleable.AnimationSet_startOffset)) {            mFlags |= PROPERTY_START_OFFSET_MASK;        }    }    a.recycle();

}

正文7、8、9、10、11 是判断 duration、fillBefore 、fillAfter、repeatMode 和 startOffset 属性是否设置了值,如果有就将 mFlags 设置相应的值。

咱们回到下面的 AnimationUtils 的 createAnimationFromXml(Context c, XmlPullParser parser,AnimationSet parent, AttributeSet attrs) 办法,如果不是 set 标签,是其余标签(alpha、scale、rotate 和 translate ),那么就创立相应的 Animation 子类对象,就拿正文6 来说,创立 AlphaAnimation 对象,咱们看看 AlphaAnimation(Context context, AttributeSet attrs) 的构造方法;

public AlphaAnimation(Context context, AttributeSet attrs) {

    ......    //12、    mFromAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_fromAlpha, 1.0f);    //13、    mToAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_toAlpha, 1.0f);    ......

}

正文12 是将 fromAlpha 属性解析进去;正文13 是将 toAlpha 属性解析进去。

到了这里,布局动画的解析和创立过程咱们曾经晓得了,动画个别是某个具体的 View 来操作的,而 LayoutAnimation 是针对 ViewGroup 的所有子 View 进行动画操作,咱们来看它的具体实现过程,咱们来看看 ViewGroup 的散发绘制过程,也就是 ViewGroup 的 dispatchDraw(Canvas canvas) 办法;

@Overrideprotected void dispatchDraw(Canvas canvas) {    ......    if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {        ......        for (int i = 0; i < childrenCount; i++) {            final View child = children[i];            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {                final ViewGroup.LayoutParams params = child.getLayoutParams();                                //14、                attachLayoutAnimationParameters(child, params, i, childrenCount);                                //15、                bindLayoutAnimation(child);            }        }        ......    }    ......}

咱们看正文15,是为 ViewGroup 的子元素中的 params 创立 layoutAnimationParameters,也就是为 ViewGroup 的子元素中的 params 创立动画,咱们看 ViewGroup 的 attachLayoutAnimation-Parameters(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) {                //16、        animationParams = new LayoutAnimationController.AnimationParameters();        params.layoutAnimationParameters = animationParams;    }        //17、    animationParams.count = count;        //18、    animationParams.index = index;

}

看正文16,如果 ViewGroup 的实现类没有重写 attachLayoutAnimation-Parameters(View child,LayoutParams params, int index, int count) 办法,那么默认给每一个 ViewGroup 的实现类的子元素的 params 中的 layoutAnimationParameters 赋值 LayoutAnimationController.Anima-tionParameters 对象,如果 ViewGroup 的实现类(比方 GridView)重写了 attachLayoutAnimationParameters(View child,LayoutParams params, int index, int count) 办法,则能够给每一个 ViewGroup 的实现类的子元素的 params 中的 layoutAnimationParameters 赋值 GridLayout-AnimationController.AnimationParameters 对象;正文17 示意子元素的数量;正文18 示意子元素的地位。

咱们看回正文15 的代码,也就是 ViewGroup 的 bindLayout-Animation(View child) 办法;

private void bindLayoutAnimation(View child) {

        //19、    Animation a = mLayoutAnimationController.getAnimationForView(child);        //20、    child.setAnimation(a);

}

正文19 示意为每一个行将成为 View 的动画计算偏移工夫并返回一个 Animation 对象;正文20 示意为每一个 View 设置一个动画;咱们看一下正文19 中 LayoutAnimationController 的 getAnimationForView(View view) 办法;

public final Animation getAnimationForView(View view) {

        //21、    final long delay = getDelayForView(view) + mAnimation.getStartOffset();        //22、    mMaxDelay = Math.max(mMaxDelay, delay);    try {        final Animation animation = mAnimation.clone();                //23、        animation.setStartOffset(delay);        return animation;    } catch (CloneNotSupportedException e) {        return null;    }

}

正文21 示意依据 View 数量和 View 索引地位,计算 View 执行动画的偏移工夫;正文22 示意最大延迟时间;正文23 示意设置动画开始的执行工夫;getDelayForView(View view) 办法在 Android 零碎源码中是有2种实现,一种是 LayoutAnimationController 的,一种是 GridLayoutAnimation-Controller 的,咱们先看 LayoutAnimationController 的 getDelay-ForView(View view) 办法;

protected long getDelayForView(View view) {

    ViewGroup.LayoutParams lp = view.getLayoutParams();    //24、    LayoutAnimationController.AnimationParameters params = lp.layoutAnimationParameters;    ......    //25、    final float delay = mDelay * mAnimation.getDuration();    //26、    final long viewDelay = (long) (getTransformedIndex(params) * delay);    //27、    final float totalDelay = delay * params.count;    //28、    if (mInterpolator == null) {        mInterpolator = new LinearInterpolator();    }    float normalizedDelay = viewDelay / totalDelay;    //29、    normalizedDelay = mInterpolator.getInterpolation(normalizedDelay);    //30、    return (long) (normalizedDelay * totalDelay);

}

正文24 示意获取 View 的动画参数;正文25 示意延迟时间,如总工夫2000ms,提早20%,那么 delay=400 ms;正文26 示意依据 View 的地位计算它的延迟时间,例如 getTransformedIndex(params) 的后果为 3,那么延时就是 3 400 = 1200;正文27 示意总的延迟时间,比方 params.count 是9,那么总的延迟时间是 400 9;正文28 示意用于调整动画执行机会的线性插值器;正文29 示意用差值器从新计算延迟时间;正文30 示意从新计算通过差值器调整后的延迟时间。

咱们看回正文26 中的 getTransformedIndex(params) 语句,也就是 LayoutAnimationController 的 getTransformedIndex(AnimationPara-meters params) 办法;

protected int getTransformedIndex(AnimationParameters params) {

    switch (getOrder()) {        case ORDER_REVERSE:            return params.count - 1 - params.index;        case ORDER_RANDOM:            if (mRandomizer == null) {                mRandomizer = new Random();            }            return (int) (params.count * mRandomizer.nextFloat());        case ORDER_NORMAL:        default:            return params.index;    }

}

到了这里咱们能够晓得, getTransformedIndex(AnimationPara-meters params) 办法是管制 View 动画的执行程序,它的执行程序有3中,就是 ORDER_REVERSE、ORDER_RANDOM 和 ORDER_NORMAL,其中 ORDER_REVERSE 示意倒序执行,ORDER_RANDOM 示意随机执行,ORDER_NORMAL 示意程序执行。

下面提到过 getDelayForView(View view) 办法在 Android 零碎源码中是有2种实现,一种是 LayoutAnimationController 的,曾经剖析完了;一种是 GridLayoutAnimationController 的,咱们看一下 GridLayout-AnimationController 的 getDelayForView(View view) 办法;

@Overrideprotected long getDelayForView(View view) {    ......    //31、    final int column = getTransformedColumnIndex(params);        //32、    final int row = getTransformedRowIndex(params);    ......    float totalDelay;    long viewDelay;    ......    switch (mDirectionPriority) {                //33、        case PRIORITY_COLUMN:            viewDelay = (long) (row * rowDelay + column * rowsCount * rowDelay);            totalDelay = rowsCount * rowDelay + columnsCount * rowsCount * rowDelay;            break;                        //34、        case PRIORITY_ROW:            viewDelay = (long) (column * columnDelay + row * columnsCount * columnDelay);            totalDelay = columnsCount * columnDelay + rowsCount * columnsCount * columnDelay;            break;                        //35、        case PRIORITY_NONE:        default:            viewDelay = (long) (column * columnDelay + row * rowDelay);            totalDelay = columnsCount * columnDelay + rowsCount * rowDelay;            break;    }    ......    return (long) (normalizedDelay * totalDelay);}

正文31 示意第几列;正文32 示意第几行;正文33、34 和 35 示意依据方向优先级计算 View 的延迟时间和总延迟时间,其中 PRIORITY_COLUMN 示意列优先,PRIORITY_ROW 示意行优先,PRIORITY_NONE 示意行和列同时进行。

咱们回到正文31 的代码,也就是 GridLayoutAnimationController 的 getTransformedColumnIndex(AnimationParameters params) 办法;

private int getTransformedColumnIndex(GridLayoutAnimationController.AnimationParameters params) {

    int index;    switch (getOrder()) {        case ORDER_REVERSE:            index = params.columnsCount - 1 - params.column;            break;        case ORDER_RANDOM:            if (mRandomizer == null) {                mRandomizer = new Random();            }            index = (int) (params.columnsCount * mRandomizer.nextFloat());            break;        case ORDER_NORMAL:        default:            index = params.column;            break;    }    int direction = mDirection & DIRECTION_HORIZONTAL_MASK;    //36、    if (direction == DIRECTION_RIGHT_TO_LEFT) {        index = params.columnsCount - 1 - index;    }    return index;

}

getTransformedColumnIndex(AnimationParameters params) 办法先从动画的执行程序计算出开始动画的列索引,假如动画的执行程序是 ORDER_NORMAL 且 index 是0,看正文 36,又如果动画的方向是 right_to_left,也就是从右往左,又假如 params.columnsCount 是 10,那么最初的 index 就是 10 -1 -0 等于9 ;正文 32 的代码中 GridLayoutAnimationController 的 getTransformedRowIndex(Anim-ationParameters params) 办法也是相似这样的推理,不过计算的是第几列,这里我就不再剖析它了,有趣味的读者能够看看它的实现。

到这里,咱们把握了 GridLayoutAnimationController 和 Layout-AnimationController 的原理,咱们就能够自定义一个 Layout-AnimationController 来实现非凡的成果:写一个线性布局的 RecyclerView 并展现10条 item 数据,从倒数第6个 item 开始进行动画并顺次倒序到顺数的第一个 item 进行动画(也就是倒数第6个item实现动画后提早肯定的工夫进行倒数第7个item实现动画,直到顺数第一个item实现动画);当顺数的第一个 item 实现动画后再让倒数的第5个 item 进行动画,直到倒数的第一个 item 实现动画;如果以上这些看不懂能够先看演示成果,再来回顾这些内容。

(1)、自定义一个 LayoutAnimationController 名叫 Custom-LayoutAnimationController :

public class CustomLayoutAnimationController extends LayoutAnimationController {

public CustomLayoutAnimationController(Context context, AttributeSet attrs) {    super(context, attrs);}public CustomLayoutAnimationController(Animation animation) {    super(animation);}public CustomLayoutAnimationController(Animation animation, float delay) {    super(animation, delay);    Log.d("LayoutAnimation","--CustomLayoutAnimationController");}@Overrideprotected int getTransformedIndex(AnimationParameters params) {    int index = params.count - 6 - params.index;    if (index <= -1) {        return params.index;    }    return index;}

}

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

public class LayoutAnimationController2Activity extends AppCompatActivity {

RecyclerView mRv;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_layout_animation_controller2);    mRv = findViewById(R.id.rv);    final Animation animation = AnimationUtils.loadAnimation(this, R.anim.item_animation_drop_down);    LayoutAnimationController layoutAnimation = new CustomLayoutAnimationController(animation);    layoutAnimation.setDelay(0.85f);    mRv.setLayoutAnimation(layoutAnimation);    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);    linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);    mRv.setLayoutManager(linearLayoutManager);    mRv.setAdapter(new LayoutAnimationControllerAdapter(this));}

}

(3)LayoutAnimationController2Activity 对应 xml 布局文件 activity_layout_animation_controller2.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="match_parent"tools:context=".LayoutAnimationController2Activity">

</android.support.v7.widget.RecyclerView>

(4)在 anim 文件夹上面新建一个 animation 对应的 xml 文件 item_animation_drop_down.xml :

<set xmlns:android="http://schemas.android.com/apk/res/android"

android:duration="800"android:fillAfter="true"android:fillBefore="true"android:repeatMode="restart"android:shareInterpolator="@android:anim/decelerate_interpolator"><translate    android:fromYDelta="-20%"    android:toYDelta="0" /><alpha    android:fromAlpha="0"    android:toAlpha="1" /><scale    android:fromXScale="105%"    android:fromYScale="105%"    android:pivotX="50%"    android:pivotY="50%"    android:toXScale="100%"    android:toYScale="100%" />

</set>

(5)RecyclerView 对应的 Adapter 实现类 LayoutAnimation-ControllerAdapter :

public class LayoutAnimationControllerAdapter extends RecyclerView.Adapter<LayoutAnimationControllerAdapter.LinearViewHolder> {

private Context mContext;public LayoutAnimationControllerAdapter(Context context) {    mContext = context;}@NonNull@Overridepublic LayoutAnimationControllerAdapter.LinearViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {    View item = LayoutInflater.from(mContext).inflate(R.layout.item_1, parent, false);    return new LinearViewHolder(item);}@Overridepublic void onBindViewHolder(@NonNull LayoutAnimationControllerAdapter.LinearViewHolder holder, final int position) {    holder.mTv.setText("第" + (position+1) + "个条目");}@Overridepublic int getItemCount() {    return 10;}class LinearViewHolder extends RecyclerView.ViewHolder {    TextView mTv;    public LinearViewHolder(View itemView) {        super(itemView);        mTv = itemView.findViewById(R.id.tv);    }}

}

(6)LayoutAnimationControllerAdapter 对应的 item 布局文件 item_1.xml :

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

xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/tv"android:layout_marginTop="10px"android:text="你好"android:layout_width="match_parent"android:background="#00FF00"android:layout_height="50px">

</TextView>

演示成果如下所示: