Google官方动画教程2
说明
承接之前教程1,继续讲解Google官方动画教程,接下来的教程将会更复杂一下,也会更有意思一些。
Youtube上视频地址
Github上代码地址
11 Bitmap Allocation
这节课程讲述了如何快速加载图片,并且通过重复使用已经存在的Bitmap来减少GC。
在这需要声明的是:这种方法只适合于加载相同尺寸的图片。
其实原理颇为简单,每张图片都会在内存中申请内存空间,当一张图片即将被一张新图片替代时,这时候新图片可以使用即将被替代的图片的内存空间,从而提高效率。
这种方式使用起来十分简单,在 BitmapFactory.decodeResource(Resources res, int id, Options opts)方法中需要传入一个Options,只需要对Options进行如下设置即可,其中mLastBitmap是指即将被替代的图片。
bitmapOptions.inBitmap = mLastBitmap;
当没有使用这种方法的时候,改变图片,我们可以看见在不断GC,而使用该方法以后,我们发现很少发生GC。
使用这个方法有3点需要注意的地方:
- 必须是相同尺寸额图片;
- 对于某些版本必须设置mBitmapOptions.inSampleSize = 1;
- 对于某些版本必须设置mBitmapOptions.inMutable = true。
12 Layout Transitions
在Android中我们可以往一个容器里面添加和移除View,一般情况下是没有动画效果的,如下面所示:
LayoutTransitions_1
这节课讲解了如何在容器里面添加、移除、改变子View大小的情况下设置动画,其实这种动画的实现比较简单,只需要几行代码就实现了。
首先我们需要在容器的xml中添加相应属性,有了这个属性,当对容器添加或者移除子View的时候就会有比较好的动画效果。
android:animateLayoutChanges="true"
如果需要在子View改变大小的情况下动画,则需要添加下面的代码:
LayoutTransition transition = container.getLayoutTransition();
// New capability as of Jellybean; monitor the container for *all* layout changes
// (not just add/remove/visibility changes) and animate these changes as well.
transition.enableTransitionType(LayoutTransition.CHANGING);
需要注意的是:上面的代码在Jellybean版本开始有效
设置之后的效果如下所示:
13 Picture Viewer
在第8课CrossFading Animations中讲述了可以使用TransitionDrawable来实现在两个drawable之间渐变转换,这节课讲述了另外一个实现方式。
实现原理很简单,布局文件中添加两个ImageView,一个作为显示层,一个作为底层,之后显示层透明度逐渐变为0,底层透明度逐渐变为1,最后将显示层设置为正在显示的图片,底层显示即将显示的图片,核心代码如下所示:
prevImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Use ViewPropertyAnimator to fade the previous imageView out and the next one in
prevImageView.animate().alpha(0).withLayer();
nextImageView.animate().alpha(1).withLayer().withEndAction(
new Runnable() {
// When the animation ends, set up references to change the prev/next
// associations
@Override
public void run() {
mCurrentDrawable =
(mCurrentDrawable + 1) % drawables.length;
int nextDrawableIndex =
(mCurrentDrawable + 1) % drawables.length;
prevImageView.setImageDrawable(drawables[mCurrentDrawable]);
nextImageView.setImageDrawable(drawables[nextDrawableIndex]);
nextImageView.setAlpha(0f);
prevImageView.setAlpha(1f);
}
});
}
});
效果如下所示:
PictureViewer
14 Window Zoom Transitions
这节课重复讲述了第9课的内容,可以通过图片放大的形式设置activity的启动动画。
Intent i = new Intent(HomeActivity.this, DetailActivity.class);
i.putExtra(DetailActivity.EXTRA_COLOUR, colour);
Bundle b = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
//b = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(),
//view.getHeight()).toBundle();
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
bitmap.eraseColor(colour);
b = ActivityOptions.makeThumbnailScaleUpAnimation(view, bitmap, 0, 0).toBundle();
}
startActivity(i, b);
动画效果如下所示:
WindowZoomTransitions
15 Custom Activity Animations
在之前的课程中我们通过ActivityOptions.makeThumbnailScaleUpAnimation(...)方法可以以缩略图的方式打开一个Activity,可是这样的实现存在一个问题,那就是:当新打开的activity关闭的时候不会有缩回的动画。
要实现activity关闭的时候有缩回的动画其实也比较简单,只需要按照下面4个步骤就可以了。
- 取消新打开activity启动和关闭动画;
- 打开新activity的时候传递图片的资源、大小和位置信息传递给新的activity;
- 新activity打开时,根据传递过来的信息创建启动动画;
- 新activity关闭时,根据传递过来的信息创建关闭动画。
实现效果如下所示:
CustomActivityAnimations
16 Animating ListView Deletion
这节讲述了如何制作滑动删除条目的ListView,其实实现起来也不是很复杂,大致分为如下几步:
- 设置ListView子View的OnTouchListener;
- 在OnTouchListener里面获取Down、Move、Up事件,判断是否发生移动;
- 如果子OnTouchListener发生移动,根据移动的距离调整子View的位置和透明度,使得子View跟随着手指移动;
- OnTouchListener移动的过程中,得到子View的起始Y坐标和高度,绘制相应的背景;
- 当OnTouchListener收到Up事件的时候,判断整个移动距离是否大于子View宽度的1/4,如果大于的话则继续完成删除动画,如果不大于的话则执行动画恢复到初始状态。
OnTouchListener处理Move事件的代码:
float x = event.getX() + v.getTranslationX();
float deltaX = x - mDownX;
float deltaXAbs = Math.abs(deltaX);
if (!mSwiping) {
if (deltaXAbs > mSwipeSlop) {
mSwiping = true;
mListView.requestDisallowInterceptTouchEvent(true);
mBackgroundContainer.showBackground(v.getTop(), v.getHeight());
}
}
if (mSwiping) {
v.setTranslationX((x - mDownX));
v.setAlpha(1 - deltaXAbs / v.getWidth());
}
OnTouchListener处理Up事件的代码:
float x = event.getX() + v.getTranslationX();
float deltaX = x - mDownX;
float deltaXAbs = Math.abs(deltaX);
float fractionCovered;
float endX;
float endAlpha;
final boolean remove;
if (deltaXAbs > v.getWidth() / 4) {
// Greater than a quarter of the width - animate it out
fractionCovered = deltaXAbs / v.getWidth();
endX = deltaX < 0 ? -v.getWidth() : v.getWidth();
endAlpha = 0;
remove = true;
} else {
// Not far enough - animate it back
fractionCovered = 1 - (deltaXAbs / v.getWidth());
endX = 0;
endAlpha = 1;
remove = false;
}
// Animate position and alpha of swiped item
// NOTE: This is a simplified version of swipe behavior, for the
// purposes of this demo about animation. A real version should use
// velocity (via the VelocityTracker class) to send the item off or
// back at an appropriate speed.
long duration = (int) ((1 - fractionCovered) * SWIPE_DURATION);
mListView.setEnabled(false);
v.animate().setDuration(duration).
alpha(endAlpha).translationX(endX).
withEndAction(new Runnable() {
@Override
public void run() {
// Restore animated values
v.setAlpha(1);
v.setTranslationX(0);
if (remove) {
animateRemoval(mListView, v);
} else {
mBackgroundContainer.hideBackground();
mSwiping = false;
mListView.setEnabled(true);
}
}
});
整个动画效果如下所示:
AnimatingListViewDeletion
17 Animating ListView Deletion: Now on Gingerbread!
注:Github源码名称为ListViewItemAnimations
在16课中,我们使用了ViewPropertyAnimator来实现动画,可是ViewPropertyAnimator是在API level 12才加入的,现在如果希望这个动画在低版本上也能够实现,则可以使用AlphaAnimation、TranslateAnimation和AnimationSet一起来实现。整体思路和上面一摸一样,在这列出一个方法的代码:
private void setSwipePosition(View view, float deltaX) {
float fraction = Math.abs(deltaX) / view.getWidth();
if (isRuntimePostGingerbread()) {
view.setTranslationX(deltaX);
view.setAlpha(1 - fraction);
} else {
// Hello, Gingerbread!
TranslateAnimation swipeAnim = new TranslateAnimation(deltaX, deltaX, 0, 0);
mCurrentX = deltaX;
mCurrentAlpha = (1 - fraction);
AlphaAnimation alphaAnim = new AlphaAnimation(mCurrentAlpha, mCurrentAlpha);
AnimationSet set = new AnimationSet(true);
set.addAnimation(swipeAnim);
set.addAnimation(alphaAnim);
set.setFillAfter(true);
set.setFillEnabled(true);
view.startAnimation(set);
}
}
18 Animating Multiple Properties in Parallel
这节课讲解了如何改变View的多个属性来产生动画,包括如下4种方式:
- 通过ValueAnimator添加UpdateListener,根据fraction的值来分别改变多个属性的值;
- 通过ViewPropertyAnimator来实现多个属性改变的动画;
- 通过ObjectAnimator和AnimatorSet结合来实现多个属性改变的动画;
- 通过ObjectAnimator和PropertyValuesHolder来实现多个属性改变的动画。
代码如下所示:
/**
* A very manual approach to animation uses a ValueAnimator to animate a fractional
* value and then turns that value into the final property values which are then set
* directly on the target object.
*/
public void runValueAnimator(final View view) {
ValueAnimator anim = ValueAnimator.ofFloat(0, 400);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
float fraction = animator.getAnimatedFraction();
view.setTranslationX(TX_START + fraction * (TX_END - TX_START));
view.setTranslationY(TY_START + fraction * (TY_END - TY_START));
}
});
anim.start();
}
/**
* ViewPropertyAnimator is the cleanest and most efficient way of animating
* View properties, even when there are multiple properties to be animated
* in parallel.
*/
public void runViewPropertyAnimator(View view) {
view.animate().translationX(TX_END).translationY(TY_END);
}
/**
* Multiple ObjectAnimator objects can be created and run in parallel.
*/
public void runObjectAnimators(View view) {
ObjectAnimator.ofFloat(view, View.TRANSLATION_X, TX_END).start();
ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, TY_END).start();
// Optional: use an AnimatorSet to run these in parallel
}
/**
* Using PropertyValuesHolder objects enables the use of a single ObjectAnimator
* per target, even when there are multiple properties being animated on that target.
*/
public void runObjectAnimator(View view) {
PropertyValuesHolder pvhTX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, TX_END);
PropertyValuesHolder pvhTY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, TY_END);
ObjectAnimator.ofPropertyValuesHolder(view, pvhTX, pvhTY).start();
}
动画效果如下所示:
AnimatingMultipleProperties
19 Curved Motion
如何制作一个曲线运动的动画呢?这是个有意思的事情。
其实这有一个简单的实现,比如X轴我们选择线性的插值器,而在Y轴上我们选择加速的插值器,其实这样就能完成一个简单的曲线动画。
这节课里面,讲述了如何实现更为复杂、更为定制化的曲线动画。
整个实现较为复杂,想要理解整个过程需要对android动画有较好掌握,在这推荐这篇文章给大家:
Property Anim详解
整个实现可以分为如下几步:
- 按钮点击的时候获取按钮的起始坐标;
- 按钮点击后根据相对布局设置按钮新的位置;
- 在按钮移动到新的位置之前获取按钮新的位置坐标;
- 根据起点坐标、终点坐标和曲线的两个控制点生成曲线的路径坐标;
- 根据路径坐标设置按钮的每个时刻的位置。
具体的实现说明可参考英文文章:Curved Motion in Android(翻墙的能力是必须会的。。。)
按钮点击后的相关代码:
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Capture current location of button
final int oldLeft = mButton.getLeft();
final int oldTop = mButton.getTop();
// Change layout parameters of button to move it
moveButton();
// Add OnPreDrawListener to catch button after layout but before drawing
mButton.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
mButton.getViewTreeObserver().removeOnPreDrawListener(this);
// Capture new location
int left = mButton.getLeft();
int top = mButton.getTop();
int deltaX = left - oldLeft;
int deltaY = top - oldTop;
// Set up path to new location using a B�zier spline curve
AnimatorPath path = new AnimatorPath();
path.moveTo(-deltaX, -deltaY);
path.curveTo(-(deltaX/2), -deltaY, 0, -deltaY/2, 0, 0);
// Set up the animation
final ObjectAnimator anim = ObjectAnimator.ofObject(
CurvedMotion.this, "buttonLoc",
new PathEvaluator(), path.getPoints().toArray());
anim.setInterpolator(sDecelerateInterpolator);
anim.start();
return true;
}
});
}
});
moveButton()方法的代码:
/**
* Toggles button location on click between top-left and bottom-right
*/
private void moveButton() {
LayoutParams params = (LayoutParams) mButton.getLayoutParams();
if (mTopLeft) {
params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
} else {
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
}
mButton.setLayoutParams(params);
mTopLeft = !mTopLeft;
}
PathEvaluator的代码:
/**
* This evaluator interpolates between two PathPoint values given the value t, the
* proportion traveled between those points. The value of the interpolation depends
* on the operation specified by the endValue (the operation for the interval between
* PathPoints is always specified by the end point of that interval).
*/
public class PathEvaluator implements TypeEvaluator<PathPoint> {
@Override
public PathPoint evaluate(float t, PathPoint startValue, PathPoint endValue) {
float x, y;
if (endValue.mOperation == PathPoint.CURVE) {
float oneMinusT = 1 - t;
x = oneMinusT * oneMinusT * oneMinusT * startValue.mX +
3 * oneMinusT * oneMinusT * t * endValue.mControl0X +
3 * oneMinusT * t * t * endValue.mControl1X +
t * t * t * endValue.mX;
y = oneMinusT * oneMinusT * oneMinusT * startValue.mY +
3 * oneMinusT * oneMinusT * t * endValue.mControl0Y +
3 * oneMinusT * t * t * endValue.mControl1Y +
t * t * t * endValue.mY;
} else if (endValue.mOperation == PathPoint.LINE) {
x = startValue.mX + t * (endValue.mX - startValue.mX);
y = startValue.mY + t * (endValue.mY - startValue.mY);
} else {
x = endValue.mX;
y = endValue.mY;
}
return PathPoint.moveTo(x, y);
}
}
最后,动画改变按钮属性的代码:
/**
* We need this setter to translate between the information the animator
* produces (a new "PathPoint" describing the current animated location)
* and the information that the button requires (an xy location). The
* setter will be called by the ObjectAnimator given the 'buttonLoc'
* property string.
*/
public void setButtonLoc(PathPoint newLoc) {
mButton.setTranslationX(newLoc.mX);
mButton.setTranslationY(newLoc.mY);
}
最后实现的动画如下所示:
CurvedMotion
20 Anticipation and Overshoot - Part 1
注:Github源码名称为LiveButton
在这一节课里面主要讲述了两个不同的插值器,一个是DecelerateInterpolator,一个是OvershootInterpolator。
两个插值器有不同的效果,第一个插值器是减速插值器,第二个插值器是越过回弹插值器。
第二个插值器会越过最大值,然后又回到最大值。
clickMeButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
clickMeButton.animate().setInterpolator(sDecelerator).
scaleX(.7f).scaleY(.7f);
} else if (arg1.getAction() == MotionEvent.ACTION_UP) {
clickMeButton.animate().setInterpolator(sOvershooter).
scaleX(1f).scaleY(1f);
}
return false;
}
});
动画效果如下所示:
LiveButton
第二篇文章就讲到这里,希望对大家有所帮助。感兴趣的话不妨看看第三篇文章哦!