Material Motion for Android

Posted xiaoqiang_0719

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Material Motion for Android相关的知识,希望对你有一定的参考价值。

Building Beautiful Transitions with Material Motion for android

Container transform(MaterialContainerTransform)

MaterialContainerTransform 是一个共享元素动画, 但和传统的Android共享元素动画不同,它不是围绕单一的共享内容(如图像)设计的。这里的共享元素指的是开始视图ViewGroup容器将其大小和形状转换为结束视图ViewGroup容器的大小和形状,可以理解为开始和结束容器视图是容器转换的“共享元素”。并且可以用在 Fragments, Activities 或者 Views.

使用场景

由于gif图太大只上传了一个中间过程的效果,可去下面《参考文献》官方描述中查看gif图

在这里插入图片描述

  1. 从一个卡片到详情页
  2. 从列表的item到详情页
  3. 从一个FloatingActionButton到详情页
  4. 从搜索框扩展到搜索详情

Transition between Fragments

共享元素的MaterialContainerTransform(无需定义退出动画,默认翻转进入动画)

1、Set MaterialCardView transitionName(email_item_layout、fragment_email)

email_item_layout:

        <com.google.android.material.card.MaterialCardView
            android:id="@+id/card_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:elevation="@dimen/plane_00"
            android:clickable="true"
            android:focusable="true"
            android:transitionName="@{@string/email_card_transition_name(email.id)}"
            android:onClick="@{(view) -> listener.onEmailClicked(view, email)}"
            android:onLongClick="@{(view) -> listener.onEmailLongPressed(email)}"
            app:cardPreventCornerOverlap="false">
            
    
  fragment_email:
  
        <com.google.android.material.card.MaterialCardView
            android:id="@+id/email_card_view"
            android:transitionName="@string/email_card_detail_transition_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

2、HomeFragment

RecycleView点击事件:

    override fun onEmailClicked(cardView: View, email: Email) {
     
        exitTransition = MaterialElevationScale(false).apply {
            duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
        }
        reenterTransition = MaterialElevationScale(true).apply {
            duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
        }
        
        val emailCardDetailTransitionName = getString(R.string.email_card_detail_transition_name)
        val extras = FragmentNavigatorExtras(cardView to emailCardDetailTransitionName)
        val directions = HomeFragmentDirections.actionHomeFragmentToEmailFragment(email.id)
        findNavController().navigate(directions, extras)
    }
    
    
    **********************************
    这里注意,理论上后面写好EmailFragment的共享动画之后应该是可以实现共享效果了  
    但是没有从RecycleView的item位置进行动画,退出之后也没有回到item的位置 
    还需要在onViewCreated执行如下操作,因为列表绘制完成,我没没机会配置我们的Transition
    这相当于是在列表绘制过程中加入Transition动画,如果过使用RecycleView执行动画必须要加这两句话
    **********************************
    
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
       
        postponeEnterTransition()
        view.doOnPreDraw { startPostponedEnterTransition() }
        
    }
    
    
    

3、EmailFragment

跳转进入的界面:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        sharedElementEnterTransition = MaterialContainerTransform().apply {
            // Scope the transition to a view in the hierarchy so we know it will be added under
            // the bottom app bar but over the elevation scale of the exiting HomeFragment.
            drawingViewId = R.id.nav_host_fragment
            duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
            scrimColor = Color.TRANSPARENT
            setAllContainerColors(requireContext().themeColor(R.attr.colorSurface))
        }
    }

explain:onEmailClicked为点击事件,其中exitTransition为HomeFragment的退出动画,reenterTransition为重新进入动画。extras 为跳转EmailFragment的共享元素写法。

EmailFragment中MaterialContainerTransform执行共享元素动画,默认情况不需要设置退出动画,Android过渡系统会在导航返回时自动反转回去

  • drawingViewId =R.id.nav_host_fragment,运行的container transform与fragment 容器在同一级别,以确保它被绘制在底部应用程序栏和浮动动作按钮的下方。
  • scrimColor 它控制绘制在动画容器后面的半透明阴影的颜色。默认情况下,它被设置为32%的黑色。在这里它被设置为透明,意味着没有阴影被绘制
  • setAllContainerColors如果你的开始或结束视图本身没有绘制背景,设置这些背景填充颜色会很有用

从 startView 到 endView 的MaterialContainerTransform

例如从Activity的FloatingActionButton跳转到Fragment:

FloatingActionButton的点击事件:

    private fun navigateToCompose() {
    
        这里可以定义一个当前Fragment的退出动画,保持连贯的视觉效果
        
        val directions = ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId)
        findNavController(R.id.nav_host_fragment).navigate(directions)
    }


跳转到的Fragment(startView、endView)默认无动画,需自己定义退出动画

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        binding.run {

            enterTransition = MaterialContainerTransform().apply {
                startView = requireActivity().findViewById(R.id.fab)
                endView = emailCardView
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
                scrimColor = Color.TRANSPARENT
                containerColor = requireContext().themeColor(R.attr.colorSurface)
                startContainerColor = requireContext().themeColor(R.attr.colorSecondary)
                endContainerColor = requireContext().themeColor(R.attr.colorSurface)
            }
            
            returnTransition = Slide().apply {
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
                addTarget(R.id.email_card_view)
            }
        }
    }

Shared axis (MaterialSharedAxis)

Shared axis 看上去像平移动画,官方展示的三个例子分别是,横向平移、纵向平移和Z轴平移。

使用场景

在这里插入图片描述

  1. 从左到右是一个流程的(注册流程)使用 x-axis
  2. 从上倒下一步一步走的流程使用 y-axis
  3. 一个 parent-child navigation transitions 使用 z-axis

代码示例

点击跳转,当前页面先执行exitTransition动画:

    private fun navigateToSearch() {
        // TODO: Set up MaterialSharedAxis transition as exit and reenter transitions.
        currentNavigationFragment?.apply {
            exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).apply {
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
            }
            reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false).apply {
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
            }
        }
        val directions = SearchFragmentDirections.actionGlobalSearchFragment()
        findNavController(R.id.nav_host_fragment).navigate(directions)
    }
    
    
进入页面执行enterTransition动画:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).apply {
            duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
        }
        returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false).apply {
            duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
        }
    }

Fade through (MaterialFadeThrough)

Fade Through 本质上是一个透明度+缩放动画,官方的建议是用在两个关联性不强的界面的跳转中。

使用场景

在这里插入图片描述

  1. 点击底部导航栏切换
  2. 点击刷新列表
  3. 切换账户的场景

代码示例

    private fun navigateToCompose() {

        currentNavigationFragment?.apply {
            exitTransition = MaterialFadeThrough().apply {
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
            }
            reenterTransition = MaterialFadeThrough().apply {
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
            }
        }
        val directions = ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId)
        findNavController(R.id.nav_host_fragment).navigate(directions)
    }

Fade (MaterialFade)

Fade 动画和 Fade Through就动画本质而言,它们的确是一样的透明度+缩放动画,但是官方建议,如果发生在同一个界面,比如弹出Dialog、Menu等这类的弹框可以考虑这种动画。

使用场景

在这里插入图片描述

  1. A dialog
  2. A menu
  3. A snackbar
  4. A FAB

代码示例

跟MaterialFadeThrough使用和效果基本一致

    
        exitTransition = MaterialFade().apply {
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
            }
        
        
        enterTransition = MaterialFade().apply {
            duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
        }        

官方文档中只提供了上述四种Motion的描述,查看源代码发现还有以下动画效果


MaterialElevationScale

效果上跟MaterialSharedAxis很相似
MaterialElevationScale(false),注意下参数这个boolen值,它代表是视觉上是扩张还是收缩

代码示例

exitTransition是一个渐隐缩小后退的动画  

reenterTransition是一个渐隐放大的动画  

    private fun navigateToCompose() {

        currentNavigationFragment?.apply {
            exitTransition = MaterialElevationScale(false).apply {
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
            }
            reenterTransition = MaterialElevationScale(true).apply {
                duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
            }
        }
        val directions = ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId)
        findNavController(R.id.nav_host_fragment).navigate(directions)
    }

参考文献

material-components-android,github官方介绍地址

https://codelabs.developers.google.com/codelabs/material-motion-android

https://juejin.cn/post/6976102174627463198

以上是关于Material Motion for Android的主要内容,如果未能解决你的问题,请参考以下文章

Material Motion for Android

Motion Layout OnSwipe 禁止点击 YoutubePlayer (YoutubePlayer API)

《Motion Design for iOS》(二十)

《Motion Design for iOS》(二十一)

MOTION-MATCHING IN UBISOFT’S FOR HONOR翻译

《Motion Design for iOS》(十八)