Android 过渡动画原来可以这样写
DeaCPIQ
8年前
<h2>关键类</h2> <ul> <li>android.transition.TransitionManager</li> <li>android.transition.Transition 抽象类 <ul> <li>TransitionSet <ul> <li>AutoTransition</li> </ul> </li> <li>ChangeBounds</li> <li>Visibility 抽象类 <ul> <li>Fade</li> <li>Explode ( design 包中无适配)</li> <li>Slide ( design 包中无适配)</li> </ul> </li> <li>TextScale</li> <li>ChangeClipBounds ( design 包中无适配)</li> <li>ChangeImageTransform ( design 包中无适配)</li> <li>ChangeScroll ( design 包中无适配)</li> <li>ChangeTransform ( design 包中无适配)</li> </ul> </li> </ul> <h2><strong>介绍</strong></h2> <p>Visibility 抽象类的子类:Fade, Explode, Slide 动画作用于 View 的 Visibility 属性改变的时候。</p> <h2>适配</h2> <p>对应的适配包在design com.android.support:design:x.x.x 包中</p> <pre> <code class="language-java">compile 'com.android.support:design:25.0.0'</code></pre> <p>更好的适配方案:(使用的时候注意导包 com.transitionseverywhere.xxx )</p> <pre> <code class="language-java">compile "com.andkulikov:transitionseverywhere:1.6.9"</code></pre> <p><!--more--></p> <h2><strong>上代码</strong></h2> <p>下面的例子 使用适配包 compile "com.andkulikov:transitionseverywhere:1.6.9" 测试系统 Android4.1</p> <h2><strong>默认效果</strong></h2> <h3><strong>布局关键代码:</strong></h3> <pre> <code class="language-java"><LinearLayout android:id="@+id/ll_container_one" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical"> <Button android:id="@+id/btn_one" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fad"/> <TextView android:id="@+id/tv_one" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Transitions are awesome!" android:visibility="gone"/> </LinearLayout></code></pre> <h3><strong>Activity 中代码:</strong></h3> <pre> <code class="language-java">final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one); final TextView text = (TextView) findViewById(R.id.tv_one); final Button button = (Button) findViewById(R.id.btn_one); button.setOnClickListener(v -> { TransitionManager.beginDelayedTransition(transitionsContainer); if (text.getVisibility() == View.VISIBLE) { text.setVisibility(View.GONE); } else { text.setVisibility(View.VISIBLE); } });</code></pre> <h3><strong>最终效果:</strong></h3> <p><img src="https://simg.open-open.com/show/e4cb9ffb0716d933a94ce31be842cb0c.gif"></p> <p>默认效果</p> <h3><strong>代码解释</strong></h3> <p>TransitionManager 中的 beginDelayedTransition 方法:</p> <pre> <code class="language-java">public static void beginDelayedTransition(final ViewGroup sceneRoot){...} public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {...}</code></pre> <ol> <li>方法一:效果如上面的默认效果</li> <li>方法二:参数而用于定制动画效果, 参数为 Transition 的子类,常用的有:Fade, ChangeBounds, Slide,</li> </ol> <p>对用画具体效果做定制:设置动画时间,设置动画加速度,设置动画延时</p> <pre> <code class="language-java">transition.setDuration(300); transition.setInterpolator(new FastOutSlowInInterpolator()); transition.setStartDelay(200);</code></pre> <h2><strong>Fade 淡出淡入</strong></h2> <h3><strong>布局如上 更改 Activity 中代码:</strong></h3> <pre> <code class="language-java">final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one); final TextView text = (TextView) findViewById(R.id.tv_one); final Button button = (Button) findViewById(R.id.btn_one); button.setOnClickListener(v -> { Fade fade = new Fade(); TransitionManager.beginDelayedTransition(transitionsContainer, fade); if (text.getVisibility() == View.VISIBLE) { text.setVisibility(View.GONE); } else { text.setVisibility(View.VISIBLE); } });</code></pre> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/04f41b3d7a63e66c05eacc1537e284e6.gif"></p> <p style="text-align:center">Fade效果</p> <h2><strong>Slide 移动</strong></h2> <h3><strong>布局如上 更改 Activity 中代码:</strong></h3> <pre> <code class="language-java">final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one); final TextView text = (TextView) findViewById(R.id.tv_one); final Button button = (Button) findViewById(R.id.btn_one); button.setOnClickListener(v -> { // Slide slide = new Slide(); Slide slide = new Slide(Gravity.RIGHT); TransitionManager.beginDelayedTransition(transitionsContainer, slide); if (text.getVisibility() == View.VISIBLE) { text.setVisibility(View.GONE); } else { text.setVisibility(View.VISIBLE); } });</code></pre> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/cc8fa52b656a004d0e2e95c43a6768d9.gif"></p> <p style="text-align:center">Slide</p> <h3><strong>Slide 构造方法</strong></h3> <pre> <code class="language-java">/** * Constructor using the default {@link Gravity#BOTTOM} * slide edge direction. */ public Slide() { setSlideEdge(Gravity.BOTTOM); }</code></pre> <p>设置滑出方向:</p> <pre> <code class="language-java">/** * Constructor using the provided slide edge direction. */ public Slide(@GravityFlag int slideEdge) { setSlideEdge(slideEdge); }</code></pre> <h2><strong>Scale 缩放</strong></h2> <h3><strong>布局如上 更改 Activity 中代码:</strong></h3> <pre> <code class="language-java">final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one); final TextView text = (TextView) findViewById(R.id.tv_one); final Button button = (Button) findViewById(R.id.btn_one); button.setOnClickListener(v -> { // Scale scale = new Scale(); Scale scale = new Scale(0.7f); TransitionManager.beginDelayedTransition(transitionsContainer, scale); if (text.getVisibility() == View.VISIBLE) { text.setVisibility(View.GONE); } else { text.setVisibility(View.VISIBLE); } });</code></pre> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/4caaf3f400b0a67d7e23b60c2bc11e6e.gif"></p> <p style="text-align:center">Scale</p> <h3><strong>Scale 构造方法</strong></h3> <pre> <code class="language-java">public Scale() { } /** * @param disappearedScale Value of scale on start of appearing or in finish of disappearing. * Default value is 0. Can be useful for mixing some Visibility * transitions, for example Scale and Fade */ public Scale(float disappearedScale) { setDisappearedScale(disappearedScale); }</code></pre> <p>参数设置了缩放起始值或者最终值。</p> <h2><strong>TransitionSet 动画集合</strong></h2> <pre> <code class="language-java">final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one); final TextView text = (TextView) findViewById(R.id.tv_one); final Button button = (Button) findViewById(R.id.btn_one); button.setOnClickListener(v -> { TransitionSet set = new TransitionSet() .addTransition(new Scale(0.7f)) .addTransition(new Fade()) .setInterpolator(visible ? new LinearOutSlowInInterpolator() : new FastOutLinearInInterpolator()); TransitionManager.beginDelayedTransition(transitionsContainer, set); if (text.getVisibility() == View.VISIBLE) { text.setVisibility(View.GONE); } else { text.setVisibility(View.VISIBLE); } });</code></pre> <h3>最终效果</h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/677e357c6baada35402723f4d2f6621d.gif"></p> <p style="text-align:center">TransitionSet</p> <h2><strong>Recolor 颜色渐变</strong></h2> <p>空间的背景色或者文字颜色改变的动画</p> <h3>布局</h3> <pre> <code class="language-java"><LinearLayout android:id="@+id/ll_container_recolor" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" android:visibility="visible"> <Button android:id="@+id/btn_recolor" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Recolor"/> <Button android:id="@+id/btn_normal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="Normal" android:visibility="visible"/> </LinearLayout></code></pre> <h3><strong>Activity 代码</strong></h3> <pre> <code class="language-java">final ViewGroup transitionsContainerRecolor = (ViewGroup) findViewById(R.id.ll_container_recolor); final Button btnRecolor = (Button) findViewById(R.id.btn_recolor); final Button btnNormal = (Button) findViewById(R.id.btn_normal); int green = getResources().getColor(R.color.green); int white = getResources().getColor(R.color.white); int grey = getResources().getColor(R.color.grey); btnRecolor.setOnClickListener(v -> { TransitionManager.beginDelayedTransition(transitionsContainerRecolor, new Recolor()); // btnRecolor.setBackgroundColor(visible ? green : white); // 无动画效果 btnRecolor.setTextColor(visible ? white : grey); // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { // btnRecolor.setBackground(new ColorDrawable(visible ? green : white)); // } else { btnRecolor.setBackgroundDrawable(new ColorDrawable(visible ? green : white)); // } visible = !visible; }); btnNormal.setOnClickListener(v -> { btnNormal.setBackgroundColor(visible ? green : white); btnNormal.setTextColor(visible ? white : green); visible = !visible; });</code></pre> <p>注意: btnRecolor.setBackgroundColor(visible ? green : white); // 无动画效果 通过 setBackgroundColor 背景色时没有动画效果,可以使用 setBackground , setBackgroundDrawable</p> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/b141153f1968cb3f782b61858bee5e85.gif"></p> <p style="text-align:center">ReColor</p> <h2><strong>Rotate 旋转</strong></h2> <h3><strong>布局</strong></h3> <pre> <code class="language-java"><LinearLayout android:id="@+id/ll_container_rotate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" android:visibility="visible"> <Button android:id="@+id/btn_rotate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Rotate"/> <ImageView android:id="@+id/iv_rotate" android:layout_width="40dp" android:layout_height="40dp" android:layout_marginTop="10dp" android:src="@drawable/ic_clear_black_24dp" android:visibility="visible"/> </LinearLayout></code></pre> <h3><strong>Activity 代码</strong></h3> <pre> <code class="language-java">final ViewGroup transitionsContainerRotate = (ViewGroup) findViewById(R.id.ll_container_rotate); final Button btnRotate = (Button) findViewById(R.id.btn_rotate); ImageView ivRotate = (ImageView) findViewById(R.id.iv_rotate); btnRotate.setOnClickListener(v -> { TransitionManager.beginDelayedTransition(transitionsContainerRotate, new Rotate()); ivRotate.setRotation(isRotated ? 0 : 135); isRotated = !isRotated; });</code></pre> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/3cc750a908fae12b6f0cda9310d80541.gif"></p> <p style="text-align:center">Rotate</p> <h2><strong>ChangeText 文字改变时动画</strong></h2> <h3><strong>布局</strong></h3> <pre> <code class="language-java"><LinearLayout android:id="@+id/ll_container_change_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" android:visibility="visible"> <Button android:id="@+id/btn_change_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ChangeText"/> <TextView android:id="@+id/tv_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Transitions are awesome!" android:visibility="visible"/> </LinearLayout></code></pre> <h3><strong>Activity 代码</strong></h3> <pre> <code class="language-java">final LinearLayout transitionsContainerChangeText = (LinearLayout) findViewById(R.id.ll_container_change_text); final TextView tvText = (TextView) findViewById(R.id.tv_text); final Button btnChangeText = (Button) findViewById(R.id.btn_change_text); String secText = " Second Text"; String firstText = "First Text"; btnChangeText.setOnClickListener(v -> { TransitionManager.beginDelayedTransition(transitionsContainerChangeText, new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN)); tvText.setText(isFirstText ? secText : firstText); isFirstText = !isFirstText; });</code></pre> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/16bccb8531c88a6c79cb489689eea533.gif"></p> <p style="text-align:center">ChangeText</p> <h2><strong>TransitionName 制作 打乱动画</strong></h2> <p>使用场景:</p> <ol> <li> <p>需要动态生成 ViewGroup 的子 View , 并且子 View 内容需要更新时</p> </li> <li> <p>需要制作随机时</p> </li> </ol> <h3><strong>布局</strong></h3> <p>注意:这里把 Button 移到了 LinearLayout 的外面,原因是一会创建子 View 的时候会先删除所有子 View(Button 也会被删除)。</p> <pre> <code class="language-java"><Button android:id="@+id/btn_transition_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="打乱"/> <LinearLayout android:id="@+id/ll_container_transition_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" android:visibility="visible"></code></pre> <h3><strong>Activity 代码</strong></h3> <pre> <code class="language-java">// TransitionName 做打乱动画 final LinearLayout transitionsContainerTransitionName = (LinearLayout) findViewById(R.id.ll_container_transition_name); final Button btnTransitionName = (Button) findViewById(R.id.btn_transition_name); LayoutInflater inflater = LayoutInflater.from(this); ArrayList<String> titles = new ArrayList<>(); for (int i = 0; i < 4; i++) { titles.add(String.format(Locale.CHINA, "Item %d", i)); } createViews(inflater, transitionsContainerTransitionName, titles); btnTransitionName.setOnClickListener(v -> { TransitionManager.beginDelayedTransition(transitionsContainerTransitionName, new ChangeBounds()); Collections.shuffle(titles); createViews(inflater, transitionsContainerTransitionName, titles); }); // 独立的方法 // In createViews we should provide transition name for every view. private static void createViews(LayoutInflater inflater, ViewGroup layout, List<String> titles) { layout.removeAllViews(); for (String title : titles) { TextView textView = (TextView) inflater.inflate(R.layout.text_view, layout, false); textView.setText(title); TransitionManager.setTransitionName(textView, title); layout.addView(textView); } }</code></pre> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/52f66efc165533e94b6c14b6b041c2a1.gif"></p> <p style="text-align:center">TransitionName</p> <h2><strong>Explode and Propagation 爆炸和传播</strong></h2> <p>爆炸效果,和移动过渡动画比较相似,不过子 View 的移动方向是由其所在的位置决定的。子 View 的移动方向需要通过计算得到(通过 setEpicenterCallback 方法)</p> <h3><strong>关键代码</strong></h3> <p>这个例子使用 RecyclerView 和 GridLayoutManager 做基本布局,点击里面的 item 让其消失。</p> <pre> <code class="language-java">public void onClick(View clickedView) { // save rect of view in screen coordinates final Rect viewRect = new Rect(); clickedView.getGlobalVisibleRect(viewRect); // create Explode transition with epicenter Transition explode = new Explode() .setEpicenterCallback(new Transition.EpicenterCallback() { @Override public Rect onGetEpicenter(Transition transition) { return viewRect; } }); explode.setDuration(1000); TransitionManager.beginDelayedTransition(recyclerView, explode); // remove all views from Recycler View recyclerView.setAdapter(null); }</code></pre> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/0d30164aaafc276d93b45e4fd417d873.gif"></p> <p style="text-align:center">Exlode</p> <h2><strong>ChangeImageTransform</strong></h2> <h2><strong>Path (Curved) motion 路径过渡动画</strong></h2> <p>所有的过渡动画都需要两个值:起始值和结束值</p> <p>比如:通过 ChangeBounds 来改变 view 的位置,通过 setPathMotion 来提供路径</p> <h3><strong>布局代码</strong></h3> <pre> <code class="language-java"><FrameLayout android:id="@+id/fl_container_path_motion" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/btn_path_motion" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Path Motion"/> </FrameLayout></code></pre> <h3><strong>Activity 代码</strong></h3> <pre> <code class="language-java">// path motion 路径过渡动画 final FrameLayout transitionsContainerPathMotion = (FrameLayout) findViewById(R.id.fl_container_path_motion); Button btnPathMotion = (Button) findViewById(R.id.btn_path_motion); btnPathMotion.setOnClickListener(v -> { TransitionManager.beginDelayedTransition(transitionsContainerPathMotion, new ChangeBounds().setPathMotion(new ArcMotion()).setDuration(500)); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) btnPathMotion.getLayoutParams(); params.gravity = isReturnAnimation ? (Gravity.LEFT | Gravity.TOP) : (Gravity.BOTTOM | Gravity.RIGHT); btnPathMotion.setLayoutParams(params); isReturnAnimation = !isReturnAnimation; });</code></pre> <p>这里通过 LayoutParams 来控制 Button 在其父控件中的位置。</p> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/957ce02954d8a587a69e5e9c6204334c.gif"></p> <p style="text-align:center">Path Motion</p> <h2><strong>Targets 设置动画的目标对象</strong></h2> <h3><strong>小总结</strong></h3> <p>定义动画:</p> <pre> <code class="language-java">TransitionManager.beginDelayedTransition( viewGroup, transition);</code></pre> <p>默认情况下,这里的 transition 动画会作用于 viewGroup 中的所有子 View</p> <p>当我们需要在一个 ViewGroup 中定义多个动画,作用于不同的子 View 该如何做?</p> <h3><strong>比如 让一个 TextView 移动, 另一个 TextView 淡出</strong></h3> <h3><strong>布局</strong></h3> <pre> <code class="language-java"><LinearLayout android:id="@+id/ll_container_target" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" android:visibility="visible"> <Button android:id="@+id/btn_target" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Target"/> <TextView android:id="@+id/tv_target_fade" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Transitions are awesome fade!" android:visibility="visible"/> <TextView android:id="@+id/tv_target_slide" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Transitions are awesome slide!" android:visibility="visible"/> </LinearLayout></code></pre> <h3><strong>Activity 代码</strong></h3> <pre> <code class="language-java">// Targets 设置动画的目标对象 final LinearLayout transitionsContainerTarget = (LinearLayout) findViewById(R.id.ll_container_target); final Button btnTarget = (Button) findViewById(R.id.btn_target); final TextView tvFade = (TextView) findViewById(R.id.tv_target_fade); final TextView tvSlide = (TextView) findViewById(R.id.tv_target_slide); btnTarget.setOnClickListener(v -> { Slide slide = new Slide(Gravity.RIGHT); slide.excludeTarget(tvFade, true); Fade fade = new Fade(); fade.excludeTarget(tvSlide, true); TransitionSet transitionSet = new TransitionSet() .addTransition(slide) .addTransition(fade); TransitionManager.beginDelayedTransition(transitionsContainerTarget, transitionSet); if (tvFade.getVisibility() == View.VISIBLE) { tvFade.setVisibility(View.GONE); tvSlide.setVisibility(View.GONE); } else { tvFade.setVisibility(View.VISIBLE); tvSlide.setVisibility(View.VISIBLE); } });</code></pre> <h3><strong>最终效果</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/c4e18a8ba5b133fc77ec56cc360c633e.gif"></p> <p style="text-align:center">Target处理</p> <h3><strong>transition 其他有关 target 方法</strong></h3> <p>Methods to add target:</p> <ul> <li>addTarget(View target) — view itself</li> <li>addTarget(int targetViewId) — id of view</li> <li>addTarget(String targetName) — do you remember about method TransitionManager.setTransitionName?</li> <li>addTarget(Class targetType) — for example android.widget.TextView.class</li> </ul> <p>To remove target:</p> <ul> <li>removeTarget(View target)</li> <li>removeTarget(int targetId)</li> <li>removeTarget(String targetName)</li> <li>removeTarget(Class target)</li> </ul> <p>To exclude some views:</p> <ul> <li>excludeTarget(View target, boolean exclude)</li> <li>excludeTarget(int targetId, boolean exclude)</li> <li>excludeTarget(Class type, boolean exclude)</li> <li>excludeTarget(Class type, boolean exclude)</li> </ul> <p>And for excluding all children of some ViewGroup:</p> <ul> <li>excludeChildren(View target, boolean exclude)</li> <li>excludeChildren(int targetId, boolean exclude)</li> <li>excludeChildren(Class type, boolean exclude)</li> </ul> <h2><strong>使用 xml 创建 Transition</strong></h2> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:app="http://schemas.android.com/apk/res-auto" app:transitionOrdering="together" app:duration="400"> <changeBounds/> <changeImageTransform/> <fade app:fadingMode="fade_in" app:startDelay="200"> <targets> <target app:targetId="@id/transition_title"/> </targets> </fade> </transitionSet></code></pre> <p>使用</p> <pre> <code class="language-java">TransitionInflater.from(getContext()).inflateTransition(R.anim.my_the_best_transition);</code></pre> <h2><strong>Activity and Fragment transitions</strong></h2> <h2><strong>Custom Transitions</strong></h2> <p>参考:</p> <ul> <li><a href="/misc/goto?guid=4959722824848642100" rel="nofollow,noindex">Animate all the things. Transitions in Android</a></li> <li><a href="/misc/goto?guid=4959722824946308584" rel="nofollow,noindex">原作者github</a></li> </ul> <p> </p> <p>来自:http://www.jianshu.com/p/89a9cdde42ae</p> <p> </p>