当前位置: 首页 > news >正文

android 动画_炫酷的Android过渡动画,让APP应用富有生机

「【桃花潭水深千尺,不及汪伦送我情】」

不知道大家有没有发现,Android版的掘金有下面这个小小动画:点击作者头像跳转到作者的详情页,而作者头像会从当前界面通过动画过渡详情页界面。

640?wx_fmt=gif

知识贫乏限制了我的视野,真心想不到这怎么实现的?

最近在写动画方面文章时候,从网上找到了答案:「Activity过渡动画中的共享元素过渡」

本文的初衷,是和大家一起扫盲,如果对你有用,「欢迎点赞」,让更多的小伙伴多学点知识。小小的动画,隐藏着巨大的知识点;怪不得面试造火箭,工作拧螺丝,这是知识储备,虽然可能一辈子也用不上。

「系列好文推荐」

Android属性动画,看完这篇够用了吧

Android矢量图动画:每人送一辆掘金牌小黄车

一、Activity切换过渡动画

Activity过渡动画包含「进入过渡」「退出过渡」「共享元素过渡」三个动画,它们同样仅支持Android 5.0+版本。

一)、共享元素过渡动画

共享元素过渡指的两个Activity共享的视图如何在两个Activity之间进行过渡。例如上面的Gif图,共享视图就是ImageView

共享元素也分一个元素和多个元素。

定义共享元素过渡效果「步骤」如下:

  1. 在两个Activity定义两个相同类型的View;
  2. 给两个View设置相同的transitionName属性;
  3. 通过ActivityOptions.makeSceneTransitionAnimation()函数生成Bundle对象;
  4. startActivity()函数传递bundle对象。

「栗子讲解,清晰易懂:」

  1. 分别在activity_first.xmlactivity_second.xml布局文件定义ImageView组件,并将transitionName属性设为activityTransform
<?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/white"    android:orientation="vertical">            android:id="@+id/ivImage"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:src="@mipmap/ic_one"        android:transitionName="activityTransform" />            android:id="@+id/tvText"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="10dp"        android:gravity="center"        android:text="我是第一个Activity"        android:textColor="@color/c_333"        android:textSize="18sp" /><?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">            android:id="@+id/ivImage"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:adjustViewBounds="true"        android:src="@mipmap/ic_one"        android:transitionName="activityTransform" />            android:id="@+id/tvText"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_above="@id/ivImage"        android:layout_marginBottom="10dp"        android:gravity="center"        android:text="我是第2个Activity"        android:textColor="@color/c_333"        android:textSize="18sp" />

「预览图」640?wx_fmt=pngactivityTransform属性也可以通过代码设置。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {    ivImage.transitionName="activityTransform"}
  1. FirstActivity中给ImageView设置点击事件,跳转到第二个Activity。
