小言_互联网的博客

Android仿抖音主页效果实现

269人阅读  评论(0)

目录

写在前面

一、准备工作

1.1、主页面布局

1.2、列表Item布局

1.3、列表Item适配器

二、自定义LayoutManager

三、实现播放


补充:源码地址:https://github.com/JArchie/TiktokDemo

写在前面

各位老铁,我又来啦!既然来了,那肯定又来搞事情啦,话不多说,先上图!

“抖音”都玩过吧,是不是很好玩,我反正是天天刷,作为一个非著名的Android低级攻城狮,虽然技术菜的一匹,但是也经常刷着刷着会思考:咦?这玩意是用哪个控件做的?这个效果是咋实现的啊?由于本人技术水平有限,所以今天咱就先挑个比较简单的来看看是如何实现的,思考再三,我们就拿抖音首页的这个效果来练练手吧,话不多说,开搞!

一、准备工作

我们先不急着写代码,先对抖音的这种效果做一个简单的分析,首先需要明确的是它是可以滑动的,并且可以上滑回去,也可以下滑到下一个,滑动的数量跟随视频的个数而定,到这里其实能实现这种效果的控件就已经被缩小到一个范围内了。初步判定可以使用ViewPager或者是RecyclerView来实现,你细想一下,它实际上就是一个列表啊,每一屏的视频效果就是一个单独的Item,并且它的列表Item的数量可以很大,至少目前你应该没有哪一次是能把抖音滑到底的吧,那最后咱们使用RecyclerView来实现这个效果。

为什么不用ViewPager?我们需要的是每次只加载一屏,ViewPager默认会有预加载机制,并且数据量较大的时候性能表现也是很差的。反之,RecyclerView最好的性能就是只加载一屏幕的Item,并且处理海量数据时性能更优,所以我们选用RecyclerView实现。

基础列表的承载控件我们已经选好了,然后通过上面的效果不难发现,每一屏里面实际上只有一个Item,所以基础的页面布局你应该也知道该怎么做了。然后就是视频播放了,由于这里我们只是仿照实现抖音的主页面效果,最核心的实际上是实现这个RecyclerView滑动的效果,所以代码我这里是尽量考虑简单化,因此视频播放就直接使用的Android原生的VideoView来做的,效果肯定不会多好,如果你对视频播放这块要求比较高的话,可以考虑使用基于ijkplayer实现的一些比较优秀的开源库,再或者能力强的自己基于ffmpeg定制开发播放器。

OK,到这里基本的分析就已经做完了,下面我们就先来实现基础代码吧!

先把需要的图片和视频文件准备好哦,别忘记了,视频这里放在res/raw目录下:

1.1、主页面布局

新建一个TiktokIndexActivity.java,创建布局文件activity_tiktok_layout.xml:


  
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width= "match_parent"
  4. android:layout_height= "match_parent">
  5. <android.support.v7.widget.RecyclerView
  6. android:id= "@+id/mRecycler"
  7. android:layout_width= "match_parent"
  8. android:layout_height= "match_parent"
  9. android:background= "@color/color_01"/>
  10. <RelativeLayout
  11. android:layout_width= "match_parent"
  12. android:layout_height= "35dp"
  13. android:layout_marginTop= "36dp">
  14. <LinearLayout
  15. android:layout_width= "wrap_content"
  16. android:layout_height= "wrap_content"
  17. android:layout_centerInParent= "true"
  18. android:orientation= "horizontal"
  19. android:gravity= "center_vertical">
  20. <TextView
  21. android:layout_width= "wrap_content"
  22. android:layout_height= "wrap_content"
  23. android:layout_marginRight= "16dp"
  24. android:text= "南京"
  25. android:textColor= "#f2f2f2"
  26. android:textSize= "18sp"
  27. android:textStyle= "bold" />
  28. <TextView
  29. android:layout_width= "wrap_content"
  30. android:layout_height= "wrap_content"
  31. android:layout_marginRight= "16dp"
  32. android:text= "关注"
  33. android:textColor= "#f2f2f2"
  34. android:textSize= "18sp"
  35. android:textStyle= "bold" />
  36. <TextView
  37. android:layout_width= "wrap_content"
  38. android:layout_height= "wrap_content"
  39. android:text= "推荐"
  40. android:textColor= "@android:color/white"
  41. android:textSize= "20sp"
  42. android:textStyle= "bold" />
  43. </LinearLayout>
  44. <ImageView
  45. android:layout_width= "30dp"
  46. android:layout_height= "30dp"
  47. android:layout_alignParentRight= "true"
  48. android:layout_centerVertical= "true"
  49. android:layout_marginRight= "16dp"
  50. android:src= "@drawable/search_icon"
  51. android:tint= "#f2f2f2" />
  52. </RelativeLayout>
  53. <LinearLayout
  54. android:id= "@+id/mBottomLayout"
  55. android:layout_width= "match_parent"
  56. android:layout_height= "?actionBarSize"
  57. android:background= "@color/color_01"
  58. android:layout_alignParentBottom= "true"
  59. android:gravity= "center_vertical"
  60. android:orientation= "horizontal">
  61. <TextView
  62. android:layout_width= "0dp"
  63. android:layout_height= "wrap_content"
  64. android:layout_weight= "1"
  65. android:gravity= "center"
  66. android:text= "首页"
  67. android:textColor= "@android:color/white"
  68. android:textSize= "18sp"
  69. android:textStyle= "bold" />
  70. <TextView
  71. android:layout_width= "0dp"
  72. android:layout_height= "wrap_content"
  73. android:layout_weight= "1"
  74. android:gravity= "center"
  75. android:text= "朋友"
  76. android:textColor= "#f2f2f2"
  77. android:textSize= "17sp"
  78. android:textStyle= "bold" />
  79. <LinearLayout
  80. android:layout_width= "0dp"
  81. android:layout_height= "match_parent"
  82. android:layout_weight= "1"
  83. android:gravity= "center">
  84. <ImageView
  85. android:layout_width= "50dp"
  86. android:layout_height= "30dp"
  87. android:scaleType= "fitCenter"
  88. android:src= "@drawable/icon_add" />
  89. </LinearLayout>
  90. <TextView
  91. android:layout_width= "0dp"
  92. android:layout_height= "wrap_content"
  93. android:layout_weight= "1"
  94. android:gravity= "center"
  95. android:text= "消息"
  96. android:textColor= "#f2f2f2"
  97. android:textSize= "17sp"
  98. android:textStyle= "bold" />
  99. <TextView
  100. android:layout_width= "0dp"
  101. android:layout_height= "wrap_content"
  102. android:layout_weight= "1"
  103. android:gravity= "center"
  104. android:text= "我"
  105. android:textColor= "#f2f2f2"
  106. android:textSize= "17sp"
  107. android:textStyle= "bold" />
  108. </LinearLayout>
  109. </RelativeLayout>

1.2、列表Item布局

由于我们的VideoView想要自己设置宽和高,所以这里自定义一个VideoView,重写onMeasure()测量方法:


  
  1. public class CusVideoView extends VideoView {
  2. public CusVideoView(Context context) {
  3. super(context);
  4. }
  5. public CusVideoView(Context context, AttributeSet attrs) {
  6. super(context, attrs);
  7. }
  8. public CusVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
  9. super(context, attrs, defStyleAttr);
  10. }
  11. @Override
  12. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  13. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  14. int width = getDefaultSize(getWidth(), widthMeasureSpec);
  15. int height = getDefaultSize(getHeight(), heightMeasureSpec);
  16. setMeasuredDimension(width, height);
  17. }
  18. }

然后接着来编写每一屏Item的布局文件:item_tiktok_layout.xml


  
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app= "http://schemas.android.com/apk/res-auto"
  4. xmlns:tools= "http://schemas.android.com/tools"
  5. android:id= "@+id/mRootView"
  6. android:layout_width= "match_parent"
  7. android:layout_height= "match_parent">
  8. <com.jarchie.androidui.tiktok.CusVideoView
  9. android:id= "@+id/mVideoView"
  10. android:layout_width= "match_parent"
  11. android:layout_height= "match_parent"
  12. android:clickable= "false"
  13. android:focusable= "false" />
  14. <ImageView
  15. android:id= "@+id/mThumb"
  16. android:layout_width= "match_parent"
  17. android:layout_height= "match_parent"
  18. android:clickable= "false"
  19. android:focusable= "false"
  20. android:scaleType= "centerCrop"
  21. android:visibility= "visible" />
  22. <LinearLayout
  23. android:layout_width= "wrap_content"
  24. android:layout_height= "wrap_content"
  25. android:layout_alignParentEnd= "true"
  26. android:layout_centerVertical= "true"
  27. android:layout_marginRight= "10dp"
  28. android:gravity= "center"
  29. android:orientation= "vertical">
  30. <RelativeLayout
  31. android:layout_width= "wrap_content"
  32. android:layout_height= "wrap_content">
  33. <de.hdodenhof.circleimageview.CircleImageView
  34. android:layout_width= "60dp"
  35. android:layout_height= "60dp"
  36. android:layout_alignParentTop= "true"
  37. android:src= "@drawable/icon_avatar"
  38. app:civ_border_color= "@android:color/white"
  39. app:civ_border_width= "2dp" />
  40. <ImageView
  41. android:layout_width= "20dp"
  42. android:layout_height= "20dp"
  43. android:layout_centerHorizontal= "true"
  44. android:layout_marginTop= "50dp"
  45. android:background= "@drawable/circle_big_red"
  46. android:scaleType= "centerInside"
  47. android:src= "@drawable/add_icon"
  48. android:tint= "@android:color/white" />
  49. </RelativeLayout>
  50. <TextView
  51. android:layout_width= "50dp"
  52. android:layout_height= "50dp"
  53. android:layout_marginTop= "16dp"
  54. android:drawableTop= "@drawable/heart_icon"
  55. android:gravity= "center"
  56. android:text= "8.88w"
  57. android:textColor= "@android:color/white" />
  58. <TextView
  59. android:layout_width= "50dp"
  60. android:layout_height= "50dp"
  61. android:layout_marginTop= "16dp"
  62. android:drawableTop= "@drawable/msg_icon"
  63. android:gravity= "center"
  64. android:text= "9.99w"
  65. android:textColor= "@android:color/white" />
  66. <TextView
  67. android:layout_width= "50dp"
  68. android:layout_height= "50dp"
  69. android:layout_marginTop= "16dp"
  70. android:drawableTop= "@drawable/share_icon"
  71. android:gravity= "center"
  72. android:text= "6.66w"
  73. android:textColor= "@android:color/white" />
  74. </LinearLayout>
  75. <de.hdodenhof.circleimageview.CircleImageView
  76. android:layout_width= "60dp"
  77. android:layout_height= "60dp"
  78. android:layout_alignParentEnd= "true"
  79. android:layout_alignParentBottom= "true"
  80. android:layout_marginRight= "10dp"
  81. android:layout_marginBottom= "60dp"
  82. android:src= "@drawable/header"
  83. app:civ_border_color= "@color/color_01"
  84. app:civ_border_width= "12dp" />
  85. <LinearLayout
  86. android:layout_width= "wrap_content"
  87. android:layout_height= "wrap_content"
  88. android:layout_alignParentBottom= "true"
  89. android:layout_marginLeft= "10dp"
  90. android:layout_marginBottom= "60dp"
  91. android:orientation= "vertical">
  92. <TextView
  93. android:id= "@+id/mTitle"
  94. android:layout_width= "wrap_content"
  95. android:layout_height= "wrap_content"
  96. android:lineSpacingExtra= "5dp"
  97. android:textColor= "@android:color/white"
  98. android:textSize= "16sp"
  99. tools:text= "测试测试数据哈哈哈哈\n家里几个垃圾了个两个垃圾" />
  100. <LinearLayout
  101. android:layout_width= "match_parent"
  102. android:layout_height= "wrap_content"
  103. android:layout_marginTop= "10dp"
  104. android:orientation= "horizontal"
  105. android:gravity= "center_vertical">
  106. <ImageView
  107. android:layout_width= "wrap_content"
  108. android:layout_height= "wrap_content"
  109. android:src= "@drawable/icon_douyin" />
  110. <TextView
  111. android:id= "@+id/mMarquee"
  112. android:layout_width= "100dp"
  113. android:layout_height= "wrap_content"
  114. android:layout_marginLeft= "10dp"
  115. android:textColor= "@android:color/white"
  116. android:singleLine= "true"
  117. android:ellipsize= "marquee"
  118. android:marqueeRepeatLimit= "marquee_forever"
  119. android:focusable= "true"
  120. android:focusableInTouchMode= "true"
  121. android:textSize= "14sp"/>
  122. </LinearLayout>
  123. </LinearLayout>
  124. <ImageView
  125. android:id= "@+id/mPlay"
  126. android:layout_width= "100dp"
  127. android:layout_height= "100dp"
  128. android:layout_centerInParent= "true"
  129. android:alpha= "0"
  130. android:clickable= "true"
  131. android:focusable= "true"
  132. android:src= "@drawable/play_arrow" />
  133. </RelativeLayout>

