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

2023-11-20  本文已影响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
上一篇下一篇

猜你喜欢

热点阅读