实现两个场景的变换,翻转动画

作者:新葡京简介

新葡京8455 1iu

前言

前段时间公司把登录、注册页面换成在一个页面通过点击翻转一个视图,视图的一个面做登录,一个页面做注册。这里简单的写了个雏形demo,其实也非常简单。

项目的任务,需要实现一个类似于下图的翻转动画,图片在翻转的同时,还要进行改变。

诞生于4.4的transition框架为在不同的UI状态之间产生动画效果提供了非常方便的API。5.0中Activity和Fragment 转场变换也是建立在Transitions框架的新特性之上的。

将一个view绕y轴旋转180度是这样的:

正文

其实就一个原理,那就是通过CATransform3D实现view的翻转。

自定义一个view,在这个view上面再放两个view,一个我是命名为topView,另外一个命名为bottomView,topView为首次进来展示给我们的view;

新葡京8455 2

topView放在最前面

新葡京8455 3

bottomview放在最下面

先把bottomView以y轴旋转180度,这样等会整个view旋转过来的时候bottomview才不会是左右倒起的。并且先隐藏。

新葡京8455 4

旋转动画

旋转到一半的时候,整体view垂直于整个屏幕,此时bottom,topview交换隐藏、显示。就这样简单的动画效果就出来了 ,很简单,大神勿喷。demo

新葡京8455 5

该框架主要基于两个概念:scenes和transitions

新葡京8455 6旋转.gif

目标动画

1. Secene

Transition Framework 核心就是根据Scene的不同帮助开发者们自动生成动画

官方文档A scene represents the collection of values that various properties in the View hierarchy will have when the scene is applied. A Scene can be configured to automatically run a Transition when it is applied, which will animate the various property changes that take place during the scene change.

通俗的解释就是这个类存储着一个根view下的各种view的属性。

创建一个 Scene有两种方法

// sceneRoot是Scene的 Container,也可以说是它的根布局1. Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) ;2. new Scene(ViewGroup sceneRoot, View layout);

例子如下:

 protected Scene scene1; protected Scene scene2; protected void initScene(@IdRes int sceneRoot, @LayoutRes int scene1_layout, @LayoutRes int scene2_layout) { ViewGroup sceneRoot= (ViewGroup) findViewById; scene1= Scene.getSceneForLayout(sceneRoot,scene1_layout,this); scene2=Scene.getSceneForLayout(sceneRoot,scene2_layout,this); TransitionManager.go; //先把初始状态设置为scene1 }

或者

 protected Scene scene1; protected Scene scene2; ViewGroup sceneRoot=findViewById(R.id.rootView); View view1= LayoutInflater.from.inflate(R.layout.changeclipbounds_scene,null); View view2= LayoutInflater.from.inflate(R.layout.changeclipbounds_scene,null); ImageView iv1=view1.findViewById(R.id.imageView); ImageView iv2=view2.findViewById(R.id.imageView); iv1.setClipBounds(new Rect(0,0,100,100)); iv2.setClipBounds(new Rect(100,100,200,200)); scene1=new Scene(sceneRoot,view1); scene2=new Scene(sceneRoot,view2); TransitionManager.go;//先把初始状态设置为scene1

sceneRoot 在动画开始时,会将sceneRoot中的所有子View都remove掉,然后在sceneRoot 中加载我们的end Scene。

所以,对于end Scene,如果是通过代码new Scene(mSceneRoot, view)创建的Scene其实对于view是有要求的:view是没有parentview的,不然在addview的时候会报错验证代码如下:

//测试该段代码 LinearLayout container=new LinearLayout(appContext); View view= LayoutInflater.from(appContext).inflate(R.layout.layout_temp,null); FrameLayout frameLayout=new FrameLayout(appContext); frameLayout.addView; container.addView;//logjava.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.at android.view.ViewGroup.addViewInner(ViewGroup.java:4917)at android.view.ViewGroup.addView(ViewGroup.java:4748)

//scene1.xml<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:andro xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/bangtang" tools:layout_editor_absoluteX="128dp" tools:layout_editor_absoluteY="58dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="58dp" app:layout_constraintEnd_toEndOf="parent" /> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/shengdanlaoren" tools:layout_editor_absoluteX="49dp" tools:layout_editor_absoluteY="226dp" app:layout_constraintTop_toTopOf="@ id/imageView3" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="49dp" /> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/xueren" tools:layout_editor_absoluteX="210dp" tools:layout_editor_absoluteY="226dp" android:layout_marginTop="40dp" app:layout_constraintTop_toBottomOf="@ id/imageView1" android:layout_marginEnd="46dp" app:layout_constraintEnd_toEndOf="parent" /></android.support.constraint.ConstraintLayout>//scene2.xml<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:andro xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/shengdanlaoren" tools:layout_editor_absoluteX="128dp" tools:layout_editor_absoluteY="58dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="58dp" app:layout_constraintEnd_toEndOf="parent" /> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/xueren" tools:layout_editor_absoluteX="49dp" tools:layout_editor_absoluteY="226dp" app:layout_constraintTop_toTopOf="@ id/imageView1" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="49dp" /> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/bangtang" tools:layout_editor_absoluteX="210dp" tools:layout_editor_absoluteY="226dp" android:layout_marginTop="40dp" app:layout_constraintTop_toBottomOf="@ id/imageView2" android:layout_marginEnd="46dp" app:layout_constraintEnd_toEndOf="parent" /></android.support.constraint.ConstraintLayout>

新葡京8455 7scene1和scene2的效果对比图

Activity代码:

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_base_scene); Button btn=findViewById; btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { clickChange; ViewGroup sceneRoot= (ViewGroup) findViewById; scene1= Scene.getSceneForLayout(sceneRoot,scene1_layout,this); scene2=Scene.getSceneForLayout(sceneRoot,scene2_layout,this); TransitionManager.go;//先把初始状态设置为scene1 } protected void clickChange(){ TransitionManager.go(isScene1?scene2:scene1,getTransition; isScene1=!isScene1; } Transition getTransition() { return new ChangeBounds(); }

getTransition方法提供的是两个Scene的切换效果,下面会讲到,所以先不管它。最后出来的效果是:

新葡京8455 8新葡京8455 9scene1和scene2的 Id 对比图

根据效果图和Id对比图可以发现切换时是相同id的View之间互相切换,那么如果两个Scene之间View的Id不对等(id不相同 或者 一多一少)呢?

新葡京8455 10View的Id不对等

这个时候可以试着运行一下就会发现:仍然可以实现两个场景的切换,但是切换过程没有任何动画效果(即Transition没有起作用),就只是简单的替换。

上面例子getTransition方法返回ChangeBounds(),其实就是一种Transition的实现,下面来详细了解一下:

正面是:

最开始,直接使用UIView的动画。

2. transitions

当一个Scene发生改变时,transition主要负责:

  1. 捕捉每个View在开始场景和结束场景时的状态。
  2. 根据两个场景之间的区别创建一个Animator

新葡京8455 11正面

代码很简单:

2.1.1 ChangeBounds :检测view的位置边界创建移动和缩放动画

捕获共享元素的layout bound,然后播放layout bound变化动画。ChangeBounds 是共享元素变换中用的最多的,因为前后两个activity中共享元素的大小和位置一般都是不同的。

根据始末位置画出Path,再根据Path创造Animator

新葡京8455 12GIFchangebounds.gif

/** * This transition captures the layout bounds of target views before and after * the scene change and animates those changes during the transition. * * <p>A ChangeBounds transition can be described in a resource file by using the * tag <code>changeBounds</code>, using its attributes of * {@link android.R.styleable#ChangeBounds} along with the other standard * attributes of {@link android.R.styleable#Transition}.</p> */public class ChangeBounds extends Transition { ...}

反面是:

- (void)viewAnimation {

[UIView transitionWithView:_imageView duration:2*actionSeconds options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{

if (_imageView.tag == 101) {

_imageView.image = [UIImage imageNamed:@"logo"];

_imageView.tag = 102;

} else {

_imageView.image = [UIImage imageNamed:@"icon"];

_imageView.tag = 101;

}

}completion:^(BOOL finished) {

[self viewAnimation];

}];

}

2.1.2 ChangeClipBounds :检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果

捕获共享元素clip bounds,然后播放clip bounds变化动画。

新葡京8455 13GIFchangeClipBounds.gif

/** * ChangeClipBounds captures the {@link android.view.View#getClipBounds()} before and after the * scene change and animates those changes during the transition. */public class ChangeClipBounds extends Transition { ... @Override public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, ... Rect start =  startValues.values.get(PROPNAME_CLIP); Rect end =  endValues.values.get(PROPNAME_CLIP); boolean endIsNull = end == null; ... endValues.view.setClipBounds; RectEvaluator evaluator = new RectEvaluator(new Rect; ObjectAnimator animator = ObjectAnimator.ofObject(endValues.view, "clipBounds", evaluator, start, end); if (endIsNull) { final View endView = endValues.view; animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { endView.setClipBounds; } }); } return animator; }}

