https://developer.android.google.cn/training/animation/crossfade.html
两个视图淡出淡入
淡化动画(也被称为溶解)逐渐淡出一个 UI 组件的同时淡入另一个。这个效果在你想要交换内容或视图的时候很有用。交替淡化很微妙也很短暂但是提供了一种从一个屏幕到另一个屏幕的液体过渡。当你不想用他们的时候,过渡就总显得突兀和急促。
新建视图
新建两个你想要交替淡化的视图。下面的实例新建了一个进度条和滚动文本视图:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView style="?android:textAppearanceMedium" android:lineSpacingMultiplier="1.2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/lorem_ipsum" android:padding="16dp" /> </ScrollView> <ProgressBar android:id="@+id/loading_spinner" style="?android:progressBarStyleLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> </FrameLayout>
设置动画
要设置动画:
- 为你想要交替淡化的视图新建成员变量。之后在动画效果中修改视图的时候你会需要这些引用。
- 对淡入的视图,设置可见度为 GONE. 这是为了防止视图占用布局空间然后从视图计算中被遗漏,加速过程。
- 在成员变量里缓存 config_shortAnimTime 系统属性。这个属性为动画定义了一个标准的“短”持续时间。这个持续时间对微妙的动画或者发生的非常快速的动画非常理想。 config_longAnimTime 和 config_mediumAnimTime 也都可用,如果你想用的话。
这是个使用上一个代码片段作为活动内容视图布局的示例:
public class CrossfadeActivity extends Activity { private View mContentView; private View mLoadingView; private int mShortAnimationDuration; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_crossfade); mContentView = findViewById(R.id.content); mLoadingView = findViewById(R.id.loading_spinner); // Initially hide the content view. mContentView.setVisibility(View.GONE); // Retrieve and cache the system\'s default "short" animation time. mShortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime); }
交替淡化视图
现在视图已经准备好了,按下面的步骤来让它们交替淡化:
对淡入的视图,设置 alpha 值为0,可见度为 VISIBLE (记得它初始化设置是 GONE).这让视图可见不过完全透明。
对淡入的视图,动画的 alpha 值从0到1。同时,对于淡出的视图来说,动画的 alpha 值为从1到0。
使用 Animator.AnimatorListener 中的 onAnimationEnd(),设置淡出的视图的可见度为 GONE.即使 alpha 值为0,把视图的可见度设置为 GONE 防止视图占用布局空间然后从视图计算中被遗漏,加速过程。
下面的方法展示了一个这样做的示例:
private View mContentView; private View mLoadingView; private int mShortAnimationDuration; ... private void crossfade() { // Set the content view to 0% opacity but visible, so that it is visible // (but fully transparent) during the animation. mContentView.setAlpha(0f); mContentView.setVisibility(View.VISIBLE); // Animate the content view to 100% opacity, and clear any animation // listener set on the view. mContentView.animate() .alpha(1f) .setDuration(mShortAnimationDuration) .setListener(null); // Animate the loading view to 0% opacity. After the animation ends, // set its visibility to GONE as an optimization step (it won\'t // participate in layout passes, etc.) mLoadingView.animate() .alpha(0f) .setDuration(mShortAnimationDuration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLoadingView.setVisibility(View.GONE); } }); }
https://developer.android.google.cn/training/animation/screen-slide.html
使用 ViewPager 滚屏
滚屏是一整个屏幕到另一个之间的过渡,是一种想设置向导或幻灯片展示一样常见的 UI 组件。这节课教你如何使用 support library.ViewPager 提供的 ViewPager 制作滚屏来自动让你的屏幕滚动动画。
新建视图
新建一个你稍后会用到的碎片内容的布局文件。下面的代码包含了一个展示文本的文本视图:
<!-- fragment_screen_slide_page.xml --> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView style="?android:textAppearanceMedium" android:padding="16dp" android:lineSpacingMultiplier="1.2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/lorem_ipsum" /> </ScrollView>
同时也为碎片内容定义了一个字符串。
新建碎片
新建一个 Fragment 类,它返回你刚在 onCreateView() 方法里新建的布局。然后当你需要一个展示给用户的新页面时,你可以在父活动里新建这个碎片的实例:
import android.support.v4.app.Fragment; ... public class ScreenSlidePageFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ViewGroup rootView = (ViewGroup) inflater.inflate( R.layout.fragment_screen_slide_page, container, false); return rootView; } }
添加 ViewPager
ViewPager 有内置的滑动收拾赖在页面之间过渡,他们默认会显示滚动屏幕动画,所以不需要再新建了。ViewPager 使用 PageAdapter 为新页面提供显示支持,所以 PagerAdapter 会用到你之前新建的碎片类。
一开始,先新建一个含有 ViewPager 的布局:
<!-- activity_screen_slide.xml --> <android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" />
按下面的步骤新建活动:
- 设置内容视图为带有 ViewPager 的布局
- 新建继承了 FragmentStatePagerAdapter 抽象类的类并引用 getItem() 方法来支持 ScreenSlidePageFragment 实例作为新页面。页面适配器也需要你引用 getCount() 方法,它会返回适配器会新建的页面数量(示例中为5个)。
- 给ViewPager 安装 PagerAdapter
- 把返回按钮向后移动到碎片的虚拟栈来处理设备的返回按钮。如果用户已经在第一页,就返回活动的后栈。
import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; ... public class ScreenSlidePagerActivity extends FragmentActivity { /** * The number of pages (wizard steps) to show in this demo. */ private static final int NUM_PAGES = 5; /** * The pager widget, which handles animation and allows swiping horizontally to access previous * and next wizard steps. */ private ViewPager mPager; /** * The pager adapter, which provides the pages to the view pager widget. */ private PagerAdapter mPagerAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_screen_slide); // Instantiate a ViewPager and a PagerAdapter. mPager = (ViewPager) findViewById(R.id.pager); mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); mPager.setAdapter(mPagerAdapter); } @Override public void onBackPressed() { if (mPager.getCurrentItem() == 0) { // If the user is currently looking at the first step, allow the system to handle the // Back button. This calls finish() on this activity and pops the back stack. super.onBackPressed(); } else { // Otherwise, select the previous step. mPager.setCurrentItem(mPager.getCurrentItem() - 1); } } /** * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in * sequence. */ private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { public ScreenSlidePagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return new ScreenSlidePageFragment(); } @Override public int getCount() { return NUM_PAGES; } } }
使用 PageTransformer 定制动画
要显示一个合默认滚屏动画不同的动画,引用 ViewPager.PageTransformer 接口然后把它提供给页面视图。这个接口只有一个方法, transformPage(). 在屏幕的过渡的每个点,就会为每个可视的页面调用一次这个方法(一般来说就一个也米看),对相邻的页面来说只是离开屏幕。比如,如果第三个页面是可见的,用户把它拽向页面四,页面二、三、四就会在这个手势的每一步调用 transformPage() 方法。
在 transformPage() 的引用里,你可以根据屏幕上页面的位置判断哪个页面需要转变来新建自定义滚动动画,也就是获取 transformPage() 方法的 position 参数。
position 参数通过给出的页面和屏幕中心的关系指明了它的位置。它是一个随着用户滚动屏幕而改变的动态参数。当一个页面充满屏幕,它的位置值就是0。一个页面刚被从右边拽出屏幕的时候,它的位置值是1。如果用户滚动到两个页面之间,页面一的位置值是-0.5,页面二的位置值是0.5。根绝屏幕上页面的位置,你可以通过使用 setAlpha(), setTranslationX(), 或者 setScaleY() 这样的方法设置页面属性来新建自定义滚屏动画。
当你有引用 PageTransformer 的时候,和你的引用一起调用 setPageTransformer() 来应用你的自定义动画。比如,拂过你又一个名为 ZoomOutPageTransformer 的 PageTransformer,你可以像这样设置你的自定义动画:
ViewPager mPager = (ViewPager) findViewById(R.id.pager); ... mPager.setPageTransformer(true, new ZoomOutPageTransformer());
缩放页面转换
这种页面转换在滚动相邻的页面时会收缩然后淡化页面。当一个页面靠近中心的时候,它会变回它的正常尺寸然后淡入。
public class ZoomOutPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.85f; private static final float MIN_ALPHA = 0.5f; public void transformPage(View view, float position) { int pageWidth = view.getWidth(); int pageHeight = view.getHeight(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 1) { // [-1,1] // Modify the default slide transition to shrink the page as well float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); float vertMargin = pageHeight * (1 - scaleFactor) / 2; float horzMargin = pageWidth * (1 - scaleFactor) / 2; if (position < 0) { view.setTranslationX(horzMargin - vertMargin / 2); } else { view.setTranslationX(-horzMargin + vertMargin / 2); } // Scale the page down (between MIN_SCALE and 1) view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); // Fade the page relative to its size. view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } }
深度页面变换
这种页面转换方式对向左滑动的页面使用默认滚动动画,对向右滑动的页面使用“深度”动画。这种深度动画让页面淡出,然后把它线性的缩小。
注意:在深度动画期间,默认的动画(滚屏)依然占着位,你必须使用负 X 转变抵消滚屏效果。例如:
view.setTranslationX(-1 * view.getWidth() * position);
下面的示例展示了如何在一个工作页面转换中抵消默认的滚屏动画:
public class DepthPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.75f; public void transformPage(View view, float position) { int pageWidth = view.getWidth(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 0) { // [-1,0] // Use the default slide transition when moving to the left page view.setAlpha(1); view.setTranslationX(0); view.setScaleX(1); view.setScaleY(1); } else if (position <= 1) { // (0,1] // Fade the page out. view.setAlpha(1 - position); // Counteract the default slide transition view.setTranslationX(pageWidth * -position); // Scale the page down (between MIN_SCALE and 1) float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)); view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } }
https://developer.android.google.cn/training/animation/cardflip.html
显示翻牌动画
这节课向你展示了如何使用自定义碎片动画做一个翻牌动画。内容视图之间通过显示一个模拟牌被翻转的动画来显示翻牌动画。
新建动画
为翻牌新建动画。你需要两个动画器,一个为前面的卡牌动画从左边消失,一个为从左边出现。你也需要两个动画器为后面的卡牌动画从右边消失和出现。
card_flip_left_in.xml
<set xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Before rotating, immediately set the alpha to 0. --> <objectAnimator android:valueFrom="1.0" android:valueTo="0.0" android:propertyName="alpha" android:duration="0" /> <!-- Rotate. --> <objectAnimator android:valueFrom="-180" android:valueTo="0" android:propertyName="rotationY" android:interpolator="@android:interpolator/accelerate_decelerate" android:duration="@integer/card_flip_time_full" /> <!-- Half-way through the rotation (see startOffset), set the alpha to 1. --> <objectAnimator android:valueFrom="0.0" android:valueTo="1.0" android:propertyName="alpha" android:startOffset="@integer/card_flip_time_half" android:duration="1" /> </set>
card_flip_left_out.xml
<set xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Rotate. --> <objectAnimator android:valueFrom="0" android:valueTo="180" android:propertyName="rotationY" android:interpolator="@android:interpolator/accelerate_decelerate" android:duration="@integer/card_flip_time_full" /> <!-- Half-way through the rotation (see startOffset), set the alpha to 0. --> <objectAnimator android:valueFrom="1.0" android:valueTo="0.0" android:propertyName="alpha" android:startOffset="@integer/card_flip_time_half" android:duration="1" /> </set>
card_flip_right_in.xml
<set xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Before rotating, immediately set the alpha to 0. --> <objectAnimator android:valueFrom="1.0" android:valueTo="0.0" android:propertyName="alpha" android:duration="0" /> <!-- Rotate. --> <objectAnimator android:valueFrom="180" android:valueTo="0" android:propertyName="rotationY" android:interpolator="@android:interpolator/accelerate_decelerate" android:duration="@integer/card_flip_time_full" /> <!-- Half-way through the rotation (see startOffset), set the alpha to 1. --> <objectAnimator android:valueFrom="0.0" android:valueTo="1.0" android:propertyName="alpha" android:startOffset="@integer/card_flip_time_half" android:duration="1" /> </set>
card_flip_right_out.xml
<set xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Rotate. --> <objectAnimator android:valueFrom="0" android:valueTo="-180" android:propertyName="rotationY" android:interpolator="@android:interpolator/accelerate_decelerate" android:duration="@integer/card_flip_time_full" /> <!-- Half-way through the rotation (see startOffset), set the alpha to 0. --> <objectAnimator android:valueFrom="1.0" android:valueTo="0.0" android:propertyName="alpha" android:startOffset="@integer/card_flip_time_half" android:duration="1" /> </set>
新建视图
“卡牌”的每一面都是能包含任何你想要的内容的独立布局,比如两屏幕的文本,两个图像,或任何能反转的视图的结合。你会用到稍后会动画化的碎片里的两个布局。下面的布局新建了一个现实文本的卡牌的一面:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#a6c" android:padding="16dp" android:gravity="bottom"> <TextView android:id="@android:id/text1" style="?android:textAppearanceLarge" android:textStyle="bold" android:textColor="#fff" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/card_back_title" /> <TextView style="?android:textAppearanceSmall" android:textAllCaps="true" android:textColor="#80ffffff" android:textStyle="bold" android:lineSpacingMultiplier="1.2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/card_back_description" /> </LinearLayout>
另一面显示一个 ImageView
<ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image1" android:scaleType="centerCrop" android:contentDescription="@string/description_image_1" />
新建碎片
为你的卡牌的正面和背面新建碎片类。这些类会为每个碎片返回你之前在 onCreateView() 方法里的布局。你可以在你想展示卡牌的父活动里新建这个碎片的实例。下面的示例展示了使用它们的父活动里的嵌套碎片类。
public class CardFlipActivity extends Activity { ... /** * A fragment representing the front of the card. */ public class CardFrontFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_card_front, container, false); } } /** * A fragment representing the back of the card. */ public class CardBackFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_card_back, container, false); } } }
让卡牌反转动画
现在,你需要展示父活动里的碎片了。要这样做,先创建活动的布局。下面的示例新建了一个你能在运行时添加碎片的 FrameLayout :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" />
在活动代码里,设置内容视图为你刚新建的布局。在活动新建的时候显示一个默认碎片也是个好主意,所以下面的示例活动展示了如何默认显示卡牌的前面:
public class CardFlipActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_activity_card_flip); if (savedInstanceState == null) { getFragmentManager() .beginTransaction() .add(R.id.container, new CardFrontFragment()) .commit(); } } ... }
现在你展示了卡牌的前面,你可以在合适的时候使用反转动画显示卡牌的背面了。按照下面的步骤新建一个显示卡牌另一面的方法:
设置你之前新建的自定义动画为碎片过渡。
使用一个新碎片替换当前显示的碎片然后使用你新建的自定义动画把这个事件动画化。
把之前显示的碎片添加到碎片的返回栈,这样当用户按下返回按钮的时候,卡牌就会翻转回来。
private void flipCard() { if (mShowingBack) { getFragmentManager().popBackStack(); return; } // Flip to the back. mShowingBack = true; // Create and commit a new fragment transaction that adds the fragment for // the back of the card, uses custom animations, and is part of the fragment // manager\'s back stack. getFragmentManager() .beginTransaction() // Replace the default fragment animations with animator resources // representing rotations when switching to the back of the card, as // well as animator resources representing rotations when flipping // back to the front (e.g. when the system Back button is pressed). .setCustomAnimations( R.animator.card_flip_right_in, R.animator.card_flip_right_out, R.animator.card_flip_left_in, R.animator.card_flip_left_out) // Replace any fragments currently in the container view with a // fragment representing the next page (indicated by the // just-incremented currentPage variable). .replace(R.id.container, new CardBackFragment()) // Add this transaction to the back stack, allowing users to press // Back to get to the front of the card. .addToBackStack(null) // Commit the transaction. .commit(); }
https://developer.android.google.cn/training/animation/zoom.html
缩放视图
这节课演示了如何做一个触碰缩放动画对一些像相册之类的应用让一个缩略图动画转化为全尺寸的图片是很有用的。
新建视图
新建一个含有你想要缩放内容的小和大两种版本的布局文件。下面的例子为可点击的图片缩略图新建了一个 ImageButton 和一个展示图片放大视图的 ImageView.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <ImageButton android:id="@+id/thumb_button_1" android:layout_width="100dp" android:layout_height="75dp" android:layout_marginRight="1dp" android:src="@drawable/thumb1" android:scaleType="centerCrop" android:contentDescription="@string/description_image_1" /> </LinearLayout> <!-- This initially-hidden ImageView will hold the expanded/zoomed version of the images above. Without transformations applied, it takes up the entire screen. To achieve the "zoom" animation, this view\'s bounds are animated from the bounds of the thumbnail button above, to its final laid-out bounds. --> <ImageView android:id="@+id/expanded_image" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" android:contentDescription="@string/description_zoom_touch_close" /> </FrameLayout>
设置缩放动画
应用好你的布局,设置能出发缩放动画的事件处理器。下面的示例给 ImageButton 添加了一个 View.OnClickListener,当用户点击图片按钮的时候执行缩放动画。
public class ZoomActivity extends FragmentActivity { // Hold a reference to the current animator, // so that it can be canceled mid-way. private Animator mCurrentAnimator; // The system "short" animation time duration, in milliseconds. This // duration is ideal for subtle animations or animations that occur // very frequently. private int mShortAnimationDuration; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_zoom); // Hook up clicks on the thumbnail views. final View thumb1View = findViewById(R.id.thumb_button_1); thumb1View.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { zoomImageFromThumb(thumb1View, R.drawable.image1); } }); // Retrieve and cache the system\'s default "short" animation time. mShortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime); } ... }
缩放视图
你需要在合适的时候把正常尺寸的视图动画化为所放过的视图。一般来说,你需要从正常尺寸视图的边界动画化到大尺寸视图的边界。下面的方法向你展示了如何按照下面的步骤引用一个把缩略图缩放到放大尺寸视图的缩放动画:
- 把高分辨率图像分配到隐藏的“放大” ImageView 里。下面的示例在 UI 线程里简单加载了一个大图资源。你会想要在别的线程里加载以免造成 UI 线程阻塞然后在 UI 线程里设置位图,位图分辨率不应该大过屏幕尺寸。
- 计算 ImageView 开始和结束的边界。
- 为四个方向的每一个都动画化并同时对 X,Y(SCALE_X, SCALE_Y) 的属性选型,从开始边界到结束边界。这四个动画都会被添加到一个 AnimatorSet 里,隐刺他们能在同时启动。
- 当图像已经放大时用户触碰屏幕,执行一个类似的但是相反的动画缩放回去。你可以通过添加给 ImageView 一个 View.OnClickListener 来这么做。点击的时候,ImageView 最小化回缩略图的尺寸然后设置可见性为 GONE 来隐藏。
private void zoomImageFromThumb(final View thumbView, int imageResId) { // If there\'s an animation in progress, cancel it // immediately and proceed with this one. if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); } // Load the high-resolution "zoomed-in" image. final ImageView expandedImageView = (ImageView) findViewById( R.id.expanded_image); expandedImageView.setImageResource(imageResId); // Calculate the starting and ending bounds for the zoomed-in image. // This step involves lots of math. Yay, math. final Rect startBounds = new Rect(); final Rect finalBounds = new Rect(); final Point globalOffset = new Point(); // The start bounds are the global visible rectangle of the thumbnail, // and the final bounds are the global visible rectangle of the container // view. Also set the container view\'s offset as the origin for the // bounds, since that\'s the origin for the positioning animation // properties (X, Y). thumbView.getGlobalVisibleRect(startBounds); findViewById(R.id.container) .getGlobalVisibleRect(finalBounds, globalOffset); startBounds.offset(-globalOffset.x, -globalOffset.y); finalBounds.offset(-globalOffset.x, -globalOffset.y); // Adjust the start bounds to be the same aspect ratio as the final // bounds using the "center crop" technique. This prevents undesirable // stretching during the animation. Also calculate the start scaling // factor (the end scaling factor is always 1.0). float startScale; if ((float) finalBounds.width() / finalBounds.height() > (float) startBounds.width() / startBounds.height()) { // Extend start bounds horizontally startScale = (float) startBounds.height() / finalBounds.height(); float startWidth = startScale * finalBounds.width(); float deltaWidth = (startWidth - startBounds.width()) / 2; startBounds.left -= deltaWidth; startBounds.right += deltaWidth; } else { // Extend start bounds vertically startScale = (float) startBounds.width() / finalBounds.width(); float startHeight = startScale * finalBounds.height(); float deltaHeight = (startHeight - startBounds.height()) / 2; startBounds.top -= deltaHeight; startBounds.bottom += deltaHeight; } // Hide the thumbnail and show the zoomed-in view. When the animation // begins, it will position the zoomed-in view in the place of the // thumbnail. thumbView.setAlpha(0f); expandedImageView.setVisibility(View.VISIBLE); // Set the pivot point for SCALE_X and SCALE_Y transformations // to the top-left corner of the zoomed-in view (the default // is the center of the view). expandedImageView.setPivotX(0f); expandedImageView.setPivotY(0f); // Construct and run the parallel animation of the four translation and // scale properties (X, Y, SCALE_X, and SCALE_Y). AnimatorSet set = new AnimatorSet(); set .play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left, finalBounds.left)) .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top, finalBounds.top)) .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f)); set.setDuration(mShortAnimationDuration); set.setInterpolator(new DecelerateInterpolator()); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mCurrentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { mCurrentAnimator = null; } }); set.start(); mCurrentAnimator = set; // Upon clicking the zoomed-in image, it should zoom back down // to the original bounds and show the thumbnail instead of // the expanded image. final float startScaleFinal = startScale; expandedImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); } // Animate the four positioning/sizing properties in parallel, // back to their original values. AnimatorSet set = new AnimatorSet(); set.play(ObjectAnimator .ofFloat(expandedImageView, View.X, startBounds.left)) .with(ObjectAnimator .ofFloat(expandedImageView, View.Y,startBounds.top)) .with(ObjectAnimator .ofFloat(expandedImageView, View.SCALE_X, startScaleFinal)) .with(ObjectAnimator .ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal)); set.setDuration(mShortAnimationDuration); set.setInterpolator(new DecelerateInterpolator()); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { thumbView.setAlpha(1f); expandedImageView.setVisibility(View.GONE); mCurrentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { thumbView.setAlpha(1f); expandedImageView.setVisibility(View.GONE); mCurrentAnimator = null; } }); set.start(); mCurrentAnimator = set; } }); }
https://developer.android.google.cn/training/animation/layout.html
更改动画布局
布局动画是一种每次你对布局配置进行更改时系统都会运行的预加载的动画。你需要做的就是在布局中设置一个参数通知 Android 系统动画这些布局更改,系统默认动画会为你执行。
提示:如果你想提供自定义布局动画,新建 LayoutTransition 对象然后使用 setLayoutTransition() 方法把它提供给布局。
新建布局
在活动的布局 XML 文件里,设置你想启动动画的布局的 android:animateLayoutChanges 属性为 true。比如:
<LinearLayout android:id="@+id/container" android:animateLayoutChanges="true" ... />
添加,修改或移除布局的项目
现在,你需要做的就是添加,移除或修改布局里的项目,然后项目就会自动动画化:
private ViewGroup mContainerView; ... private void addItem() { View newView; ... mContainerView.addView(newView, 0); }