1.3、列表Item适配器

然后创建列表Item的适配器TiktokAdapter.java:这里视频封面图片我是自己弄了两张图片,效果看上去不大好,有更好的方案的可以留言一起探讨哦!


  
  1. public class TiktokAdapter extends RecyclerView.Adapter<TiktokAdapter.ViewHolder> {
  2. private int[] videos = {R.raw.v1, R.raw.v2};
  3. private int[] imgs = {R.drawable.fm1, R.drawable.fm2};
  4. private List<String> mTitles = new ArrayList<>();
  5. private List<String> mMarqueeList = new ArrayList<>();
  6. private Context mContext;
  7. public TiktokAdapter(Context context) {
  8. this.mContext = context;
  9. mTitles.add( "@乔布奇\nAndroid仿抖音主界面UI效果,\n一起来学习Android开发啊啊啊啊啊\n#Android高级UIAndroid开发");
  10. mTitles.add( "@乔布奇\nAndroid RecyclerView自定义\nLayoutManager的使用方式,仿抖音效果哦");
  11. mMarqueeList.add( "哈哈创作的原声-乔布奇");
  12. mMarqueeList.add( "嘿嘿创作的原声-Jarchie");
  13. }
  14. @NonNull
  15. @Override
  16. public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
  17. return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_tiktok_layout, viewGroup, false));
  18. }
  19. @Override
  20. public void onBindViewHolder(@NonNull final ViewHolder holder, int pos) {
  21. //第一种方式:获取视频第一帧作为封面图片
  22. // MediaMetadataRetriever media = new MediaMetadataRetriever();
  23. // media.setDataSource(mContext,Uri.parse("android.resource://" + mContext.getPackageName() + "/" + videos[pos % 2]));
  24. // holder.mThumb.setImageBitmap(media.getFrameAtTime());
  25. //第二种方式:使用固定图片作为封面图片
  26. holder.mThumb.setImageResource(imgs[pos % 2]);
  27. holder.mVideoView.setVideoURI(Uri.parse( "android.resource://" + mContext.getPackageName() + "/" + videos[pos % 2]));
  28. holder.mTitle.setText(mTitles.get(pos % 2));
  29. holder.mMarquee.setText(mMarqueeList.get(pos % 2));
  30. holder.mMarquee.setSelected( true);
  31. }
  32. @Override
  33. public int getItemCount() {
  34. return Integer.MAX_VALUE;
  35. }
  36. public class ViewHolder extends RecyclerView.ViewHolder {
  37. RelativeLayout mRootView;
  38. ImageView mThumb;
  39. ImageView mPlay;
  40. TextView mTitle;
  41. TextView mMarquee;
  42. CusVideoView mVideoView;
  43. public ViewHolder(@NonNull View itemView) {
  44. super(itemView);
  45. mRootView = itemView.findViewById(R.id.mRootView);
  46. mThumb = itemView.findViewById(R.id.mThumb);
  47. mPlay = itemView.findViewById(R.id.mPlay);
  48. mVideoView = itemView.findViewById(R.id.mVideoView);
  49. mTitle = itemView.findViewById(R.id.mTitle);
  50. mMarquee = itemView.findViewById(R.id.mMarquee);
  51. }
  52. }
  53. }

二、自定义LayoutManager

我们使用RecyclerView都知道哈,要想让RecylcerView正常工作必须要有两个东西:①、Adapter,负责界面显示适配的,这里我们已经弄好了;②、LayoutManager,告诉列表如何摆放,所以现在想要实现抖音的列表效果的关键就在于这个LayoutManager了,并且普通的LinearLayoutManager是不行的,我们需要自己去实现这个LayoutManger。这里我们取个巧,直接继承LinearLayoutManager来实现自定义布局管理器。

