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点需要注意的地方:

  1. 必须是相同尺寸额图片;
  2. 对于某些版本必须设置mBitmapOptions.inSampleSize = 1;
  3. 对于某些版本必须设置mBitmapOptions.inMutable = true。
BitmapAllocation

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版本开始有效
设置之后的效果如下所示:

LayoutTransitions_2


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个步骤就可以了。

  1. 取消新打开activity启动和关闭动画;
  2. 打开新activity的时候传递图片的资源、大小和位置信息传递给新的activity;
  3. 新activity打开时,根据传递过来的信息创建启动动画;
  4. 新activity关闭时,根据传递过来的信息创建关闭动画。

实现效果如下所示:


CustomActivityAnimations

16 Animating ListView Deletion

这节讲述了如何制作滑动删除条目的ListView,其实实现起来也不是很复杂,大致分为如下几步:

  1. 设置ListView子View的OnTouchListener;
  2. 在OnTouchListener里面获取Down、Move、Up事件,判断是否发生移动;
  3. 如果子OnTouchListener发生移动,根据移动的距离调整子View的位置和透明度,使得子View跟随着手指移动;
  4. OnTouchListener移动的过程中,得到子View的起始Y坐标和高度,绘制相应的背景;
  5. 当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种方式:

  1. 通过ValueAnimator添加UpdateListener,根据fraction的值来分别改变多个属性的值;
  2. 通过ViewPropertyAnimator来实现多个属性改变的动画;
  3. 通过ObjectAnimator和AnimatorSet结合来实现多个属性改变的动画;
  4. 通过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详解
整个实现可以分为如下几步:

  1. 按钮点击的时候获取按钮的起始坐标;
  2. 按钮点击后根据相对布局设置按钮新的位置;
  3. 在按钮移动到新的位置之前获取按钮新的位置坐标;
  4. 根据起点坐标、终点坐标和曲线的两个控制点生成曲线的路径坐标;
  5. 根据路径坐标设置按钮的每个时刻的位置。

具体的实现说明可参考英文文章: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

第二篇文章就讲到这里,希望对大家有所帮助。感兴趣的话不妨看看第三篇文章哦!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容