新葡京8455 14反面

但是,这样实现的动画,图片在翻转的时候,背景会变暗。

2.1.3 ChangeImageTransform :检测ImageView(这里是专指ImageView)的尺寸,位置以及ScaleType,并创建相应动画。

捕获共享元素(ImageView)的transform matrices 属性,然后播放ImageViewtransform matrices 属性变化动画。与ChangeBounds相结合,这个变换可以让ImageView在动画中高效实现大小,形状或者ImageView.ScaleType

属性平滑过度。

/** * This Transition captures an ImageView's matrix before and after the * scene change and animates it during the transition. * * <p>In combination with ChangeBounds, ChangeImageTransform allows ImageViews * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents * smoothly.</p> */public class ChangeImageTransform extends Transition { ... /** * Creates an Animator for ImageViews moving, changing dimensions, and/or changing * {@link android.widget.ImageView.ScaleType}. * * @param sceneRoot The root of the transition hierarchy. * @param startValues The values for a specific target in the start scene. * @param endValues The values for the target in the end scene. * @return An Animator to move an ImageView or null if the View is not an ImageView, * the Drawable changed, the View is not VISIBLE, or there was no change. */ @Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null) { return null; } Rect startBounds =  startValues.values.get(PROPNAME_BOUNDS); Rect endBounds =  endValues.values.get(PROPNAME_BOUNDS); if (startBounds == null || endBounds == null) { return null; } Matrix startMatrix =  startValues.values.get(PROPNAME_MATRIX); Matrix endMatrix =  endValues.values.get(PROPNAME_MATRIX); boolean matricesEqual = (startMatrix == null && endMatrix == null) || (startMatrix != null && startMatrix.equals(endMatrix)); if (startBounds.equals(endBounds) && matricesEqual) { return null; } ImageView imageView = (ImageView) endValues.view; Drawable drawable = imageView.getDrawable(); int drawableWidth = drawable.getIntrinsicWidth(); int drawableHeight = drawable.getIntrinsicHeight(); ObjectAnimator animator; if (drawableWidth == 0 || drawableHeight == 0) { animator = createNullAnimator(imageView); } else { if (startMatrix == null) { startMatrix = Matrix.IDENTITY_MATRIX; } if (endMatrix == null) { endMatrix = Matrix.IDENTITY_MATRIX; } ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix); animator = createMatrixAnimator(imageView, startMatrix, endMatrix); } return animator; } ...}

有时我们可能需要将背面设置成不同的图片或view,如下:

新葡京8455 15

