转载请注明出处(万分感谢!):
http://blog.csdn.net/javazejian/article/details/52571779
出自【zejian的博客】
关联文章:
走进绚烂多彩的属性动画-Property Animation(上篇)
走进绚烂多彩的属性动画-Property Animation之Interpolator和TypeEvaluator(下篇)
属性动画-Property Animation之ViewPropertyAnimator 你应该知道的一切
Android布局动画之animateLayoutChanges与LayoutTransition
关于布局动画是针对ViewGroup而言的,意指ViewGroup在增加子View或者删除子View时其子View的过渡动画,在android官网有这么一个简单的例子,其效果如下,接下来我们就通过这个例子来入门
事实上,实现上面ViewGroup的布局动画非常简单,我们只要给子View所在的ViewGroup的xml中添加下面的属性即可:
android:animateLayoutChanges="true"
通过设置以上代码,当ViewGroup在添加View时,子View会呈现出过渡的动画效果,这个动画效果是android默认的显示效果,而且无法使用自定义的动画来替换这个效果,不过还是有其他方式可以实现自定义的过渡动画滴,这个我们后面会分析,下面看看android官方给的demo的代码实现:
activity_layout_changes.xml布局文件
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:showDividers="middle"
android:divider="?android:dividerHorizontal"
android:animateLayoutChanges="true"
android:paddingLeft="16dp"
android:paddingRight="16dp" />
ScrollView>
<TextView android:id="@android:id/empty"
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="32dp"
android:text="@string/message_empty_layout_changes"
android:textColor="?android:textColorSecondary" />
FrameLayout>
布局代码比较简单,使用了一个ScrollView包裹了LinearLayout布局并在LinearLayout中添加了属性android:animateLayoutChanges="true"
,启动了android自带的布局动画,之所以使用ScrollView,是为了在添加子View多时可以进行滑动,而TextView只不过在没有子view时一个提示语。接着看看子View的布局文件list_item_example.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:orientation="horizontal"
android:showDividers="middle"
android:divider="?android:dividerVertical"
android:dividerPadding="8dp"
android:gravity="center">
<TextView android:id="@android:id/text1"
style="?android:textAppearanceMedium"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:paddingLeft="?android:listPreferredItemPaddingLeft" />
<ImageButton android:id="@+id/delete_button"
android:layout_width="48dp"
android:layout_height="match_parent"
android:src="@drawable/ic_list_remove"
android:background="?android:selectableItemBackground"
android:contentDescription="@string/action_remove_item" />
LinearLayout>
主要控件有用于显示每个子View内容的TextView和用于响应删除子View事件的ImageButton,最后就是Activity的实现:
LayoutChangesActivity.java
package com.example.android.animationsdemo;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* This sample demonstrates how to use system-provided, automatic layout transitions. Layout
* transitions are animations that occur when views are added to, removed from, or changed within
* a {@link ViewGroup}.
*
* In this sample, the user can add rows to and remove rows from a vertical
* {@link android.widget.LinearLayout}.
*/
public class LayoutChangesActivity extends Activity {
private ViewGroup mContainerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout_changes);
mContainerView = (ViewGroup) findViewById(R.id.container);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
//响应添加事件的menu
getMenuInflater().inflate(R.menu.activity_layout_changes, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
return true;
case R.id.action_add_item:
//添加子View
findViewById(android.R.id.empty).setVisibility(View.GONE);
addItem();
return true;
}
return super.onOptionsItemSelected(item);
}
private void addItem() {
//实例化一个子View
final ViewGroup newView = (ViewGroup) LayoutInflater.from(this).inflate(
R.layout.list_item_example, mContainerView, false);
// 随机设置子View的内容
((TextView) newView.findViewById(android.R.id.text1)).setText(
COUNTRIES[(int) (Math.random() * COUNTRIES.length)]);
//设置删除按钮的监听
newView.findViewById(R.id.delete_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mContainerView.removeView(newView);
// If there are no rows remaining, show the empty view.
if (mContainerView.getChildCount() == 0) {
findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
}
}
});
//添加子View
mContainerView.addView(newView, 0);
}
/**
* A static list of country names.
*/
private static final String[] COUNTRIES = new String[]{
"Belgium", "France", "Italy", "Germany", "Spain",
"Austria", "Russia", "Poland", "Croatia", "Greece",
"Ukraine",
};
}
代码比较简单,mContainerView作为子View的承载容器,我们可以不断添加子View,也可以移除子View,由于我们在布局文件中启动了android自带的布局动画,所以在添加子View或移除子View都会有过度动画,现在运行程序,效果如下:
这就是android中最简单的布局动画
除了上面的布局动画外,有时我们可能需要第一次加载ListView或者GridView的时候能有个动画的过度效果,以便达到更好的体验,如下ListView加载子View的效果:
事实上实现这种效果也比较简单,我们只需要在ListView的布局文件中添加android:layoutAnimation=”@anim/layout”属性即可。接下来给出实现步骤:
实现动画效果
left_into.xml
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromXDelta="100%"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="0" />
<alpha
android:duration="500"
android:fromAlpha="0"
android:toAlpha="1" />
set>
这个比较简单,就一个平移和透明度的效果,接着创建layout_animation.xml
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/left_into"
android:animationOrder="normal"
android:delay="0.5"
/>
这里简单介绍一下layoutAnimation标签属性:
属性名 | 含义 |
---|---|
android:delay | delay的单位为秒,表示子View进入的延长时间 |
android:animationOrder | 子类的进入方式 ,其取值有,normal 0 默认 ,reverse 1 倒序 ,random 2 随机 |
android:animation | 子view要执行的具体动画的文件,自定义即可 |
然后设置给listView控件即可,activity_listview.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
-- 设置在这里 ⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️ -->
android:layoutAnimation="@anim/layout_animation"
>
ListView>
LinearLayout>
ListViewActivity.java代码如下:
package com.example.android.animationsdemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zejian
* Time 16/9/17.
* Description:
*/
public class ListViewActivity extends Activity {
private ListView listView;
private List list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listview);
listView= (ListView) findViewById(R.id.listView);
initData();
listView.setAdapter(new Myadpter());
}
public void initData(){
list = new ArrayList<>();
for(int i=1;i<30;i++){
list.add("布局动画listView测试_"+i);
}
}
private class Myadpter extends BaseAdapter{
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv= new TextView(ListViewActivity.this);
tv.setTextSize(20);
tv.setHeight(100);
tv.setText(list.get(position));
return tv;
}
}
}
执行以上代码,效果就是前面我们给出listView进入的动态效果,这里特别注意的是,动画只在listView子View第一次进入时有效,如果后面动态给listView添加子View时,此动画效果无效,当然除了通过xml设置外,我们还可以通过代码动态设置,代码范例如下,这里就不演示了。
//通过加载XML动画设置文件来创建一个Animation对象,子View进入的动画
Animation animation=AnimationUtils.loadAnimation(this, R.anim.left_into);
//得到一个LayoutAnimationController对象;
LayoutAnimationController lac=new LayoutAnimationController(animation);
//设置控件显示的顺序;
lac.setOrder(LayoutAnimationController.ORDER_REVERSE);
//设置控件显示间隔时间;
lac.setDelay(0.5f);
//为ListView设置LayoutAnimationController属性;
listView.setLayoutAnimation(lac);
通过AnimationUtils.loadAnimation加载子View的动画来并返回一个Animation对象,然后将Animation对象设置到LayoutAnimationController中并返回LayoutAnimationController对象,配置LayoutAnimationController对象的一些属性,最后设置到ListView中,其中LayoutAnimationController对应layoutAnimation标签,这里需要注意的是layoutAnimation动画不仅仅限于ListView,GridView中,也可用于一切ViewGroup中。
前面我们说过ViewGroup在设置android:animateLayoutChanges="true"
后在添加或者删除子view时可以启用系统带着的动画效果,但这种效果无法通过自定义动画去替换。不过还好android官方为我们提供了LayoutTransition类,通过LayoutTransition就可以很容易为ViewGroup在添加或者删除子view设置自定义动画的过渡效果了。
LayoutTransition类用于当前布局容器中需要View添加,删除,隐藏,显示时设置布局容器子View的过渡动画。也就是说利用LayoutTransition,可以分别为需添加或删除的View对象在移动到新的位置的过程添加过渡的动画效果。我们可以通过setLayoutTransition()方法为布局容器ViewGroup设置LayoutTransition对象,代码如下:
//初始化容器动画
LayoutTransition mTransitioner = new LayoutTransition();
container.setLayoutTransition(mTransitioner);
一般地,Layout中的子View对象有四种动画变化的形式,如下:
属性值 | 含义 |
---|---|
LayoutTransition.APPEARING | 子View添加到容器中时的过渡动画效果。 |
LayoutTransition.CHANGE_APPEARING | 子View添加到容器中时,其他子View位置改变的过渡动画 |
LayoutTransition.DISAPPEARING | 子View从容器中移除时的过渡动画效果。 |
LayoutTransition.CHANGE_DISAPPEARING | 子View从容器中移除时,其它子view位置改变的过渡动画 |
LayoutTransition.CHANGING | 子View在容器中位置改变时的过渡动画,不涉及删除或者添加操作 |
下面给出一个例子,先看布局文件activity_layout_animation.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="addView"
android:text="添加控件" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="removeView"
android:text="移除控件" />
LinearLayout>
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
LinearLayout>
比较简单不啰嗦,接着看看LayoutAnimationActivity.java
package com.example.android.animationsdemo;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
/**
* Created by zejian
* Time 16/9/17.
* Description:
*/
public class LayoutAnimationActivity extends Activity {
private int i = 0;
private LinearLayout container;
private LayoutTransition mTransitioner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout_animation);
container = (LinearLayout) findViewById(R.id.container);
//构建LayoutTransition
mTransitioner = new LayoutTransition();
//设置给ViewGroup容器
container.setLayoutTransition(mTransitioner);
setTransition();
}
private void setTransition() {
/**
* 添加View时过渡动画效果
*/
ObjectAnimator addAnimator = ObjectAnimator.ofFloat(null, "rotationY", 0, 90,0).
setDuration(mTransitioner.getDuration(LayoutTransition.APPEARING));
mTransitioner.setAnimator(LayoutTransition.APPEARING, addAnimator);
/**
* 移除View时过渡动画效果
*/
ObjectAnimator removeAnimator = ObjectAnimator.ofFloat(null, "rotationX", 0, -90, 0).
setDuration(mTransitioner.getDuration(LayoutTransition.DISAPPEARING));
mTransitioner.setAnimator(LayoutTransition.DISAPPEARING, removeAnimator);
/**
* view 动画改变时,布局中的每个子view动画的时间间隔
*/
mTransitioner.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
mTransitioner.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);
/**
*LayoutTransition.CHANGE_APPEARING和LayoutTransition.CHANGE_DISAPPEARING的过渡动画效果
* 必须使用PropertyValuesHolder所构造的动画才会有效果,不然无效!使用ObjectAnimator是行不通的,
* 发现这点时真特么恶心,但没想到更恶心的在后面,在测试效果时发现在构造动画时,”left”、”top”、”bottom”、”right”属性的
* 变动是必须设置的,至少设置两个,不然动画无效,问题是我们即使这些属性不想变动!!!也得设置!!!
* 我就问您恶不恶心!,因为这里不想变动,所以设置为(0,0)
*
*/
PropertyValuesHolder pvhLeft =
PropertyValuesHolder.ofInt("left", 0, 0);
PropertyValuesHolder pvhTop =
PropertyValuesHolder.ofInt("top", 0, 0);
PropertyValuesHolder pvhRight =
PropertyValuesHolder.ofInt("right", 0, 0);
PropertyValuesHolder pvhBottom =
PropertyValuesHolder.ofInt("bottom", 0, 0);
/**
* view被添加时,其他子View的过渡动画效果
*/
PropertyValuesHolder animator = PropertyValuesHolder.ofFloat("scaleX", 1, 1.5f, 1);
final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(
this, pvhLeft, pvhBottom, animator).
setDuration(mTransitioner.getDuration(LayoutTransition.CHANGE_APPEARING));
//设置过渡动画
mTransitioner.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);
/**
* view移除时,其他子View的过渡动画
*/
PropertyValuesHolder pvhRotation =
PropertyValuesHolder.ofFloat("scaleX", 1, 1.5f, 1);
final ObjectAnimator changeOut = ObjectAnimator.ofPropertyValuesHolder(
this, pvhLeft, pvhBottom, pvhRotation).
setDuration(mTransitioner.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
mTransitioner.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeOut);
}
public void addView(View view) {
i++;
Button button = new Button(this);
button.setText("布局动画_" + i);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
container.addView(button, Math.min(1, container.getChildCount()), params);
}
public void removeView(View view) {
if (i > 0)
container.removeViewAt(0);
}
}
简单分析一下,LayoutTransition.APPEARING和LayoutTransition.DISAPPEARING的情况下直接使用属性动画来设置过渡动画效果即可,而对于LayoutTransition.CHANGE_APPEARING和LayoutTransition.CHANGE_DISAPPEARING必须使用PropertyValuesHolder所构造的动画才会有效果,不然无效,真特么恶心, ,但没想到更恶心的在后面,在测试效果时发现在构造动画时,”left”、”top”、”bottom”、”right”属性的变动是必须设置的,至少设置两个,不然动画无效,最坑爹的是我们即使这些属性不想变动!!!也得设置!!!我就问您恶不恶心!,那么我们不想改变这四个属性时该如何设置呢?这时只要传递的可变参数都一样就行如下面的(0,0)也可以是(100,100)即可(坑爹啊!测试半天才发现,一直在考虑代码有没有问题,最后发现时特么的也是醉了…….):
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top",0,0);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("left",100,100);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("bottom",0,0);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("right",0,0);
还有点需要注意的是在使用的ofInt,ofFloat中的可变参数值时,第一个值和最后一个值必须相同,不然此属性将不会有动画效果,比如下面首位相同是有效的
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top",100,0,100);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top",0,100,0);
但是如果是下面的设置就是无效的:
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("left",0,100);
关于PropertyValuesHolder,后面我会分开单独一篇来分析,这里我们只需知道PropertyValuesHolder构造的动画可以设置给ObjectAnimator.ofPropertyValuesHolder()便可以产生响应的动画效果即可,该方法原型如下:
public static ObjectAnimator ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)
最后我们通过setAniamtor的方法设置LayoutTransition的5种状态下的过渡动画,最后运行一下程序,效果如下:
最后这里小结一下LayoutTransition的一些常用函数:
函数名称 | 说明 |
---|---|
setAnimator(int transitionType, Animator animator) | 设置不同状态下的动画过渡,transitionType取值为, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING |
setDuration(long duration) | 设置所有动画完成所需要的时长 |
setDuration(int transitionType, long duration) | 设置特定type类型动画时长,transitionType取值为, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING |
setStagger(int transitionType, long duration) | 设置特定type类型动画的每个子item动画的时间间隔 ,transitionType取值为: APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING |
setInterpolator(int transitionType, TimeInterpolator interpolator) | 设置特定type类型动画的插值器, transitionType取值为: APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING |
setStartDelay(int transitionType, long delay) | 设置特定type类型动画的动画延时 transitionType取值为, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING |
addTransitionListener(TransitionListener listener) | 设置监听器TransitionListener |
关于监听器接口TransitionListener原型如下:
/**
* This interface is used for listening to starting and ending events for transitions.
*/
public interface TransitionListener {
/**
* 监听LayoutTransition当前对应的transitionType类型动画开始
* @param transition LayoutTransition对象实例
* @param container LayoutTransition绑定的容器-container
* @param view 当前在做动画的View对象
* @param transitionType LayoutTransition类型,取值有:APPEARING、DISAPPEARING、
* CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING
*/
public void startTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType);
/**
* 监听LayoutTransition当前对应的transitionType类型动画结束
* @param transition LayoutTransition对象实例
* @param container LayoutTransition绑定的容器-container
* @param view 当前在做动画的View对象
* @param transitionType LayoutTransition类型,取值有:APPEARING、DISAPPEARING、
* CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING
*/
public void endTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType);
}
注释比较清晰,就不过多说明,我们如果想在某种transitionType类型动画开或者结束时设置某些操作,便可实现该接口,测试效果也比较简单,这里就不举例了。ok~,关于布局动画就先了解这么多吧。
关联文章:
走进绚烂多彩的属性动画-Property Animation(上篇)
走进绚烂多彩的属性动画-Property Animation之Interpolator和TypeEvaluator(下篇)
属性动画-Property Animation之ViewPropertyAnimator 你应该知道的一切
Android布局动画之animateLayoutChanges与LayoutTransition