《Android开发艺术探索》第7章- Android 动画深入分析读书笔记
目錄
- 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-逐幀動畫ValueAnimatorObjectAnimator1.2 Android 動畫的特點是什么?
| Tween Animation(補間動畫) | 通過對控件不斷地執行圖像變換(平移、縮放、旋轉、透明度)從而產生動畫,是一種漸進式動畫,支持自定義。 |
| Frame Animation(逐幀動畫) | 通過順序地播放一系列圖像從而產生動畫效果 |
| ValueAnimator | 不會對控件執行任何操作,只會在監聽回調中返回值得漸變過程 |
| ObjectAnimator | 通過動態地改變控件得屬性從而達到動畫效果 |
1.3 Tween Animation 補間動畫中的軸點是什么作用?
在縮放動畫和旋轉動畫中有軸點的概念,默認情況下軸點是 View 的中心點。
對于縮放動畫來來說,如果軸點是 View 的中心點,在水平方向上會導致 View 向左右兩個方向同時縮放;如果把軸點設為 View 的右邊界,那么 View 只會向左邊進行縮放。
對于旋轉動畫來說,View 是圍繞著軸點進行旋轉的,如果軸點是 View 的中心點,那么 View 的旋轉看起來就是穩定地旋轉,否則會形成一種偏心旋轉效果。
1.4 自定義 View 動畫的步驟是什么?
這里展示一個來自 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 添加出場動畫效果。
需要注意的是,enterAnim 和 exitAnim 都是定義在 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 如何使用屬性動畫對任意屬性做動畫?
嘗試給控件提供該屬性的 get 和 set 方法,這樣屬性動畫會根據外界傳遞的該屬性的初始值和最終值,以動畫的效果多次去調用 set 方法,每次傳遞給 set 方法的值都不一樣,這樣隨著時間的推移,所傳遞的值越來越接近最終值。
需要說明的是,set 方法是必須的,get 方法只有在動畫沒有傳遞初始值的時候才需要,因為此時系統需要通過 get 方法去獲取初始值,如果沒有 get 方法則會取動畫參數類型的默認值作為初始值,當無法獲取動畫參數類型的默認值時,則會直接崩潰。
如果不能給直接給控件添加該屬性的 get 和 set 方法,則可以使用一個類來包裝原始的控件對象,間接地為其提供 get 和 set 方法;
采用 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 方法即可。
代碼不用改動,再次查看效果:
我們還可以使用一個類包裝 Button 對象來實現:
點擊按鈕時的代碼如下:
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. 使用動畫的注意事項
參考
- Android setVisibility(View.GONE)無效的問題及原因分析
總結
以上是生活随笔為你收集整理的《Android开发艺术探索》第7章- Android 动画深入分析读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Geotrust证书新申请及续费
- 下一篇: Admob设置Android设备为测试设