想要实现抖音那样的丝滑上下滑动切换效果?本文手把手教你使用ViewPager2打造高性能竖屏视频流,涵盖播放器联动、手势控制、预加载优化等核心功能,性能媲美原生!
一、效果预览与核心特性
📱 最终效果
- 垂直滑动切换视频页面
- 滑动过程中自动播放/暂停
- 滑动阻尼效果与速度控制
- 视频预加载与缓存管理
- 双击点赞、滑动交互等手势
🎯 技术架构
┌─────────────────────────────────────┐
│ UI Layer │
│ ┌─────────┐ ┌─────────┐ │
│ │VideoFrag│ │VideoFrag│ … │
│ └─────────┘ └─────────┘ │
└───────────────┬────────────────────┘
│ ViewPager2 (Vertical)
┌───────────────┴────────────────────┐
│ Logic Layer │
│ ┌────────────────────────────┐ │
│ │ VideoPlayerManager │ │
│ └────────────────────────────┘ │
│ ┌────────────────────────────┐ │
│ │ GestureDetector │ │
│ └────────────────────────────┘ │
└────────────────────────────────────┘
二、核心实现步骤
1. 垂直方向ViewPager2配置
class TiktokViewPager2 @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager2(context, attrs) {
init {
orientation = ORIENTATION_VERTICAL
overScrollMode = View.OVER_SCROLL_NEVER
// 优化滑动体验
(getChildAt(0) as? RecyclerView)?.apply {
overScrollMode = View.OVER_SCROLL_NEVER
layoutManager = LinearLayoutManager(context).apply {
orientation = RecyclerView.VERTICAL
}
}
}
// 自定义滑动阻尼
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
val result = super.onInterceptTouchEvent(ev)
(getChildAt(0) as? RecyclerView)?.let {
recyclerView ->
when (ev.action) {
MotionEvent.ACTION_MOVE -> {
// 顶部下拉阻尼
if (!recyclerView.canScrollVertically(–1) && ev.y – startY > 0) {
return false
}
// 底部上拉阻尼
if (!recyclerView.canScrollVertically(1) && ev.y – startY < 0) {
return false
}
}
}
}
return result
}
}
2. 视频Fragment实现(ExoPlayer)
class VideoFragment : Fragment() {
private lateinit var binding: FragmentVideoBinding
private var player: ExoPlayer? = null
private var videoUrl: String? = null
// 播放状态
var isPlaying = false
private set
companion object {
private const val ARG_VIDEO_URL = \”video_url\”
fun newInstance(videoUrl: String): VideoFragment {
return VideoFragment().apply {
arguments = Bundle().apply {
putString(ARG_VIDEO_URL, videoUrl)
}
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentVideoBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
videoUrl = arguments?.getString(ARG_VIDEO_URL)
initPlayer()
initGesture()
}
private fun initPlayer() {
player = ExoPlayer.Builder(requireContext()).build().apply {
setMediaItem(MediaItem.fromUri(videoUrl ?: return))
repeatMode = Player.REPEAT_MODE_ONE
addListener(playerListener)
binding.playerView.player = this
prepare()
}
}
private val playerListener = object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_READY -> {
binding.progressBar.visibility = View.GONE
if (isResumed && isVisible) {
startPlay()
}
}
Player.STATE_BUFFERING -> {
binding.progressBar.visibility = View.VISIBLE
}
}
}
}
fun startPlay() {
player?.playWhenReady = true
isPlaying = true
}
fun pausePlay() {
player?.playWhenReady = false
isPlaying = false
}
fun releasePlayer() {
player?.release()
player = null
}
override fun onResume() {
super.onResume()
if (isVisibleToUser) {
startPlay()
}
}
override fun onPause() {
super.onPause()
pausePlay()
}
override fun onDestroy() {
super.onDestroy()
releasePlayer()
}
// 手势控制
private fun initGesture() {
val gestureDetector = GestureDetector(requireContext(),
object : GestureDetector.SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent): Boolean {
// 双击点赞
showLikeAnimation(e.x, e.y)
return true
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
// 单击控制播放/暂停
if (isPlaying) pausePlay() else startPlay()
return true
}
})
binding.root.setOnTouchListener {
v, event ->
gestureDetector.onTouchEvent(event)
true
}
}
private fun showLikeAnimation(x: Float, y: Float) {
// 实现点赞动画
val likeView = ImageView(requireContext()).apply {
setImageResource(R.drawable.ic_like)
layoutParams = ViewGroup.LayoutParams(100, 100)
x = x – 50
y = y – 50
}
binding.root.addView(likeView)
likeView.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.alpha(0f)
.translationY(–100f)
.setDuration(800)
.withEndAction {
binding.root.removeView(likeView) }
.start()
}
}
3. 智能适配器(带预加载)
class VideoPagerAdapter(
fragment: Fragment,
private val videoList: List<VideoItem>
) : FragmentStateAdapter(fragment) {
// 预加载管理
private val preloadManager = VideoPreloadManager()
override fun getItemCount()