RecyclerView里面有这样一个接口:下面是这个接口的系统源码


  
  1. //这两个方法不是成对出现的,也没有顺序
  2. public interface OnChildAttachStateChangeListener {
  3. void onChildViewAttachedToWindow(@NonNull View var1);
  4. void onChildViewDetachedFromWindow(@NonNull View var1);
  5. }

它里面有两个方法,可以监听列表的Item添加进来和移除出去的两个动作,这是不是就很符合我们现在的使用场景啊,我们的每一屏只有一个Item,并且要在它被添加进来的时候播放视频,在移除时释放掉,所以我们需要实现这个接口。

需要注意的是,这个接口必须在LayoutManager成功进行初始化之后才能监听,所以我们在LayoutManager中重写onAttachedToWindow()方法,在它里面添加这个接口的监听:


  
  1. @Override
  2. public void onAttachedToWindow(RecyclerView view) {
  3. view.addOnChildAttachStateChangeListener( this);
  4. super.onAttachedToWindow(view);
  5. }

 完了之后呢,会重写接口中的两个方法,在这两个方法里面我们就可以来实现播放和暂停的操作了。那么这里问题又来了,播放和暂停这两个动作都涉及到一个问题,你是播放上一个视频还是播放下一个视频,因为列表是可以往下滑也可以往上滑的啊,所以我们还得重写另一个监听位移变化的方法:scrollVerticallyBy(),这里dy的值为正数是往上滑,负数是往下滑


  
  1. private int mDrift; //位移,用来判断移动方向
  2. @Override
  3. public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
  4. this.mDrift = dy;
  5. return super.scrollVerticallyBy(dy, recycler, state);
  6. }

OK,这样我们就可以判断是向上滑还是向下滑了,那么上面onChildViewAttachedToWindow()这两个方法就可以写了,在这两个方法中,我们需要把具体的业务逻辑回调到Activity里面去处理,所以这里我们还需要再自定义一个接口OnPageSlideListener :


  
  1. public interface OnPageSlideListener {
  2. //释放的监听
  3. void onPageRelease(boolean isNext, int position);
  4. //选中的监听以及判断是否滑动到底部
  5. void onPageSelected(int position, boolean isBottom);
  6. }

现在来处理上面的两个回调接口onChildViewAttachedToWindow()和onChildViewDetachedFromWindow():


  
  1. private OnPageSlideListener mOnPageSlideListener;
  2. //Item添加进来
  3. @Override
  4. public void onChildViewAttachedToWindow(@NonNull View view) {
  5. //播放视频操作,判断将要播放的是上一个视频,还是下一个视频
  6. if (mDrift > 0) { //向上
  7. if (mOnPageSlideListener != null)
  8. mOnPageSlideListener.onPageSelected(getPosition(view), true);
  9. } else { //向下
  10. if (mOnPageSlideListener != null)
  11. mOnPageSlideListener.onPageSelected(getPosition(view), false);
  12. }
  13. }
  14. //Item移除出去
  15. @Override
  16. public void onChildViewDetachedFromWindow(@NonNull View view) {
  17. //暂停播放操作
  18. if (mDrift >= 0) {
  19. if (mOnPageSlideListener != null)
  20. mOnPageSlideListener.onPageRelease( true, getPosition(view));
  21. } else {
  22. if (mOnPageSlideListener != null)
  23. mOnPageSlideListener.onPageRelease( false, getPosition(view));
  24. }
  25. }

既然这里是通过接口的方式回调到Activity中实现,所以我们还得给它设置一个接口:


  
  1. //接口注入
  2. public void setOnPageSlideListener(OnPageSlideListener mOnViewPagerListener) {
  3. this.mOnPageSlideListener = mOnViewPagerListener;
  4. }

写到这里,当然还不行,此时如果你去跑你的项目,你会发现它还是会像普通的RecyclerView一样随意的滑动,所以我们还需要一个类的帮助才行:PagerSnapHelper,它可以实现让RecyclerView像ViewPager一样的滑动效果,这里我们给它绑定上RecyclerView:


  
  1. private PagerSnapHelper mPagerSnapHelper;
  2. public CustomLayoutManager(Context context, int orientation, boolean reverseLayout) {
  3. super(context, orientation, reverseLayout);
  4. mPagerSnapHelper = new PagerSnapHelper();
  5. }
  6. @Override
  7. public void onAttachedToWindow(RecyclerView view) {
  8. view.addOnChildAttachStateChangeListener( this);
  9. mPagerSnapHelper.attachToRecyclerView(view);
  10. super.onAttachedToWindow(view);
  11. }

