我心向橙 發表於 2026-1-9 15:44:00

View共享动效

<p>从当前View过渡到另一个View,常规做法是针对View的坐标跟大小一起做平移,如果针对视频过渡,还更麻烦。</p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202601/583064-20260109132353690-267303294.gif"></p>
<p>常规动效实现(这里根据上面效果为例子),因为需要根据当前View的位置跟大小开始缩放过渡,并且过渡后的View样式跟过渡前的有差异,参数都无法动态获取</p>
<p><strong>常规动效缺点:</strong></p>
<blockquote>
<p>1、动效参数难获取,每次变更ui都要调整,很费时(ui上面透明区域变更,参数不是动态获取的就要跟着调整,动效复杂的话调整很费劲)</p>
<p>2、视频过渡麻烦,需要根据播放进度截图等处理</p>
<p>3、不好在界面之间解耦,很多逻辑会冗余</p>
<p>4、动效处理麻烦,需要针对坐标跟大小动效</p>
<p>5、过渡View差异大的,动效时还需要专门绘制动效的view视图,而不是直接过渡</p>
</blockquote>
<p>而共享动效更丝滑,可以直接从ViewA过渡到ViewB,并且支持Activity到Activity或者Fragment到Fragment</p>
<p><strong>下面提到的所有ViewA都是指过渡前的View,ViewB指过渡后的View</strong></p>
<p>如果对动效要求不高,那么实现很简单,只需要在跳转的时候标识为共享动效就行</p>
<blockquote>
<p>原生的共享动效流程是在ViewA到ViewB时,自动对ViewA做过渡,动效可以自己定义,缩放平移渐变都行</p>
<p>而退出动效时,是对ViewB做动效,从B过渡到ViewA,所以这里是被限制的</p>
</blockquote>
<p>上面效果是自定义的动效,因为无论入场跟退场,都是针对ViewB去做的动效</p>
<p>上面动效还有一个问题,就是层级问题,这里是有一个背景图,背景图中间有几个透明区域,而ViewA则刚好填补了透明区域,所以看着他们就好像一个整体</p>
<p><strong>共享动效的优势:</strong></p>
<blockquote>
<p>1、动效参数动态获取,只要调整好布局,可以复用动效</p>
<p>2、过渡简单,无论图片还是视频,都统一处理</p>
<p>3、界面解耦,可以在不同的Fragment或者Activity处理对应的逻辑</p>
<p>4、动效简单,一个属性动画解决所有问题</p>
<p>5、动效直接过渡,不需要中间动效层</p>
</blockquote>
<p><strong>难点:</strong></p>
<blockquote>
<p>1、共享动效需要在同一共享层级下处理,否则无效</p>
<p>2、针对不规则区域,需要借助UI来绘制区域(比如左边的圆形区域)</p>
<p>3、背景盖在View上面时,需要对层级进行处理,因为ViewB跟ViewA需要在同一共享层级,如果有背景图的View盖在A上面,B也会在背景图下面,就会被图片遮挡</p>
</blockquote>
<p>这里使用Fragment来实现动效,方便处理多个跳转</p>
<p>首先在Activity中需要提供一个容器,用来加载Fragment,这是必须的</p>
<p>但是在此,需要先考虑上面的一个难点,就是背景图怎么处理,背景使用一个ImageView来单独显示,需要放在Activity中,否则跟共享动效会有冲突</p>
<p>但是如果放在Activity中,又会存在共享层级问题,背景View需要在ViewA的上层才行(如果都是矩形区域,就没这个问题,我这边有个不规则圆形,但是我们的viewA是矩形,所以得靠背景将边上都盖住)</p>
<p>这里在我做之前想了挺久的,尝试了好几种方案,都因为共享层级冲突导致动效无法进行,最后得出了一个结论,那就是背景View必须在ViewA上面才行</p>
<p><strong>尝试的方案:</strong></p>
<blockquote>
<p>1、将背景View放底层,对不规则区域裁剪(直接放弃,这形状不好处理,特别是我这边还有视频,裁不了一点)</p>
<p>2、将背景View放在Fragment中,每个Fragment都有一个背景(直接放弃,需求是过渡缩放,如果每个Fragment都有背景,那背景View也会跟着缩放)</p>
<p>3、将背景View放在FragmentA中,由A来显示背景跟ViewA(直接放弃,跟Activity中没什么两样,背景View依然需要在上层,共享动效是ok了,但是过渡的ViewB被遮挡了)</p>
<p>4、动态调整视图层级,背景View在上层,点击动效时调整到下层(直接放弃,调整层级容易出问题,而且还有不规则的圆形ViewA,在层级变化时显示有问题)</p>
<p>5、让UI出土,将边上的区域一起抠出来,这样所有透明区域都是矩形,问题解决(繁琐,并且对拼接的细节处需要刚刚好,但是方案可行)</p>
<p>6、终极方案,在Activity的容器中,将背景View设置在ViewA下面,动效时针对ViewB层级调整,完美解决,并且简单</p>
</blockquote>
<p>最终方案很简单,主要就是需要一个思路,在所有View都处于同一共享层级下时,View层级就可以随意调整了,Activity布局如下</p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202601/583064-20260109175806525-206892440.png"></p>
<div class="cnblogs_code"><img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_682aafdb-9b38-41c7-9e1b-65893724f031" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_682aafdb-9b38-41c7-9e1b-65893724f031" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">androidx.constraintlayout.widget.ConstraintLayout </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
    xmlns:app</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res-auto"</span><span style="color: rgba(255, 0, 0, 1)">
    xmlns:tools</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/tools"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/root_container"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      app:layout_constraintStart_toStartOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(255, 0, 0, 1)">
      app:layout_constraintTop_toTopOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

      <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">ImageView
            </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_img"</span><span style="color: rgba(255, 0, 0, 1)">
            android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
            android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
            android:elevation</span><span style="color: rgba(0, 0, 255, 1)">="1dp"</span><span style="color: rgba(255, 0, 0, 1)">
            android:scaleType</span><span style="color: rgba(0, 0, 255, 1)">="fitXY"</span><span style="color: rgba(255, 0, 0, 1)">
            android:src</span><span style="color: rgba(0, 0, 255, 1)">="@drawable/test_bg"</span><span style="color: rgba(255, 0, 0, 1)">
            app:layout_constraintBottom_toBottomOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(255, 0, 0, 1)">
            app:layout_constraintEnd_toEndOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(255, 0, 0, 1)">
            app:layout_constraintStart_toStartOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(255, 0, 0, 1)">
            app:layout_constraintTop_toTopOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">androidx.constraintlayout.widget.ConstraintLayout</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">ActivityMainBinding</span></div>
