目录

  • 1. View 动画
    • 1.1 Android 动画的分类有哪些?
    • 1.2 Android 动画的特点是什么?
    • 1.3 Tween Animation 补间动画中的轴点是什么作用?
    • 1.4 自定义 View 动画的步骤是什么?
  • 2. View 动画的特殊使用场景
    • 2.1 如何控制 ViewGroup 中子元素的出场效果?
    • 2.2 如何自定义 Activity 的切换效果?
    • 2.3 如何自定义 Fragment 的切换效果?
  • 3. 属性动画
    • 3.1 插值器和估值器的作用分别是什么?
    • 3.2 如何使用属性动画对任意属性做动画?
  • 4. 使用动画的注意事项
  • 参考

1. View 动画

1.1 Android 动画的分类有哪些?

总共有两类动画:View Animation(视图动画) 和 Property Animation(属性动画)。其中,View Animation 又包括 Tween Animation(补间动画)和 Frame Animation(逐帧动画);Property Animation 又包括 ValueAnimator 和 ObjectAnimator。

#mermaid-svg-JEiT0zcdjO4blM7A .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-JEiT0zcdjO4blM7A .label text{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .node rect,#mermaid-svg-JEiT0zcdjO4blM7A .node circle,#mermaid-svg-JEiT0zcdjO4blM7A .node ellipse,#mermaid-svg-JEiT0zcdjO4blM7A .node polygon,#mermaid-svg-JEiT0zcdjO4blM7A .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-JEiT0zcdjO4blM7A .node .label{text-align:center;fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .node.clickable{cursor:pointer}#mermaid-svg-JEiT0zcdjO4blM7A .arrowheadPath{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-JEiT0zcdjO4blM7A .flowchart-link{stroke:#333;fill:none}#mermaid-svg-JEiT0zcdjO4blM7A .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-JEiT0zcdjO4blM7A .edgeLabel rect{opacity:0.9}#mermaid-svg-JEiT0zcdjO4blM7A .edgeLabel span{color:#333}#mermaid-svg-JEiT0zcdjO4blM7A .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-JEiT0zcdjO4blM7A .cluster text{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-JEiT0zcdjO4blM7A .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-JEiT0zcdjO4blM7A text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-JEiT0zcdjO4blM7A .actor-line{stroke:grey}#mermaid-svg-JEiT0zcdjO4blM7A .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-JEiT0zcdjO4blM7A .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-JEiT0zcdjO4blM7A #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-JEiT0zcdjO4blM7A .sequenceNumber{fill:#fff}#mermaid-svg-JEiT0zcdjO4blM7A #sequencenumber{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A #crosshead path{fill:#333;stroke:#333}#mermaid-svg-JEiT0zcdjO4blM7A .messageText{fill:#333;stroke:#333}#mermaid-svg-JEiT0zcdjO4blM7A .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-JEiT0zcdjO4blM7A .labelText,#mermaid-svg-JEiT0zcdjO4blM7A .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-JEiT0zcdjO4blM7A .loopText,#mermaid-svg-JEiT0zcdjO4blM7A .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-JEiT0zcdjO4blM7A .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-JEiT0zcdjO4blM7A .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-JEiT0zcdjO4blM7A .noteText,#mermaid-svg-JEiT0zcdjO4blM7A .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-JEiT0zcdjO4blM7A .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-JEiT0zcdjO4blM7A .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-JEiT0zcdjO4blM7A .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-JEiT0zcdjO4blM7A .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A .section{stroke:none;opacity:0.2}#mermaid-svg-JEiT0zcdjO4blM7A .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-JEiT0zcdjO4blM7A .section2{fill:#fff400}#mermaid-svg-JEiT0zcdjO4blM7A .section1,#mermaid-svg-JEiT0zcdjO4blM7A .section3{fill:#fff;opacity:0.2}#mermaid-svg-JEiT0zcdjO4blM7A .sectionTitle0{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .sectionTitle1{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .sectionTitle2{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .sectionTitle3{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-JEiT0zcdjO4blM7A .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A .grid path{stroke-width:0}#mermaid-svg-JEiT0zcdjO4blM7A .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-JEiT0zcdjO4blM7A .task{stroke-width:2}#mermaid-svg-JEiT0zcdjO4blM7A .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A .taskText:not([font-size]){font-size:11px}#mermaid-svg-JEiT0zcdjO4blM7A .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-JEiT0zcdjO4blM7A .task.clickable{cursor:pointer}#mermaid-svg-JEiT0zcdjO4blM7A .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-JEiT0zcdjO4blM7A .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-JEiT0zcdjO4blM7A .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-JEiT0zcdjO4blM7A .taskText0,#mermaid-svg-JEiT0zcdjO4blM7A .taskText1,#mermaid-svg-JEiT0zcdjO4blM7A .taskText2,#mermaid-svg-JEiT0zcdjO4blM7A .taskText3{fill:#fff}#mermaid-svg-JEiT0zcdjO4blM7A .task0,#mermaid-svg-JEiT0zcdjO4blM7A .task1,#mermaid-svg-JEiT0zcdjO4blM7A .task2,#mermaid-svg-JEiT0zcdjO4blM7A .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-JEiT0zcdjO4blM7A .taskTextOutside0,#mermaid-svg-JEiT0zcdjO4blM7A .taskTextOutside2{fill:#000}#mermaid-svg-JEiT0zcdjO4blM7A .taskTextOutside1,#mermaid-svg-JEiT0zcdjO4blM7A .taskTextOutside3{fill:#000}#mermaid-svg-JEiT0zcdjO4blM7A .active0,#mermaid-svg-JEiT0zcdjO4blM7A .active1,#mermaid-svg-JEiT0zcdjO4blM7A .active2,#mermaid-svg-JEiT0zcdjO4blM7A .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-JEiT0zcdjO4blM7A .activeText0,#mermaid-svg-JEiT0zcdjO4blM7A .activeText1,#mermaid-svg-JEiT0zcdjO4blM7A .activeText2,#mermaid-svg-JEiT0zcdjO4blM7A .activeText3{fill:#000 !important}#mermaid-svg-JEiT0zcdjO4blM7A .done0,#mermaid-svg-JEiT0zcdjO4blM7A .done1,#mermaid-svg-JEiT0zcdjO4blM7A .done2,#mermaid-svg-JEiT0zcdjO4blM7A .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-JEiT0zcdjO4blM7A .doneText0,#mermaid-svg-JEiT0zcdjO4blM7A .doneText1,#mermaid-svg-JEiT0zcdjO4blM7A .doneText2,#mermaid-svg-JEiT0zcdjO4blM7A .doneText3{fill:#000 !important}#mermaid-svg-JEiT0zcdjO4blM7A .crit0,#mermaid-svg-JEiT0zcdjO4blM7A .crit1,#mermaid-svg-JEiT0zcdjO4blM7A .crit2,#mermaid-svg-JEiT0zcdjO4blM7A .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-JEiT0zcdjO4blM7A .activeCrit0,#mermaid-svg-JEiT0zcdjO4blM7A .activeCrit1,#mermaid-svg-JEiT0zcdjO4blM7A .activeCrit2,#mermaid-svg-JEiT0zcdjO4blM7A .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-JEiT0zcdjO4blM7A .doneCrit0,#mermaid-svg-JEiT0zcdjO4blM7A .doneCrit1,#mermaid-svg-JEiT0zcdjO4blM7A .doneCrit2,#mermaid-svg-JEiT0zcdjO4blM7A .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-JEiT0zcdjO4blM7A .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-JEiT0zcdjO4blM7A .milestoneText{font-style:italic}#mermaid-svg-JEiT0zcdjO4blM7A .doneCritText0,#mermaid-svg-JEiT0zcdjO4blM7A .doneCritText1,#mermaid-svg-JEiT0zcdjO4blM7A .doneCritText2,#mermaid-svg-JEiT0zcdjO4blM7A .doneCritText3{fill:#000 !important}#mermaid-svg-JEiT0zcdjO4blM7A .activeCritText0,#mermaid-svg-JEiT0zcdjO4blM7A .activeCritText1,#mermaid-svg-JEiT0zcdjO4blM7A .activeCritText2,#mermaid-svg-JEiT0zcdjO4blM7A .activeCritText3{fill:#000 !important}#mermaid-svg-JEiT0zcdjO4blM7A .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-JEiT0zcdjO4blM7A g.classGroup text .title{font-weight:bolder}#mermaid-svg-JEiT0zcdjO4blM7A g.clickable{cursor:pointer}#mermaid-svg-JEiT0zcdjO4blM7A g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-JEiT0zcdjO4blM7A g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-JEiT0zcdjO4blM7A .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-JEiT0zcdjO4blM7A .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-JEiT0zcdjO4blM7A .dashed-line{stroke-dasharray:3}#mermaid-svg-JEiT0zcdjO4blM7A #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A .commit-id,#mermaid-svg-JEiT0zcdjO4blM7A .commit-msg,#mermaid-svg-JEiT0zcdjO4blM7A .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-JEiT0zcdjO4blM7A g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-JEiT0zcdjO4blM7A g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-JEiT0zcdjO4blM7A g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-JEiT0zcdjO4blM7A .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-JEiT0zcdjO4blM7A .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-JEiT0zcdjO4blM7A .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-JEiT0zcdjO4blM7A .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-JEiT0zcdjO4blM7A .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-JEiT0zcdjO4blM7A .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-JEiT0zcdjO4blM7A .edgeLabel text{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-JEiT0zcdjO4blM7A .node circle.state-start{fill:black;stroke:black}#mermaid-svg-JEiT0zcdjO4blM7A .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-JEiT0zcdjO4blM7A #statediagram-barbEnd{fill:#9370db}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-state .divider{stroke:#9370db}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-JEiT0zcdjO4blM7A .note-edge{stroke-dasharray:5}#mermaid-svg-JEiT0zcdjO4blM7A .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-JEiT0zcdjO4blM7A .error-icon{fill:#522}#mermaid-svg-JEiT0zcdjO4blM7A .error-text{fill:#522;stroke:#522}#mermaid-svg-JEiT0zcdjO4blM7A .edge-thickness-normal{stroke-width:2px}#mermaid-svg-JEiT0zcdjO4blM7A .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-JEiT0zcdjO4blM7A .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-JEiT0zcdjO4blM7A .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-JEiT0zcdjO4blM7A .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-JEiT0zcdjO4blM7A .marker{fill:#333}#mermaid-svg-JEiT0zcdjO4blM7A .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-JEiT0zcdjO4blM7A {color: rgba(0, 0, 0, 0.75);font: ;}

Android动画
ViewAnimation-视图动画-API1引入
PropertyAnimation-属性动画-API1引入
TweenAnimation-补间动画
FrameAnimation-逐帧动画
ValueAnimator
ObjectAnimator

1.2 Android 动画的特点是什么?

动画 特点
Tween Animation(补间动画) 通过对控件不断地执行图像变换(平移、缩放、旋转、透明度)从而产生动画,是一种渐进式动画,支持自定义
Frame Animation(逐帧动画) 通过顺序地播放一系列图像从而产生动画效果
ValueAnimator 不会对控件执行任何操作,只会在监听回调中返回值得渐变过程
ObjectAnimator 通过动态地改变控件得属性从而达到动画效果

1.3 Tween Animation 补间动画中的轴点是什么作用?

在缩放动画和旋转动画中有轴点的概念,默认情况下轴点是 View 的中心点。

对于缩放动画来来说,如果轴点是 View 的中心点,在水平方向上会导致 View 向左右两个方向同时缩放;如果把轴点设为 View 的右边界,那么 View 只会向左边进行缩放。

对于旋转动画来说,View 是围绕着轴点进行旋转的,如果轴点是 View 的中心点,那么 View 的旋转看起来就是稳定地旋转,否则会形成一种偏心旋转效果。

1.4 自定义 View 动画的步骤是什么?

  1. 集成 Animation 抽象类;
  2. 重写它的 initializeapplyTransformation 方法;
  3. initialize 方法中做一些初始化工作;
  4. applyTransformation 方法中进行相应的矩阵变换。