推荐阅读:让你明明白白的使用RecyclerView——SnapHelper详解

到这里已经可以实现类似ViewPager滑动的效果了,但是我们还需要重写一个方法,不然的话向下滑动播放的时候会有Bug:因为onChildViewAttachedToWindow()和onChildViewDetachedFromWindow()这两个方法并不是成对出现的,它们二者之间也是没有顺序的,因此这里我们再来监听一下滑动状态的改变:判断已经处理完成即手指抬起时的状态


  
  1. @Override
  2. public void onScrollStateChanged(int state) {
  3. switch (state) {
  4. case RecyclerView.SCROLL_STATE_IDLE:
  5. View view = mPagerSnapHelper.findSnapView( this); //拿到当前进来的View
  6. int position = getPosition(view);
  7. if (mOnPageSlideListener != null) {
  8. mOnPageSlideListener.onPageSelected(position, position == getItemCount() - 1);
  9. }
  10. break;
  11. }
  12. }

CustomLayoutManager完整代码如下:


  
  1. public class CustomLayoutManager extends LinearLayoutManager implements RecyclerView.OnChildAttachStateChangeListener {
  2. private int mDrift; //位移,用来判断移动方向
  3. private PagerSnapHelper mPagerSnapHelper;
  4. private OnPageSlideListener mOnPageSlideListener;
  5. public CustomLayoutManager(Context context) {
  6. super(context);
  7. }
  8. public CustomLayoutManager(Context context, int orientation, boolean reverseLayout) {
  9. super(context, orientation, reverseLayout);
  10. mPagerSnapHelper = new PagerSnapHelper();
  11. }
  12. @Override
  13. public void onAttachedToWindow(RecyclerView view) {
  14. view.addOnChildAttachStateChangeListener( this);
  15. mPagerSnapHelper.attachToRecyclerView(view);
  16. super.onAttachedToWindow(view);
  17. }
  18. //Item添加进来
  19. @Override
  20. public void onChildViewAttachedToWindow(@NonNull View view) {
  21. //播放视频操作,判断将要播放的是上一个视频,还是下一个视频
  22. if (mDrift > 0) { //向上
  23. if (mOnPageSlideListener != null)
  24. mOnPageSlideListener.onPageSelected(getPosition(view), true);
  25. } else { //向下
  26. if (mOnPageSlideListener != null)
  27. mOnPageSlideListener.onPageSelected(getPosition(view), false);
  28. }
  29. }
  30. //Item移除出去
  31. @Override
  32. public void onChildViewDetachedFromWindow(@NonNull View view) {
  33. //暂停播放操作
  34. if (mDrift >= 0) {
  35. if (mOnPageSlideListener != null)
  36. mOnPageSlideListener.onPageRelease( true, getPosition(view));
  37. } else {
  38. if (mOnPageSlideListener != null)
  39. mOnPageSlideListener.onPageRelease( false, getPosition(view));
  40. }
  41. }
  42. @Override
  43. public void onScrollStateChanged(int state) { //滑动状态监听
  44. switch (state) {
  45. case RecyclerView.SCROLL_STATE_IDLE:
  46. View view = mPagerSnapHelper.findSnapView( this);
  47. int position = getPosition(view);
  48. if (mOnPageSlideListener != null) {
  49. mOnPageSlideListener.onPageSelected(position, position == getItemCount() - 1);
  50. }
  51. break;
  52. }
  53. }
  54. @Override
  55. public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
  56. this.mDrift = dy;
  57. return super.scrollVerticallyBy(dy, recycler, state);
  58. }
  59. //接口注入
  60. public void setOnPageSlideListener(OnPageSlideListener mOnViewPagerListener) {
  61. this.mOnPageSlideListener = mOnViewPagerListener;
  62. }
  63. }

三、实现播放

