Transition和共享元素

1        ViewOverlay

ViewOverlay是Android 4.3以后(API 18+)新增的一个类,它是view的最上面的一个透明的层,我们可以在这个层之上添加内容而不会影响到整个布局结构。这个层和我们的界面大小相同,可以理解成一个浮动在界面表面的二维空间。其实,ViewOverlay能做的事情RelativeLayout都能够实现,只是ViewOverlay提供了一种更友好的方式,它上面的任何view都不会响应onTouch()onClick()事件。最重要的是,利用ViewOverlay可以让某个view在任意的布局元区域播放动画,不管该view是否属于区域的子view,后面的Transition和共享元素都是在ViewOverlay之上实现的。

ViewOverlay是如何工作的呢?首先任何view我们都可以通过getOverlayView()方法获得ViewOverlay然后通过add()、remove()、clear()等方法对里面的子view进行操作。

如下图所示,就是使用ViewOverlay所达到的效果:


2        Scene

Scene(场景)被用来记录视图中所有view和它的属性值, API提供有两种方法可以创建场景,用来记录视图。

a.  直接用new Scene()的方法;

Scene mScene = new Scene(mSceneRoot, (ViewGroup) mSceneRoot.findViewById(R.id.container));

或者

Scene mScene = new Scene(mSceneRoot, findViewById(R.id.scene));

 

b.  使用静态方法;

Scene mScene = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, this);

 

注意:不管使用上面那种方式,都要使用到mSceneRoot(场景的根目录),而mSceneRoot在动画启动的时候,都会调用remove掉所有的子view,然后再加载结束场景里面的view,所以对于使用第一种方式创建场景的时候(特别是结束场景),一定要确保这个view是mSceneRoot的直接子view,或者这个view没有parentview,否则会报错。

3        Transition

3.1 使用Transition进行场景切换

首先,我们来看一个简单的例子,下方就是效果图:




这是一个简单的动画,三个图形按照顺时针的方向依次转动,如何实现的呢?以前的方法就是给每个view设置位移动画,而使用Transition的话,就会特别简单:

首先来定义一个activity,用来展示视图

<?xml version="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:tools="http://schemas.android.com/tools"
   
android:id="@+id/activity_change_scence"
   
android:layout_width="match_parent"
   
android:layout_height="match_parent"
   
android:paddingBottom="@dimen/activity_vertical_margin"
   
android:paddingLeft="@dimen/activity_horizontal_margin"
   
android:paddingRight="@dimen/activity_horizontal_margin"
   
android:paddingTop="@dimen/activity_vertical_margin"
   
tools:context="com.example.kriszhang.testtransition.ChangeScenceActivity"
>
   
<Button
       
android:layout_width="match_parent"
       
android:layout_height="wrap_content"
       
android:id="@+id/button2"
       
android:text="add target"
       
android:gravity="center"
/>
   
<Button
    
   android:layout_width="match_parent"
       
android:layout_height="wrap_content"
       
android:layout_below="@+id/button2"
       
android:layout_marginTop="5dp"
       
android:id="@+id/button"
       
android:text="change scene"
       
android:gravity="center"
/>

   
<!--这个View用来做动画的父布局-->
   
<FrameLayout
       
android:layout_width="match_parent"
       
android:layout_height="wrap_content"
       
android:layout_below="@+id/button"
       
android:id="@+id/rootView"
>


   
<include layout="@layout/scene1"></include>
   
</FrameLayout>

</
RelativeLayout>

 

定义一个起始布局,scene1.xml

<?xml version="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   
android:layout_width="match_parent"
   
android:layout_height="400dp"
>

   
<ImageView
       
android:id="@+id/image1"
       
android:layout_width="150dp"
       
android:layout_height="150dp"
       
android:layout_centerInParent="true"
       
android:src="@drawable/image1"
/>

   
<ImageView
    
   android:id="@+id/image3"
       
android:layout_width="150dp"
       
android:layout_height="150dp"
       
android:layout_alignParentLeft="true"
       
android:layout_below="@id/image1"
       
android:src="@drawable/image3"
/>

   
<ImageView
      
 android:id="@+id/image2"
       
android:layout_width="150dp"
       
android:layout_height="150dp"
       
android:layout_alignParentRight="true"
       
android:layout_below="@id/image1"
       
android:src="@drawable/image2"
/>
</
RelativeLayout>

 

定义一个结束时布局,scene2.xml

<?xml version="1.0" encoding="utf-8"?>
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="400dp">
  
    <ImageView
        android:id="@+id/image3"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerInParent="true"
        android:src="@drawable/image3" />
  
    <ImageView
        android:id="@+id/image2"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/image3"
        android:src="@drawable/image2" />
  
    <ImageView
        android:id="@+id/image1"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_alignParentRight="true"
        android:layout_below="@id/image3"
        android:alpha="20"
        android:src="@drawable/image1" />
</RelativeLayout>

 

现在开启动画

//加载Scene
  scene2 = Scene.getSceneForLayout(rootView, R.layout.scene2, this);
TransitionManager.go(scene2, new ChangeBounds());
 
    TransitionManager中的go()方法如果只传一个场景参数,如TransitionManager.go(scene2);系统会默认传入一个new AutoTransition(),当然我们也可以通过传入一个transition来控制整个场景动画的效果或者场景中某个view的动画效果,其中ChangeBoundsAutoTransition都是transition的子类,后面会详细讲到。

总的来说,就是先定义一个起始场景和结束场景(我们在以前的动画中称之为关键帧),然后通过TransitionManager的方法来进行场景切换。



3.2 使用Transition控制某个View的变化

很多时候,我们只需要改变当前已经展示的视图中的某个view,这时候就可以使用延迟动画。

a. Slide(划动)



//向右滑出
  Slide slide = new Slide(Gravity.RIGHT);
//设置滑动的时间
  slide.setDuration(1000);
TransitionManager.beginDelayedTransition(mViewGroup, slide);
text.setVisibility(visible ? View.VISIBLE : View.GONE);
  visible = !visible;

 

b. Fade(淡入淡出)


//默认是淡入淡出
  TransitionManager.beginDelayedTransition(mViewGroup);
  // TransitionManager.beginDelayedTransition(mViewGroup,new Fade());
  text.setVisibility(visible ? View.VISIBLE : View.GONE);
  visible = !visible;

 

c. Explode and Propagation (粒子扩散)

使用 Explode 可以做粒子扩散的效果,粒子扩散的中心点可以通过setEpicenterCallback 方法设定。具体扩散的效果可以通过TransitionPropagation 设定,TransitionPropagation 会计算每个动画的开始延迟时间。比如默认情况下Explode 使用的CircularPropagation,这个是一个圆形扩散效果,每个元素执行扩散动画的延迟时间是其距中心的距离决定的。我们使用setPropagation 方法就可以设置TransitionPropagation.



@Override
  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    // save rect of view in screen coordinates
    final Rect viewRect = new Rect();
//获取view所在屏幕坐标中的可是区域
    view.getGlobalVisibleRect(viewRect);
  
    // create Explode transition with epicenter
    Explode explode = new Explode();
    explode.setEpicenterCallback(new Transition.EpicenterCallback() {
        @Override
        public Rect onGetEpicenter(Transition transition) {
            return viewRect;
        }
    });
    explode.setDuration(1000);
    TransitionManager.beginDelayedTransition(gridView, explode);
    // remove all views from Recycler View
    gridView.setAdapter(null);
}

 

d. ChangeImageTransform(改变图片大小)

ChangeImageTransform可以对一个图片的矩阵信息进行变换,当我们改变ImageView的 scaleType 属性时,就非常有用。很多时候我们可以结合 ChangeBounds 来改变位置,大小以及 scaleType。



TransitionManager.beginDelayedTransition(transitionsContainer,newTransitionSet()
      .addTransition(newChangeBounds())
      .addTransition(newChangeImageTransform()));
  
  ViewGroup.LayoutParamsparams= imageView.getLayoutParams();
  params.height = expanded ?ViewGroup.LayoutParams.MATCH_PARENT :
      ViewGroup.LayoutParams.WRAP_CONTENT;
  imageView.setLayoutParams(params);
  
  imageView.setScaleType(expanded ?ImageView.ScaleType.CENTER_CROP :
      ImageView.ScaleType.FIT_CENTER);

 

e. Path (路径)

使用 setPathMotion 方法,可以在任意两点之间的位置变换做路径动画,比如使用 ChangeBounds 改变 View的位置:



TransitionManager.beginDelayedTransition(transitionsContainer,
      newChangeBounds().setPathMotion(newArcMotion()).setDuration(500));
  
  FrameLayout.LayoutParamsparams=(FrameLayout.LayoutParams) button.getLayoutParams();
  params.gravity = isReturnAnimation ?(Gravity.LEFT |Gravity.TOP):
      (Gravity.BOTTOM |Gravity.RIGHT);
  button.setLayoutParams(params);

 