这里展示一个来自 ApiDemos 里的翻转动画效果:

public class FlipAnimation extends Animation {private float mStartDegree;private float mEndDegree;private float mCenterX;private float mCenterY;private Camera mCamera;private boolean mFlag;private float mZDistance = 400f;public FlipAnimation(float startDegree, float endDegree, float centerX, float centerY, boolean flag) {mStartDegree = startDegree;mEndDegree = endDegree;mCenterX = centerX;mCenterY = centerY;mFlag = flag;}@Overridepublic void initialize(int width, int height, int parentWidth, int parentHeight) {super.initialize(width, height, parentWidth, parentHeight);mCamera = new Camera();}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {super.applyTransformation(interpolatedTime, t);final float startDegree = mStartDegree;final float endDegree = mEndDegree;final float zDistance = mZDistance;final float centerX = mCenterX;final float centerY = mCenterY;float currDegree = startDegree + interpolatedTime * (endDegree - startDegree);Camera camera = mCamera;Matrix matrix = t.getMatrix();camera.save();if (mFlag) {camera.translate(0, 0, interpolatedTime * zDistance);} else {camera.translate(0,0, (1 - interpolatedTime) * zDistance);}camera.rotateX(currDegree);camera.getMatrix(matrix);camera.restore();matrix.preTranslate(-centerX, -centerY);matrix.postTranslate(centerX, centerY);}
}

