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

news2025/2/23 6:17:41

文章目录

    • 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代码

<ImageView
   android: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.R

class ShareElementActivity1 : AppCompatActivity() {

    private var imgView: ImageView? = null

    override 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">

    <ImageView
        android: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" />

    <Button
        android: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.R

class ShareElementActivity2 : AppCompatActivity() {

    private var imgView: ImageView? = null

    override 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">

    <ImageView
        android: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" />

    <Button
        android: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.RcvAdapter

class ShareElementListActivity : AppCompatActivity() {

    private var rcv: RecyclerView? = null
    private var adapter: RcvAdapter? = null
    private 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 = adapter
        rcv?.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.R

class RcvAdapter(val context: Context, val dataList: MutableList<String>) :
    RecyclerView.Adapter<RcvAdapter.RcvViewHolder>() {

    var onItemClickListener: OnRcvItemClickListener? = null

    class RcvViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        private var container: ViewGroup? = null
        private var tvContent: TextView? = null

        init {
            container = itemView.findViewById(R.id.ll_item_container)
            tvContent = itemView.findViewById(R.id.tv_content)
        }

        fun bind(textContent: String, position: Int) {
            tvContent?.text = textContent
            container?.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.RecyclerView
        android: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">

    <ImageView
        android: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.ConstraintLayout
        android: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"
        >

        <TextView
            android: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" />

        <TextView
            android: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. 提供了两个完整的代码示例演示了共享元素转场动画效果,读者可以非常方便的复制代码来实现文章中的效果,不会有那种虎头蛇尾,缺少各种上下文代码的烂尾教程所带来的困扰🤣。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1986046.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

树莓派4B学习笔记24:Python_SYN6288语音模块的控制函数

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: ​ Opencv 版本是4.5.1&#xff1a; ​ Python 版本3.7.3&#xff1a; 今日学习SYN6288语…

叛逆,批判

1、对以往说法的批判之一&#xff08;第一次这么公开批判是2004-2005年&#xff09;&#xff1a; 这部英文版的《数学百科全书》似乎是从俄语版翻译过来的&#xff1f;我查了三本引用的图书文献&#xff0c;都没有关于“nonsingular”和“singular”的类似下面的说法&#xff…

加密市场再遭重创:多重利空因素引发超10亿美元抛售潮

四年前的全球“熔断潮”仿佛还在眼前&#xff0c;如今金融市场再度迎来剧烈震荡&#xff0c;全球股市与加密货币市场遭遇多重利空冲击&#xff0c;尤其是比特币和以太坊的闪崩使得市场恐慌情绪蔓延。这个“黑色星期一”不仅见证了股市的跳水&#xff0c;还标志着加密市场的又一…

每日学习笔记:C++ STL算法之已排序区间算法

目录 查找元素 检查某个元素是否存在: binary_search(beg, end, value) binary_search(beg, end, value, op) 检查数个元素是否存在&#xff1a; includes(beg, end, searchBeg, searchEnd) includes(beg, end, searchBeg, searchEnd, op) ​编辑 查找第一个或最后一…

网安新声 | 微软蓝屏事件安全启示录

网安加社区【网安新声】栏目&#xff0c;汇聚网络安全领域的权威专家与资深学者&#xff0c;紧跟当下热点安全事件、剖析前沿技术动态及政策导向&#xff0c;以专业视野和前瞻洞察&#xff0c;引领行业共同探讨并应对新挑战的策略与可行路径。 2024年7月19日&#xff0c;微软发…

搭建Mysql主从架构

一、主从 1、备份的三种类型 热备份逻辑备份物理备份 二、集群 1、概述 高可用集群&#xff08;HA&#xff09;是指通过特殊软件&#xff0c;把独立的服务器连接起来&#xff0c;组成一个能够提供故障间切换功能的集群。 2、标准 可用性级别 年宕机时间 描述 99% 3.65天 …

蜂窝网络架构

2G/3G 4G eNB RF-RRU eCPRI RRU-BBU 光纤 5G From 38.300 AMF处理信令等&#xff0c;UPF 用户面&#xff0c;后面还有SMF

Day20 文件IO

feof&#xff08;FILE*&#xff09; 用于检测文件是否到达结尾 ferror&#xff08;FILE*&#xff09; 检测文件是否发生错误 clearerr&#xff08;FILE*&#xff09; 清除错误 一.文件IO的特性 1 没有缓存区 2 操作对象不在是流&#xff0c;而是文件描述符 3…

埃拉托斯特尼筛法,厄拉多塞筛算法,C#实现

埃拉托斯特尼筛法&#xff0c;厄拉多塞筛算法 这个 很多地方的定义不一样。 虽然名字相同&#xff0c;但是具体的定义还是有差距。 有的地方描述的是&#xff1a; 厄拉多塞筛算法(Eratosthenes Sieve)是一种求素数的方法&#xff0c;由古希腊数学家厄拉多塞提出。它的原理是&…

如何下载学术论文、专利(最全)——包含免费方法

经常有同学问&#xff0c;如何下载论文 一、中文论文 万方&#xff08;在校生一般学校已购买权限&#xff0c;具体可看自己学校图书馆&#xff09; 网址&#xff1a;https://www.wanfangdata.com.cn/ 2. 知网&#xff08;在校生一般学校已购买权限&#xff0c;具体可看自己学…

【ML】transform 之 encode 及其实现细节

transform 之 encode 及其实现细节 1. transform (seq2seq) 是什么2. transform (seq2seq) 具体如何实现3. transform (seq2seq) 可以解决哪些类型的问题4. 补充问题4.1 残差连接&#xff08;Residual Connection&#xff09;是什么如何实现4.1.2 残差连接的具体实现&#xff1…

一文搞懂后端面试之MySQL MVCC【中间件 | 数据库 | MySQL | 隔离级别 | Read View】

为什么需要MVCC 锁本身就是用于并发控制的&#xff0c;那么为什么InnoDB还要引入MVCC&#xff0c;读写都加锁不就可以控制住并发吗&#xff1f; 锁确实可以&#xff0c;但是性能太差。如果是纯粹的锁&#xff0c;那么写和写、读和写、读和读之间都是互斥的。如果是读写锁&…

C#TreeView控件应用

1、代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace TestApp…

两数之和 II(LeetCode)

题目 给你一个下标从 1 开始的整数数组 &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 的两个数。 如果设这两个数分别是 和 &#xff0c;则 。 以长度为 的整数数组 形式返回这两个整数的下标 和 。 你可以假设每个输入…

【从零开始一步步学习VSOA开发】并行RPC服务端

并行RPC服务端 概念 之前 RPC 使用方式中的所有回调函数都是串行执行的&#xff0c;VSOA 为 C 语言开发版本提供了并行处理 RPC 请求的功能&#xff0c;开发者可以通过并发 RPC 相关接口创建线程池并行处理 RPC 请求。并发服务模式通过多线程来实现&#xff0c;能有效提高响应…

8.06 C++作业

使用类定义实现隔离储存计算 1.头文件 #ifndef CLASS_H #define CLASS_H #include <iostream> using namespace std;class rect { private:int width;int height; public:void init(int width,int height);void show();void setw(int w);void seth(int h); };#endif //…

gradle安装及配置

文章目录 一、下载安装包二、解压文件三、环境变量配置四、验证安装结果五、配置国内源六、IDEA配置 一、下载安装包 从gradle官网下载安装包&#xff0c;官网地址为&#xff1a;https://gradle.org/releases/ 我们只需要下载编译好的文件即可。 二、解压文件 解压文件到指定…

睡前故事—星际旅行者

文章目录 欢迎来到《Bedtime Stories Time》。这是一个我们倾听、放松、并逐渐入睡的播客。感谢你收听并支持我们&#xff0c;希望你能将这个播客作为你睡前例行活动的一部分。今晚我们将讲述《星际旅行者》的故事。一个宁静的夜晚&#xff0c;希望你现在已经感到平静、放松&am…

PXE的使用

配置前提 1、挂载镜像源&#xff0c;可正常下载软件 [rootredhat-7 ~]# mkdir -p /rhel7 ----创建挂载点目录 [rootredhat-7 ~]# mount /dev/sr0 /rhel7/ ----挂载镜像源至挂载点&#xff08;临时挂载&#xff0c;重启失效&#xff09;[rootredhat-7 ~]# vim /etc/yum.repos.…

[CR]厚云填补_GridDehazeNet+

GridDehazeNet: An Enhanced Multi-Scale Network With Intra-Task Knowledge Transfer for Single Image Dehazing Abstract 雾霾等恶劣天气条件会降低自动驾驶和智能交通系统的性能。作为一种潜在的补救措施&#xff0c;我们提出了一种增强的多尺度网络&#xff0c;称为GridD…