2.1.4 ChangeScroll :滑动的属性发生了变化
/** * This transition captures the scroll properties of targets before and after * the scene change and animates any changes. */public class ChangeScroll extends Transition { ... @Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null) { return null; } final View view = endValues.view; int startX =  startValues.values.get(PROPNAME_SCROLL_X); int endX =  endValues.values.get(PROPNAME_SCROLL_X); int startY =  startValues.values.get(PROPNAME_SCROLL_Y); int endY =  endValues.values.get(PROPNAME_SCROLL_Y); Animator scrollXAnimator = null; Animator scrollYAnimator = null; if (startX != endX) { view.setScrollX; scrollXAnimator = ObjectAnimator.ofInt(view, "scrollX", startX, endX); } if (startY != endY) { view.setScrollY; scrollYAnimator = ObjectAnimator.ofInt(view, "scrollY", startY, endY); } return TransitionUtils.mergeAnimators(scrollXAnimator, scrollYAnimator); }}

新葡京8455 16双面view.gif

view动画

2.1.5 ChangeTransform :检测view的scale和rotation创建缩放和旋转动画

捕获共享元素的缩放与旋转属性 ,然后播放缩放与旋转属性变化动画。

新葡京8455 17GIFchangetransform.gif

/** * This Transition captures scale and rotation for Views before and after the * scene change and animates those changes during the transition. * * A change in parent is handled as well by capturing the transforms from * the parent before and after the scene change and animating those during the * transition. */public class ChangeTransform extends Transition { ... @Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null || !startValues.values.containsKey(PROPNAME_PARENT) || !endValues.values.containsKey(PROPNAME_PARENT)) { return null; } ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); boolean handleParentChange = mReparent && !parentsMatch(startParent, endParent); Matrix startMatrix =  startValues.values.get(PROPNAME_INTERMEDIATE_MATRIX); if (startMatrix != null) { startValues.values.put(PROPNAME_MATRIX, startMatrix); } Matrix startParentMatrix =  startValues.values.get(PROPNAME_INTERMEDIATE_PARENT_MATRIX); if (startParentMatrix != null) { startValues.values.put(PROPNAME_PARENT_MATRIX, startParentMatrix); } // First handle the parent change: if (handleParentChange) { setMatricesForParent(startValues, endValues); } // Next handle the normal matrix transform: ObjectAnimator transformAnimator = createTransformAnimator(startValues, endValues, handleParentChange); if (handleParentChange && transformAnimator != null && mUseOverlay) { createGhostView(sceneRoot, startValues, endValues); } return transformAnimator; } ... /** * PathAnimatorMatrix allows the translations and the rest of the matrix to be set * separately. This allows the PathMotion to affect the translations while scale * and rotation are evaluated separately. */ private static class PathAnimatorMatrix { ... }}

这种效果如何实现?

这是因为,在翻转的时候,imageView的alpha值会变化。

2.2.1 TransitionManager.go(Scene scene, Transition transition)

直接在切换Scene时,设置效果

我的思路:

在一个透明view上依次放两个view,下面那个是bottomView,上面那个是topView,将bottomView旋转180度。翻转的时候,将透明view旋转180度。

为了图片背景不变暗,所以决定使用layer层的动画。

2.2.2 根据设置的 transition文件 自动生成Animator效果

在XML中或者在代码中设置,举个例子就是: res->transition文件下创建transition文件

//xxx.xml Fade、Slide、Explode :渐入、滑动、爆炸<?xml version="1.0" encoding="utf-8"?><transitionSet xmlns:andro><changeBounds/> <explode/> <fade/></transitionSet>//或者直接new Fade()等等

新葡京8455 18fade_and_slide.gif

设置 transition文件 自动生成Animator效果,通常在两种情况下使用:

  1. 切换Activity/Fragment时设置场景切换效果(这个部分在后续的文章会讲到)
  2. beginDelayedTransition()设置延时动画

为什么要将bottomView旋转180度?

如果不旋转,效果是这样的:

新葡京8455 19图片是反的.gif

