Activity动画(二)
2014-11-12
Activity动画(二)
如果要统一所有的Activity的打开可关闭动画,可以有一个很简便的方法:就是设置Application的theme。在Application的theme里有一个Android:windowAnimationStyle的属性,可以定义了一个Activity的打开和关闭动画。例如下面的例子就自定义了一个theme。
````
<style name="AnimationActivity" mce_bogus="1" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/translate_between_interface_right_in</item>
<item name="android:activityOpenExitAnimation">@anim/translate_between_interface_left_out</item>
<item name="android:activityCloseEnterAnimation">@anim/translate_between_interface_left_in</item>
<item name="android:activityCloseExitAnimation">@anim/translate_between_interface_right_out</item>
<item name="android:taskOpenEnterAnimation">@anim/translate_between_interface_right_in</item>
<item name="android:taskOpenExitAnimation">@anim/translate_between_interface_left_out</item>
<item name="android:taskCloseEnterAnimation">@anim/translate_between_interface_left_in</item>
<item name="android:taskCloseExitAnimation">@anim/translate_between_interface_right_out</item>
</style>
````
将这个theme运用到Application的theme里,所以的Activity就会按照你定义的动画来交互了。
不过,现实的需求往往是更复杂的动画或者交互,不可能所有的界面的交互动画都一致的。有些页面需要特别定制,而且可能很复杂。例如我们要实现类似于iOS的NavigationViewController的页面右滑关闭页面的交互动画,或者要实现点击一张图片缩略图,然后它渐渐放大,点击关闭它又慢慢缩回原处的动画,按照之前介绍的方法,都无法实现。
在iOS自带相册APP里,点击一个缩略图,缩略图渐渐放大进入大图查看。这个交互基本成了一个相册必备的标准交互。Android上的相册很多也用了这种动画交互。如果缩略图的查看和大图查看都是在同一个Activity中完成的话,还是可以比较容易实现的。但是如果缩略的查看和大图的查看分处在不同的Activity中的话,那这就不是很好实现了。这里我准备讲讲可以怎么实现这个功能。而且这个方法可以推广出去,很多类型的Activity切换动画都可以按照这种方式实现。
现在假设查看缩略图的Activity是A,查看大图的Activity是B。
- 一 设置B的的theme为透明的transparent。
- 二 在A中,启动B前,把一些必要的信息打包成Bundle传给B,启动B后把默认的启动动画覆盖,overridePendingTransition(0, 0)。例如把被点击的ImageView的在屏幕中的坐标,还有该ImageView的width和height,还有该ImageView所显示的图片资源id,或者URL,都存入Bundle中,讲该Bundle放入Intent中,启动B时传给B。
- 三 在B的onCreate方法中,把Bundle中的信息取出来,并且对你想要做动画的ImageView中,获取ViewTreeObserver实例,加入一个OnPreDrawListener,在OnPreDrawListener的onPreDraw方法中,对ImageView做放大动画,初始位置就是从Bundle中获取,最终位置根据要显示的图片的大小来定。
- 四 关闭B Activity时,对ImageView做一个缩小的动画,初始位置就是它现在的位置,最终位置是最开始从Bundle中获取的那个位置。覆盖Activity的finish方法,同样要将Activity的默认动画覆盖掉overridePendingTransition(0, 0)。
我们看一下具体怎么实现,有什么要注意的。
一· 设置B的的theme为透明的transparent
<style name="Transparent">
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<activity android:name="com.example.android.activityanim.PictureDetailsActivity"
android:label="@string/subactivity_name"
android:theme="@style/Transparent" >
</activity>
这里主要是将B Activity的window和它的背景设置成透明的。所有的Activity都有一个默认的样式,默认样式不是透明的。设置成透明的话,在启动B Activity时它的背景是透明的,并且调用overridePendingTransition(0, 0)覆盖掉默认动画,你不会觉察到已经从A进入到B界面。
- 二. 在A中,启动B前,把一些必要的信息打包成Bundle传给B,启动B后把默认的启动动画覆盖,overridePendingTransition(0, 0)。
// Interesting data to pass across are the thumbnail size/location, the
// resourceId of the source bitmap, the picture description, and the
// orientation (to avoid returning back to an obsolete configuration if
// the device rotates again in the meantime)
int[] screenLocation = new int[2];
v.getLocationOnScreen(screenLocation);
PictureData info = mPicturesData.get(v);
Intent subActivity = new Intent(ActivityAnimations.this,
PictureDetailsActivity.class);
int orientation = getResources().getConfiguration().orientation;
subActivity.
putExtra(PACKAGE + ".orientation", orientation).
putExtra(PACKAGE + ".resourceId", info.resourceId).
putExtra(PACKAGE + ".left", screenLocation[0]).
putExtra(PACKAGE + ".top", screenLocation[1]).
putExtra(PACKAGE + ".width", v.getWidth()).
putExtra(PACKAGE + ".height", v.getHeight()).
startActivity(subActivity);
// Override transitions: we don't want the normal window animation in addition
// to our custom one
overridePendingTransition(0, 0);
这里有个关键点,获取被点击的View的在屏幕上的位置是通过getLocationOnScreen()方法。这个位置在B中很重要。另外还有宽高也是必须信息,被点击的图片resId或者URL等其他信息。
二. 在B的onCreate方法中,把Bundle中的信息取出来,并且对你想要做动画的ImageView中,获取ViewTreeObserver实例,加入一个OnPreDrawListener,在OnPreDrawListener的onPreDraw方法中,对ImageView做放大动画,初始位置就是从Bundle中获取,最终位置根据要显示的图片的大小来定。
因为第一步,在用户觉察不到的情况下已进入Activity B,接下来只要在B的视图出现的时候,以一个缩略图放大的动画效果出现就行了。用户看到的效果是点击了A的一个缩略图,缩略图逐渐放大的过程,用户很容易认为这是在A中完成的,实际上这所有的动画都是在B中完成的。看看B中相关的代码:
// Retrieve the data we need for the picture/description to display and
// the thumbnail to animate it from
Bundle bundle = getIntent().getExtras();
Bitmap bitmap = BitmapUtils.getBitmap(getResources(),
bundle.getInt(PACKAGE_NAME + ".resourceId"));
String description = bundle.getString(PACKAGE_NAME + ".description");
final int thumbnailTop = bundle.getInt(PACKAGE_NAME + ".top");
final int thumbnailLeft = bundle.getInt(PACKAGE_NAME + ".left");
final int thumbnailWidth = bundle.getInt(PACKAGE_NAME + ".width");
final int thumbnailHeight = bundle.getInt(PACKAGE_NAME + ".height");
mOriginalOrientation = bundle.getInt(PACKAGE_NAME + ".orientation");
mBitmapDrawable = new BitmapDrawable(getResources(), bitmap);
mImageView.setImageDrawable(mBitmapDrawable);
ViewTreeObserver observer = mImageView.getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mImageView.getViewTreeObserver().removeOnPreDrawListener(this);
// Figure out where the thumbnail and full size versions are, relative
// to the screen and each other
int[] screenLocation = new int[2];
mImageView.getLocationOnScreen(screenLocation);
mLeftDelta = thumbnailLeft - screenLocation[0];
mTopDelta = thumbnailTop - screenLocation[1];
// Scale factors to make the large version the same size as the thumbnail
mWidthScale = (float) thumbnailWidth / mImageView.getWidth();
mHeightScale = (float) thumbnailHeight / mImageView.getHeight();
runEnterAnimation();
return true;
}
});
ViewTreeObserver是View树监听类,可以监听View的一些变化。其中ViewTreeObserver.OnPreDrawListener内部类,是当视图要被绘制时会被回调的类。我们只要在创建一个ViewTreeObserver.OnPreDrawListener实例,传给ViewTreeObserver,就可以在View被绘制时回调onPreDraw方法,我们所要做的就是在这个方法中开始动画。这里注意在动画开始前已经把要显示的图片设置上去了。 runEnterAnimation方法:
// Set starting values for properties we're going to animate. These
// values scale and position the full size version down to the thumbnail
// size/location, from which we'll animate it back up
mImageView.setPivotX(0);
mImageView.setPivotY(0);
mImageView.setScaleX(mWidthScale);
mImageView.setScaleY(mHeightScale);
mImageView.setTranslationX(mLeftDelta);
mImageView.setTranslationY(mTopDelta);
// Animate scale and translation to go from thumbnail to full size
mImageView.animate().setDuration(duration).
scaleX(1).scaleY(1).
translationX(0).translationY(0).
setInterpolator(sDecelerator));
这段代码先将ImageView从大图缩小到我们点击的那个缩略图的大小和位置,然后开始放大动画到它大图显示的样子。这样就实现了我们想要的点击缩略图放大到大图的效果。
- 关闭B Activity时,对ImageView做一个缩小的动画,初始位置就是它现在的位置,最终位置是最开始从Bundle中获取的那个位置。覆盖Activity的finish方法,同样要将Activity的默认动画覆盖掉overridePendingTransition(0, 0)
@Override
public void finish() {
super.finish();
// override transitions to skip the standard window animations
overridePendingTransition(0, 0);
}
/**
* Overriding this method allows us to run our exit animation first, then exiting
* the activity when it is complete.
*/
@Override
public void onBackPressed() {
runExitAnimation(new Runnable() {
public void run() {
// *Now* go ahead and exit the activity
finish();
}
});
}
private void runExitAnimation(Runnable endAction) {
// Animate image back to thumbnail size/location
mImageView.animate().setDuration(duration).
scaleX(mWidthScale).scaleY(mHeightScale).
translationX(mLeftDelta).translationY(mTopDelta).
withEndAction(endAction);
}
当用户关闭界面的时候,先执行ImageView的缩小动画,缩小到原来缩略图的位置。当动画结束后,finish掉Activity并覆盖掉默认的Activity动画。
总结:第一步先要把Activity的theme变成透明。第覆盖掉Activity的启动或者关闭的默认动画效果。第三步如果要做进入动画,则可以实现ViewTreeObserver.OnPreDrawListener接口,在onPreDraw方法中做动画。如果是做退出动画,可以先对视图做动画,在动画结束的时候调用finish方法。
demo代码.该demo是dev-bytes上的demo代码。
扩展
如果B要支持左右滑动查看大图,然后在查看某已图片时点击返回键,图片要缩小至该图缩略图的位置上,这该怎么做? 这种需求改变对A变化不大,但是对B的改变还是很大的,首先B要支持滑屏,一般来说要用到ViewPage或类似的控件。另外还有滑动时B如何获取数据,返回时如何获取当前大图对应的缩略图在A中的位置。我想实现方法应该有多种,我能想到的一种比较丑陋但是又比较简单的做法是,定义一个接口,声明了一些获取数据和缩略图位置等方法。然后A实现这个接口,传给B作为一个静态变量保存起来,这样A与B就可以通信了。这样只要B左右滑动的时候,A也保持一致的上下滑动,在B要关闭的时候,先获取到相应缩略图的问题,就能做动画了。
Category: 生活 Tagged: Android 技术 动画