<p>这样所有的View包括背景View都处于一个共享层级,然后将ViewA的Fragment先添加进去</p>
<div class="cnblogs_code"><img id="code_img_closed_9df15a25-2428-422a-9a6c-b604592f18fb" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_9df15a25-2428-422a-9a6c-b604592f18fb" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_9df15a25-2428-422a-9a6c-b604592f18fb" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.Bundle
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.ViewGroup
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.appcompat.app.AppCompatActivity
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.view.WindowInsetsCompat
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.view.WindowInsetsControllerCompat
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.example.myapplication.databinding.ActivityMainBinding

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MainActivity : AppCompatActivity() {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val mBinding by lazy {
      ActivityMainBinding::bind.invoke(findViewById</span>&lt;ViewGroup&gt;(android.R.id.content).getChildAt(0<span style="color: rgba(0, 0, 0, 1)">))
    }
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val TAG = "MainActivity"
    <span style="color: rgba(0, 0, 255, 1)">private</span> val fragment =<span style="color: rgba(0, 0, 0, 1)"> Test1Fragment()

    override fun onCreate(savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">      StrictMode.setThreadPolicy(
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">            ThreadPolicy.Builder()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">                .detectCustomSlowCalls()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">                .detectDiskReads()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">                .detectNetwork()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">                .penaltyLog()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">                .build()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">      )</span>
<span style="color: rgba(0, 0, 0, 1)">      supportFragmentManager.beginTransaction().replace(R.id.root_container, fragment)
            .commitAllowingStateLoss()
      val windowInsetsControllerCompat </span>=<span style="color: rgba(0, 0, 0, 1)"> WindowInsetsControllerCompat(window, window.decorView)
      windowInsetsControllerCompat.systemBarsBehavior </span>=<span style="color: rgba(0, 0, 0, 1)"> WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
      windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars())
      window.setDecorFitsSystemWindows(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
    }

}</span></pre>
</div>
<span class="cnblogs_code_collapse">MainActivity</span></div>
<p>基础布局很简单,就是ViewA,调整好位置,刚好处于透明区域</p>
<div class="cnblogs_code"><img id="code_img_closed_701116c2-9cf7-4dc4-bb57-931c3ac31358" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_701116c2-9cf7-4dc4-bb57-931c3ac31358" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_701116c2-9cf7-4dc4-bb57-931c3ac31358" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
    xmlns:app</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res-auto"</span><span style="color: rgba(255, 0, 0, 1)">
    xmlns:tools</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/tools"</span><span style="color: rgba(255, 0, 0, 1)">
    android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/test1"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:orientation</span><span style="color: rgba(0, 0, 255, 1)">="vertical"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.myapplication.CustomVideo
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_dim"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="336dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="72dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginStart</span><span style="color: rgba(0, 0, 255, 1)">="696dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginTop</span><span style="color: rgba(0, 0, 255, 1)">="840dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:transitionName</span><span style="color: rgba(0, 0, 255, 1)">="shared_dim"</span><span style="color: rgba(255, 0, 0, 1)">
      tools:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/black"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.myapplication.CustomVideo
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_ceiling"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="663dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="366dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginStart</span><span style="color: rgba(0, 0, 255, 1)">="939dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginTop</span><span style="color: rgba(0, 0, 255, 1)">="296dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:transitionName</span><span style="color: rgba(0, 0, 255, 1)">="shared_ceiling"</span><span style="color: rgba(255, 0, 0, 1)">
      tools:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/black"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.myapplication.CustomVideo
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_csd"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="385dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="240dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginStart</span><span style="color: rgba(0, 0, 255, 1)">="1081dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginTop</span><span style="color: rgba(0, 0, 255, 1)">="775dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:transitionName</span><span style="color: rgba(0, 0, 255, 1)">="shared_csd"</span><span style="color: rgba(255, 0, 0, 1)">
      tools:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/black"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.myapplication.CustomVideo
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_psd"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="385dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="240dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginStart</span><span style="color: rgba(0, 0, 255, 1)">="1477dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginTop</span><span style="color: rgba(0, 0, 255, 1)">="775dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:transitionName</span><span style="color: rgba(0, 0, 255, 1)">="shared_psd"</span><span style="color: rgba(255, 0, 0, 1)">
      tools:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/black"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">FragmentTest1Binding</span></div>
