安卓两个页面组件的无缝衔接part1(共享元素)
对于界面切换时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里,需要注意的只有三点:
- 由于第二个页面SecondListAct的共享元素是在RecyclerView里,因此需要在Activity#onCreate里延迟过渡动画执行,
postponeEnterTransition()
就是这个作用。如果共享元素是在activity的xml布局里,不需要延迟执行过渡动画 - 调用
postponeEnterTransition
后不要忘记调用startPostponedEnterTransition
执行过渡动画,
忘记调用startPostponedEnterTransition
会让你的应用处于死锁状态,用户无法进入下个Activity。在代码中,我们是在RecyclerView#doOnPreDraw
中执行动画的。 - 不要将共享元素延迟超过到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
,这样能保证音视频的丝滑衔接,音频也不会被中断。
参考文献: