Android中LayoutAnimation的分析(三)

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) 方法;

@Override
protected 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) 方法;

@Override
protected 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");
}

@Override
protected 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;
@Override
protected 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


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">

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

android:duration="800"
android:fillAfter="true"
android:fillBefore="true"
android:repeatMode="restart"
android:shareInterpolator="@android:anim/decelerate_interpolator">





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

public class LayoutAnimationControllerAdapter extends RecyclerView.Adapter {

private Context mContext;

public LayoutAnimationControllerAdapter(Context context) {
    mContext = context;
}

@NonNull
@Override
public LayoutAnimationControllerAdapter.LinearViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View item = LayoutInflater.from(mContext).inflate(R.layout.item_1, parent, false);
    return new LinearViewHolder(item);
}

@Override
public void onBindViewHolder(@NonNull LayoutAnimationControllerAdapter.LinearViewHolder holder, final int position) {
    holder.mTv.setText("第" + (position+1) + "个条目");
}

@Override
public 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 :


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">

演示效果如下所示:

你可能感兴趣的:(androidjava)