ivImage.setOnClickListener {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//判断Android版本        val bundle =            ActivityOptions.makeSceneTransitionAnimation(this, ivImage, "activityTransform")                .toBundle()        startActivity(Intent(this, SecondActivity::class.java), bundle)    } else {        startActivity(Intent(this, SecondActivity::class.java))    }}

代码中,先判断当前Android版本是否大于等于5.0,大于或等于Android 5.0的话就设置共享元素动画,小于5.0 就正常启动第二个Activity

通过ActivityOptions.makeSceneTransitionAnimation()创建启动Activity过渡的一些参数,makeSceneTransitionAnimation()函数第一个参数为Activity对象;第二个参数为共享元素组件,这里设置为idivImageImageView视图;第三个参数为transitionName属性的值,这里是activityTransform。在调用AcivityOptions对象toBundle函数,包装成Bundle对象。

「效果图:」

640?wx_fmt=gif「多个共享元素过渡」

多个共享元素过渡也很简单,只需要调用makeSceneTransitionAnimation()函数的另外一个重载函数即可。

  1. 在前面XML布局的基础上,给TextView增加transitionName属性:textTransform
<?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/white"    android:orientation="vertical">            android:id="@+id/ivImage"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:src="@mipmap/ic_one"        android:transitionName="activityTransform" />            android:id="@+id/tvText"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="10dp"        android:gravity="center"        android:transitionName="textTransform"        android:text="我是第一个Activity"        android:textColor="@color/c_333"        android:textSize="18sp" /><?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">            android:id="@+id/ivSecondImage"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:adjustViewBounds="true"        android:src="@mipmap/ic_one"        android:transitionName="activityTransform" />            android:layout_width="match_parent"        android:layout_height="wrap_content"        android:transitionName="textTransform"        android:layout_above="@id/ivSecondImage"        android:layout_marginBottom="10dp"        android:gravity="center"        android:text="我是第2个Activity"        android:textColor="@color/c_333"        android:textSize="18sp" />
  1. 构建多个Pair对象,并传递给makeSceneTransitionAnimation()函数,启动Activity
ivImage.setOnClickListener {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {                     val imagePair=Pair(ivImage,"activityTransform")    val textPair=Pair(ivImage,"textTransform")        val bundle =        ActivityOptions.makeSceneTransitionAnimation(this,                imagePair,textPair).toBundle()                startActivity(Intent(this, SecondActivity::class.java), bundle)     } else {        startActivity(Intent(this, SecondActivity::class.java))    }}

这里主要是通过将共享视图和transitionName属性的值包装到Pair对象,其他操作和一个共享元素的操作步骤并无区别。

「效果图:」

640?wx_fmt=gif

「深坑提醒」

有时从RecyclerView界面进入到详情页,由于详情页加载延迟,可能出现没有效果。例如ImageView从网络加载图片,可能A界面到B界面没效果,B回到A界面有效果。

640?wx_fmt=gif解决步骤:

  1. setContentView后添加下面代码,延迟加载过渡动画。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {    postponeEnterTransition()}
  1. 在共享元素视图加载完毕,或者图片加载完毕后调用下面代码,开始加载过渡动画。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {    startPostponedEnterTransition()}

640?wx_fmt=gif例如我是在Glide加载完再调用:

 Glide.with(mContext)                    .asBitmap()                    .load(value?.avatar ?: "")                    .listener(object : RequestListener {                        override fun onResourceReady(resource: Bitmap?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {                            animatorCallback?.invoke()//回调开始加载过渡动画                            return false                        }                        override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean {                            animatorCallback?.invoke()//回调开始加载过渡动画                            return false                        }                    })                    .apply(RequestOptions.circleCropTransform())                    .placeholder(R.mipmap.ic_default)                    .error(R.mipmap.ic_default)                    .into(authorBinding!!.ivAvatar)

大家也可以考虑下面代码:

shareElement.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {                override fun onPreDraw(): Boolean {                    shareElement!!.viewTreeObserver.removeOnPreDrawListener(this)                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {                        animatorCallback?.invoke()                    }                    return true                }            })

二)、Activity进入过渡与退出过渡动画

与共享元素相反的,就是Activity进入与退出过渡动画,两个Activity之间在没有共享的视图情况下进行动画切换。下面先看三种动画效果图:「爆炸式效果」「淡入淡出式效果」「滑动式效果」

  • 「爆炸式」:将视图移入场景中心或从中移出;
  • 「滑动式」:将视图从场景的其中一个边缘移入或移出;
  • 「爆炸式」:通过更改视图的不透明度,在场景中添加视图或从中移除视图;

第一个界面采用Fade淡入淡出效果,第二个界面采用了Explode爆炸效果。

640?wx_fmt=gif前后两个界面都采用了Slide滑入滑出效果。

640?wx_fmt=gif利用Android现有的过渡框架,实现起来是很简单的,步骤如下:

  1. ActivityonCreate()方法中调用 setContentView()前设置启用窗口过渡属性;
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
  1. 创建过渡效果对象SlideExplodeFade;
val slide=Slide()slide.slideEdge=Gravity.STARTslide.duration=300//效果时长,一般Activity切换时间很短,不建议设置过长

如果是Slide效果,可以设置slideEdge属性来指定滑动方向,默认是Gravity.BOTTOM

  1. 将过渡效果设置给window相关属性,设置;
//退出当前界面的过渡动画window.exitTransition = slide//进入当前界面的过渡动画window.enterTransition = slide//重新进入界面的过渡动画window.reenterTransition = slide
  1. 调用第二个Activity界面,使用过渡效果。
 startActivity(        Intent(this, SecondActivity::class.java),        ActivityOptions.makeSceneTransitionAnimation(this).toBundle())

那么ActivityOnCreate()方法看起来是这样子的。

    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {            window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)            window.allowEnterTransitionOverlap=false            Slide().apply {                duration = 300                excludeTarget(android.R.id.statusBarBackground, true)                excludeTarget(android.R.id.navigationBarBackground, true)            }.also {                window.exitTransition = it                window.enterTransition = it                window.reenterTransition = it            }        }                setContentView(R.layout.activity_first)        ivContent.setOnClickListener {            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {                startActivity(                    Intent(this, SecondActivity::class.java),                    ActivityOptions.makeSceneTransitionAnimation(this).toBundle()                )            }        }    }

上面代码中调用 了excludeTarget()方法将状态栏和导航栏排除在过渡动画效果之外。否则会跟着一起起动画效果,不是很美观。

正常情况,退出与进入过渡动画会有一小段交叉的过程,而window.allowEnterTransitionOverlap=false就是禁止交叉,只有退出过渡动画结束后才会再显示进入过渡动画。

如果第二个Activityfinish掉后,回到第一个Activity界面也想有过渡效果,就不要手动调用finish(),可以调用finishAfterTransition ()方法。

三)、兼容Android 5.0前

