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

Android中的沉浸式丝滑转场之共享元素转场动画

文章目录

    • 1. 介绍
    • 2. 实现方法
    • 3. 举例演示
      • 3.1 举例一:普通页面间共享元素转场动画
      • 3.2 举例二:列表页面共享元素转场动画
    • 4. 总结

1. 介绍

在Android开发中,经常会有页面转场的动画效果。普通的转场动画不过是左进右出,渐显渐隐,局限于整个页面。而对于页面中的某个元素,尤其是图片,如果能很自然的过渡到下一个页面,那么用户体验会非常丝滑。这就是Android中的共享元素动画

来看下效果吧:

  1. 场景一:普通页面间共享元素转场动画
    共享元素转场动画
  2. 场景二:列表页面共享元素转场动画
    共享列表元素转场动画

2. 实现方法

Android中提供了 ActivityOptionsCompat.makeSceneTransitionAnimation 方法来实现场景转场动画,配合xml中的属性android:transitionName,在Activity跳转时指定共享的元素View,生成bundle对象,然后传入startActivity方法中。系统便会自动为你实现上述效果。

比如页面 A 跳到页面 B,共享页面里的两个 ImageView 做动画,那么在页面A startActivity 时,就需要使用ActivityOptionsCompat.makeSceneTransitionAnimation生成 Bundle:

public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity,@NonNull View sharedElement, @NonNull String sharedElementName)

makeSceneTransitionAnimation 方法需要提供三个参数:

  • Activity activity :包含共享元素的 activity,也就是当前 activity;
  • View sharedElement: 需要有共享动画效果的 View,也就是页面A的 ImageView;
  • String sharedElementName: 共享元素的名字,随便起个名字,只要跟目标页面xml里的android:transitionName 一致即可

该方法返回值是 ActivityOptionsCompat,需要调用 toBundle 方法,将其转换为 Activity 间数据传递用的 Bundle 对象。

接着,调用 startActivity(it, bundle) 方法,将bundle 传入即可。

在页面B中,需要有对应的 ImageView,在它的xml布局里,需要添加属性android:transitionName,标明它是目标的共享元素。这样页面A中的 ImageView 就能共享到页面B的 ImageView上了。它们之间的大小位置等差异会以动画的形式自然过渡。

核心代码如下:

 fun jump(view: View) {// 生成转场动画的bundle对象val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this, imgView!!, "share_image").toBundle()// 如果有参数传递,可以这么添加bundle?.putString("key1", "value1")Intent(this, ShareElementActivity2::class.java).let {it.putExtras(bundle!!)startActivity(it, bundle!!)}
}

xml代码

<ImageViewandroid:id="@+id/imageView"android:layout_width="60dp"android:layout_height="60dp"android:src="@drawable/test1"android:transitionName="share_image"
/>

3. 举例演示

接下来,我们用完整的例子来演示共享动画效果。(本案例使用kotlin语言演示,Java也是同理; 本案例中使用到的资源文件test1.jpeg 是一张普通的测速图片,你可以随便找一张图片代替。)

3.1 举例一:普通页面间共享元素转场动画

效果描述:我们将实现两个页面之间的跳转,由 ShareElementActivity1 跳转到 ShareElementActivity2。这两个页面里都有一个 ImageView 展示一张图片,这两张图片的大小位置有差异,前者60x60, 后者200x200。我们要实现的共享动画效果是:页面跳转过程中,这两张图片自然过渡,看起来是前面的小图,自然放大到后者的动画一样,并且整个页面也是自然过渡,没有生硬切换的迹象;页面返回时,大图自然缩小到小图,也是非常自然的动画过渡。具体效果可参考章节1中的效果图。

ShareElementActivity1代码:

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.core.app.ActivityOptionsCompat
import com.example.mytest.Rclass ShareElementActivity1 : AppCompatActivity() {private var imgView: ImageView? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_share_element1)imgView = findViewById(R.id.imageView)}fun jump(view: View) {val bundle =ActivityOptionsCompat.makeSceneTransitionAnimation(this, imgView!!, "share_image").toBundle()bundle?.putString("key1", "value1")Intent(this, ShareElementActivity2::class.java).let {it.putExtras(bundle!!)startActivity(it, bundle!!)}}
}