页面布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutandroid:id="@+id/rl"xmlns:android="http://schemas.android.com/apk/res/android"android:gravity="center"android:layout_width="match_parent"android:layout_height="match_parent"><ImageViewandroid:id="@+id/cab"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/junk_cab"android:layout_centerHorizontal="true" /><ImageViewandroid:id="@+id/junk_ok"android:visibility="invisible"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:src="@drawable/ok"android:layout_below="@id/cab"android:layout_centerHorizontal="true" /><ImageViewandroid:id="@+id/bin"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:src="@drawable/junk_bin"android:layout_below="@id/cab"android:layout_centerHorizontal="true" />
</RelativeLayout>

页面代码如下:

public class CustomFlipAnimationActivity extends Activity {private RelativeLayout mRl;private ImageView mIvCab;private ImageView mIvJunkOk;private ImageView mIvBin;public static void start(Context context) {Intent starter = new Intent(context, CustomFlipAnimationActivity.class);context.startActivity(starter);}private boolean mInvisToVis = true;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_custom_flip_animation);initViews();mRl.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Rect rect = new Rect();mRl.getHitRect(rect);final float centerX = rect.centerX();final float centerY = rect.centerY();FlipAnimation flipAnimation = new FlipAnimation(0.0F, 90.0F, centerX, centerY, true);flipAnimation.setDuration(600L);flipAnimation.setInterpolator(new AccelerateInterpolator());flipAnimation.setAnimationListener(new Animation.AnimationListener() {@Overridepublic final void onAnimationStart(Animation paramAnonymousAnimation) {}@Overridepublic final void onAnimationEnd(Animation paramAnonymousAnimation) {if (mInvisToVis) {mIvJunkOk.setVisibility(View.VISIBLE);mIvCab.setVisibility(View.INVISIBLE);mIvBin.setVisibility(View.INVISIBLE);} else {mIvJunkOk.setVisibility(View.INVISIBLE);mIvCab.setVisibility(View.VISIBLE);mIvBin.setVisibility(View.VISIBLE);}mInvisToVis = !mInvisToVis;FlipAnimation localb = new FlipAnimation(270.0F, 360.0F, centerX, centerY, false);localb.setDuration(600L);localb.setFillAfter(true);localb.setInterpolator(new DecelerateInterpolator());localb.setAnimationListener(null);mRl.startAnimation(localb);}@Overridepublic final void onAnimationRepeat(Animation paramAnonymousAnimation) {}});mRl.startAnimation(flipAnimation);}});}private void initViews() {mRl = (RelativeLayout) findViewById(R.id.rl);mIvCab = (ImageView) findViewById(R.id.cab);mIvJunkOk = (ImageView) findViewById(R.id.junk_ok);mIvBin = (ImageView) findViewById(R.id.bin);}
}

翻转效果如下:

2. View 动画的特殊使用场景