如果Android 5.0前也想要有切换动画怎么办?

  1. res/anim文件夹下创建想要的效果:
    xmlns:android="http://schemas.android.com/apk/res/android"        android:interpolator="@interpolator/decelerate_quad"        android:fromAlpha="0.0"        android:toAlpha="1.0"        android:duration="@android:integer/config_longAnimTime" />
  1. 在启动Activity后调用overridePendingTransition()方法。
val intent = Intent(this, TestActivity2::class.java)startActivity(intent)overridePendingTransition(R.anim.fade_in, R.anim.fade_out)

overridePendingTransition()方法第一个参数为下一个界面进入动画,第二个参数为当前界面退出动画。

到这里,Activity的切换使用过渡动画基本就结束了。有朋友可能会问,只有Activity切换才能应用过渡效果么?

二、布局变化过渡效果

在上一节要理解一个概念:场景。布局的显示与隐藏可以理解分别为一个场景,过渡动画就是解决场景切换带来的生硬视觉感受。Activity切换过渡动画指在两个Activity之间,而布局变化过渡动画,是指同个Activity之间View的变化过渡动画。

一)、手动创建Scene

手动创建场景的话,需要我们自己创建起始和结束场景,利用现有的过渡效果来达到两个场景的切换。默认情况下,当前界面就是起始场景。

  1. 创建起始场景和结束场景的xml布局。起始场景和结束场景需要有相同根元素,例如下面代码idflConatentFrameLayout布局。
<?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">            android:id="@+id/tvText"        android:text="内容过渡动画"        android:gravity="center"        android:textSize="18sp"        android:layout_width="match_parent"        android:layout_height="50dp"/>            android:id="@+id/flContent"        android:layout_weight="1"        android:layout_width="match_parent"        android:layout_height="0dp">          

初始视图,第一个场景,布局layout_first_scene.xml

<?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">            android:id="@+id/tvFirst"        android:textSize="18sp"        android:layout_marginTop="100dp"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:gravity="center_horizontal|top"        android:text="感谢大家阅读文章" />