对应布局文件:activity_share_element1.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".animation.ShareElementActivity1"tools:ignore="MissingDefaultResource"><ImageViewandroid:id="@+id/imageView"android:layout_width="60dp"android:layout_height="60dp"android:layout_marginTop="96dp"android:scaleType="centerCrop"android:src="@drawable/test1"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.5"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/button3"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:text="跳转到share2"android:onClick="jump"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

目标页面:ShareElementActivity2

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import com.example.mytest.Rclass ShareElementActivity2 : AppCompatActivity() {private var imgView: ImageView? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_share_element2)imgView = findViewById(R.id.imageView)}fun back(view: View) {// 调用this.finish()不会有共享元素转场退出效果// this.finish()// 模拟返回键,调用系统的back按键,会有共享元素转场退出效果onBackPressed()}
}

对应布局文件:activity_share_element2.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".animation.ShareElementActivity2"tools:ignore="MissingDefaultResource"><ImageViewandroid:id="@+id/imageView"android:layout_width="200dp"android:layout_height="200dp"android:layout_marginTop="96dp"android:scaleType="centerCrop"android:src="@drawable/test1"android:transitionName="share_image"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.459"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/button3"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:text="返回"android:onClick="back"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

注意:
如果要手动从目标页面返回,不能直接调finish方法,那样就不会走系统的共享元素动画了。应该调用系统的back按键onBackPressed,这样就会有共享元素转场退出效果。

3.2 举例二:列表页面共享元素转场动画

接下来再来举个更实用的例子,从列表上点击进入详情页,应该是非常常见的场景。这里更适合这种共享元素无缝切换的效果,给用户的感觉会非常沉浸式。

列表页ShareElementListActivity,这里我们用到了 RecyclerView+Adapter来实现一个简单的列表

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.core.app.ActivityOptionsCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.mytest.R
import com.example.mytest.recyclerviewclick.RcvAdapterclass ShareElementListActivity : AppCompatActivity() {private var rcv: RecyclerView? = nullprivate var adapter: RcvAdapter? = nullprivate var dataList: MutableList<String> = mutableListOf()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_share_element_list)initData()rcv = findViewById(R.id.rcv)adapter = RcvAdapter(this, dataList).apply {onItemClickListener = object: RcvAdapter.OnRcvItemClickListener {override fun onItemClicked(position: Int, view: View) {val imageView = view.findViewById<ImageView>(R.id.iv_cover)val bundle =ActivityOptionsCompat.makeSceneTransitionAnimation(this@ShareElementListActivity, imageView!!, "share_image").toBundle()Intent(this@ShareElementListActivity, ShareElementActivity2::class.java).let {this@ShareElementListActivity.startActivity(it, bundle)}}}}rcv?.adapter = adapterrcv?.layoutManager = LinearLayoutManager(this)}private fun initData() {for (i in 0..100) {dataList.add("条目 $i")}}
}

列表数据适配器类:RcvAdapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.example.mytest.Rclass RcvAdapter(val context: Context, val dataList: MutableList<String>) :RecyclerView.Adapter<RcvAdapter.RcvViewHolder>() {var onItemClickListener: OnRcvItemClickListener? = nullclass RcvViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {private var container: ViewGroup? = nullprivate var tvContent: TextView? = nullinit {container = itemView.findViewById(R.id.ll_item_container)tvContent = itemView.findViewById(R.id.tv_content)}fun bind(textContent: String, position: Int) {tvContent?.text = textContentcontainer?.isSelected = true}}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RcvViewHolder {val view = LayoutInflater.from(context).inflate(R.layout.list_item_layout, parent, false)return RcvViewHolder(view)}override fun onBindViewHolder(holder: RcvViewHolder, position: Int) {holder.bind(dataList.get(position), position)holder.itemView.setOnClickListener {Toast.makeText(it.context, "点击了$position", Toast.LENGTH_SHORT).show()onItemClickListener?.onItemClicked(position, it)}}override fun getItemCount(): Int {return dataList.size}interface OnRcvItemClickListener {fun onItemClicked(position: Int, view: View)}
}

布局文件:

  1. ShareElementListActivity页面布局文件:activity_share_element_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".animation.ShareElementListActivity"tools:ignore="MissingDefaultResource"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rcv"android:layout_width="match_parent"android:layout_height="match_parent"tools:listitem="@layout/list_item_layout"/>
</androidx.constraintlayout.widget.ConstraintLayout>
  1. RcvAdapter用到的一个列表Item的布局文件:list_item_layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/ll_item_container"android:layout_width="match_parent"android:layout_height="100dp"><ImageViewandroid:id="@+id/iv_cover"android:layout_width="100dp"android:layout_height="80dp"android:scaleType="centerCrop"android:src="@drawable/test1"android:layout_marginStart="10dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="10dp"app:layout_constraintStart_toEndOf="@id/iv_cover"app:layout_constraintTop_toTopOf="@id/iv_cover"app:layout_constraintBottom_toBottomOf="@id/iv_cover"><TextViewandroid:id="@+id/tv_content"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你好啊"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_content_2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="你好啊"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/tv_content" /></androidx.constraintlayout.widget.ConstraintLayout></androidx.constraintlayout.widget.ConstraintLayout>

目标页面仍是 ShareElementActivity2,代码在3.1 例子里,不再赘述。

4. 总结

  1. 本文介绍了Android中共享元素转场动画的效果演示,尤其是比较常见的列表页面共享元素转场动画
  2. 介绍了实现方法:通过Android中提供了 ActivityOptionsCompat.makeSceneTransitionAnimation 方法来实现场景转场动画,配合xml中的属性android:transitionName,在Activity跳转时指定共享的元素View,生成bundle对象,然后传入startActivity方法中,系统便会自动为你实现上述效果。
  3. 提供了两个完整的代码示例演示了共享元素转场动画效果,读者可以非常方便的复制代码来实现文章中的效果,不会有那种虎头蛇尾,缺少各种上下文代码的烂尾教程所带来的困扰🤣。

如果你对这篇文章有更好的建议,欢迎评论留言交流;
如果这篇文章对你有用,欢迎支持!感谢支持哦😊~

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 机器学习之主成分分析(PCA)
  • Mipi SoundWire Spec 详解4.1
  • sql注入复现(1-14关)
  • linux下的C++程序
  • 【Linux】常见指令
  • 无人机挂载抓捕网
  • 基于Python的数据科学系列(1):Python基础
  • Android HandlerThread泄漏FD问题
  • 学习笔记五:在k8s中安装EFK组件
  • Java多商户新零售超市外卖商品系统
  • Project Euler_Problem 587_Concave Triangle (背包问题)
  • 力扣399.除法求值
  • Python 日志处理分析简介
  • AD交互式布局以及快捷键的设置
  • 内网穿透原理,免费内网穿透(简单使用),公网主动访问内网。
  • [笔记] php常见简单功能及函数
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • 11111111
  • Asm.js的简单介绍
  • eclipse(luna)创建web工程
  • gf框架之分页模块(五) - 自定义分页
  • iOS | NSProxy
  • JAVA_NIO系列——Channel和Buffer详解
  • Java到底能干嘛?
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • Puppeteer:浏览器控制器
  • Python十分钟制作属于你自己的个性logo
  • STAR法则
  • TypeScript迭代器
  • 初识 beanstalkd
  • 大整数乘法-表格法
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 好的网址,关于.net 4.0 ,vs 2010
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 那些年我们用过的显示性能指标
  • 驱动程序原理
  • 推荐一个React的管理后台框架
  • 我与Jetbrains的这些年
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • ​【原创】基于SSM的酒店预约管理系统(酒店管理系统毕业设计)
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • ​Python 3 新特性:类型注解
  • ​queue --- 一个同步的队列类​
  • ​人工智能书单(数学基础篇)
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (2.2w字)前端单元测试之Jest详解篇
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转载)虚幻引擎3--【UnrealScript教程】章节一:20.location和rotation
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换