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)"><?</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)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">androidx.constraintlayout.widget.ConstraintLayout</span><span style="color: rgba(0, 0, 255, 1)">></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><ViewGroup>(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)"><?</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)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">></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进行了 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 >= 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>-><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 <= 0 || mBinding.videoData.height <= 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>-><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)"><?</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)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">merge</span><span style="color: rgba(0, 0, 255, 1)">></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>-><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的 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)"><?</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)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">></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>还有个细节就是 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>重点在 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 -> executeOperations -> 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>-><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<View>(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<View>(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、使用 isEnter 来区分是入场还是退场,因为这里违反了原生的规则(上面有讲),入场退场都是针对ViewB去做动效</p>
<p>2、如果是原生,只对 endValues.view 做动效,入场时 endValues.view是ViewA,退场时是ViewB,所以这里专门处理了一下,test1View表示上面提到的ViewA,test2View表示ViewB</p>
<p>3、共享动效是通过Fragment的显示隐藏状态控制,如果状态不对,动效无法执行,比如A跳转到B,那么A会被隐藏,但是我需要A显示,在A的基础上过渡到B,所以这里需要特殊处理</p>
<p>4、退场时,ViewB的Fragment被 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)"><?</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)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">></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>使用 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 > 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>讲绘制好的新图片 mBinding.ivDim.setImage(imageBmp),这样就成了指定形状的样式了</p>
<p>视频效果</p>
<p>针对视频动效,这里需要一个图片去过渡,在ViewA进入ViewB之前,截取当前播放的视频图片,用于过渡到ViewB,等过渡完成后在ViewB中继续播放ViewA进度的视频</p>
<p>这里视频使用 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<String, ExoPlayer><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 -->$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 -->$error"<span style="color: rgba(0, 0, 0, 1)">)
}
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
Log.d(
TAG,
</span>"onPlayWhenReadyChanged state ->$playWhenReady- reason -->$reason"<span style="color: rgba(0, 0, 0, 1)">
)
}
override fun onPlayerError(error: PlaybackException) {
Log.d(TAG, </span>"onPlayerError error -->$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>-><span style="color: rgba(0, 0, 0, 1)">
val allCodecs: List</span><MediaCodecInfo> =<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><MediaCodecInfo> =<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> &&<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> && !<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> &&<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)"><?</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)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</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)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">FrameLayout</span><span style="color: rgba(0, 0, 255, 1)">></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、播放器可以使用 SurfaceControl 复用,这样可以在多个界面共享一个视图View</p>
<p>上面优化后面有空在出新篇</p><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/19461453
頁:
[1]