第二个场景,布局layout_second_scene.xml

<?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">            android:textSize="18sp"        android:layout_marginTop="100dp"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:gravity="center_horizontal|top"        android:text="我是新小梦\n欢迎大家点赞支持一下" />
  1. 创建起始场景和结束场景。
val firstScene = Scene.getSceneForLayout(flContent, R.layout.layout_first_scene, this)val secondScene = Scene.getSceneForLayout(flContent, R.layout.layout_sencod_scene, this)

默认情况下,过渡动画应用整个场景,如果场景某个View不参加,可以通过过渡效果对象removeTarget()方法进行移除。

Slide(Gravity.TOP).removeTarget(tvNoJoin)
  1. 点击时,进行场景过渡。
tvText.setOnClickListener {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {        if (isFirst) {            TransitionManager.go(secondScene, Slide(Gravity.TOP))        }else{            TransitionManager.go(firstScene, Slide(Gravity.TOP))        }        isFirst=!isFirst    }}

TransitionManager.go()第一个参数表示结束场景,第二个参数表示当前场景退出时过渡效果,当前场景就是初始场景。

「效果图:」

640?wx_fmt=gif

二)、系统自动创建Scene

这种情况,我们调用TransitionManager.beginDelayedTransition(sceneRoot)函数时,系统会自动记录当前sceneRoot节点下所有要进行动画的视图作为起始节点,下一帧中再次记录sceneRoot子节点下所有 起始场景进行动画状态的视图作为结束场景。这种一般用来改变视图的属性,然后进行动画过渡,如View的宽高。

「栗子」

定义只有一个正方形的View,通过改变正方形的宽高为原来的2倍,来看看动画效果。

  1. activity_text.xml布局文件,定义idsceneRoot的根节点,也是场景的根节点。
<?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/sceneRoot"    android:background="@color/colorPrimary">            android:id="@+id/vSquare"        android:layout_width="100dp"        android:layout_height="100dp"        android:layout_centerHorizontal="true"        android:layout_marginTop="50dp"        android:background="@color/white" />
  1. TestActivityOnCreate方法中调用下面代码,将正方形的宽高设置200dp。
vSquare.setOnClickListener {   TransitionManager.beginDelayedTransition(sceneRoot)   vSquare.layoutParams.apply {       width = dp2px(200f, this@TestActivity)       height = dp2px(200f, this@TestActivity)   }.also {       vSquare.layoutParams = it   }}

「效果图:」

640?wx_fmt=gif

三、过渡动画效果

上面的动画效果,都是采用系统内置的,那具体有哪些动画效果,或支持自定义么?

过渡效果类都继承自Transition类,Transition类持有场景切动画的相关信息,子类的主要作用是捕获属性值(例如起始值和结束值)和如何演奏动画。从这里也可以看出,过渡动画也是属性动画的一个扩展与应用。

一)、系统自带过渡动画效果

系统支持将任何扩展Visibility类的过渡作为进入或退出过渡,内置继承自Visibility的类有ExplodeSlideFade;支持共享元素过渡的有:

  • changeScroll 为目标视图滑动添加动画效果
  • changeBounds 为目标视图布局边界的变化添加动画效果
  • changeClipBounds 为目标视图裁剪边界的变化添加动画效果
  • changeTransform  为目标视图缩放和旋转方面的变化添加动画效果
  • changeImageTransform  为目标图片尺寸和缩放方面的变化添加动画效果

代码示例:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {    TransitionSet().apply {        addTransition(ChangeImageTransform())        addTransition(ChangeBounds())        addTransition(Fade(Fade.MODE_IN))    }.also {       window.sharedElementEnterTransition=it    }}

TransitionSet对象是动画的合集,可以将多个过渡效果组织起来。

也可以通过XML布局来实现,在res/transition文件夹创建``:

<?xml version="1.0" encoding="utf-8"?>    android:duration="300"    android:transitionOrdering="together">            

transitionSetfade...等等的一些属性和 Android矢量图动画:每人送一辆掘金牌小黄车文章 讲到的一些属性大同小异,这里不再复述。

代码调用:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {    TransitionInflater.from(this).inflateTransition(R.transition.transition_set).also {        window.sharedElementEnterTransition=it    }}

「效果图:」

640?wx_fmt=gif当现有的过渡效果不满足日常需求时,可以通过继承Transition,定制自己的动画特效。

二)、自定义过渡动画

子类继承Transition类,并重写其三个方法。

class MyTransition : Transition() {   override fun captureStartValues(transitionValues: TransitionValues?) {}   override fun captureEndValues(transitionValues: TransitionValues?) {}   override fun createAnimator(       sceneRoot: ViewGroup?,       startValues: TransitionValues?,       endValues: TransitionValues?   ): Animator {       return super.createAnimator(sceneRoot, startValues, endValues)   }   }

captureStartValues()captureEndValues()方法是必须实现的,捕获动画的起始值和结束值,而createAnimator()方法,是用来创建自定义的动画。

参数TransitionValues可以理解是用来存储View的一些属性值,参数sceneRoot为根视图。

自定义过渡效果感兴趣可以参考:Android自定义Transition动画

好啦,过渡动画就讲到这里~

参考文章:

官网文档

酷炫的Activity切换动画,打造更好的用户体验

「【点个赞,日后好查看】」

相关文章:

  • phpstorm xdebug本地调试断点不生效_卓象程序员:PHPStudy PHPStorm XDebug调试
  • 游戏脚本高级编程_godot游戏引擎实现TCP和UDP(网络联机,多人游戏)
  • 12333新农合网上查询_以下这些你们都懂?社保,医保,新农合,城镇医保都是什么鬼?...
  • batch normalization_深度学习各种normalization
  • python底层实现原理_Vue底层实现原理概述
  • layui后台模板_基于SpringBoot+Mybatis+Vue搭建后台管理模板框架,CRUD源码分享
  • 怎么在ubuntu上运行python_ubuntu怎么运行python
  • python判断图片相似度_opencv+python用直方图进行相似度判断、对比
  • python发送qq邮件_python实现QQ邮箱发送邮件
  • html开关_用树莓派DIY“啪啪”声控开关
  • python练习项目_python练习项目
  • java程序员修炼之道 pdf_《程序员修炼之道(第2版)》到货!屹立20年影响力大作归来!...
  • python读取特定两列_如何基于python中的where函数获取两列值
  • python人工智能框架有哪些_10个最佳的人工智能开发框架和AI库
  • python2md5迁移_Python远程获取MD5校验码并在web上显示
  • AWS实战 - 利用IAM对S3做访问控制
  • bearychat的java client
  • Fastjson的基本使用方法大全
  • Map集合、散列表、红黑树介绍
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • Python进阶细节
  • Yii源码解读-服务定位器(Service Locator)
  • 从PHP迁移至Golang - 基础篇
  • 老板让我十分钟上手nx-admin
  • 浅谈web中前端模板引擎的使用
  • 区块链分支循环
  • 使用权重正则化较少模型过拟合
  • 通过几道题目学习二叉搜索树
  • 我看到的前端
  • 原生JS动态加载JS、CSS文件及代码脚本
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (二)丶RabbitMQ的六大核心
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (转)JAVA中的堆栈
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转)人的集合论——移山之道
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .NET Core 版本不支持的问题
  • .NET Framework Client Profile - a Subset of the .NET Framework Redistribution
  • .net 后台导出excel ,word
  • .NET面试题(二)
  • .Net转前端开发-启航篇,如何定制博客园主题
  • /etc/sudoer文件配置简析
  • /使用匿名内部类来复写Handler当中的handlerMessage()方法
  • @Autowired和@Resource装配
  • [20170705]diff比较执行结果的内容.txt