2.1 如何控制 ViewGroup 中子元素的出场效果?

使用 <layoutAnimation>,一般用于 ListView

2.2 如何自定义 Activity 的切换效果?

使用 overridePendingTransition(int enterAnim, int exitAnim) 方法,这个方法必须在 startActivity(Intent intent) 或者 finish() 之后被调用才有效。

当启动一个 Activity 时,overridePendingTransition(int enterAnim, int exitAnim) 方法中的 enterAnim 表示给新打开的 Actiivty 添加入场动画效果,exitAnim 表示给当前的 Activity 添加出场动画效果。

当关闭一个 Activity 时,overridePendingTransition(int enterAnim, int exitAnim) 方法中的 enterAnim 表示恢复到前台的 Actiivty 添加入场动画效果,exitAnim 表示给关闭的 Activity 添加出场动画效果。

也就是说,overridePendingTransition(int enterAnim, int exitAnim) 为即将到来的 Activity 添加入场动画效果,为即将退出的 Activity 添加出场动画效果。

需要注意的是,enterAnimexitAnim 都是定义在 anim 目录下的动画资源文件的 id。如果不希望有任何页面切换效果,可以把这个参数都传入 0 即可。

2.3 如何自定义 Fragment 的切换效果?

使用 setCustomAnimations(@AnimRes int enter, @AnimRes int exit) 这个方法,这里同样是定义在 anim 目录下的动画资源文件的 id。

3. 属性动画

3.1 插值器和估值器的作用分别是什么?

插值器(或者说时间插值器)需要实现 Interpolator 接口或者 TimeInterpolator 接口:

/*** A time interpolator defines the rate of change of an animation. This allows animations* to have non-linear motion, such as acceleration and deceleration.*/
public interface TimeInterpolator {/*** Maps a value representing the elapsed fraction of an animation to a value that represents* the interpolated fraction. This interpolated value is then multiplied by the change in* value of an animation to derive the animated value at the current elapsed animation time.** @param input A value between 0 and 1.0 indicating our current point*        in the animation where 0 represents the start and 1.0 represents*        the end* @return The interpolation value. This value can be more than 1.0 for*         interpolators which overshoot their targets, or less than 0 for*         interpolators that undershoot their targets.*/float getInterpolation(float input);
}
public interface Interpolator extends TimeInterpolator {// A new interface, TimeInterpolator, was introduced for the new android.animation// package. This older Interpolator interface extends TimeInterpolator so that users of// the new Animator-based animations can use either the old Interpolator implementations or// new classes that implement TimeInterpolator directly.
}

需要说明的是:

getInterpolation 方法的 input 参数与我们设定的任何值都没有关系,只与时间有关,随着时间的推移,动画的进度也自然地增加,这个值也会从 0 增加到 1.0。所以,这个值的取值范围是 [0,1.0],其中 0 表示动画刚开始,1.0 表示动画结束了,0.5 表示动画进行到一半了。

getInterpolation 方法的返回值表示当前实际想要显示的进度,这个值可以超过 1.0,也可以小于 0。超过 1.0 表示超过了目标值,小于 0 表示小于开始位置。

通过 setInterpolator(TimeInterpolator value) 方法设置插值器,这是策略模式的应用了。

估值器要实现 TypeEvaluator 接口:

/*** Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators* allow developers to create animations on arbitrary property types, by allowing them to supply* custom evaluators for types that are not automatically understood and used by the animation* system.** @see ValueAnimator#setEvaluator(TypeEvaluator)*/
public interface TypeEvaluator<T> {/*** This function returns the result of linearly interpolating the start and end values, with* <code>fraction</code> representing the proportion between the start and end values. The* calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,* and <code>t</code> is <code>fraction</code>.** @param fraction   The fraction from the starting to the ending values* @param startValue The start value.* @param endValue   The end value.* @return A linear interpolation between the start and end values, given the*         <code>fraction</code> parameter.*/public T evaluate(float fraction, T startValue, T endValue);}

根据当前实际显示的进度,动画开始属性值,动画结束属性值,来计算出当前要显示的属性值。其中,fraction 的值就是插值器的 getInterpolation 方法的返回值。

