美文网首页
安卓两个页面组件的无缝衔接part1(共享元素)

安卓两个页面组件的无缝衔接part1(共享元素)

作者: 宛丘之上兮 | 来源:发表于2023-11-20 23:58 被阅读0次

    对于界面切换时View的无缝衔接,首先想到的应该是android 5.0提供的Share Element Transition(共享元素变换),本文来研究下怎么应用这个动画,先看效果:

    1-5.gif

    上面的效果图中有三个页面:MainActivity、SecondListAct、ThirdListAct,三个页面之间有三个共享元素,transitionName分别是:playView、shareview1、shareview2,图中可以看到,页面之间View元素之间的衔接很丝滑,播放视频没有卡帧、跳帧现象,而且音频也没有被中断。下面看下代码实现。

    一 第一个页面

    MainActivity.kt:

    class MainActivity : AppCompatActivity() {
        lateinit var bind: ActivityMainBinding
        var playPaused: Boolean = false
        var clickGotoPage2 = false
        var mPlayer: SysMediaPlayer = SysMediaPlayer.getInstance()
        var mSurface: Surface? = null
        var seamlessCb = object : SeamlessObserver.Callback {
            override fun onEvent(type: Int, currentAttr: ViewAttr?) {
                Log.w("zzh", "on event on mainact type=${type}")
                if (type == 1) {
                    if (mSurface != null) {
                        mPlayer.setSurface(mSurface)
                        mPlayer.start()
                    }
                }
                clickGotoPage2 = false
            }
        }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            bind = ActivityMainBinding.inflate(layoutInflater)
            setContentView(bind.root)
            bind.btn1.setOnClickListener { p0 -> onClickBtn1(p0) }
            bind.btn2.setOnClickListener { p0 -> onClickBtn2(p0) }
            SeamlessObserver.getInstance().register(seamlessCb)
    
            bind.playView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
                override fun onSurfaceTextureAvailable(surface: SurfaceTexture, w: Int, h: Int) {
                    Log.w("zzh", "surface log available in main activity w=$w h=$h thread=${Thread.currentThread()}")
                    if (mSurface == null) {
                        mPlayer!!.setAudioStreamType(AudioManager.STREAM_MUSIC)
                        mPlayer!!.setDataSource("https://hw-v.cztv.com/cztv/vod/2023/11/11/0b1098f4e2dc7e4ba4e8a0ceb39ccadf/0b1098f4e2dc7e4ba4e8a0ceb39ccadf_h264_800k_mp4.mp4_playlist.m3u8")
                        mPlayer!!.mInternalMediaPlayer.setOnPreparedListener(object : MediaPlayer.OnPreparedListener {
                            override fun onPrepared(mp: MediaPlayer?) {
                                mPlayer!!.start()
                            }
                        })
                        mPlayer!!.mInternalMediaPlayer.prepare()
                    }
                    mSurface = Surface(surface)
                    mPlayer!!.setSurface(mSurface)
                }
    
                override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, w: Int, h: Int) {
                    Log.w("zzh", "surface log size changed in main activity w=$w h=$h")
                }
    
                override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
                    Log.w("zzh", "surface log destroy in main activity")
                    return false
                }
    
                override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
                    // Log.w("zzh", "surface log update in main activity")
                }
            }
    
            var callback = DefaultShareCallback()
            callback.setTag("MainActivity")
            setExitSharedElementCallback(callback)
        }
    
        private fun onClickBtn1(v: View?) {
            clickGotoPage2 = true
            var bundle =
                ActivityOptions.makeSceneTransitionAnimation(this, bind.btn1, "shareview1").toBundle()
    
            val p1: Pair<View, String> = Pair<View, String>(bind.btn1, bind.btn1.transitionName)
            val p2: Pair<View, String> = Pair<View, String>(bind.btn2, bind.btn2.transitionName)
            val p3: Pair<View, String> = Pair<View, String>(bind.playView, bind.playView.transitionName)
            bundle = ActivityOptions.makeSceneTransitionAnimation(this, p1, p2, p3).toBundle()
    
            val intent = Intent(this, SecondAct::class.java)
            startActivity(intent, bundle)
            if (Build.VERSION.SDK_INT >= 34) {
                overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, 0, 0)
            } else {
                overridePendingTransition(0, 0)
            }
        }
    
        private fun onClickBtn2(v: View) {
            clickGotoPage2 = true
            var bundle =
                ActivityOptions.makeSceneTransitionAnimation(this, bind.btn1, "shareview1").toBundle()
            val p1: Pair<View, String> = Pair<View, String>(bind.btn1, bind.btn1.transitionName)
            val p2: Pair<View, String> = Pair<View, String>(bind.btn2, bind.btn2.transitionName)
            val p3: Pair<View, String> = Pair<View, String>(bind.playView, bind.playView.transitionName)
            bundle = ActivityOptions.makeSceneTransitionAnimation(this, p1, p2, p3).toBundle()
            val intent = Intent(this, SecondListAct::class.java)
            startActivity(intent, bundle)
            if (Build.VERSION.SDK_INT >= 34) {
                overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, 0, 0)
            } else {
                overridePendingTransition(0, 0)
            }
        }
    
        override fun onPause() {
            super.onPause()
            if (!clickGotoPage2) {
                mPlayer.pause()
                playPaused = true
            }
        }
    
        override fun onResume() {
            if (playPaused) {
                mPlayer.start()
            }
            clickGotoPage2 = false
            super.onResume()
        }
    
        override fun onDestroy() {
            super.onDestroy()
            SeamlessObserver.getInstance().unregister(seamlessCb)
        }
    
    }
    

    activity_main.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <TextureView
            android:id="@+id/playView"
            android:transitionName="playView"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_width="match_parent"
            android:layout_height="160dp"/>
    
        <Button
            android:id="@+id/btn1"
            android:transitionName="shareview1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Btn1"
            app:layout_constraintTop_toBottomOf="@id/playView"
            app:layout_constraintStart_toStartOf="parent" />
    
        <Button
            android:id="@+id/btn2"
            android:transitionName="shareview2"
            android:layout_marginStart="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Btn2"
            app:layout_constraintTop_toBottomOf="@id/playView"
            app:layout_constraintStart_toEndOf="@id/btn1" />
    
        <SeekBar
            android:id="@+id/seekbar"
            app:layout_constraintBottom_toBottomOf="@id/playView"
            android:layout_width="match_parent"
            android:layout_height="4dp"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    核心代码在函数onClickBtn1里,共三个共享元素,组成三个Pair对p1、p2、p3,Pair的first是View实例,Pair的second是View的transitionName,然后通过ActivityOptions.makeSceneTransitionAnimation(this, p1, p2, p3).toBundle()生成一个Bundle对象,再调用startActivity(intent, bundle)即可,非常简单。

    二 第二个页面

    SecondListAct.kt

    class SecondListAct : AppCompatActivity() {
        lateinit var bind: ActivitySecondListBinding
        lateinit var mAdapter: DemoAdapter
        var seamlessCb = object : SeamlessObserver.Callback {
            override fun onEvent(type: Int, a: ViewAttr?) {
                Log.w("zzh", "on event on second lsit act")
                mAdapter.resetSurface(bind.recycler, 1)
            }
        }
        override fun onCreate(savedInstanceState: Bundle?) {
            var callback = DefaultShareCallback()
            callback.setTag("SecondListAct")
            setEnterSharedElementCallback(callback)
    
            postponeEnterTransition() // 暂时阻止启动共享元素 Transition
            SeamlessObserver.getInstance().register(seamlessCb)
            super.onCreate(savedInstanceState)
            bind = ActivitySecondListBinding.inflate(layoutInflater)
            supportActionBar?.title = "SecondList"
            setContentView(bind.root)
            var lm = LinearLayoutManager(this)
            lm.orientation = RecyclerView.VERTICAL
            bind.recycler.layoutManager = lm
    
            mAdapter = DemoAdapter(this)
            bind.recycler.adapter = mAdapter
            bind.recycler.doOnPreDraw {
                startPostponedEnterTransition() // 共享元素准备好后调用 startPostponedEnterTransition 来恢复过渡效果
            }
    
            bind.testbtn.setOnClickListener { gotoThirdListAct() }
        }
    
        override fun onBackPressed() {
            SeamlessObserver.getInstance().unregister(seamlessCb)
            SeamlessObserver.getInstance().execute(1, null)
            super.onBackPressed()
            Log.w("zzh", "haha on act back press")
        }
    
        private fun gotoThirdListAct() {
            var holder: DemoAdapter.ThisVH = bind.recycler.findViewHolderForLayoutPosition(1) as DemoAdapter.ThisVH
    
            val p1: Pair<View, String> = Pair<View, String>(holder.btn1, holder.btn1.transitionName)
            val p2: Pair<View, String> = Pair<View, String>(holder.btn2, holder.btn2.transitionName)
            val p3: Pair<View, String> = Pair<View, String>(holder.playView, holder.playView.transitionName)
            var bundle = ActivityOptions.makeSceneTransitionAnimation(this, p1, p2, p3).toBundle()
            val intent = Intent(this, ThirdListAct::class.java)
            startActivity(intent, bundle)
            if (Build.VERSION.SDK_INT >= 34) {
                overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, 0, 0)
            } else {
                overridePendingTransition(0, 0)
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
            SeamlessObserver.getInstance().unregister(seamlessCb)
        }
    }
    

    activity_second_list.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".SecondListAct">
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
        <Button
            android:id="@+id/testbtn"
            android:text="前往新list页面"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginBottom="-20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    在第二个页面SecondListAct里,需要注意的只有三点:

    1. 由于第二个页面SecondListAct的共享元素是在RecyclerView里,因此需要在Activity#onCreate里延迟过渡动画执行,postponeEnterTransition()就是这个作用。如果共享元素是在activity的xml布局里,不需要延迟执行过渡动画
    2. 调用postponeEnterTransition后不要忘记调用startPostponedEnterTransition执行过渡动画,
      忘记调用startPostponedEnterTransition会让你的应用处于死锁状态,用户无法进入下个Activity。在代码中,我们是在RecyclerView#doOnPreDraw中执行动画的。
    3. 不要将共享元素延迟超过到1s以上。延迟时间过长会在应用中产生不必要的卡顿,影响用户体验。

    三 第三个页面

    ThirdListAct.kt

    class ThirdListAct : AppCompatActivity() {
        lateinit var bind: ActivitySecondListBinding
        lateinit var mAdapter: DemoAdapter
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            postponeEnterTransition() // 暂时阻止启动共享元素 Transition
            bind = ActivitySecondListBinding.inflate(layoutInflater)
            supportActionBar?.title = "ThirdList"
            setContentView(bind.root)
            bind.testbtn.visibility = View.GONE
            var lm = LinearLayoutManager(this)
            lm.orientation = RecyclerView.VERTICAL
            bind.recycler.layoutManager = lm
    
            mAdapter = DemoAdapter(this, 2, R.layout.list_item_demo2)
            bind.recycler.adapter = mAdapter
            bind.recycler.doOnPreDraw {
                startPostponedEnterTransition() // 共享元素准备好后调用 startPostponedEnterTransition 来恢复过渡效果
            }
    
            onBackPressedDispatcher.addCallback(this, object  : OnBackPressedCallback(false) {
                override fun handleOnBackPressed() {
                    Log.w("zzh", "haha on act handle back press")
                }
            })
        }
    
        override fun onBackPressed() {
            SeamlessObserver.getInstance().execute(1, null)
            super.onBackPressed()
            Log.w("zzh", "haha on act back press")
        }
    }
    

    上面是第三个页面ThirdListAct的代码,和第二个页面的代码几乎一样的,没啥特殊的,xml布局都是同一个。

    四 其余辅助类源码

    SeamlessObserver.java

    public class SeamlessObserver {
        private List<Callback> callbacks = new ArrayList<>();
        private SeamlessObserver(){}
    
        private static class Holder {
            private static SeamlessObserver INSTANCE = new SeamlessObserver();
        }
    
        public static SeamlessObserver getInstance() {
            return Holder.INSTANCE;
        }
    
        public void register(Callback cb) {
            if (cb == null || callbacks.contains(cb)) {
                return;
            }
            callbacks.add(cb);
        }
    
        public void unregister(Callback cb) {
            if (!callbacks.contains(cb)) {
                return;
            }
            callbacks.remove(cb);
        }
    
        public void execute(int type, ViewAttr attr) {
            if (callbacks.size() > 0) {
                Callback callback = callbacks.get(callbacks.size() - 1);
                callback.onEvent(type, attr);
            }
            // for (Callback callback : callbacks) {
            // }
        }
    
        public interface Callback {
            void onEvent(int type, ViewAttr attr);
        }
    }
    

    DemoAdapter.kt

    class DemoAdapter : RecyclerView.Adapter<DemoAdapter.ThisVH> {
        private val mDatas: ArrayList<String> = ArrayList<String>()
        private var mSelected = -1
        private var mLayoutId = -1
        var mPlayer: SysMediaPlayer = SysMediaPlayer.getInstance()
        var mSurface: Surface? = null
    
        constructor(ctx: Context, defaultIndex: Int = 1, layoutId: Int = R.layout.list_item_demo) {
            for (i in 0..6) {
                mDatas.add("index:$i")
            }
            mSelected = defaultIndex
            mLayoutId = layoutId
        }
    
        public fun resetSurface(recyclerView: RecyclerView, pos: Int) {
            Log.w("zzh", "reset surface pos=$mSelected")
            notifyItemChanged(mSelected)
            // holder.bind(mSelected)
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThisVH {
            val view: View =
                LayoutInflater.from(parent.context).inflate(mLayoutId, parent, false)
            return ThisVH(parent, view)
        }
    
        override fun getItemCount(): Int {
            return mDatas.size
        }
    
        override fun onBindViewHolder(holder: ThisVH, position: Int) {
            holder.bind(position)
        }
    
        inner class ThisVH(parent: ViewGroup, itemView: View) : RecyclerView.ViewHolder(itemView) {
            var playView: TextureView
            var btn1: TextView
            var btn2: TextView
    
            init {
                playView = itemView.findViewById(R.id.playView)
                btn1 = itemView.findViewById(R.id.btn1)
                btn2 = itemView.findViewById(R.id.btn2)
            }
    
            fun bind(pos: Int) {
                btn1.text = "btn1 index=$pos"
                btn2.text = "btn2 index=$pos"
                Log.w("zzh", "bind view holder pos=$pos holder=$this")
                // itemView.setBackgroundColor(itemView.context.resources.getColor(if (mSelected == pos) android.R.color.holo_red_dark else R.color.teal_700))
                if (mSelected == pos) {
                    playView.transitionName = "playView"
                    btn1.transitionName = "shareview1"
                    btn2.transitionName = "shareview2"
                } else {
                    playView.transitionName = ""
                    btn1.transitionName = ""
                    btn2.transitionName = ""
                }
    
                playView.surfaceTextureListener = object : SurfaceTextureListener {
                    override fun onSurfaceTextureAvailable(s: SurfaceTexture, w: Int, h: Int) {
                        if (mSelected == pos) {
                            mSurface = Surface(s)
                            mPlayer.setSurface(mSurface)
                        }
                    }
    
                    override fun onSurfaceTextureSizeChanged(s: SurfaceTexture, w: Int, h: Int) {
                    }
    
                    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
                        if (mSelected == pos) {
                            Log.w("zzh", "haha texture destroy")
                        }
                        return false
                    }
    
                    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
                    }
    
                }
            }
        }
    }
    

    list_item_demo.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/teal_700"
        android:layout_marginBottom="5dp">
        <TextureView
            android:id="@+id/playView"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_width="match_parent"
            android:layout_height="160dp"/>
    
        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Btn1"
            app:layout_constraintTop_toBottomOf="@id/playView"
            app:layout_constraintStart_toStartOf="parent" />
    
        <Button
            android:id="@+id/btn2"
            android:layout_marginStart="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Btn2"
            app:layout_constraintTop_toBottomOf="@id/playView"
            app:layout_constraintStart_toEndOf="@id/btn1" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    SysMediaPlayer.java

    public class SysMediaPlayer {
        public MediaPlayer mInternalMediaPlayer;
    
        private enum PlayerState {
            STATE_ERROR,
            STATE_IDLE,
            STATE_INITIALIZED,
            STATE_PREPARING,
            STATE_PREPARED,
            STATE_STARTED,
            STATE_PAUSED,
            STATE_STOPPED,
            STATE_COMPLETE,
        }
        private PlayerState mPlayerState;
    
        public SysMediaPlayer(Context context, int resId) {
            mInternalMediaPlayer = MediaPlayer.create(context, resId);
        }
    
        private SysMediaPlayer() {
            mInternalMediaPlayer = new MediaPlayer();
        }
    
        private static class Holder {
            private static SysMediaPlayer INSTANCE = new SysMediaPlayer();
        }
        public static SysMediaPlayer getInstance() {
            return Holder.INSTANCE;
        }
    
        public void setDataSource(String s) {
            try {
                Map<String, String> headers = new HashMap<String, String>();
                mInternalMediaPlayer.setDataSource(s);
            } catch (Exception e) {
                Log.e("zzh", "set data source error", e);
            }
        }
    
        public void setDataSource(Context context, Uri s) {
            try {
                mInternalMediaPlayer.setDataSource(context, s);
            } catch (Exception e) {
                Log.e("zzh", "set data source2 error", e);
            }
        }
    
        public void setAudioStreamType(int type) {
            mInternalMediaPlayer.setAudioStreamType(type);
        }
    
        public void setSurface(Surface surface) {
            mInternalMediaPlayer.setSurface(surface);
        }
    
        public void prepareAsync() throws IllegalStateException {
            if (mPlayerState == PlayerState.STATE_INITIALIZED || mPlayerState == PlayerState.STATE_STOPPED) {
                mInternalMediaPlayer.prepareAsync();
                mPlayerState = PlayerState.STATE_PREPARING;
            }
        }
    
        public void prepare() {
            try {
                mInternalMediaPlayer.prepare();
                mPlayerState = PlayerState.STATE_PREPARED;
            } catch (IOException e) {
                Log.e("zzh", "prepare error", e);
            }
        }
    
        public void start() throws IllegalStateException {
            // if (mPlayerState == PlayerState.STATE_PREPARED || mPlayerState == PlayerState.STATE_COMPLETE || mPlayerState == PlayerState.STATE_PAUSED) {
            //     // 必须在onPrepared后设置,如果onPrepared时设置相当于调用start/pause,所以移到start接口
            //     // realSetSpeed();
            //     mInternalMediaPlayer.start();
            // }
            mInternalMediaPlayer.start();
            mPlayerState = PlayerState.STATE_STARTED;
        }
    
        public void pause() throws IllegalStateException {
            if (mPlayerState == PlayerState.STATE_STARTED) {
            }
            mPlayerState = PlayerState.STATE_PAUSED;
            mInternalMediaPlayer.pause();
        }
    
        public void stop() throws IllegalStateException {
            if (mPlayerState == PlayerState.STATE_STARTED ||
                    mPlayerState == PlayerState.STATE_PREPARED ||
                    mPlayerState == PlayerState.STATE_PAUSED ||
                    mPlayerState == PlayerState.STATE_COMPLETE) {
                mInternalMediaPlayer.stop();
                mPlayerState = PlayerState.STATE_STOPPED;
            }
        }
    
        public void release() {
            mInternalMediaPlayer.release();
        }
    }
    

    SecondAct.kt

    class SecondAct : AppCompatActivity() {
        lateinit var bind: ActivitySecondBinding
        var mPlayer: SysMediaPlayer = SysMediaPlayer.getInstance()
        var mSurface: Surface? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            bind = ActivitySecondBinding.inflate(layoutInflater)
            setContentView(bind.root)
            supportActionBar?.title = "Second"
    
            bind.page2playView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
                override fun onSurfaceTextureAvailable(surface: SurfaceTexture, w: Int, h: Int) {
                    Log.w("zzh", "surface log available in 2 activity w=$w h=$h thread=${Thread.currentThread()}")
                    if (mSurface == null) {
                        mSurface = Surface(surface)
                        mPlayer!!.setSurface(mSurface)
                        mPlayer!!.start()
                    }
                }
    
                override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, w: Int, h: Int) {
                    Log.w("zzh", "surface log size changed in 2 activity w=$w h=$h")
                }
    
                override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
                    return false
                }
    
                override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
                }
            }
        }
    
        override fun onBackPressed() {
            SeamlessObserver.getInstance().execute(1, null)
            super.onBackPressed()
        }
    }
    

    activity_second.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".SecondAct">
        <TextureView
            android:id="@+id/page2playView"
            android:transitionName="playView"
            app:layout_constraintBottom_toTopOf="@id/page2btn1"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_width="match_parent"
            android:layout_height="160dp"/>
    
        <Button
            android:id="@+id/page2btn1"
            android:transitionName="shareview1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Btn1"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" />
    
        <Button
            android:id="@+id/page2btn2"
            android:transitionName="shareview2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Btn2"
            android:layout_marginEnd="5dp"
            app:layout_constraintEnd_toStartOf="@id/page2btn1"
            app:layout_constraintBottom_toBottomOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    ViewAttr.java

    public class ViewAttr {
    }
    

    DefaultShareCallback.java

    public class DefaultShareCallback extends SharedElementCallback {
        private String TAG = "nihao";
        public void setTag(String tag) {
            TAG = "nihao:" + tag;
        }
        // 动画开始
        public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
            super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots);
            Log.w(TAG, "onSharedElementStart");
        }
    
        // 动画结束
        public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
            super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots);
            Log.w(TAG, "onSharedElementEnd");
        }
    
        // 被移除的共享元素,即不需要进行共享元素动画的view
        public void onRejectSharedElements(List<View> rejectedSharedElements) {
            super.onRejectSharedElements(rejectedSharedElements);
            Log.w(TAG, "onRejectSharedElements");
        }
    
        // 共享元素view和name的键值对
        public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
            super.onMapSharedElements(names, sharedElements);
            Log.w(TAG, "onMapSharedElements names=" + names + " sharedElements=" + sharedElements);
        }
    
        // 将view的信息以parcelable的对象类型保存,用于activity之间传递
        public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds) {
            Log.w(TAG, "onCaptureSharedElementSnapshot");
            return super.onCaptureSharedElementSnapshot(sharedElement, viewToGlobalMatrix, screenBounds);
        }
    
        // 将parcelable对象重新转成view
        public View onCreateSnapshotView(Context context, Parcelable snapshot) {
            Log.w(TAG, "onCreateSnapshotView");
            return super.onCreateSnapshotView(context, snapshot);
        }
    
        // 共享元素已经拿到
        public void onSharedElementsArrived(List<String> sharedElementNames, List<View> sharedElements, OnSharedElementsReadyListener listener) {
            super.onSharedElementsArrived(sharedElementNames, sharedElements, listener);
            Log.w(TAG, "onSharedElementsArrived");
        }
    }
    

    为了保持三个页面里播放器的流畅和无缝衔接,需要将三个页面公用一个播放器实例,因为代码中将播放器设计成了单例模式SysMediaPlayer.java,这样能保证音视频的丝滑衔接,音频也不会被中断。





    参考文献:

    1. 视频无缝续播的一些解决方案
    2. JiaoZiVideoPlayer
    3. GSYVideoPlayer
    4. Android 共享元素动画分析及背景空白的解决方案
    5. Android Activity共享元素动画分析
    6. 官方Scene和Transition

    相关文章

      网友评论

          本文标题:安卓两个页面组件的无缝衔接part1(共享元素)

          本文链接:https://www.haomeiwen.com/subject/keivwdtx.html