与此同时,配置 Transitions 也非常容易,你可以给一些特殊目标的 View 指定 Transitions,仅仅只有它们才能有动画.

增加动画目标:

·        addTarget(View target) .view

·        addTarget(inttargetViewId). 通过viewid

·        addTarget(StringtargetName)  . TransitionManager
.setTransitionName
方法设定的标识符相对应。

·        addTarget(ClasstargetType) .类的类型,比如Android.widget.TextView.class

移除动画目标:

·        removeTarget(View target)

·        removeTarget(int targetId)

·        removeTarget(StringtargetName)

·        removeTarget(Class target)

排除不想做动画的view

·        excludeTarget(View target,boolean exclude)

·        excludeTarget(int targetId,boolean exclude)

·        excludeTarget(Class type,boolean exclude)

·        excludeTarget(Class type,boolean exclude)

排除某个 ViewGroup 的所有子 View

·        excludeChildren(Viewtarget, boolean exclude)

·        excludeChildren(inttargetId, boolean exclude)

·        excludeChildren(Class type,boolean exclude)

 

当然也可以使用xml文件来设置transition,需要将 Translation 资源放在 res/anim目录,例如:

<?xml version="1.0" encoding="utf-8"?>
  <transitionSetxmlns: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>
              <targetapp:targetId="@id/transition_title"/>
          </targets>
      </fade>
  </transitionSet>
  
  // And inflating:
  TransitionInflater.from(getContext()).inflateTransition(R.anim.my_the_best_transition);

 

我们还可以自己定义Transition,自定Transitions,我们需要实现三个方法:captureStartValuescaptureEndValues createAnimator.前面两个方法用来捕捉 view在转场前后的状态。

4        共享元素

在Android 5.0(API 21) 之后,基于Transition的动画被运用到Activity和Fragment控件之间的切换上。

4.1 Activity之间的切换

a.设置两个Activity的theme:(非必要,不使用的时候,目前没有发现有什么问题)
android:theme="@style/AppTheme"

 

<style name="AppTheme" parent="Theme.AppCompat">
    <!-- Customize your theme here. -->
    <item name="android:windowContentTransitions">true</item>
</style>

 

b. 在xml布局中需要共享元素的view都设置transitionName:
<ImageView
    android:id="@+id/detail_image"
    android:layout_width="120dp"
    android:layout_height="120dp"
    android:transitionName="@string/image_transition"/>

 

c. 启动activity:
/**
 * 使用共享元素启动activity
 * @param activity 从哪个页面跳转
 * @param person 需要传递的参数对象
 * @param view 设置为共享元素的控件
 * @param str 两个activity中共享元素的transitionName
 */
  public static void startActivity(Activity activity,Person person,View view,String str) {
    Intent intent = new Intent(activity, DetailActivity.class);
    intent.putExtra("person",person);
    Pair<View,String> pair =Pair.create(view,str);
    activity.startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(activity, pair).toBundle());
}

 

4.2   Fragment之间的切换

a. 设置进入和退出时的动画:
detailFragment.setSharedElementEnterTransition(new DetailTransition());
setExitTransition(new Fade());
detailFragment.setEnterTransition(new Fade());
detailFragment.setSharedElementReturnTransition(new DetailTransition());

 

 

b. 启动Fragment:
getActivity().getSupportFragmentManager().beginTransaction()
        .addSharedElement(holder.getImageView(), getResources().getString(R.string.image_transition))
          .replace(R.id.main_cl_container, detailFragment)
        .addToBackStack(null)
        .commit();

 

c. 共享元素的实现原理:

当Activity A 调用 Activity B ,发生的事件流如下:

1)Activity A调用startActivity(), Activity B被创建,测量,同时初始化为半透明的窗口和透明的背景颜色。

2)framework重新分配每个共享元素在B中的位置与大小,使其跟A中一模一样。之后,B的进入变换(enter transition)捕获到共享元素在B中的初始状态。

3)framework重新分配每个共享元素在B中的位置与大小,使其跟B中的最终状态一致。之后,B的进入变换(enter transition)捕获到共享元素在B中的结束状态。

4)B的进入变换(enter transition)比较共享元素的初始和结束状态,同时基于前后状态的区别创建一个Animator(属性动画对象)。

5)framework 命令A隐藏其共享元素,动画开始运行。随着动画的进行,framework 逐渐将B的activity窗口显示出来,当动画完成,B的窗口才完全可见。

 

 

参考:http://codecloud.net/15307.html

           http://www.jianshu.com/p/b72718bade45

           http://www.jianshu.com/p/692284dc3646

           http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0130/2384.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值