图片是反的。为了能在旋转180度之后得到正确的图片,只有先将它旋转180度,这样两次旋转180后就是正的了。这就是为什么要先将bottomView旋转180度的原因。

代码如下:

beginDelayedTransition

为每一个结束关键帧都专门设置一个xml的Scene布局岂不是很麻烦?如图所示,点击每个图片的切换效果都差不多,那是不是就要设置4个Scene,然后对应的点击某个View就跳转到某个Scene呢?

新葡京8455 20新葡京8455,GIFbegindelayed.gif所以,就有了 : 延时动画 :beginDelayedTransition()

例子:(四个ImageView,点击每一个,那个就会放大,其他三个就会消失)

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:andro xmlns:app="http://schemas.android.com/apk/res-auto" android: android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android: android:layout_width="120dp" android:layout_height="120dp" android:layout_marginStart="72dp" android:layout_marginTop="71dp" app:srcCompat="@drawable/bangtang" android:layout_alignParentTop="true" android:layout_alignParentStart="true" /> <ImageView android: android:layout_width="120dp" android:layout_height="120dp" app:srcCompat="@drawable/shengdanshu" android:layout_alignTop="@ id/imageView1" android:layout_alignParentEnd="true" android:layout_marginEnd="30dp" /> <ImageView android: android:layout_width="120dp" android:layout_height="120dp" app:srcCompat="@drawable/xueren" android:layout_alignTop="@ id/imageView4" android:layout_alignStart="@ id/imageView1" /> <ImageView android: android:layout_width="120dp" android:layout_height="120dp" app:srcCompat="@drawable/xunlu" android:layout_marginTop="24dp" android:layout_below="@ id/imageView2" android:layout_alignStart="@ id/imageView2" /></RelativeLayout>// explode_fade_changebounds.xml<?xml version="1.0" encoding="utf-8"?><transitionSet xmlns:andro><changeBounds/> <explode/> <fade/></transitionSet>

public class BeginDelayedActivity extends AppCompatActivity implements View.OnClickListener{ ImageView iv1,iv2,iv3,iv4; ViewGroup rootView; boolean isBig=false; int primarySize; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_begin_delayed); rootView=findViewById(R.id.rootView); iv1=findViewById(R.id.imageView1); iv2=findViewById(R.id.imageView2); iv3=findViewById(R.id.imageView3); iv4=findViewById(R.id.imageView4); primarySize=iv1.getLayoutParams().width; iv1.setOnClickListener; iv2.setOnClickListener; iv3.setOnClickListener; iv4.setOnClickListener; } @Override public void onClick { //start scene 是当前的scene TransitionManager.beginDelayedTransition(rootView, TransitionInflater.from.inflateTransition(R.transition.explode_fade_changebounds)); //next scene 此时通过代码已改变了scene statue changeScene; } private void changeScene { changeSize; changeVisibility(iv1,iv2,iv3,iv4); v.setVisibility(View.VISIBLE); } private void changeSize { isBig=!isBig; ViewGroup.LayoutParams layoutParams = v.getLayoutParams(); if{ layoutParams.width=(1.5*primarySize); layoutParams.height=(1.5*primarySize); }else { layoutParams.width=primarySize; layoutParams.height=primarySize; } v.setLayoutParams(layoutParams); } /** * VISIBLE和INVISIBLE状态切换 * @param ivs */ private void changeVisibility(ImageView ... ivs) { for (View view:ivs){ view.setVisibility(view.getVisibility()==View.VISIBLE?View.INVISIBLE:View.VISIBLE); } }}

我们在这做详细的分析 :假设最开始每个view都是可见的:

  1. 当点击事件发生之后调用TransitionManager的beginDelayedTransition()方法,并且传递了mRootView和一个Fade对象最为参数。之后,framework会立即调用transition类的captureStartValues()方法为每个view保存其当前的可见状态(visibility)。
  2. 当beginDelayedTransition返回之后,在上面的代码中将每个view设置为不可见。
  3. 在接下来的显示中framework会调用transition类的captureEndValues()方法,记录每个view最新的可见状态。
  4. 接着,framework调用transition的createAnimator()方法。transition会分析每个view的开始和结束时的数据发现view在开始时是可见的,结束时是不可见的。Fade(transition的子类)会利用这些信息创建一个用于把view的alpha属性变为0的AnimatorSet,并且将此AnimatorSet对象返回。
  5. framework会运行返回的Animator,导致所有的View都渐渐消失。

这样就达到了:通过属性的改变,就发生动画...达到了代码的精简

切换两个view的时机?

当动画进行到一半的时候,将bottomView移到上面。

- (void)simpleLyerRotation

{

CABasicAnimation* basicAnimation;

basicAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];

basicAnimation.toValue = [NSNumber numberWithFloat: M_PI ];

basicAnimation.duration = 1.2*actionSeconds;

basicAnimation.cumulative = NO;

basicAnimation.repeatCount = 1;

basicAnimation.fillMode = kCAFillModeForwards;

basicAnimation.removedOnCompletion = NO;

basicAnimation.delegate = self;

[_imageView.layer addAnimation:basicAnimation forKey:@"rotationAnimation"];

[self performSelector:@selector(changeImg) withObject:nil afterDelay:0.6 * actionSeconds];

}

- (void)changeImg

{

if (_imageView.tag == 101) {

_imageView.image = [UIImage imageNamed:@"logo"];

_imageView.tag = 102;

} else {

_imageView.image = [UIImage imageNamed:@"icon"];

_imageView.tag = 101;

}

}

3. 总结

  1. 创建两个Scene(起始关键帧 和 结束关键帧)
  2. 利用系统内置的或自定义的transitions创建Animator
  3. 开启动画

这样就是实现了简单的一个动画效果,这个过程我们只关心 开始状态和结束状态,并为状态的变化规定了变化规律(transitions),然后自动帮我们生成效果

新葡京8455 21transitions_diagram.png

由此可见 :transition框架的两个主要优点第一、Transitions抽象和封装了属性动画,Animator的概念对开发者来说是透明的,因此它极大的精简了代码量。开发者所做的所有事情只是改变一下view前后的状态数据,Transition就会自动的根据状态的区别去生成动画效果。第二、不同场景之间变换的动画效果可以简单的通过使用不同的Transition类来改变

基础的介绍就先讲到这里!!!上面讲到了基础的 TransitionManager.go()beginDelayedTransition() 开启动画。其实还有一种开启方式更为常见 :setEnterTransition()/setSharedElementEnterTransition() //当然,这得看下回分解

Transition系列文章一、初识Transition—实现两个场景的变换二、番外篇 Transition之ViewOverlay三、定义 界面指定元素 或界面间共享元素 的转场动画基础四、Content Transition实现非共享元素转场五、SharedElementTransition之Activity间的转场六、SharedElementTransition之Fragment间的转场七、番外篇- 自定义Visibility八、5.0以下实现共享转场

本篇参考:Activity和Fragment Transition介绍Android 过渡(Transition)动画解析之基础篇animatedTransitionsLearn-master

代码实现:

这是一个自定义view

.h文件:

#import <UIKit/UIKit.h>@interface TwoSidedView : UIView/** 顶部view */@property (nonatomic, strong) UIView *topView;/** 底部view */@property (nonatomic, strong) UIView *bottomView;/** 翻转 @param duration 翻转动画所需时间 @param completion 动画结束后的回调 */- turnWithDuration:(NSTimeInterval)duration completion:completion;@end

.m文件:

#import "TwoSidedView.h"@implementation TwoSidedView/** 设置顶部view */- setTopView:topView { _topView = topView; [self addSubview:topView]; [self bringSubviewToFront:_topView];}/** 设置底部view */- setBottomView:bottomView { _bottomView = bottomView; [self addSubview:_bottomView]; [self sendSubviewToBack:_bottomView]; // 翻转180度 CATransform3D transform = CATransform3DMakeRotation(M_PI, 0, 1, 0); _bottomView.layer.transform = transform;}/** 翻转 @param duration 翻转动画所需时间 @param completion 动画结束后的回调 */- turnWithDuration:(NSTimeInterval)duration completion:completion{ if (!self.topView || !self.bottomView) { NSAssert(NO, @"未设置topView或bottomView"); } // 动画进行到一半的时候将bottomView移到上层 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (duration / 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self bringSubviewToFront:self.bottomView]; }); // 翻转180度 [UIView animateWithDuration:duration animations:^{ CATransform3D transform = CATransform3DMakeRotation(M_PI, 0, 1, 0); self.layer.transform = transform; } completion:^(BOOL finished) { if (completion) { completion(); } }];}@end

但是,效果有问题,翻转的时候,会转到反面去。

这里是demo

新葡京8455 22

2017/12/15更新

根据评论列表中的表鱼香肉丝_我鱼呢的建议,将翻转动画改为UIView提供的系统方法。代码如下:

#import "TwoSidedView.h"@implementation TwoSidedView { BOOL _isTurning; // 是否正在翻转 BOOL _isReversed; // 是否反面朝上}- (instancetype)initWithFrame:frame { if (self = [super initWithFrame:frame]) { _isTurning = NO; _isReversed = NO; } return self;}/** 设置顶部view */- setTopView:topView { _topView = topView; [self addSubview:_topView]; [self bringSubviewToFront:_topView];}/** 设置底部view */- setBottomView:bottomView { _bottomView = bottomView; [self addSubview:_bottomView]; [self sendSubviewToBack:_bottomView];}/** 翻转 @param duration 翻转动画所需时间 @param completion 动画结束后的回调 */- turnWithDuration:(NSTimeInterval)duration completion:completion{ if (!self.topView || !self.bottomView) { NSAssert(NO, @"未设置topView或bottomView"); } // 正在动画中不能重复执行 if (_isTurning) { return; } _isTurning = YES; if (_isReversed) { // 此时反面朝上 // 从反面翻转到正面 [UIView transitionFromView:self.bottomView toView:self.topView duration:duration options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) { !completion ?: completion(); _isTurning = NO; _isReversed = NO; }]; } else { // 此时正面朝上 // 从正面翻转到反面 [UIView transitionFromView:self.topView toView:self.bottomView duration:duration options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) { !completion ?: completion(); _isTurning = NO; _isReversed = YES; }]; }}@end

代码已更新到GitHub,感谢大家的建议。

简单layer动画

仔细分析之后,我发现,需要达到的功能是:图片先顺着旋转90°,接着再逆着旋转90°。这样,就不会显示反着的图片。

这样的话,就需要一个连续的动画效果。

使用CABasicAnimation,我无法实现这样的功能。后来,发现可以使用CAKeyframeAnimation,来创建这样的动画效果。

代码如下:

- (void)layerRotation  {

CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];

// 旋转角度, 其中的value表示图像旋转的最终位置

keyAnimation.values = [NSArray arrayWithObjects:

[NSValue valueWithCATransform3D:CATransform3DMakeRotation(0, 0,1,0)],

[NSValue valueWithCATransform3D:CATransform3DMakeRotation((M_PI/2), 0,1,0)],

[NSValue valueWithCATransform3D:CATransform3DMakeRotation(0, 0,1,0)],

nil];

keyAnimation.cumulative = NO;

keyAnimation.duration = 1.2 * actionSeconds;

keyAnimation.repeatCount = 1;

keyAnimation.removedOnCompletion = NO;

keyAnimation.delegate = self;

[_imageView.layer addAnimation:keyAnimation forKey:@"transform"];

[self performSelector:@selector(changeImg) withObject:nil afterDelay:0.6 * actionSeconds];

}

本文由新葡京8455发布,转载请注明来源

关键词: