带你走一波Transition Animator转场动画相关事项(一)[通俗易懂]

带你走一波Transition Animator转场动画相关事项(一)[通俗易懂]Transition可以简单理解为一个过渡框架方便在开始场景到结束场景(不局限于Activity跟Fragment等页面跳转过程,页面中的控件的变化过程也是场景)设置转场动画(例如,淡入/淡出视图或更改视图尺寸)的一个API。 在Andorid 4.4.2引入的Transiti…

本篇文章已授权微信公众号guolin_blog(郭霖)独家发布

动画系列文章
带你走一波Android自定义Animator属性动画相关事项(一)

一、简述

Transition可以简单理解为一个过渡框架方便在开始场景到结束场景(不局限于ActivityFragment等页面跳转过程,页面中的控件的变化过程也是场景)设置转场动画(例如,淡入/淡出视图或更改视图尺寸)的一个API。 在Andorid 4.4.2引入的Transition框架,Andorid 5.0以上的版本跳转过渡则建立在该功能上。

二、关键概念

有两个关键概念:场景scene跟转场transition

  • scene:定义给定应用程序的UI。
  • transition:定义两个场景之间的动态变化。

scene开始时,Transition有两个主要职责:   

  1. 在开始和结束的scene捕捉每个视图的状态。
  2. 创建一个Animator根据视图,将动画的差异从一个场景到另一个。
官方示意图.png

三、关键类TransitionManager

SceneTransition联系起来,提供了几个设置场景跟转场的设置方法。

image.png

四、Transition相关内容

系统内置transition.png

系统有实现了部分的转场动画的类,自己根据需求去处理,我这里就简单演示一下里面的几个类,其它的大家自己去试试

1.transition的创建

1.1. 使用布局的方式:在res下创建transition目录,接着创建.xml文件

创建单一转场效果res/transition/slide_transition.xml

<slide  xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:slideEdge="top" />

创建 多转场res/transition/mulity_transition

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="together">
    <explode
        android:duration="1000"
        android:interpolator="@android:interpolator/accelerate_decelerate" />
    <fade
        android:duration="1000"
        android:fadingMode="fade_in_out"
        android:interpolator="@android:interpolator/accelerate_decelerate" />
    <slide
        android:duration="500"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:slideEdge="top" />
</transitionSet>

载入.xml文件(多转场跟单一转场都是使用同一方法)

val transition =TransitionInflater.from(this).inflateTransition(R.transition.fade_transition)

1.2. 使用代码创建translation的方式

//------------------------------- 创建单一转场效果
val translation =  Slide().apply { 
       duration = 500
       interpolator = AccelerateDecelerateInterpolator()
       slideEdge = Gravity.TOP
}

//------------------------------- 创建多转场效果
val transitionSet = TransitionSet()
                transitionSet.addTransition(Fade())
                transitionSet.addTransition(Slide())
                transitionSet.setOrdering(ORDERING_TOGETHER)

2. 使用&常用API

  • 基本使用
//root_view是本布局中的最底层的布局,自己可以指定 但是要包含将要进行动画的控件
//单转场
TransitionManager.beginDelayedTransition(root_view, translation) 
toggleVisibility(view_text,view_blue, view1_red, view_yellow)

//多转场
TransitionManager.beginDelayedTransition(root_view, transitionSet) //多转场
toggleVisibility(view_text,view_blue, view1_red, view_yellow)
    /** * 四个有颜色的方块的隐藏跟显示 */
private fun toggleVisibility(vararg views: View?) {
  for (view in views) {
   view!!.visibility =
      if (view!!.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE
   }
}

效果图:

gifeditor_20191218_165504.gif

这里你可以略清楚转场动画的用意,就是你指定两个场景 比如例子中的开始是View都显示,第二个场景是View都隐藏,设置的transitionSet或者translation就是用于中间变化的过程使用的动画。实际上也是里面使用了属性动画进行处理的。(下面自定义转场动画的时候会说到)

//点击按钮
R.id.btn_change_bounds -> {
  TransitionManager.beginDelayedTransition(root_view, ChangeBounds())
  var lp = view1_red.layoutParams
  if (lp.height == 500) {
    lp.height = 200
    } else {
    lp.height = 500
    }
  view1_red.layoutParams = lp
}
//红框剪切的
R.id.btn_change_clip_bounds -> {
  TransitionManager.beginDelayedTransition(root_view, ChangeClipBounds())
  val r = Rect(20, 20, 100, 100)
  if (r == view1_red.clipBounds) {
    view1_red.clipBounds = null
  } else {
    view1_red.clipBounds = r
  }
}
// 蓝色方块中的字内部滑动
R.id.btn_change_scroll -> {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    val t = ChangeScroll()
    TransitionManager.beginDelayedTransition(root_view, t)
  }
  if(view_text.scrollX == -50 && view_text.scrollY == -50){
    view_text.scrollTo(0,0)
  }else{
    view_text.scrollTo(-50,-50)
  }
}

gifeditor_20191220_180102.gif

3. translation.Targets

配置Transition可以给一些特殊目标的View或者去掉目标View指定Transitions.

增加动画目标addTarget(View target)
addTarget(int targetViewId)
addTarget(String targetName) : 与 TransitionManager.setTransitionName方法设定的标识符相对应。
addTarget(Class targetType) : 类的类型 ,比如android.widget.TextView.class
移除动画目标
removeTarget(View target)
removeTarget(int targetId)
removeTarget(String targetName)
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(View target, boolean exclude) excludeChildren(int targetId, boolean exclude)
excludeChildren(Class type, boolean exclude)

4. 自定义 Transition动画

主要三个方法,跟属性定义。

  1. 属性定义:官网提醒我们避免跟其他的属性名同名,建议我们命名规则:package_name:transition_class:property_name

  2. 三个方法:captureStartValues()captureEndValues()createAnimator()

  • captureStartValues(transitionValues: TransitionValues) 开始场景会多次调用,在这里你调用transitionValues.values[你定义的属性名]并将此时属性的值赋值给它
  • captureEndValues(transitionValues: TransitionValues) 结束场景会多次调用,在这里你调用transitionValues.values[你定义的属性名]并将此时属性的值赋值给它
  • createAnimator( sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues? ): Animator? 重点是这个函数,我们在这里根据开始的场景跟结束的场景值,定义对应的属性动画,并通过监听属性动画addUpdateListener的方法,进行对应的属性改变。
  1. 补充说明:captureStartValues()captureEndValues()实际上是用于将此时的改变的属性值,存储到TransitionValues中的hashMap中(定义的属性名为key属性值为对应的value),方便我们在后面createAnimator根据存储的值进行属性动画的创建。
  • 例子:自定义背景颜色属性转场动画
   /** * Create by ldr * on 2019/12/23 16:02. */
class ChangeColorTransition : Transition() {

    companion object {
        /** * 根据官网提供的命名规则 package_name:transition_class:property_name,避免跟与其他 TransitionValues 键起冲突 * 将颜色值存储在TransitionValues对象中的键 */
        private const val PROPNAME_BACKGROUND = "com.mzs.myapplication:transition_colors:background"
    }

    /** * 添加背景Drawable的属性值到目标的TransitionsValues.value映射 */
    private fun captureValues(transitionValues: TransitionValues?) {
        val view = transitionValues?.view ?: return
        //保存背景的值,供后面使用
        transitionValues.values[PROPNAME_BACKGROUND] = (view.background as ColorDrawable).color
    }
    //关键方法一 :捕获开始的场景值,多次调用
    override fun captureStartValues(transitionValues: TransitionValues) {
        if (transitionValues.view.background is ColorDrawable)
            captureValues(transitionValues)
    }
   //关键方法二 :捕获结束的场景值,多次调用。
   // 将场景中的属性值存储到transitionValues的
    override fun captureEndValues(transitionValues: TransitionValues) {
        if (transitionValues.view.background is ColorDrawable)
            captureValues(transitionValues)
    }

 //关键方法三:根据 override fun createAnimator(
        sceneRoot: ViewGroup?,
        startValues: TransitionValues?,
        endValues: TransitionValues?
    ): Animator? {
        //存储一个方便的开始和结束参考目标。
        val view = endValues!!.view
        //存储对象包含背景属性为开始和结束布局
        var startBackground = startValues!!.values[PROPNAME_BACKGROUND]
        var endBackground = endValues!!.values[PROPNAME_BACKGROUND]
        //如果没有背景等的直接忽略掉
        if (startBackground != endBackground) {
          //定义属性动画。
            var animator = ValueAnimator.ofObject(ArgbEvaluator(), startBackground, endBackground)
        //设置监听更新属性
            animator.addUpdateListener { animation ->
                var value = animation?.animatedValue
                if (null != value) {
                    view.setBackgroundColor(value as Int)
                }
            }
            return animator
        }
        return null
    }
}

代码中使用

var changeColorTransition = ChangeColorTransition()
changeColorTransition.duration = 2000
TransitionManager.beginDelayedTransition(root_view, changeColorTransition)
val backDrawable = view1_red.background as ColorDrawable
if (backDrawable.color == Color.RED) {
  view1_red.setBackgroundColor(Color.BLUE)
} else {
  view1_red.setBackgroundColor(Color.RED)
}

gifeditor_20191224_112303.gif

五、Scene的相关内容

1.Scene的创建

sceneRoot是要进行场景变化的根布局,不用非得是整个布局的根布局,只要是包含了场景变化的根布局可以了。 R.layout.scene_layout0R.layout.scene_layout1中的要进行转场动画的控件id一致

  • 通过 Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) 方法。
var scene0 = Scene.getSceneForLayout(sceneRoot,R.layout.scene_layout0,this)
var scene1 = Scene.getSceneForLayout(sceneRoot,R.layout.scene_layout1,this)
  • 通过Scene()构造函数

val view =  LayoutInflater.from(this).inflate(R.layout.scene_layout0,sceneRoot,false)
val scene0 = Scene(sceneRoot,view)
val view1 =  LayoutInflater.from(this).inflate(R.layout.scene_layout1,sceneRoot,false)
val scene1 = Scene(sceneRoot,view1)

这里有一点需要注意:LayoutInflater.from(this).inflate(R.layout.scene_layout0,sceneRoot,false),最后一个参数要传false,不然一旦你的view添加到sceneRoot中,你去调用TransitionManager.go()传入参数就会报错 IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

----------------------------scene_layout0的布局----------------------------------
<?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="match_parent"
    android:orientation="vertical">


    <ImageView
        android:id="@+id/black_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="18dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="48dp"
        android:src="@drawable/shape_black_circle" />

    <ImageView
        android:id="@+id/yellow_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="48dp"
        android:layout_marginEnd="40dp"
        android:layout_marginRight="10dp"
        android:src="@drawable/shape_yellow_circle" />

    <ImageView
        android:id="@+id/red_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_below="@+id/black_circle"
        android:layout_alignParentStart="true"
        android:layout_marginStart="13dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="39dp"
        android:src="@drawable/shape_red_circle" />

    <ImageView
        android:id="@+id/blue_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="241dp"
        android:layout_marginEnd="45dp"
        android:layout_marginRight="10dp"
        android:src="@drawable/shape_blue_circle" />
</RelativeLayout>

----------------------------scene_layout1的布局----------------------------------
与scene_layout0一样,只是ImageView的位置更换了一下。

两个场景要进行转场变化的控件id是一致的。

我通过实践发现了一个问题:当多个转场控件放到不同的ViewGroup下面,而不是在同一个ViewGroup的布局下面,产生的动画会有不一致的情况。

上面的所有ImageView都放在RelativeLayout的布局下面,与使用Linearlayout为纵向根布局再加上两个子横向Linearlayout,再将ImageView两两放置到子横向Linearlayout中,你会看到位置变化的转场效果可能不是你所期望的。(这里应该是因为不在同一个ViewGroup下导致的)

2. 使用

//场景1:
val transition = TransitionInflater.from(this).inflateTransition(R.transition.explore_transtion)
TransitionManager.go(scene0,transition)

//场景2:
val transition = TransitionInflater.from(this).inflateTransition(R.transition.explore_transtion)
TransitionManager.go(scene1,transition)

gifeditor_20191225_100316.gif

六、Activity间的转场动画

图解.png

1. 基本主要API

  • window.enterTransition: 进入时候的转场效果
  • window.exitTransition: 退出时候的转场效果
  • window.reenterTransition: 重新进入的转场效果
  • window.returnTransition: 回退的时候的转场效果

对应样式下的

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="android:windowEnterTransition"></item> <item name="android:windowExitTransition"></item> <item name="android:windowReenterTransition"></item> <item name="android:windowReturnTransition"></item> </style>

2. Android 支持以下进入和退出过渡:

explore : 将视图移入场景中心或从中移出。
slide : 将视图从场景的其中一个边缘移入或移出。
fade : 通过更改视图的不透明度,在场景中添加视图或从中移除视图。
系统支持将任何扩展 Visibility 类的过渡作为进入或退出过渡。

3. 基本使用

onCreate()中设置转场动画

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setUpWindow()
    }
    private fun setUpWindow() {
       window.let {
            it.exitTransition = TransitionInflater.from(this).inflateTransition(R.transition.fade_transtion)
            it.enterTransition = Explode().apply {
                duration = 500
            }
            it.reenterTransition = Explode().apply {
                duration = 500
            }
            it.returnTransition = Slide().apply {
                duration = 500
            }
        }
        }
    }

跳转的时候,startActivity增加bundle

val intent = Intent(this@MainActivity, SampleTranslateActivity::class.java)
val bundle =  ActivityOptionsCompat.makeSceneTransitionAnimation(this).toBundle()//Androidx提供的类
//val bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()//不是Andoridx的时候使用ActivityOptions
startActivity(intent,bundle)

gifeditor_20191226_111126.gif

上面的效果存在一些问题,有些动画重叠在一块了。 我们需要设置一下代码让进入退出的动画按序完成而不重叠到一块的时候,

setWindowAllowEnterTransitionOverlap(false)
setWindowAllowReturnTransitionOverlap(false)

或者在Activity或者Application对应的样式下面增加

<item name="android:windowAllowEnterTransitionOverlap">false</item>
<item name="android:windowAllowReturnTransitionOverlap">false</item>

gifeditor_20191226_112449.gif

七、Activity间的共享转场动画

image.png

1.基本API

对应各方法进入时候的转场效果,跟上面的转场动画的api是相对的

  • window.sharedElementEnterTransition
  • window.sharedElementExitTransition
  • window.sharedElementReenterTransition
  • window.sharedElementReturnTransition 对应样式下的
 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="android:windowSharedElementEnterTransition"></item> <item name="android:windowSharedElementExitTransition"></item> <item name="android:windowSharedElementReenterTransition"></item> <item name="android:windowSharedElementReturnTransition"></item> </style>

2.基本使用

注意:版本要大于android5.0以上的,才有提供共享元素场景动画,用的时候记得做一下版本兼容

    // Check if we're running on Android 5.0 or higher
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // Apply activity transition
    } else {
        // Swap without transition
    }

2.1. 先在xml样式中开启

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
 <item name="android:windowContentTransitions">true</item>
</style>

或者代码中开启

requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS)

2.2. 定义两个布局都要设置android:transitionName 跳转布局一

    <ImageView
        android:id="@+id/image_blue"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:src="@drawable/shape_blue_circle"
        android:transitionName="blue_name"
        />
    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:transitionName="textName"
        android:text="这个是我等下转场假装变大的数据~~~~"
        />

布局二

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="68dp"
        android:src="@drawable/shape_blue_circle"
        android:transitionName="blue_name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="68dp"
        android:text="TextView"
        android:transitionName="textName"
        android:textSize="23sp"
        android:textColor="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

2.3 在两个Activity中分别设置共享元素的转场动画

window.sharedElementEnterTransition = ChangeBounds()
window.sharedElementExitTransition = ChangeBounds()

2.3 跳转开始

val intent = Intent(this@MainActivity, ShareElementActivity2::class.java)
// 构造多个Pair 一个Pair对应一个共享元素 
val pair = Pair(image_blue as View, image_blue.transitionName)
val pair1 = Pair(text1 as View, text1.transitionName)
// 将多个共享元素传入
val options = ActivityOptions.makeSceneTransitionAnimation(
  this@MainActivity,
  pair, pair1
)
startActivity(intent, options.toBundle())

gifeditor_20191226_142635.gif

限制(选自Android官方文档)

  • Android 版本在 4.0(API Level 14)4.4.2(API Level 19) 使用 Android Support Library’s

  • 应用于 SurfaceView 的动画可能无法正确显示。 SurfaceView 实例是从非界面线程更新的,因此这些更新与其他视图的动画可能不同步。

  • 当应用于 TextureView 时,某些特定过渡类型可能无法产生所需的动画效果。

  • 扩展 AdapterView 的类(例如 ListView)会以与过渡框架不兼容的方式管理它们的子视图。如果您尝试为基于 AdapterView 的视图添加动画效果,则设备显示屏可能会挂起。

  • 如果您尝试使用动画调整 TextView 的大小,则文本会在该对象完全调整过大小之前弹出到新位置。为了避免出现此问题,请勿为调整包含文本的视图的大小添加动画效果。

本章的源码:

github.com/lovebluedan…

感谢:

Android官方文档
github.com/lgvalle/Mat…
github.com/codepath/an…

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13420.html

(0)

相关推荐

  • 代理《神途》有哪些优势和劣势_代理过神途的心得

    代理《神途》有哪些优势和劣势_代理过神途的心得自2014年起,代理加盟《神途》的人越来越多,那么《神途》究竟有哪些优势,会受广大私服、工作室,甚至是个人玩家所青睐呢?

    2023-06-27
    123
  • kafka datahub_hadoop HA

    kafka datahub_hadoop HA一、概述 EFAK(Eagle For Apache Kafka,以前称为 Kafka Eagle)是一款由国内公司开源的Kafka集群监控系统,可以用来监视kafka集群的broker状态、Topi

    2023-05-19
    141
  • iPad Python工程师

    iPad Python工程师Python是一门开放性强、易于学习和使用的计算机语言,而iPad则是一款集娱乐、办公和学习于一体的便携式设备。在这篇文章中,我们将探讨如何将iPad与Python结合,成为一名iPad Python工程师。

    2024-04-19
    71
  • timestampdiff(oracle中timestamp)

    timestampdiff(oracle中timestamp)

    2023-10-01
    123
  • 文件同步网盘数据会有遗失的风险吗?[通俗易懂]

    文件同步网盘数据会有遗失的风险吗?[通俗易懂]文件同步网盘数据会有遗失的风险吗?如果我们将自己的文件存储在文件同步网盘中,但是因为某些网盘自身存在某些问题或缺陷,会导致网盘中的数据丢失,同时又没有办法找回。那么我们存储的这些数据,也就永久的遗失…

    2023-04-11
    177
  • 使用Python if语句多个条件判断

    使用Python if语句多个条件判断Python作为广泛应用的编程语言之一,其if语句是编程中非常重要的一部分,在判断特定条件下的程序流程上有着非常重要的作用。当需要在多个条件中进行判断时,Python if语句的多个条件判断就成为了解决问题的关键。

    2024-04-15
    83
  • mysql数据库异常怎样排查_使用数据库遇到的问题

    mysql数据库异常怎样排查_使用数据库遇到的问题近一个月处理历史数据问题时,居然连续遇到了2个MySQL BUG,分享给大家一下,也欢迎指正是否有问题。 BUG1: 数据库版本: MySQL5.7.25 – 28 操作系统: Centos 7.7(

    2023-03-14
    149
  • 如何在CMD中运行Python文件

    如何在CMD中运行Python文件Python是一种强大的、易于上手的编程语言,它适用于开发各种类型的应用程序,从小型脚本到大型Web应用程序,以及科学和数学应用程序等领域。在Windows平台上,CMD(命令提示符)是一个强大的工具,可以帮助你在本地计算机上运行Python代码。本文将向你介绍如何在CMD中运行Python文件。

    2024-06-07
    47

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注