<p><img alt="image" width="513" height="299" loading="lazy" src="https://img2024.cnblogs.com/blog/583064/202601/583064-20260109145919407-1760447077.png"></p>
<p>dump布局可以看到,背景View在最上面,test1在上层,所以在Activity中对背景view进行了&nbsp;elevation 处理,让背景始终在ViewA的上面,这样就盖住了所有的A布局(对应下面test1布局)</p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202601/583064-20260109144059716-1216340559.png"></p>
<p>CustomVideo用来显示过渡View,这里ViewA跟ViewB都用的CustomVideo,只是ViewB加了黑色背景边框用于区分</p>
<div class="cnblogs_code"><img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_b35426c8-26f0-466b-bf2c-8c2de90ed1ab" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_b35426c8-26f0-466b-bf2c-8c2de90ed1ab" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Bitmap
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.Handler
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.Looper
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.AttributeSet
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.Log
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.PixelCopy
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.widget.FrameLayout
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.widget.ImageView
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.graphics.createBitmap
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.view.isVisible
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.example.myapplication.databinding.LayoutVideoBinding
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.PlaybackException
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.Player

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CustomVideo @JvmOverloads constructor(
    context: Context, attrs: AttributeSet</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
) : FrameLayout(context, attrs) {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val mBinding = LayoutVideoBinding.inflate(LayoutInflater.from(context), <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val mHandler =<span style="color: rgba(0, 0, 0, 1)"> Handler(Looper.getMainLooper())

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val playListener =<span style="color: rgba(0, 0, 0, 1)"> object : Player.Listener {
      override fun onPlayerError(error: PlaybackException) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onPlayerError(error)
            Log.e(</span>"CustomVideo", "onPlayerError $error"<span style="color: rgba(0, 0, 0, 1)">)
            PlayerManager.releasePlayer(mBinding.videoData)
      }

      override fun onPlayerErrorChanged(error: PlaybackException</span>?<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onPlayerErrorChanged(error)
            Log.e(</span>"CustomVideo", "onPlayerErrorChanged $error"<span style="color: rgba(0, 0, 0, 1)">)
      }

      override fun onRenderedFirstFrame() {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onRenderedFirstFrame()
            mBinding.ivImg.isVisible </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
      }
    }

    fun setImage(bitmap: Bitmap) {
      mBinding.ivImg.scaleType </span>=<span style="color: rgba(0, 0, 0, 1)"> ImageView.ScaleType.CENTER
      mBinding.ivImg.setImageBitmap(bitmap)
      mBinding.videoData.isVisible </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
    }

    fun start(bitmap: Bitmap</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span>, position: Long = -1<span style="color: rgba(0, 0, 0, 1)">) {
      Log.d(</span>"CustomVideo", "start position=$position"<span style="color: rgba(0, 0, 0, 1)">)
      mBinding.ivImg.setImageBitmap(bitmap)
      mBinding.ivImg.isVisible </span>= bitmap != <span style="color: rgba(0, 0, 255, 1)">null</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (position &gt;= 0<span style="color: rgba(0, 0, 0, 1)">) {
            PlayerManager.startPlay(
                </span>"/androidres/app_assets/com.zeekr.screensaver/picture_res/dynamic/8/csd/Metallic_caramel.mp4"<span style="color: rgba(0, 0, 0, 1)">,
                mBinding.videoData,
                position,
                playListener
            )
      }
    }

    fun getCurrentFrameBitmap(callBack: (Bitmap, Long) </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> Unit) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">此时视频surface处于view.GONE状态.</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (mBinding.videoData.width &lt;= 0 || mBinding.videoData.height &lt;= 0<span style="color: rgba(0, 0, 0, 1)">) {
            Log.d(</span>"CustomVideo", "视频没准备好,且视频控件处于可点击状态那么直接返回"<span style="color: rgba(0, 0, 0, 1)">)
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
      }
      val currentPosition </span>=<span style="color: rgba(0, 0, 0, 1)"> PlayerManager.getCurrentPosition(mBinding.videoData)
      val bmp </span>=<span style="color: rgba(0, 0, 0, 1)"> createBitmap(mBinding.videoData.width, mBinding.videoData.height)
      Log.d(</span>"CustomVideo", "getCurrentFrameBitmap $currentPosition $bmp"<span style="color: rgba(0, 0, 0, 1)">)
      PixelCopy.request(
            mBinding.videoData,
            bmp,
            { copyResult </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> callBack.invoke(bmp, currentPosition) },
            mHandler
      )
    }

}</span></pre>
</div>
<span class="cnblogs_code_collapse">CustomVideo</span></div>
<div class="cnblogs_code"><img id="code_img_closed_cc3a87f7-de32-4005-a299-ff74c8166225" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_cc3a87f7-de32-4005-a299-ff74c8166225" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_cc3a87f7-de32-4005-a299-ff74c8166225" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">merge </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:orientation</span><span style="color: rgba(0, 0, 255, 1)">="vertical"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">SurfaceView
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/video_data"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">ImageView
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_img"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:scaleType</span><span style="color: rgba(0, 0, 255, 1)">="fitXY"</span><span style="color: rgba(255, 0, 0, 1)">
      android:src</span><span style="color: rgba(0, 0, 255, 1)">="@drawable/test1"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">merge</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">LayoutVideoBinding</span></div>
<p>在基础布局的Fragment中逻辑很简单,只有点击时间,跳转到ViewB的Fragment</p>
<div class="cnblogs_code"><img id="code_img_closed_9f20040c-2805-4a6e-bd72-36fd6b3e4316" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_9f20040c-2805-4a6e-bd72-36fd6b3e4316" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_9f20040c-2805-4a6e-bd72-36fd6b3e4316" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.Bundle
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.View
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.ViewGroup
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.fragment.app.Fragment
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.example.myapplication.databinding.FragmentTest1Binding

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Test1Fragment : Fragment() {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val TAG = "Test1Fragment"
    <span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> lateinit var mBinding: FragmentTest1Binding
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val testCsd =<span style="color: rgba(0, 0, 0, 1)"> TestCsdFragment()
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val testPsd =<span style="color: rgba(0, 0, 0, 1)"> TestPsdFragment()
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val testDim =<span style="color: rgba(0, 0, 0, 1)"> TestDimFragment()
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val testCeiling =<span style="color: rgba(0, 0, 0, 1)"> TestCeilingFragment()

    override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup</span>?<span style="color: rgba(0, 0, 0, 1)">,
      savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">
    ): View {
      mBinding </span>= FragmentTest1Binding.inflate(inflater, container, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mBinding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onViewCreated(view, savedInstanceState)
      mBinding.ivCsd.start(position </span>= 0<span style="color: rgba(0, 0, 0, 1)">)
      mBinding.ivCsd.setOnClickListener {
            mBinding.ivCsd.getCurrentFrameBitmap { bitmap, position </span>-&gt;<span style="color: rgba(0, 0, 0, 1)">
                testCsd.bitmap </span>=<span style="color: rgba(0, 0, 0, 1)"> bitmap
                testCsd.position </span>=<span style="color: rgba(0, 0, 0, 1)"> position
                val transaction </span>=<span style="color: rgba(0, 0, 0, 1)"> parentFragmentManager.beginTransaction()
                transaction.addSharedElement(mBinding.ivCsd, mBinding.ivCsd.transitionName)
                transaction.hide(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">).add(R.id.root_container, testCsd)
                transaction.addToBackStack(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
                transaction.commit()
            }
      }

      mBinding.ivPsd.setOnClickListener {
            val transaction </span>=<span style="color: rgba(0, 0, 0, 1)"> parentFragmentManager.beginTransaction()
            transaction.addSharedElement(mBinding.ivPsd, mBinding.ivPsd.transitionName)
            transaction.hide(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">).add(R.id.root_container, testPsd)
            transaction.addToBackStack(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
            transaction.commit()
      }

      mBinding.ivDim.setOnClickListener {
            val transaction </span>=<span style="color: rgba(0, 0, 0, 1)"> parentFragmentManager.beginTransaction()
            transaction.addSharedElement(mBinding.ivDim, mBinding.ivDim.transitionName)
            transaction.hide(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">).add(R.id.root_container, testDim)
            transaction.addToBackStack(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
            transaction.commit()
      }

      mBinding.ivCeiling.setOnClickListener {
            val transaction </span>=<span style="color: rgba(0, 0, 0, 1)"> parentFragmentManager.beginTransaction()
            transaction.addSharedElement(mBinding.ivCeiling, mBinding.ivCeiling.transitionName)
            transaction.hide(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">).add(R.id.root_container, testCeiling)
            transaction.addToBackStack(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
            transaction.commit()
      }
    }

}</span></pre>
</div>
<span class="cnblogs_code_collapse">Test1Fragment</span></div>
<p>需要注意的是 addSharedElement,添加要过渡的共享View,并且ViewA跟ViewB的&nbsp;transitionName 需要保持一致</p>
<p>接下来是ViewB了,我这边为了简单,就直接为每个窗口都新建了一个测试的Fragment作为ViewB</p>
<div class="cnblogs_code"><img id="code_img_closed_b943c0df-9860-42c8-aefa-5c5caf9a1e80" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_b943c0df-9860-42c8-aefa-5c5caf9a1e80" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_b943c0df-9860-42c8-aefa-5c5caf9a1e80" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
    android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/test_psd"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:background</span><span style="color: rgba(0, 0, 255, 1)">="@android:color/transparent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:elevation</span><span style="color: rgba(0, 0, 255, 1)">="2dp"</span><span style="color: rgba(255, 0, 0, 1)">
    android:transitionName</span><span style="color: rgba(0, 0, 255, 1)">="shared_psd"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.myapplication.CustomVideo
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_psd"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/black"</span><span style="color: rgba(255, 0, 0, 1)">
      android:padding</span><span style="color: rgba(0, 0, 255, 1)">="20dp"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">FragmentTestPsdBinding</span></div>
<div class="cnblogs_code"><img id="code_img_closed_95bc8e6a-d5fd-4985-a1b7-9b5f708fb6a2" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_95bc8e6a-d5fd-4985-a1b7-9b5f708fb6a2" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_95bc8e6a-d5fd-4985-a1b7-9b5f708fb6a2" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.Bundle
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.View
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.ViewGroup
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.fragment.app.Fragment
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.example.myapplication.databinding.FragmentTestPsdBinding


</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestPsdFragment : Fragment() {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> lateinit var mBinding: FragmentTestPsdBinding

    init {
      sharedElementEnterTransition </span>= CustomScaleTransition(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
      sharedElementReturnTransition </span>= CustomScaleTransition(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
    }

    override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup</span>?<span style="color: rgba(0, 0, 0, 1)">,
      savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">
    ): View {
      mBinding </span>= FragmentTestPsdBinding.inflate(inflater, container, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mBinding.root
    }
   
    override fun onViewCreated(view: View, savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onViewCreated(view, savedInstanceState)
      mBinding.ivPsd.setOnClickListener {
            parentFragmentManager.popBackStack()
      }
    }

}</span></pre>
</div>
<span class="cnblogs_code_collapse">TestPsdFragment</span></div>
<p>布局作为测试demo很简单,跟ViewA一样,只是多了一个边框用于区分</p>
<p>还有个细节就是&nbsp;elevation 的处理,因为ViewB需要在最上层显示,逻辑很简单,如果是<strong>常规动效处理,要麻烦很多,并且无法复用</strong></p>
<p><img alt="image" width="528" height="309" loading="lazy" src="https://img2024.cnblogs.com/blog/583064/202601/583064-20260109145849646-924151125.png"></p>
<p>重点在&nbsp;CustomScaleTransition 中,这个类主要用于自定义共享动效</p>
<div class="cnblogs_code"><img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_a604a946-94e5-493f-a676-971e362525b6" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_a604a946-94e5-493f-a676-971e362525b6" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.animation.Animator
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.animation.ValueAnimator
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Rect
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.Log
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.View
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.ViewGroup
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.animation.AccelerateInterpolator
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.animation.doOnEnd
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.view.isVisible
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.transition.Transition
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.transition.TransitionValues
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.max
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.min

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* 最终执行 DefaultSpecialEffectsController -&gt; executeOperations -&gt; startAnimations
* startTransitions会返回一个startedTransitions集合,这个集合就是共享资源
* executeOperations 方法被 SpecialEffectsController 的 executePendingOperations 调用

* 通过 FragmentStateManage 中的 moveToExpectedState 方法判断,enter时会通过 enqueueShow 等方法,添加 sharedView,所以需要 hide 操作,否则动效不会生效
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">class</span> CustomScaleTransition(<span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val isEnter: Boolean) : Transition() {

    companion object {
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">const</span> val TAG = "CustomScaleTransition"
      <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">const</span> val VIEW_BOUNDS = "view_bounds"<span style="color: rgba(0, 0, 0, 1)">
    }

    override fun captureStartValues(transitionValues: TransitionValues) {
      val view </span>=<span style="color: rgba(0, 0, 0, 1)"> transitionValues.view
      transitionValues.values.put(VIEW_BOUNDS, Rect(view.left, view.top, view.right, view.bottom))
    }

    override fun captureEndValues(transitionValues: TransitionValues) {
      val view </span>=<span style="color: rgba(0, 0, 0, 1)"> transitionValues.view
      transitionValues.values.put(VIEW_BOUNDS, Rect(view.left, view.top, view.right, view.bottom))
    }

    override fun createAnimator(
      sceneRoot: ViewGroup,
      startValues: TransitionValues</span>?<span style="color: rgba(0, 0, 0, 1)">,
      endValues: TransitionValues</span>?<span style="color: rgba(0, 0, 0, 1)">
    ): Animator</span>?<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (startValues == <span style="color: rgba(0, 0, 255, 1)">null</span> || endValues == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
      }
      val test1View </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (isEnter) startValues.view <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> endValues.view
      val test2View </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (isEnter) endValues.view <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> startValues.view
      val startBounds </span>=<span style="color: rgba(0, 0, 0, 1)"> startValues.values as Rect
      val endBounds </span>=<span style="color: rgba(0, 0, 0, 1)"> endValues.values as Rect
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算translation</span>
      val tX = <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isEnter) {
            startBounds.centerX().toFloat() </span>-<span style="color: rgba(0, 0, 0, 1)"> endBounds.centerX()
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            endBounds.centerX().toFloat() </span>-<span style="color: rgba(0, 0, 0, 1)"> startBounds.centerX()
      }
      val tY </span>= <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isEnter) {
            startBounds.centerY().toFloat() </span>-<span style="color: rgba(0, 0, 0, 1)"> endBounds.centerY()
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            endBounds.centerY().toFloat() </span>-<span style="color: rgba(0, 0, 0, 1)"> startBounds.centerY()
      }
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算scale</span>
      val minW =<span style="color: rgba(0, 0, 0, 1)"> min(startBounds.width(), endBounds.width())
      val minH </span>=<span style="color: rgba(0, 0, 0, 1)"> min(startBounds.height(), endBounds.height())
      val maxW </span>=<span style="color: rgba(0, 0, 0, 1)"> max(startBounds.width(), endBounds.width())
      val maxH </span>=<span style="color: rgba(0, 0, 0, 1)"> max(startBounds.height(), endBounds.height())
      val myScaleX </span>= minW.toFloat() /<span style="color: rgba(0, 0, 0, 1)"> maxW
      val myScaleY </span>= minH.toFloat() /<span style="color: rgba(0, 0, 0, 1)"> maxH
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isEnter) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> hide状态需要提前visible</span>
            (test1View.parent as? ViewGroup)?.isVisible = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> view层级处理
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">            test1View.elevation = 1f
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> back后会被移除屏幕,需要重新add,在动效完成后remove</span>
            val rootView = <span style="color: rgba(0, 0, 255, 1)">if</span> (test2View.tag == "dim"<span style="color: rgba(0, 0, 0, 1)">) {
                test2View.parent as</span>?<span style="color: rgba(0, 0, 0, 1)"> ViewGroup
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                test2View as</span>?<span style="color: rgba(0, 0, 0, 1)"> ViewGroup
            }
            rootView</span>?.isVisible = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
            sceneRoot.addView(rootView)
      }
      Log.w(TAG, </span>"tX=$tX,tY=$tY, sX=$myScaleX,sY=$myScaleY"<span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ValueAnimator.ofFloat(0f, 1f).apply {
            duration </span>= 500<span style="color: rgba(0, 0, 0, 1)">
            interpolator </span>=<span style="color: rgba(0, 0, 0, 1)"> AccelerateInterpolator()
            addUpdateListener { animation </span>-&gt;<span style="color: rgba(0, 0, 0, 1)">
                val progress </span>=<span style="color: rgba(0, 0, 0, 1)"> animation.animatedValue as Float
                test2View</span>?<span style="color: rgba(0, 0, 0, 1)">.apply {
                  (parent as</span>? ViewGroup)?.findViewById&lt;View&gt;(R.id.blur_img)?<span style="color: rgba(0, 0, 0, 1)">.let {
                        it.alpha </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (isEnter) progress <span style="color: rgba(0, 0, 255, 1)">else</span> 1 -<span style="color: rgba(0, 0, 0, 1)"> progress
                  }
                  translationX </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (isEnter) (1 - progress) * tX <span style="color: rgba(0, 0, 255, 1)">else</span> tX - ((1 - progress) *<span style="color: rgba(0, 0, 0, 1)"> tX)
                  translationY </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (isEnter) (1 - progress) * tY <span style="color: rgba(0, 0, 255, 1)">else</span> tY - ((1 - progress) *<span style="color: rgba(0, 0, 0, 1)"> tY)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">                  Log.d(TAG, "update num=$progress,translationX=${translationX},translationY=$translationY")</span>
                  scaleX = <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isEnter) {
                        myScaleX </span>+ (1 - myScaleX) *<span style="color: rgba(0, 0, 0, 1)"> progress
                  } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                        myScaleX </span>+ (1 - progress) * (1 -<span style="color: rgba(0, 0, 0, 1)"> myScaleX)
                  }
                  scaleY </span>= <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isEnter) {
                        myScaleY </span>+ (1 - myScaleY) *<span style="color: rgba(0, 0, 0, 1)"> progress
                  } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                        myScaleX </span>+ (1 - progress) * (1 -<span style="color: rgba(0, 0, 0, 1)"> myScaleX)
                  }
                }
            }
            doOnEnd {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">                test1View.elevation = 0f</span>
                (test1View.parent as? ViewGroup)?.findViewById&lt;View&gt;(R.id.blur_img)?<span style="color: rgba(0, 0, 0, 1)">.let {
                  it.alpha </span>=<span style="color: rgba(0, 0, 0, 1)"> 1f
                }
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isEnter) {
                  (test1View.parent as</span>? ViewGroup)?.isVisible = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                  val rootView </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (test2View.tag == "dim"<span style="color: rgba(0, 0, 0, 1)">) {
                        test2View.parent as</span>?<span style="color: rgba(0, 0, 0, 1)"> ViewGroup
                  } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                        test2View as</span>?<span style="color: rgba(0, 0, 0, 1)"> ViewGroup
                  }
                  rootView</span>?.isVisible = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                  sceneRoot.removeView(rootView)
                }
                Log.w(TAG, </span>"animationEnd childCount=${sceneRoot.childCount}"<span style="color: rgba(0, 0, 0, 1)">)
            }
      }
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">CustomScaleTransition</span></div>
<p>可以看到,Transition 中,所有的参数都是原生计算好了可以直接获取,如果是常规的动效处理,这些数据就是额外的逻辑,但是在共享动效中,只需要关注你的动效</p>
<p><strong>重点讲解</strong></p>
<blockquote>
<p>1、使用&nbsp;isEnter 来区分是入场还是退场,因为这里违反了原生的规则(上面有讲),入场退场都是针对ViewB去做动效</p>
<p>2、如果是原生,只对&nbsp;endValues.view 做动效,入场时&nbsp;endValues.view是ViewA,退场时是ViewB,所以这里专门处理了一下,test1View表示上面提到的ViewA,test2View表示ViewB</p>
<p>3、共享动效是通过Fragment的显示隐藏状态控制,如果状态不对,动效无法执行,比如A跳转到B,那么A会被隐藏,但是我需要A显示,在A的基础上过渡到B,所以这里需要特殊处理</p>
<p>4、退场时,ViewB的Fragment被&nbsp;popBackStack,view被remove(原生是对ViewA动效,不影响),所以这里想要针对ViewB退场,就需要让ViewB可见(sceneRoot.addView(rootView))</p>
</blockquote>
<p>到这里,共享动效已经完成,点击ViewA,直接从自身开始平移并且缩放,过渡到ViewB,退场时从原来的路径返回到ViewA。</p>
<p><strong>dim效果</strong></p>
<p>开始效果图中发现,在 iv_dim 中,对不规则圆形处理,还有高斯模糊背景过渡,这里用的图片合成</p>
<div class="cnblogs_code"><img id="code_img_closed_d83c90d1-7256-4363-82bf-2350451f24f0" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_d83c90d1-7256-4363-82bf-2350451f24f0" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_d83c90d1-7256-4363-82bf-2350451f24f0" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
    android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/test_dim"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:elevation</span><span style="color: rgba(0, 0, 255, 1)">="2dp"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.myapplication.BlurImageView
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/blur_img"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:scaleType</span><span style="color: rgba(0, 0, 255, 1)">="fitXY"</span><span style="color: rgba(255, 0, 0, 1)">
      android:src</span><span style="color: rgba(0, 0, 255, 1)">="@drawable/test1"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.myapplication.CustomVideo
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_dim"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_gravity</span><span style="color: rgba(0, 0, 255, 1)">="center"</span><span style="color: rgba(255, 0, 0, 1)">
      android:tag</span><span style="color: rgba(0, 0, 255, 1)">="dim"</span><span style="color: rgba(255, 0, 0, 1)">
      android:transitionName</span><span style="color: rgba(0, 0, 255, 1)">="shared_dim"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">FragmentTestDimBinding</span></div>
<div class="cnblogs_code"><img id="code_img_closed_c28df92b-e27b-456a-becc-ad01b6b53a1b" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_c28df92b-e27b-456a-becc-ad01b6b53a1b" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_c28df92b-e27b-456a-becc-ad01b6b53a1b" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.annotation.SuppressLint
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.BitmapFactory
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.Bundle
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.View
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.ViewGroup
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.graphics.drawable.toBitmap
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.fragment.app.Fragment
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.example.myapplication.databinding.FragmentTestDimBinding


</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestDimFragment : Fragment() {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> lateinit var mBinding: FragmentTestDimBinding

    init {
      sharedElementEnterTransition </span>= CustomScaleTransition(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
      sharedElementReturnTransition </span>= CustomScaleTransition(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
    }

    override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup</span>?<span style="color: rgba(0, 0, 0, 1)">,
      savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">
    ): View {
      mBinding </span>= FragmentTestDimBinding.inflate(inflater, container, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mBinding.root
    }

    @SuppressLint(</span>"UseCompatLoadingForDrawables"<span style="color: rgba(0, 0, 0, 1)">)
    override fun onViewCreated(view: View, savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onViewCreated(view, savedInstanceState)
      val bitmap </span>= resources.getDrawable(R.drawable.test1_dim, context?<span style="color: rgba(0, 0, 0, 1)">.theme).toBitmap()
      val mask </span>=<span style="color: rgba(0, 0, 0, 1)"> BitmapFactory.decodeResource(resources, R.drawable.edit_dim_mask)
      val imageBmp </span>=<span style="color: rgba(0, 0, 0, 1)"> MaskImageUtil.applyMask(bitmap, mask)
      mBinding.blurImg.setBlurRadius(110f)
      mBinding.ivDim.setImage(imageBmp)
      mBinding.ivDim.setOnClickListener {
            parentFragmentManager.popBackStack()
      }
    }

}</span></pre>
</div>
<span class="cnblogs_code_collapse">TestDimFragment</span></div>
<p>首先小窗口图片是一个矩形图片</p>
<p><img alt="test1_dim" width="455" height="95" loading="lazy" src="https://img2024.cnblogs.com/blog/583064/202601/583064-20260109152640476-803638410.png"></p>
<p>需要让UI准备一张合成图片,也就是不规则的圆形区域图片,用于合成绘制</p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202601/583064-20260109152744248-367656098.png"></p>
<div class="cnblogs_code"><img id="code_img_closed_93ac641c-7b07-4f32-af71-13328e620ebe" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_93ac641c-7b07-4f32-af71-13328e620ebe" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_93ac641c-7b07-4f32-af71-13328e620ebe" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Bitmap
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Canvas
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Matrix
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Paint
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.PorterDuff
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.PorterDuffXfermode
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.graphics.createBitmap

object MaskImageUtil {
   
    @JvmStatic
    fun applyMask(src: Bitmap, mask: Bitmap): Bitmap {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> createBitmap(src.width, src.height, src.config).apply {
            val canvas </span>= Canvas(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
            val paint </span>=<span style="color: rgba(0, 0, 0, 1)"> Paint(Paint.ANTI_ALIAS_FLAG)
            canvas.drawBitmap(src, 0f, 0f, paint)
            paint.xfermode </span>=<span style="color: rgba(0, 0, 0, 1)"> PorterDuffXfermode(PorterDuff.Mode.DST_IN)
            val scaleMask </span>=<span style="color: rgba(0, 0, 0, 1)"> scaleBitmap(src, mask)
            canvas.drawBitmap(scaleMask, 0f, 0f, paint)
            paint.xfermode </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun scaleBitmap(src: Bitmap, mask: Bitmap): Bitmap {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> createBitmap(src.width, src.height, src.config).apply {
            val canvas </span>= Canvas(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
            val paint </span>=<span style="color: rgba(0, 0, 0, 1)"> Paint()
            val matrix </span>=<span style="color: rgba(0, 0, 0, 1)"> Matrix()
            val ratio </span>= src.width.toFloat() /<span style="color: rgba(0, 0, 0, 1)"> mask.width.toFloat()
            matrix.postScale(ratio, ratio)
            canvas.drawBitmap(mask, matrix, paint)
      }
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">MaskImageUtil</span></div>
<p>使用&nbsp;PorterDuff.Mode.DST_IN 模式绘制成圆形区域的形状</p>
<p>高斯模糊使用上面的矩形图片处理</p>
<div class="cnblogs_code"><img id="code_img_closed_a3cd582a-2ded-4ec0-be2d-b1955c5b912c" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_a3cd582a-2ded-4ec0-be2d-b1955c5b912c" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_a3cd582a-2ded-4ec0-be2d-b1955c5b912c" class="cnblogs_code_hide">
<pre>open <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> BlurImageView(
    context: Context,
    attrs: AttributeSet
) : AppCompatImageView(
    context,
    attrs
) {
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val mBlurNode = RenderNode("blur"<span style="color: rgba(0, 0, 0, 1)">)

    init {
      mBlurNode.setRenderEffect(RenderEffect.createBlurEffect(400f, 400f, Shader.TileMode.CLAMP))
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onSizeChanged(w, h, oldw, oldh)
      mBlurNode.setPosition(</span>0, 0<span style="color: rgba(0, 0, 0, 1)">, w, h)
    }

    override fun onDraw(canvas: Canvas) {
      val imageCanvas </span>=<span style="color: rgba(0, 0, 0, 1)"> mBlurNode.beginRecording()
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onDraw(imageCanvas)
      mBlurNode.endRecording()

      canvas.drawRenderNode(mBlurNode)
    }


    fun setBlurRadius(blurRadius: Float) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (blurRadius &gt; 0<span style="color: rgba(0, 0, 0, 1)">) {
            mBlurNode.setRenderEffect(
                RenderEffect.createBlurEffect(
                  blurRadius,
                  blurRadius,
                  Shader.TileMode.CLAMP
                )
            )
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            mBlurNode.setRenderEffect(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
      }
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">BlurImageView</span></div>
<p>讲绘制好的新图片&nbsp;mBinding.ivDim.setImage(imageBmp),这样就成了指定形状的样式了</p>
<p>视频效果</p>
<p>针对视频动效,这里需要一个图片去过渡,在ViewA进入ViewB之前,截取当前播放的视频图片,用于过渡到ViewB,等过渡完成后在ViewB中继续播放ViewA进度的视频</p>
<p>这里视频使用&nbsp;ExoPlayer 播放器播放</p>
<div class="cnblogs_code"><img id="code_img_closed_650a5a47-bd9b-4cde-8041-284b7c7c63e7" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_650a5a47-bd9b-4cde-8041-284b7c7c63e7" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_650a5a47-bd9b-4cde-8041-284b7c7c63e7" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.Log
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.SurfaceView
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.lifecycle.DefaultLifecycleObserver
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.blankj.utilcode.util.Utils
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.C
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.DefaultRenderersFactory
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.ExoPlayer
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.MediaItem
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.PlaybackException
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.Player
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.Player.Listener
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.mediacodec.MediaCodecInfo
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.mediacodec.MediaCodecUtil
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.source.MediaSource
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.source.ProgressiveMediaSource
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.android.exoplayer2.upstream.DefaultDataSource


object PlayerManager : DefaultLifecycleObserver {
    </span><span style="color: rgba(0, 0, 255, 1)">const</span> val TAG = "PlayerManager"

    <span style="color: rgba(0, 0, 255, 1)">private</span> val playerMutableMap = mutableMapOf&lt;String, ExoPlayer&gt;<span style="color: rgba(0, 0, 0, 1)">()

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val listener: Listener =<span style="color: rgba(0, 0, 0, 1)"> object : Listener {
      override fun onIsPlayingChanged(isPlaying: Boolean) {
            Log.d(TAG, </span>"onIsPlayingChanged state --&gt;$isPlaying"<span style="color: rgba(0, 0, 0, 1)">)
      }

      override fun onPlayerErrorChanged(error: PlaybackException</span>?<span style="color: rgba(0, 0, 0, 1)">) {
            Log.d(TAG, </span>"onPlayerErrorChanged error --&gt;$error"<span style="color: rgba(0, 0, 0, 1)">)
      }

      override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
            Log.d(
                TAG,
                </span>"onPlayWhenReadyChanged state -&gt;$playWhenReady- reason --&gt;$reason"<span style="color: rgba(0, 0, 0, 1)">
            )
      }

      override fun onPlayerError(error: PlaybackException) {
            Log.d(TAG, </span>"onPlayerError error --&gt;$error"<span style="color: rgba(0, 0, 0, 1)">)
      }

      override fun onRenderedFirstFrame() {
            Log.d(TAG, </span>"onRenderedFirstFrame"<span style="color: rgba(0, 0, 0, 1)">)
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> fun createPlayer(url: String, position: Long = 0L<span style="color: rgba(0, 0, 0, 1)">): ExoPlayer {
      Log.d(TAG, </span>"createPlayer"<span style="color: rgba(0, 0, 0, 1)">)
      val renderersFactory </span>=<span style="color: rgba(0, 0, 0, 1)"> DefaultRenderersFactory(Utils.getApp())
            .setMediaCodecSelector { mimeType, requiresSecureDecoder, requiresTunneling </span>-&gt;<span style="color: rgba(0, 0, 0, 1)">
                val allCodecs: List</span>&lt;MediaCodecInfo&gt; =<span style="color: rgba(0, 0, 0, 1)">
                  MediaCodecUtil.getDecoderInfos(mimeType, requiresSecureDecoder, </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
                val softwareCodecs: MutableList</span>&lt;MediaCodecInfo&gt; =<span style="color: rgba(0, 0, 0, 1)">
                  ArrayList()
                </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (info in allCodecs) {
                  val name: String </span>=<span style="color: rgba(0, 0, 0, 1)"> info.toString().toLowerCase()
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (name.contains("sw") || name.contains("omx.google"<span style="color: rgba(0, 0, 0, 1)">)) {
                        softwareCodecs.add(info)
                  }
                }
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (softwareCodecs.isEmpty()) allCodecs <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> softwareCodecs
            }
      val player </span>=<span style="color: rgba(0, 0, 0, 1)"> ExoPlayer.Builder(Utils.getApp(), renderersFactory).build()
      player.trackSelectionParameters </span>=<span style="color: rgba(0, 0, 0, 1)">
            player.trackSelectionParameters.buildUpon().setMaxVideoSize(</span>1280, 720<span style="color: rgba(0, 0, 0, 1)">)
                .setMaxVideoFrameRate(</span>30<span style="color: rgba(0, 0, 0, 1)">)
                .setMinVideoFrameRate(</span>15<span style="color: rgba(0, 0, 0, 1)">)
                .setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
                .setForceHighestSupportedBitrate(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
                .build()
      player.videoScalingMode </span>=<span style="color: rgba(0, 0, 0, 1)"> C.VIDEO_SCALING_MODE_SCALE_TO_FIT
      player.repeatMode </span>=<span style="color: rgba(0, 0, 0, 1)"> Player.REPEAT_MODE_ONE
      updateMediaSource(url, Utils.getApp()).let { player.setMediaSource(it) }
      player.prepare()
      player.addListener(listener)
      player.seekTo(position)
      player.playWhenReady </span>= <span style="color: rgba(0, 0, 255, 1)">true</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> player
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun updateMediaSource(url: String, mContext: Context): MediaSource {
      val dataSourceFactory </span>=<span style="color: rgba(0, 0, 0, 1)"> DefaultDataSource.Factory(mContext)
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ProgressiveMediaSource.Factory(dataSourceFactory)
            .createMediaSource(MediaItem.fromUri(url))
    }

    @JvmStatic
    fun startPlay(
      url: String,
      surfaceView: SurfaceView,
      position: Long </span>= 0L<span style="color: rgba(0, 0, 0, 1)">,
      listener: Listener
    ) {
      Log.d(
            TAG,
            </span>"startPlay called start url $url , surfaceView $surfaceView , position $position"<span style="color: rgba(0, 0, 0, 1)">
      )
      val key </span>=<span style="color: rgba(0, 0, 0, 1)"> System.identityHashCode(surfaceView).toString()
      var player </span>=<span style="color: rgba(0, 0, 0, 1)"> playerMutableMap
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (player == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            player </span>=<span style="color: rgba(0, 0, 0, 1)"> createPlayer(url, position)
            playerMutableMap </span>=<span style="color: rgba(0, 0, 0, 1)"> player
      }
      player.addListener(listener)
      player.setVideoSurfaceView(surfaceView)
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">player.isPlaying) {
            Log.d(TAG, </span>"startPlay called $surfaceView"<span style="color: rgba(0, 0, 0, 1)">)
            player.play()
      }
    }

    @JvmStatic
    fun startPlay(url: String, surfaceView: SurfaceView, listener: Listener) {
      Log.d(TAG, </span>"startPlay called start url $url , surfaceView $surfaceView"<span style="color: rgba(0, 0, 0, 1)">)
      val key </span>=<span style="color: rgba(0, 0, 0, 1)"> System.identityHashCode(surfaceView).toString()
      var player </span>=<span style="color: rgba(0, 0, 0, 1)"> playerMutableMap
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (player == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            player </span>=<span style="color: rgba(0, 0, 0, 1)"> createPlayer(url)
            playerMutableMap </span>=<span style="color: rgba(0, 0, 0, 1)"> player
      }
      player.setVideoSurfaceView(surfaceView)
      player.addListener(listener)
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">player.isPlaying) {
            Log.d(TAG, </span>"startPlay called $surfaceView"<span style="color: rgba(0, 0, 0, 1)">)
            player.prepare()
            player.play()
      }
    }

    @JvmStatic
    fun stopPlay(surfaceView: SurfaceView) {
      val key </span>=<span style="color: rgba(0, 0, 0, 1)"> System.identityHashCode(surfaceView).toString()
      val player </span>=<span style="color: rgba(0, 0, 0, 1)"> playerMutableMap
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (player != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> player.isPlaying) {
            player.pause()
      }
    }

    @JvmStatic
    fun resumePlay(surfaceView: SurfaceView) {
      val key </span>=<span style="color: rgba(0, 0, 0, 1)"> System.identityHashCode(surfaceView).toString()
      val player </span>=<span style="color: rgba(0, 0, 0, 1)"> playerMutableMap
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (player != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">player.isPlaying) {
            player.play()
      }
    }

    @JvmStatic
    fun releasePlayer(surfaceView: SurfaceView) {
      val key </span>=<span style="color: rgba(0, 0, 0, 1)"> System.identityHashCode(surfaceView).toString()
      val player </span>=<span style="color: rgba(0, 0, 0, 1)"> playerMutableMap
      Log.d(TAG, </span>"ReleasePlayer called $key player $player"<span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (player != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            player.stop()
            player.clearVideoSurfaceView(surfaceView)
            player.removeListener(listener)
            player.release()
            playerMutableMap.remove(key)
            Log.d(TAG, </span>"release called size = ${playerMutableMap.size}"<span style="color: rgba(0, 0, 0, 1)">)
      }
    }

    @JvmStatic
    fun getCurrentPosition(surfaceView: SurfaceView): Long {
      val key </span>=<span style="color: rgba(0, 0, 0, 1)"> System.identityHashCode(surfaceView).toString()
      val player </span>=<span style="color: rgba(0, 0, 0, 1)"> playerMutableMap
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (player != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> player.isPlaying) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> player.currentPosition
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> 0L<span style="color: rgba(0, 0, 0, 1)">
    }

}</span></pre>
</div>
<span class="cnblogs_code_collapse">PlayerManager</span></div>
<p>获取当前播放进度跟截图,然后在跳转时传递给ViewB,这里没有做处理,只是简单看看</p>
<div class="cnblogs_code"><img id="code_img_closed_3f97996c-a1fa-44ca-8de4-e55791dfa1a3" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_3f97996c-a1fa-44ca-8de4-e55791dfa1a3" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_3f97996c-a1fa-44ca-8de4-e55791dfa1a3" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
    android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/test_csd"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:background</span><span style="color: rgba(0, 0, 255, 1)">="@android:color/transparent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:clipChildren</span><span style="color: rgba(0, 0, 255, 1)">="false"</span><span style="color: rgba(255, 0, 0, 1)">
    android:clipToPadding</span><span style="color: rgba(0, 0, 255, 1)">="false"</span><span style="color: rgba(255, 0, 0, 1)">
    android:elevation</span><span style="color: rgba(0, 0, 255, 1)">="2dp"</span><span style="color: rgba(255, 0, 0, 1)">
    android:transitionName</span><span style="color: rgba(0, 0, 255, 1)">="shared_csd"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.myapplication.CustomVideo
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/iv_csd"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/black"</span><span style="color: rgba(255, 0, 0, 1)">
      android:padding</span><span style="color: rgba(0, 0, 255, 1)">="20dp"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">FragmentTestCsdBinding</span></div>
<div class="cnblogs_code"><img id="code_img_closed_99aed9de-594f-470d-bf01-c66f1f38bdbd" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_99aed9de-594f-470d-bf01-c66f1f38bdbd" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_99aed9de-594f-470d-bf01-c66f1f38bdbd" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Bitmap
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.Bundle
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.View
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.ViewGroup
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.fragment.app.Fragment
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.example.myapplication.databinding.FragmentTestCsdBinding

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestCsdFragment : Fragment() {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> lateinit var mBinding: FragmentTestCsdBinding

    lateinit var bitmap: Bitmap
    var position: Long </span>= 0L<span style="color: rgba(0, 0, 0, 1)">

    override fun onCreate(savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onCreate(savedInstanceState)
      sharedElementEnterTransition </span>= CustomScaleTransition(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
      sharedElementReturnTransition </span>= CustomScaleTransition(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
    }

    override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup</span>?<span style="color: rgba(0, 0, 0, 1)">,
      savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">
    ): View {
      mBinding </span>= FragmentTestCsdBinding.inflate(inflater, container, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mBinding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle</span>?<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onViewCreated(view, savedInstanceState)
      mBinding.ivCsd.start(bitmap, position)
      mBinding.ivCsd.setOnClickListener {
            parentFragmentManager.popBackStack()
      }
    }

}</span></pre>
</div>
<span class="cnblogs_code_collapse">TestCsdFragment</span></div>
<p>无论UI如何变更,只要xml调整好位置,其它全部都能复用,省时省力</p>
<p><strong>优化空间:</strong></p>
<p>1、存在内存泄露,需要处理,上面只做简单展示</p>
<p>2、播放器可以使用&nbsp;SurfaceControl 复用,这样可以在多个界面共享一个视图View</p>
<p>上面优化后面有空在出新篇</p><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/19461453
頁: [1]
查看完整版本: View共享动效