3.2 如何使用属性动画对任意属性做动画?

  1. 尝试给控件提供该属性的 get 和 set 方法,这样属性动画会根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用 set 方法,每次传递给 set 方法的值都不一样,这样随着时间的推移,所传递的值越来越接近最终值。

    需要说明的是,set 方法是必须的,get 方法只有在动画没有传递初始值的时候才需要,因为此时系统需要通过 get 方法去获取初始值,如果没有 get 方法则会取动画参数类型的默认值作为初始值,当无法获取动画参数类型的默认值时,则会直接崩溃。

  2. 如果不能给直接给控件添加该属性的 get 和 set 方法,则可以使用一个类来包装原始的控件对象,间接地为其提供 get 和 set 方法;

  3. 采用 ValueAnimator,监听动画过程,自己实现属性的改变。

下面使用具体的例子来说明:

使用动画在 5s 内把 Button 的宽度增加到 500 px。

我们先采用继承 Button 来自定义一个 MyButton 来实现:

public class MyButton extends Button {public MyButton(Context context, AttributeSet attrs) {super(context, attrs);}public void setBreadth(int width) {getLayoutParams().width = width;requestLayout();}
}

使用属性动画来实现动画效果,代码如下:

ObjectAnimator.ofInt(mBtn5, "breadth", 500).setDuration(2000).start();

效果如下:

可以看到,动画开始的宽度是从 0 开始的,这是因为我们并没有提供 breadth 属性的 get 方法,而 breadth 参数的默认值是 0,所以动画开始的宽度是 0。那该怎么办呢?添加对应的 get 方法即可。

public class MyButton extends Button {public MyButton(Context context, AttributeSet attrs) {super(context, attrs);}public void setBreadth(int width) {getLayoutParams().width = width;requestLayout();}public int getBreadth() {return getWidth();}
}

代码不用改动,再次查看效果:

我们还可以使用一个类包装 Button 对象来实现:

private static class ViewWrapper {private View mTarget;private ViewWrapper(View target) {mTarget = target;}public int getBreadth() {return mTarget.getLayoutParams().width;}public void setBreadth(int width) {mTarget.getLayoutParams().width = width;mTarget.requestLayout();}
}

点击按钮时的代码如下:

ViewWrapper viewWrapper = new ViewWrapper(mBtn3);
ObjectAnimator.ofInt(viewWrapper, "breadth", mBtn3.getWidth(), 500).setDuration(2000).start();

同样可以实现效果。

现在需求变化了:使用动画在 5s 内把 Button 的宽度和高度都增加到 500 px。

public class MyButton extends Button {public MyButton(Context context, AttributeSet attrs) {super(context, attrs);}public void setSize(PointF point) {getLayoutParams().width = (int) point.x;getLayoutParams().height = (int) point.y;requestLayout();}
}

点击按钮的代码如下:

ObjectAnimator.ofObject(mBtn6, "size", new PointFEvaluator(), new PointF(500F,500F)).setDuration(2000).start();

运行程序,崩溃了,日志如下:

W/PropertyValuesHolder( 6601): Method getSize() with type null not found on target class class com.wzc.chapter_7.MyButton
D/AndroidRuntime( 6601): Shutting down VM
E/AndroidRuntime( 6601): FATAL EXCEPTION: main
E/AndroidRuntime( 6601): Process: com.wzc.chapter_7, PID: 6601
E/AndroidRuntime( 6601): java.lang.NullPointerException: Attempt to read from field 'float android.graphics.PointF.x' on a null object reference
E/AndroidRuntime( 6601):    at android.animation.PointFEvaluator.evaluate(PointFEvaluator.java:73)
E/AndroidRuntime( 6601):    at android.animation.PointFEvaluator.evaluate(PointFEvaluator.java:23)
E/AndroidRuntime( 6601):    at android.animation.KeyframeSet.getValue(KeyframeSet.java:202)
E/AndroidRuntime( 6601):    at android.animation.PropertyValuesHolder.calculateValue(PropertyValuesHolder.java:1017)
E/AndroidRuntime( 6601):    at android.animation.ValueAnimator.animateValue(ValueAnimator.java:1561)
E/AndroidRuntime( 6601):    at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:987)
E/AndroidRuntime( 6601):    at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:692)
E/AndroidRuntime( 6601):    at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:655)
E/AndroidRuntime( 6601):    at android.animation.ValueAnimator.start(ValueAnimator.java:1087)
E/AndroidRuntime( 6601):    at android.animation.ValueAnimator.start(ValueAnimator.java:1106)
E/AndroidRuntime( 6601):    at android.animation.ObjectAnimator.start(ObjectAnimator.java:852)
E/AndroidRuntime( 6601):    at com.wzc.chapter_7.PropertyAnimationActivity$6.onClick(PropertyAnimationActivity.java:104)
E/AndroidRuntime( 6601):    at android.view.View.performClick(View.java:7509)
E/AndroidRuntime( 6601):    at android.view.View.performClickInternal(View.java:7486)
E/AndroidRuntime( 6601):    at android.view.View.access$3600(View.java:841)
E/AndroidRuntime( 6601):    at android.view.View$PerformClick.run(View.java:28720)
E/AndroidRuntime( 6601):    at android.os.Handler.handleCallback(Handler.java:938)
E/AndroidRuntime( 6601):    at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 6601):    at android.os.Looper.loop(Looper.java:236)
E/AndroidRuntime( 6601):    at android.app.ActivityThread.main(ActivityThread.java:8059)
E/AndroidRuntime( 6601):    at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime( 6601):    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
E/AndroidRuntime( 6601):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)

从日志可以看到,是因为缺少 getSize 方法。

添加 getSize 方法:

public class MyButton extends Button {public MyButton(Context context, AttributeSet attrs) {super(context, attrs);}public void setSize(PointF point) {getLayoutParams().width = (int) point.x;getLayoutParams().height = (int) point.y;requestLayout();}public PointF getSize() {return new PointF(getWidth(), getHeight());}
}

运行程序,效果如下:

4. 使用动画的注意事项

  1. 帧动画 OOM 问题:对于帧动画来说,当图片数量较多且图片尺寸较大时就很容易出现 OOM 问题,这时我们要减少图片数量,减小图片尺寸,或者采用其他动画来实现。
  2. 无限循环属性动画内存泄漏问题:无限循环的属性动画,在 Activity 退出时要及时取消动画,否则动画会无限循环,从而导致 View 控件无法释放,进一步导致整个 Activity 无法释放,最终引起内存泄漏。作为对比,View 动画不存在这种问题。
  3. View 动画完成后 View 无法隐藏问题:View 动画是对 View 的影像做动画,并不是真正地改变 View 的状态,所以会出现动画完成后设置 setVisibility(View.GONE) 失效的问题,这个时候只要调用 View.clearAnimation() 清除 View 动画即可解决此问题。
  4. 使用 px 动画在不同设备上动画效果不同的问题:尽量使用 dp,而不要使用 px。
  5. View 动画平移后,控件点击事件仍在原位置的问题:可以改用属性动画来实现。
  6. 动画不流畅问题:建议开启硬件加速,这样可以提供动画的流畅性。
  7. onAnimationEnd没有回调的问题onAnimationEnd 可能因各种异常没被回调,建议加上超时保护或者通过 postDelay 替代 onAnimationEnd,参考:onAnimationEnd is not getting called, onAnimationStart works fine。

参考

  • Android setVisibility(View.GONE)无效的问题及原因分析

《Android开发艺术探索》第7章- Android 动画深入分析读书笔记相关推荐

  1. Android开发艺术探索——第七章:Android动画深入分析

    Android开发艺术探索--第七章:Android动画深入分析 Android的动画可以分成三种,view动画,帧动画,还有属性动画,其实帧动画也是属于view动画的一种,,只不过他和传统的平移之类 ...

  2. Android开发艺术探索 - 第9章 四大组件的工作过程

    1.Activity启动过程 ref 从Activity的startActivity方法开始.startActivity的多个重载方法,最终都会调用startActivityForResult方法.m ...

  3. Android开发艺术探索完结篇——天道酬勤

    这片文章发布,代表着我已经把本书和看完并且笔记也发布完成了,回忆了一下我看Android群英传,只用了两个月,但是看本书却花了2016年05月04日 - 2018年07月16日,整整两年多,真是惭愧 ...

  4. 《Android开发艺术探索》笔记目录

    该笔记以<Android开发艺术探索>为基础,结合Android 9.0代码和官方文档,修正了原书中表述不明确和过时的部分,同时加入了大量的个人理解. 13章,14章,15章是总结性的章节 ...

  5. Android开发艺术探索之Activity篇总结

    本文内容来自<Android开发艺术探索>第一章,个人学习提炼总结,欢迎指正. 1.1典型情况下的生命周期 onCreate():表示Activity正在被创建,初始化布局资源+Activ ...

  6. Android开发艺术探索笔记(一) Activity的生命周期和启动模式(1)

    Activity作为Android开发中最常用的一个组件,是Android开发人员必须熟悉且掌握的重要内容.同时Activity也是在面试中经常被问到的一个方向.因此,掌握Activity的重要性也不 ...

  7. Android开发艺术探索--第二章IPC机制(2)之Binder

    最近在拜读任主席的Android开发艺术探索,现在看了一半,再回头看前面的,感觉跟没有看一样,所以还是把知识点总结一下吧,这一节咱们来讲一下IPC中的Binder 直观来说,Binder是Androi ...

  8. 《Android开发艺术探索》图书勘误

    第一章 在13页提到"系统只在Activity异常终止的时候才会调用onSaveInstanceState与onRestoreInstanceState来储存和恢复数据,其他情况不会触发这个 ...

  9. Android开发艺术探索读书笔记(一)

    首先向各位严重推荐主席这本书<Android开发艺术探索>. 再感谢主席邀请写这篇读书笔记 + 书评.书已经完整的翻完一遍了,但是还没有细致的品读并run代码,最近有时间正好系统的把整本书 ...

  10. Android开发艺术探索全面解读

    概述:本系列博客是对任玉刚所著一书 Android开发艺术探索 的全民解读与学习.在这个系列博客中,会对该书中的各个章节进行细致的解读与思考,以及最重要的总结.希望在这个过程中和大家共同进步. 本书章 ...

最新文章

  1. 【JS基础】Array数组的创建与操作方法
  2. unity 游戏第一次安装完之后运行,切出来,点击桌面图标后黑屏问题
  3. GetSafeHdc( )
  4. VMware View 4.0 测试-7
  5. discuz 二次开发
  6. 就业指导——招聘信息的获取、简历投递和指导、HR面试指导
  7. SH1B LMR62014XMFE/NOPB
  8. 怎么彻底重装清空电脑_电脑开不了机怎么重装系统?不用送去维修店啦!
  9. struts处理中文乱码问题总结
  10. jquery实现图片等比例缩放,解决max-width在ie中不兼容问题
  11. RedMonk最新编程语言排行榜出炉:JS霸榜,C++下降至第7
  12. eclipse插件开发流程
  13. 周报-寒假3(淘宝主页项目练习)
  14. gds文件 导出_cadence virtuoso 批量导出gds方法
  15. redis分布式锁和调度锁
  16. 经典排序算法之--冒泡排序
  17. python3.x和python2.x唯一区别_Python3.x和Python2.x的区别
  18. 谷歌浏览器怎么拦截网页广告 5步解决广告困扰
  19. 国产deepin深度操作系统20.2.3 发布
  20. 2021-01 补丁日: 微软多个高危漏洞通告

热门文章

  1. 第一次滑雪小记——杭州临安大明山滑雪场
  2. input file类型单个文件上传formData
  3. 笔记本计算机怎么进入安全模式启动,笔记本怎么进入安全模式 【使用步骤】...
  4. API开发手册在线中文版
  5. 计算个人所得税 (10 分)2019年个税新版规定:应纳税所得额为税前工资扣除五险一金,五险一金按工资22%比例计算。 个税起征点为5000元;
  6. 启动vidalia 时不用打开firefox
  7. Java 6.22练习-----模拟物流快递系统程序设计
  8. C语言公交车线路信息查询系统
  9. 5 Linux系统编程之网络编程--学习笔记
  10. 学好UI设计必备软件