我们接着在Activity中实现播放和停止的方法:


  
  1. //播放
  2. private void playVideo() {
  3. View itemView = mRecycler.getChildAt( 0);
  4. final CusVideoView mVideoView = itemView.findViewById(R.id.mVideoView);
  5. final ImageView mPlay = itemView.findViewById(R.id.mPlay);
  6. final ImageView mThumb = itemView.findViewById(R.id.mThumb);
  7. final MediaPlayer[] mMediaPlayer = new MediaPlayer[ 1];
  8. mVideoView.start();
  9. mVideoView.setOnInfoListener( new MediaPlayer.OnInfoListener() {
  10. @Override
  11. public boolean onInfo(MediaPlayer mp, int what, int extra) {
  12. mMediaPlayer[ 0] = mp;
  13. mp.setLooping( true);
  14. mThumb.animate().alpha( 0).setDuration( 200).start();
  15. return false;
  16. }
  17. });
  18. //暂停控制
  19. mPlay.setOnClickListener( new View.OnClickListener() {
  20. boolean isPlaying = true;
  21. @Override
  22. public void onClick(View v) {
  23. if (mVideoView.isPlaying()) {
  24. mPlay.animate().alpha( 1f).start();
  25. mVideoView.pause();
  26. isPlaying = false;
  27. } else {
  28. mPlay.animate().alpha( 0f).start();
  29. mVideoView.start();
  30. isPlaying = true;
  31. }
  32. }
  33. });
  34. }
  35. //释放
  36. private void releaseVideo(int index) {
  37. View itemView = mRecycler.getChildAt(index);
  38. final CusVideoView mVideoView = itemView.findViewById(R.id.mVideoView);
  39. final ImageView mThumb = itemView.findViewById(R.id.mThumb);
  40. final ImageView mPlay = itemView.findViewById(R.id.mPlay);
  41. mVideoView.stopPlayback();
  42. mThumb.animate().alpha( 1).start();
  43. mPlay.animate().alpha( 0f).start();
  44. }

然后处理LayoutManager中回调到Activity中的播放逻辑:


  
  1. mLayoutManager.setOnPageSlideListener( new OnPageSlideListener() {
  2. @Override
  3. public void onPageRelease(boolean isNext, int position) {
  4. int index;
  5. if (isNext) {
  6. index = 0;
  7. } else {
  8. index = 1;
  9. }
  10. releaseVideo(index);
  11. }
  12. @Override
  13. public void onPageSelected(int position, boolean isNext) {
  14. playVideo();
  15. }
  16. });

Activity的完整代码如下:


  
  1. public class TikTokIndexActivity extends AppCompatActivity {
  2. private static final String TAG = TikTokIndexActivity.class.getSimpleName();
  3. private RecyclerView mRecycler;
  4. private CustomLayoutManager mLayoutManager;
  5. @Override
  6. protected void onCreate(@Nullable Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_tiktok_layout);
  9. initView();
  10. initListener();
  11. }
  12. //初始化监听
  13. private void initListener() {
  14. mLayoutManager.setOnPageSlideListener( new OnPageSlideListener() {
  15. @Override
  16. public void onPageRelease(boolean isNext, int position) {
  17. int index;
  18. if (isNext) {
  19. index = 0;
  20. } else {
  21. index = 1;
  22. }
  23. releaseVideo(index);
  24. }
  25. @Override
  26. public void onPageSelected(int position, boolean isNext) {
  27. playVideo();
  28. }
  29. });
  30. }
  31. //初始化View
  32. private void initView() {
  33. mRecycler = findViewById(R.id.mRecycler);
  34. mLayoutManager = new CustomLayoutManager( this, OrientationHelper.VERTICAL, false);
  35. TiktokAdapter mAdapter = new TiktokAdapter( this);
  36. mRecycler.setLayoutManager(mLayoutManager);
  37. mRecycler.setAdapter(mAdapter);
  38. }
  39. //播放
  40. private void playVideo() {
  41. //...这里的代码见上方说明
  42. }
  43. //释放
  44. private void releaseVideo(int index) {
  45. //...这里的代码见上方说明
  46. }
  47. }

到这里,仿抖音首页播放的效果就简单实现了,OK,咱们下期再会吧!

祝:工作顺利!


转载:https://blog.csdn.net/JArchie520/article/details/110791624
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场