1.效果预览

1.1.需要做到的真实效果

  

1.2.触发的点击事件

  在MediaArticleVideoViewBinder的每一个item点击事件中:

  1. VideoContentActivity.launch(bean);

  在NewsArticleVideoViewViewBinder的每一个item点击事件中:

  1. VideoContentActivity.launch(item);

2.视频详情的活动

2.1.首先看一下第三方库==>视频播放==>jiecaovideoplayer的使用

  github地址:https://github.com/wlsh/JieCaoVideoPlayer/

  参考博客:http://blog.csdn.net/w_l_s/article/details/53132179

  这里就不详细了解了。

  然后这里需要一个节操播放器的一个自定义帮助器 

  1. public class MyJCVideoPlayerStandard extends JCVideoPlayerStandard {
  2. public static onClickFullScreenListener onClickFullScreenListener;
  3.  
  4. public MyJCVideoPlayerStandard(Context context) {
  5. super(context);
  6. }
  7.  
  8. public MyJCVideoPlayerStandard(Context context, AttributeSet attrs) {
  9. super(context, attrs);
  10. }
  11.  
  12. public static void setOnClickFullScreenListener(onClickFullScreenListener listener) {
  13. onClickFullScreenListener = listener;
  14. }
  15.  
  16. @Override
  17. public void onClick(View v) {
  18. super.onClick(v);
  19. int id = v.getId();
  20. if (id == R.id.fullscreen) {
  21. if (onClickFullScreenListener != null) {
  22. onClickFullScreenListener.onClickFullScreen();
  23. }
  24. }
  25. }
  26.  
  27. public interface onClickFullScreenListener {
  28. void onClickFullScreen();
  29. }
  30. }

  主要工作就是设置了一个全屏监听器。

2.2.需要实现的底层接口==>IVideoContent.View

  1. public interface IVideoContent {
  2. interface View extends INewsComment.View {
  3.  
  4. /**
  5. * 设置播放器
  6. */
  7. void onSetVideoPlay(String url);
  8. }
  9.  
  10. interface Presenter extends INewsComment.Presenter {
  11.  
  12. /**
  13. * 请求数据
  14. */
  15. void doLoadVideoData(String videoid);
  16. }
  17. }

  针对新闻评论页面底层接口的改进。

  其实就是在新闻评论页面接口中添加了额外的一个方法而已。

2.3.然后就是重头戏,活动的源代码了

  1. package com.jasonjan.headnews.module.video.content;
  2.  
  3. import android.content.Intent;
  4. import android.content.pm.ActivityInfo;
  5. import android.content.res.ColorStateList;
  6. import android.graphics.PorterDuff;
  7. import android.os.Build;
  8. import android.os.Bundle;
  9. import android.support.design.widget.FloatingActionButton;
  10. import android.support.design.widget.Snackbar;
  11. import android.support.v4.widget.ContentLoadingProgressBar;
  12. import android.support.v4.widget.SwipeRefreshLayout;
  13. import android.support.v7.widget.LinearLayoutManager;
  14. import android.support.v7.widget.RecyclerView;
  15. import android.text.TextUtils;
  16. import android.view.MotionEvent;
  17. import android.view.View;
  18. import android.view.WindowManager;
  19.  
  20. import com.jasonjan.headnews.R;
  21. import com.jasonjan.headnews.adapter.DiffCallback;
  22. import com.jasonjan.headnews.bean.common.LoadingBean;
  23. import com.jasonjan.headnews.bean.common.LoadingEndBean;
  24. import com.jasonjan.headnews.bean.news.MultiNewsArticleDataBean;
  25. import com.jasonjan.headnews.global.InitApp;
  26. import com.jasonjan.headnews.main.ErrorAction;
  27. import com.jasonjan.headnews.main.IntentAction;
  28. import com.jasonjan.headnews.main.Register;
  29. import com.jasonjan.headnews.module.base.BaseActivity;
  30. import com.jasonjan.headnews.module.news.comment.INewsComment;
  31. import com.jasonjan.headnews.util.ImageLoader;
  32. import com.jasonjan.headnews.util.OnLoadMoreListener;
  33. import com.jasonjan.headnews.util.SettingUtil;
  34. import com.jasonjan.headnews.widget.MyJCVideoPlayerStandard;
  35. import com.trello.rxlifecycle2.LifecycleTransformer;
  36. import com.trello.rxlifecycle2.android.ActivityEvent;
  37.  
  38. import java.util.List;
  39.  
  40. import fm.jiecao.jcvideoplayer_lib.JCUserAction;
  41. import fm.jiecao.jcvideoplayer_lib.JCUserActionStandard;
  42. import fm.jiecao.jcvideoplayer_lib.JCVideoPlayer;
  43. import fm.jiecao.jcvideoplayer_lib.JCVideoPlayerStandard;
  44. import me.drakeet.multitype.Items;
  45. import me.drakeet.multitype.MultiTypeAdapter;
  46.  
  47. public class VideoContentActivity extends BaseActivity implements IVideoContent.View {
  48.  
  49. public static final String TAG = "VideoContentActivity";
  50. protected boolean canLoadMore = false;
  51. protected MultiTypeAdapter adapter;
  52. private String groupId;
  53. private String itemId;
  54. private String videoId;
  55. private String videoTitle;
  56. private String shareUrl;
  57. private MultiNewsArticleDataBean dataBean;
  58. private Items oldItems = new Items();
  59.  
  60. private RecyclerView recyclerView;
  61. private ContentLoadingProgressBar progressBar;
  62. private FloatingActionButton fab;
  63. private MyJCVideoPlayerStandard jcVideo;
  64. private IVideoContent.Presenter presenter;
  65. private int currentAction;
  66. private SwipeRefreshLayout swipeRefreshLayout;
  67.  
  68. public static void launch(MultiNewsArticleDataBean bean) {
  69. InitApp.AppContext.startActivity(new Intent(InitApp.AppContext, VideoContentActivity.class)
  70. .putExtra(VideoContentActivity.TAG, bean)
  71. .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
  72. }
  73.  
  74. @Override
  75. protected void onCreate(Bundle savedInstanceState) {
  76. super.onCreate(savedInstanceState);
  77. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  78. getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
  79. }
  80. setContentView(R.layout.fragment_video_content_new);
  81. presenter = new VideoContentPresenter(this);
  82. initView();
  83. initData();
  84. onLoadData();
  85. }
  86.  
  87. private void initData() {
  88. Intent intent = getIntent();
  89. try {
  90. dataBean = intent.getParcelableExtra(TAG);
  91. if (null != dataBean.getVideo_detail_info()) {
  92. if (null != dataBean.getVideo_detail_info().getDetail_video_large_image()) {
  93. String image = dataBean.getVideo_detail_info().getDetail_video_large_image().getUrl();
  94. if (!TextUtils.isEmpty(image)) {
  95. ImageLoader.loadCenterCrop(this, image, jcVideo.thumbImageView, R.color.viewBackground, R.mipmap.error_image);
  96. }
  97. }
  98. }
  99. this.groupId = dataBean.getGroup_id() + "";
  100. this.itemId = dataBean.getItem_id() + "";
  101. this.videoId = dataBean.getVideo_id();
  102. this.videoTitle = dataBean.getTitle();
  103. this.shareUrl = dataBean.getDisplay_url();
  104. oldItems.add(dataBean);
  105. } catch (NullPointerException e) {
  106. ErrorAction.print(e);
  107. }
  108.  
  109. }
  110.  
  111. private void initView() {
  112. recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
  113. recyclerView.setHasFixedSize(true);
  114. recyclerView.setLayoutManager(new LinearLayoutManager(this));
  115.  
  116. adapter = new MultiTypeAdapter(oldItems);
  117. Register.registerVideoContentItem(adapter);
  118. recyclerView.setAdapter(adapter);
  119. recyclerView.addOnScrollListener(new OnLoadMoreListener() {
  120. @Override
  121. public void onLoadMore() {
  122. if (canLoadMore) {
  123. canLoadMore = false;
  124. presenter.doLoadMoreData();
  125. }
  126. }
  127. });
  128.  
  129. MyJCVideoPlayerStandard.setOnClickFullScreenListener(new MyJCVideoPlayerStandard.onClickFullScreenListener() {
  130. @Override
  131. public void onClickFullScreen() {
  132. if (currentAction == JCUserAction.ON_ENTER_FULLSCREEN && SettingUtil.getInstance().getIsVideoForceLandscape()) {
  133. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  134. }
  135. }
  136. });
  137.  
  138. progressBar = (ContentLoadingProgressBar) findViewById(R.id.pb_progress);
  139. int color = SettingUtil.getInstance().getColor();
  140. progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
  141. progressBar.show();
  142.  
  143. swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_layout);
  144. swipeRefreshLayout.setColorSchemeColors(SettingUtil.getInstance().getColor());
  145. swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
  146. @Override
  147. public void onRefresh() {
  148. swipeRefreshLayout.post(new Runnable() {
  149. @Override
  150. public void run() {
  151. swipeRefreshLayout.setRefreshing(true);
  152. }
  153. });
  154. onLoadData();
  155. }
  156. });
  157.  
  158. fab = (FloatingActionButton) findViewById(R.id.fab);
  159. fab.setBackgroundTintList(ColorStateList.valueOf(SettingUtil.getInstance().getColor()));
  160. fab.setOnClickListener(new View.OnClickListener() {
  161. @Override
  162. public void onClick(View view) {
  163. IntentAction.send(VideoContentActivity.this, videoTitle + "\n" + shareUrl);
  164. }
  165. });
  166.  
  167. jcVideo = (MyJCVideoPlayerStandard) findViewById(R.id.jc_video);
  168. jcVideo.thumbImageView.setOnTouchListener(new View.OnTouchListener() {
  169. @Override
  170. public boolean onTouch(View view, MotionEvent motionEvent) {
  171. fab.setVisibility(View.GONE);
  172. return false;
  173. }
  174. });
  175. }
  176.  
  177. @Override
  178. public void onLoadData() {
  179. presenter.doLoadData(groupId, itemId);
  180. presenter.doLoadVideoData(videoId);
  181. }
  182.  
  183. @Override
  184. public void onSetAdapter(final List<?> list) {
  185. Items newItems = new Items();
  186. newItems.add(dataBean);
  187. newItems.addAll(list);
  188. newItems.add(new LoadingBean());
  189. DiffCallback.notifyDataSetChanged(newItems, newItems, DiffCallback.NEWS_COMMENT, adapter);
  190. oldItems.clear();
  191. oldItems.addAll(newItems);
  192. canLoadMore = true;
  193. }
  194.  
  195. @Override
  196. public void onShowLoading() {
  197. progressBar.show();
  198. }
  199.  
  200. @Override
  201. public void onHideLoading() {
  202. progressBar.hide();
  203. swipeRefreshLayout.post(new Runnable() {
  204. @Override
  205. public void run() {
  206. swipeRefreshLayout.setRefreshing(false);
  207. }
  208. });
  209. }
  210.  
  211. @Override
  212. public void onShowNetError() {
  213. Snackbar.make(recyclerView, R.string.network_error, Snackbar.LENGTH_SHORT).show();
  214. }
  215.  
  216. @Override
  217. public void setPresenter(INewsComment.Presenter presenter) {
  218.  
  219. }
  220.  
  221. @Override
  222. public <T> LifecycleTransformer<T> bindToLife() {
  223. return this.bindUntilEvent(ActivityEvent.DESTROY);
  224. }
  225.  
  226. @Override
  227. public void onShowNoMore() {
  228. runOnUiThread(new Runnable() {
  229. @Override
  230. public void run() {
  231. if (oldItems.size() > 1) {
  232. Items newItems = new Items(oldItems);
  233. newItems.remove(newItems.size() - 1);
  234. newItems.add(new LoadingEndBean());
  235. adapter.setItems(newItems);
  236. adapter.notifyDataSetChanged();
  237. } else if (oldItems.size() == 0) {
  238. oldItems.add(new LoadingEndBean());
  239. adapter.setItems(oldItems);
  240. adapter.notifyDataSetChanged();
  241. }
  242. canLoadMore = false;
  243. }
  244. });
  245. }
  246.  
  247. @Override
  248. public void onSetVideoPlay(String urls) {
  249. jcVideo.setUp(urls, JCVideoPlayerStandard.SCREEN_LAYOUT_NORMAL, videoTitle);
  250. if (SettingUtil.getInstance().getIsVideoAutoPlay()) {
  251. jcVideo.startButton.performClick();
  252. fab.setVisibility(View.GONE);
  253. }
  254.  
  255. // 设置监听事件 判断是否进入全屏
  256. JCVideoPlayer.setJcUserAction(new JCUserAction() {
  257. @Override
  258. public void onEvent(int type, String url, int screen, Object... objects) {
  259. if (type == JCUserActionStandard.ON_CLICK_START_THUMB ||
  260. type == JCUserAction.ON_CLICK_START_ICON ||
  261. type == JCUserAction.ON_CLICK_RESUME ||
  262. type == JCUserAction.ON_CLICK_START_AUTO_COMPLETE) {
  263. fab.setVisibility(View.GONE);
  264. }
  265.  
  266. if (type == JCUserAction.ON_CLICK_PAUSE || type == JCUserAction.ON_AUTO_COMPLETE) {
  267. fab.setVisibility(View.VISIBLE);
  268. }
  269.  
  270. if (type == JCUserAction.ON_ENTER_FULLSCREEN) {
  271. currentAction = JCUserAction.ON_ENTER_FULLSCREEN;
  272.  
  273. View decorView = getWindow().getDecorView();
  274. int uiOptions = 0;
  275. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
  276. uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
  277. | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  278. } else {
  279. uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  280. }
  281. decorView.setSystemUiVisibility(uiOptions);
  282.  
  283. if (slidrInterface != null) {
  284. slidrInterface.lock();
  285. }
  286. }
  287.  
  288. if (type == JCUserAction.ON_QUIT_FULLSCREEN) {
  289. currentAction = JCUserAction.ON_QUIT_FULLSCREEN;
  290.  
  291. View decorView = getWindow().getDecorView();
  292. decorView.setSystemUiVisibility(0);
  293.  
  294. if (slidrInterface != null) {
  295. slidrInterface.unlock();
  296. }
  297. }
  298. }
  299. });
  300. }
  301.  
  302. @Override
  303. protected void onPause() {
  304. super.onPause();
  305. JCVideoPlayer.releaseAllVideos();
  306. }
  307.  
  308. @Override
  309. public void onBackPressed() {
  310. if (JCVideoPlayer.backPress()) {
  311. return;
  312. }
  313. super.onBackPressed();
  314. }
  315.  
  316. }

  ①首先是启动函数,传入一个MultiNewsArticleDataBean类

    因为新闻页面也有视频类型的,这是通用Bean类。

    可以知道是哪一条新闻,有哪些视频链接。

    将数据封装成一个Bean,然后放在意图里面。

  ②然后是一个onCreate函数。

    这里处理一下沉浸式标题。

    加载布局,生成处理器。

    初始化视图,初始化数据。

    加载数据。

  ③然后是初始化视图的函数。

    首先获得recyclerView,设置适配器+recyclerView滑动监听事件。

    然后处理节操播放器的全屏点击事件。

    然后获得progressBar进度条,设置颜色。

    然后获得swipeRefreshLayout,设置颜色+刷新监听事件。

    然后活得分享浮动按钮,设置点击事件。

    然后获得节操播放器的布局,设置点击缩略图事件。

  ④然后是加载数据。

    调用处理器的加载数据函数。

    调用处理器的加载视频数据函数。

  ⑤然后设置适配器函数。

    传入一个List。

    然后处理新老数据。

  ⑥然后重写展示加载函数。

    调用了加载圈的show函数。

  ⑦然后隐藏加载函数。

    先调用加载圈的隐藏函数。

    然后调用了刷新圈的隐藏函数。

  ⑧然后重写网络错误函数。

  ⑨然后重写设置处理器,这是一个空函数。

  ⑩然后重写绑定生命周期。

  ⑪然后是重写没有更多了。

  ⑫然后重写接口函数设置视频播放。  

    这里面设置监听事件,判断是否进入全屏。

    

  ⑬然后重写暂停生命周期,设置释放所有资源。

  ⑭然后重写活动返回。

2.4.活动需要的布局==>fragment_video_content_new.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.design.widget.CoordinatorLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:background="@color/windowBackground"
  8. android:fitsSystemWindows="true">
  9.  
  10. <com.jasonjan.headnews.widget.MyJCVideoPlayerStandard
  11. android:id="@+id/jc_video"
  12. android:layout_width="match_parent"
  13. android:layout_height="220dp"
  14. android:fitsSystemWindows="true"/>
  15.  
  16. <android.support.v4.widget.SwipeRefreshLayout
  17. android:id="@+id/refresh_layout"
  18. android:layout_width="match_parent"
  19. android:layout_height="match_parent"
  20. android:layout_marginTop="196dp">
  21.  
  22. <android.support.v7.widget.RecyclerView
  23. android:id="@+id/recycler_view"
  24. android:layout_width="match_parent"
  25. android:layout_height="match_parent"/>
  26. </android.support.v4.widget.SwipeRefreshLayout>
  27.  
  28. <android.support.v4.widget.ContentLoadingProgressBar
  29. android:id="@+id/pb_progress"
  30. style="?android:attr/progressBarStyle"
  31. android:layout_width="wrap_content"
  32. android:layout_height="wrap_content"
  33. android:layout_gravity="center"/>
  34.  
  35. <android.support.design.widget.FloatingActionButton
  36. android:id="@+id/fab"
  37. android:layout_width="56dp"
  38. android:layout_height="56dp"
  39. android:layout_margin="16dp"
  40. app:elevation="16dp"
  41. app:layout_anchor="@id/jc_video"
  42. app:layout_anchorGravity="bottom|end"
  43. app:layout_behavior="com.meiji.toutiao.widget.behavior.ScrollAwareFABBehavior"
  44. app:srcCompat="@drawable/ic_share_white_24dp"/>
  45.  
  46. </android.support.design.widget.CoordinatorLayout>

  预览图片:

  

2.5.活动清单配置 

  1. <activity
  2. android:name=".module.video.content.VideoContentActivity"
  3. android:configChanges="orientation|screenSize|uiMode"
  4. android:label="@string/title_video_content"
  5. android:theme="@style/AppTheme.NoActionBar.Slidable" />

2.6.配置浮动按钮行为

  首先需要新建一个行为类==>ScrollAwareFABBehavior

  1. public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
  2.  
  3. public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
  4. super(context, attrs);
  5. }
  6.  
  7. @Override
  8. public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
  9. FloatingActionButton child,
  10. View directTargetChild,
  11. View target,
  12. int nestedScrollAxes) {
  13. return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
  14. super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
  15. }
  16.  
  17. @Override
  18. public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child,
  19. View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
  20. super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
  21. dyUnconsumed);
  22.  
  23. if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
  24. child.setVisibility(View.INVISIBLE);
  25. } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
  26. child.show();
  27. }
  28. }
  29. }

  然后是在布局中配置行为: 

  1. <android.support.design.widget.FloatingActionButton
  2. android:id="@+id/fab"
  3. android:layout_width="56dp"
  4. android:layout_height="56dp"
  5. android:layout_margin="16dp"
  6. app:elevation="16dp"
  7. app:layout_anchor="@id/jc_video"
  8. app:layout_anchorGravity="bottom|end"
  9. app:layout_behavior="com.jasonjan.headnews.widget.ScrollAwareFABBehavior"
  10. app:srcCompat="@drawable/ic_share_white_24dp"/>

3.视频详情处理器

3.1.源代码

  1. public class VideoContentPresenter extends NewsCommentPresenter implements IVideoContent.Presenter {
  2.  
  3. private static final String TAG = "VideoContentPresenter";
  4. private IVideoContent.View view;
  5.  
  6. VideoContentPresenter(IVideoContent.View view) {
  7. super(view);
  8. this.view = view;
  9. }
  10.  
  11. private static String getVideoContentApi(String videoid) {
  12. String VIDEO_HOST = "http://ib.365yg.com";
  13. String VIDEO_URL = "/video/urls/v/1/toutiao/mp4/%s?r=%s";
  14. String r = getRandom();
  15. String s = String.format(VIDEO_URL, videoid, r);
  16. // 将/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()} 进行crc32加密
  17. CRC32 crc32 = new CRC32();
  18. crc32.update(s.getBytes());
  19. String crcString = crc32.getValue() + "";
  20. String url = VIDEO_HOST + s + "&s=" + crcString;
  21. return url;
  22. }
  23.  
  24. private static String getRandom() {
  25. Random random = new Random();
  26. StringBuilder result = new StringBuilder();
  27. for (int i = 0; i < 16; i++) {
  28. result.append(random.nextInt(10));
  29. }
  30. return result.toString();
  31. }
  32.  
  33. @Override
  34. public void doLoadVideoData(String videoid) {
  35. String url = getVideoContentApi(videoid);
  36. RetrofitFactory.getRetrofit().create(IVideoApi.class).getVideoContent(url)
  37. .subscribeOn(Schedulers.io())
  38. .map(new Function<VideoContentBean, String>() {
  39. @Override
  40. public String apply(@NonNull VideoContentBean videoContentBean) throws Exception {
  41. VideoContentBean.DataBean.VideoListBean videoList = videoContentBean.getData().getVideo_list();
  42. if (videoList.getVideo_3() != null) {
  43. String base64 = videoList.getVideo_3().getMain_url();
  44. String url = (new String(Base64.decode(base64.getBytes(), Base64.DEFAULT)));
  45. Log.d(TAG, "getVideoUrls: " + url);
  46. return url;
  47. }
  48.  
  49. if (videoList.getVideo_2() != null) {
  50. String base64 = videoList.getVideo_2().getMain_url();
  51. String url = (new String(Base64.decode(base64.getBytes(), Base64.DEFAULT)));
  52. Log.d(TAG, "getVideoUrls: " + url);
  53. return url;
  54. }
  55.  
  56. if (videoList.getVideo_1() != null) {
  57. String base64 = videoList.getVideo_1().getMain_url();
  58. String url = (new String(Base64.decode(base64.getBytes(), Base64.DEFAULT)));
  59. Log.d(TAG, "getVideoUrls: " + url);
  60. return url;
  61. }
  62. return null;
  63. }
  64. })
  65. .compose(view.<String>bindToLife())
  66. .observeOn(AndroidSchedulers.mainThread())
  67. .subscribe(new Consumer<String>() {
  68. @Override
  69. public void accept(@NonNull String s) throws Exception {
  70. view.onSetVideoPlay(s);
  71. view.onHideLoading();
  72. }
  73. }, new Consumer<Throwable>() {
  74. @Override
  75. public void accept(@NonNull Throwable throwable) throws Exception {
  76. view.onShowNetError();
  77. view.onHideLoading();
  78. ErrorAction.print(throwable);
  79. }
  80. });
  81. }
  82. }

3.2.构造函数。传进来一个视图层。

3.3.一个静态函数,获取视频详情的API。

  传进来一个视频Id,传出去一个url。

3.4.一个获取随机值得函数。获取API需要加密,然后调用这个随机函数即可。

3.5.请求视频数据的函数。

  传进来一个视频id。

  调用API获取视频详情,返回一个Observable<VideoContentBean>。

  然后将String转换成自己想要的类。

  然后在subscribe中处理之后的事件,比如视图层设置播放器等。

4.API请求

4.1.源代码 

  1. public interface IVideoApi {
  2. /**
  3. * 获取视频标题等信息
  4. * http://toutiao.com/api/article/recent/?source=2&category=类型&as=A105177907376A5&cp=5797C7865AD54E1&count=20"
  5. */
  6. @GET("api/article/recent/?source=2&as=A105177907376A5&cp=5797C7865AD54E1&count=30")
  7. Observable<ResponseBody> getVideoArticle(
  8. @Query("category") String category,
  9. @Query("_") String time);
  10.  
  11. /**
  12. * 获取视频信息
  13. * Api 生成较复杂 详情查看
  14. * http://ib.365yg.com/video/urls/v/1/toutiao/mp4/视频ID?r=17位随机数&s=加密结果
  15. */
  16. @GET
  17. Observable<VideoContentBean> getVideoContent(@Url String url);
  18. }

  

4.2.获取视频标题等信息的请求接口。

  传进去一个类型,一个时间。

  传出来一个Observable<ResponseBody>

4.3.获取视频详情信息。

  传进去一个url。  

  传出来一个Observable<VideoContentBean>。

5.注册视频详情类型

5.1.调用的地方

  在视频详情活动页面的initView中 

  1. Register.registerVideoContentItem(adapter);

5.2.然后在Register中注册视频详情类型

  1. /**
  2. * 注册视频详情类型
  3. * @param adapter
  4. */
  5. public static void registerVideoContentItem(@NonNull MultiTypeAdapter adapter) {
  6. adapter.register(MultiNewsArticleDataBean.class, new VideoContentHeaderViewBinder());
  7. adapter.register(NewsCommentBean.DataBean.CommentBean.class, new NewsCommentViewBinder());
  8. adapter.register(LoadingBean.class, new LoadingViewBinder());
  9. adapter.register(LoadingEndBean.class, new LoadingEndViewBinder());
  10. }

5.3.需要注册一个视频内容头部的绑定器

  1. public class VideoContentHeaderViewBinder extends ItemViewBinder<MultiNewsArticleDataBean, VideoContentHeaderViewBinder.ViewHolder> {
  2.  
  3. @NonNull
  4. @Override
  5. protected VideoContentHeaderViewBinder.ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
  6. View view = inflater.inflate(R.layout.item_video_content_header, parent, false);
  7. return new ViewHolder(view);
  8. }
  9.  
  10. @Override
  11. protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull MultiNewsArticleDataBean item) {
  12. try {
  13. String media_avatar_url = item.getMedia_info().getAvatar_url();
  14. if (!TextUtils.isEmpty(media_avatar_url)) {
  15. ImageLoader.loadCenterCrop(holder.itemView.getContext(), media_avatar_url, holder.iv_media_avatar_url, R.color.viewBackground);
  16. }
  17.  
  18. String title = item.getTitle();
  19. String abstractX = item.getAbstractX();
  20. String source = item.getSource();
  21.  
  22. int video_duration = item.getVideo_duration();
  23. String min = String.valueOf(video_duration / 60);
  24. String second = String.valueOf(video_duration % 10);
  25. if (Integer.parseInt(second) < 10) {
  26. second = "0" + second;
  27. }
  28. String tv_video_time = min + ":" + second;
  29. String tv_comment_count = item.getComment_count() + "";
  30. final String media_id = item.getMedia_info().getMedia_id();
  31.  
  32. holder.tv_title.setText(title);
  33. holder.tv_tv_video_duration_str.setText("时长 " + tv_video_time + " | " + tv_comment_count + "评论");
  34. holder.tv_abstract.setText(abstractX);
  35. holder.tv_source.setText(source);
  36.  
  37. RxView.clicks(holder.itemView)
  38. .throttleFirst(1, TimeUnit.SECONDS)
  39. .subscribe(new Consumer<Object>() {
  40. @Override
  41. public void accept(@io.reactivex.annotations.NonNull Object o) throws Exception {
  42. MediaHomeActivity.launch(media_id);
  43. }
  44. });
  45. } catch (Exception e) {
  46. ErrorAction.print(e);
  47. }
  48. }
  49.  
  50. public class ViewHolder extends RecyclerView.ViewHolder {
  51.  
  52. private TextView tv_title;
  53. private TextView tv_tv_video_duration_str;
  54. private TextView tv_abstract;
  55. private TextView tv_source;
  56. private CircleImageView iv_media_avatar_url;
  57. private LinearLayout media_layout;
  58.  
  59. public ViewHolder(View itemView) {
  60. super(itemView);
  61. this.tv_title = itemView.findViewById(R.id.tv_title);
  62. this.tv_tv_video_duration_str = itemView.findViewById(R.id.tv_tv_video_duration_str);
  63. this.tv_abstract = itemView.findViewById(R.id.tv_abstract);
  64. this.tv_source = itemView.findViewById(R.id.tv_extra);
  65. this.iv_media_avatar_url = itemView.findViewById(R.id.iv_media_avatar_url);
  66. this.media_layout = itemView.findViewById(R.id.media_layout);
  67. }
  68. }
  69. }

5.4.还需要头部每一个item的布局

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="wrap_content"
  8. android:orientation="vertical"
  9. android:padding="16dp">
  10.  
  11. <TextView
  12. android:id="@+id/tv_title"
  13. android:layout_width="match_parent"
  14. android:layout_height="wrap_content"
  15. android:paddingTop="8dp"
  16. android:textSize="20sp"
  17. tools:text="毒舌马丁催泪讲述中国式父亲,母亲曾给他下跪,现场观众感动落泪"/>
  18.  
  19. <TextView
  20. android:id="@+id/tv_tv_video_duration_str"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:paddingBottom="8dp"
  24. android:paddingTop="8dp"
  25. tools:text="时长 3:35"/>
  26.  
  27. <TextView
  28. android:id="@+id/tv_abstract"
  29. android:layout_width="match_parent"
  30. android:layout_height="wrap_content"
  31. android:textSize="16sp"
  32. tools:text="97年驻港部队第一次进驻香港,万人空巷欢迎,场面壮观"/>
  33.  
  34. <LinearLayout
  35. android:id="@+id/media_layout"
  36. android:layout_width="wrap_content"
  37. android:layout_height="?attr/actionBarSize"
  38. android:background="?attr/selectableItemBackground"
  39. android:foreground="?attr/selectableItemBackground"
  40. android:gravity="center_vertical"
  41. android:orientation="horizontal"
  42. android:padding="8dp">
  43.  
  44. <com.jasonjan.headnews.widget.CircleImageView
  45. android:id="@+id/iv_media_avatar_url"
  46. android:layout_width="36dp"
  47. android:layout_height="36dp"
  48. android:scaleType="centerCrop"
  49. app:srcCompat="@color/viewBackground"
  50. tools:ignore="ContentDescription"/>
  51.  
  52. <TextView
  53. android:id="@+id/tv_extra"
  54. android:layout_width="wrap_content"
  55. android:layout_height="wrap_content"
  56. android:maxLines="1"
  57. android:paddingLeft="8dp"
  58. android:paddingRight="8dp"
  59. android:textStyle="bold"
  60. tools:text="龙猫公社"/>
  61.  
  62. </LinearLayout>
  63.  
  64. </LinearLayout>

  效果预览:

  

6.处理新老数据

6.1.源代码

  1. package com.jasonjan.headnews.adapter;
  2.  
  3. import android.support.v7.util.DiffUtil;
  4. import android.support.v7.widget.RecyclerView;
  5.  
  6. import com.jasonjan.headnews.bean.joke.JokeCommentBean;
  7. import com.jasonjan.headnews.bean.joke.JokeContentBean;
  8. import com.jasonjan.headnews.bean.media.MediaWendaBean;
  9. import com.jasonjan.headnews.bean.media.MultiMediaArticleBean;
  10. import com.jasonjan.headnews.bean.news.MultiNewsArticleDataBean;
  11. import com.jasonjan.headnews.bean.news.NewsCommentBean;
  12. import com.jasonjan.headnews.bean.photo.PhotoArticleBean;
  13. import com.jasonjan.headnews.bean.wenda.WendaArticleDataBean;
  14. import com.jasonjan.headnews.bean.wenda.WendaContentBean;
  15.  
  16. import java.util.List;
  17.  
  18. /**
  19. * Created by JasonJan on 2017/12/6.
  20. */
  21.  
  22. public class DiffCallback extends DiffUtil.Callback {
  23.  
  24. public static final int JOKE = 1;
  25. public static final int PHOTO = 2;
  26. public static final int NEWS_COMMENT = 5;
  27. public static final int JOKE_COMMENT = 6;
  28. public static final int MUlTI_NEWS = 7;
  29. public static final int WENDA_ARTICLE = 8;
  30. public static final int WENDA_CONTENT = 9;
  31. public static final int SEARCH = 10;
  32. public static final int MUlTI_MEDIA = 11;
  33. public static final int MEDIA_WENDA = 12;
  34. private List oldList, newList;
  35. private int type;
  36.  
  37. public DiffCallback(List oldList, List newList, int type) {
  38. this.oldList = oldList;
  39. this.newList = newList;
  40. this.type = type;
  41. }
  42.  
  43. public static void notifyDataSetChanged(List oldList, List newList, int type, RecyclerView.Adapter adapter) {
  44. DiffCallback diffCallback = new DiffCallback(oldList, newList, type);
  45. DiffUtil.DiffResult result = DiffUtil.calculateDiff(diffCallback, true);
  46. result.dispatchUpdatesTo(adapter);
  47. }
  48.  
  49. @Override
  50. public int getOldListSize() {
  51. return oldList != null ? oldList.size() : 0;
  52. }
  53.  
  54. @Override
  55. public int getNewListSize() {
  56. return newList != null ? newList.size() : 0;
  57. }
  58.  
  59. @Override
  60. public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
  61. try {
  62. switch (type) {
  63. case JOKE:
  64. return ((JokeContentBean.DataBean.GroupBean) oldList.get(oldItemPosition)).getContent().equals(
  65. ((JokeContentBean.DataBean.GroupBean) newList.get(newItemPosition)).getContent());
  66. case PHOTO:
  67. return ((PhotoArticleBean.DataBean) oldList.get(oldItemPosition)).getTitle().equals(
  68. ((PhotoArticleBean.DataBean) newList.get(newItemPosition)).getTitle());
  69. case NEWS_COMMENT:
  70. return ((NewsCommentBean.DataBean.CommentBean) oldList.get(oldItemPosition)).getText().equals(
  71. ((NewsCommentBean.DataBean.CommentBean) newList.get(newItemPosition)).getText());
  72. case JOKE_COMMENT:
  73. return ((JokeCommentBean.DataBean.RecentCommentsBean) oldList.get(oldItemPosition)).getText().equals(
  74. ((JokeCommentBean.DataBean.RecentCommentsBean) newList.get(newItemPosition)).getText());
  75. case MUlTI_NEWS:
  76. return ((MultiNewsArticleDataBean) oldList.get(oldItemPosition)).getTitle().equals(
  77. ((MultiNewsArticleDataBean) newList.get(newItemPosition)).getTitle());
  78. case WENDA_ARTICLE:
  79. return ((WendaArticleDataBean) oldList.get(oldItemPosition)).getQuestionBean().getTitle().equals(
  80. ((WendaArticleDataBean) newList.get(newItemPosition)).getQuestionBean().getTitle());
  81. case WENDA_CONTENT:
  82. return ((WendaContentBean.AnsListBean) oldList.get(oldItemPosition)).getAnsid().equals(
  83. ((WendaContentBean.AnsListBean) newList.get(newItemPosition)).getAnsid());
  84.  
  85. case MUlTI_MEDIA:
  86. return ((MultiMediaArticleBean.DataBean) oldList.get(oldItemPosition)).getTitle().equals(
  87. ((MultiMediaArticleBean.DataBean) newList.get(newItemPosition)).getTitle());
  88. case MEDIA_WENDA:
  89. return ((MediaWendaBean.AnswerQuestionBean) oldList.get(oldItemPosition)).getQuestion().getTitle().equals(
  90. ((MediaWendaBean.AnswerQuestionBean) newList.get(newItemPosition)).getQuestion().getTitle());
  91. }
  92. } catch (Exception e) {
  93. // ErrorAction.print(e);
  94. }
  95. return false;
  96. }
  97.  
  98. @Override
  99. public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
  100. try {
  101. switch (type) {
  102. case JOKE:
  103. return ((JokeContentBean.DataBean.GroupBean) oldList.get(oldItemPosition)).getShare_url().equals(
  104. ((JokeContentBean.DataBean.GroupBean) newList.get(newItemPosition)).getShare_url());
  105. case PHOTO:
  106. return ((PhotoArticleBean.DataBean) oldList.get(oldItemPosition)).getSource_url().equals(
  107. ((PhotoArticleBean.DataBean) newList.get(newItemPosition)).getSource_url());
  108. case NEWS_COMMENT:
  109. return ((NewsCommentBean.DataBean.CommentBean) oldList.get(oldItemPosition)).getUser_name().equals(
  110. ((NewsCommentBean.DataBean.CommentBean) newList.get(newItemPosition)).getUser_name());
  111. case JOKE_COMMENT:
  112. return ((JokeCommentBean.DataBean.RecentCommentsBean) oldList.get(oldItemPosition)).getId() ==
  113. ((JokeCommentBean.DataBean.RecentCommentsBean) newList.get(newItemPosition)).getId();
  114. case MUlTI_NEWS:
  115. return ((MultiNewsArticleDataBean) oldList.get(oldItemPosition)).getItem_id() ==
  116. ((MultiNewsArticleDataBean) newList.get(newItemPosition)).getItem_id();
  117. case WENDA_ARTICLE:
  118. return ((WendaArticleDataBean) oldList.get(oldItemPosition)).getQuestionBean().getContent().equals(
  119. ((WendaArticleDataBean) newList.get(newItemPosition)).getQuestionBean().getContent());
  120. case WENDA_CONTENT:
  121. return ((WendaContentBean.AnsListBean) oldList.get(oldItemPosition)).getAns_url().equals(
  122. ((WendaContentBean.AnsListBean) newList.get(newItemPosition)).getAns_url());
  123.  
  124. case MUlTI_MEDIA:
  125. return ((MultiMediaArticleBean.DataBean) oldList.get(oldItemPosition)).getAbstractX().equals(
  126. ((MultiMediaArticleBean.DataBean) newList.get(newItemPosition)).getAbstractX());
  127. case MEDIA_WENDA:
  128. return ((MediaWendaBean.AnswerQuestionBean) oldList.get(oldItemPosition)).getAnswer().getAnsid().equals(
  129. ((MediaWendaBean.AnswerQuestionBean) newList.get(newItemPosition)).getAnswer().getAnsid());
  130. }
  131. } catch (Exception e) {
  132. // ErrorAction.print(e);
  133. }
  134. return false;
  135. }
  136. }

6.2.说明一下

  因为处理新老数据代码很相似

  所以将这个类封装起来

  按照不同的参数区分不同的类型。

TouTiao开源项目 分析笔记18 视频详情页面的更多相关文章

  1. TouTiao开源项目 分析笔记20 问答详情

    1.效果预览 1.1.效果预览,从问答列表开始 前面实现了从列表到内容. 这里主要讲解从内容到详情. 点击每一个回答内容,进入回答详情页面. 1.2.触发的点击事件 在WendaContentView ...

  2. TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现

    1.预览效果 1.1.首先看一下需要实现的效果. 第一种,文字类型新闻. 第二种,图片类型新闻. 1.2.在NewsArticleTextViewBinder中设置了点击事件 RxView.click ...

  3. TouTiao开源项目 分析笔记2

    1.Constant常量定义类 1.1.源代码 public class Constant { public static final String USER_AGENT_MOBILE = " ...

  4. TouTiao开源项目 分析笔记14 段子评论

    1.段子页面详情 1.1.先看看预览界面吧 左边的页面已经实现了,现在的目的就是要实现点击左侧的每一个item 然后跳转到右边相应的段子详情页面. 1.2.首先肯定有右侧这个活动==>JokeC ...

  5. TouTiao开源项目 分析笔记12 从总体到局部 构建视频主页面

    1.构建视频主列表的整体碎片VideoTabLayout 1.1.首先创建一个VideoTabLayout package com.jasonjan.headnews.module.video; im ...

  6. TouTiao开源项目 分析笔记17 新闻媒体专栏

    1.效果预览 1.1.要实现的效果 1.2.如何调转到新闻媒体专栏 点击右上角的用户图标. 在新闻详情页面的Fragment的菜单点击事件中触发. case R.id.action_open_medi ...

  7. TouTiao开源项目 分析笔记10 实现通用普通文章片段页面

    1.RxJava的Observable数据操作符总结 1.1.Map操作符 Map操作符对原始Observable发射的没一项数据应用一个你选择的函数, 然后返回一个发射这些结果的Observable ...

  8. TouTiao开源项目 分析笔记6

    1.NewsChannelBean简单类笔记 1.1.Comparable接口的实现和使用 参考文章:Comparable接口的实现和使用. 因为NewsChannelBean实现了Comparabl ...

  9. TouTiao开源项目 分析笔记4==>一个简单APP 整体常用框架

    1.效果预览 1.1.如下图所以,到目前为止所有的功能. 2.从InitApp开始->SplashActivity->MainActivity 2.1.InitApp源代码.这是整个项目的 ...

随机推荐

  1. Springboot中SpringMvc拦截器配置与应用(实战)

    一.什么是拦截器,及其作用 拦截器(Interceptor): 用于在某个方法被访问之前进行拦截,然后在方法执行之前或之后加入某些操作,其实就是AOP的一种实现策略.它通过动态拦截Action调用的对 ...

  2. Linux命令之查看内存和CPU消耗命令TOP使用

    1)输入top ,按enter键,即可查看服务器内存消耗情况 注意:其中PID表示进程号 :%cpu表示cpu消耗情况:%M表示内存消耗情况:通常在做性能测试的时候用到该命令: 默认为实时刷新:按s键 ...

  3. 修改Linux中发送邮件中附件大小的限制

    方法一: 在命令中设定postfix的message_size_limit值 (但系统重启后会失效) postconf -e "message_size_limit = 20480000&q ...

  4. CRM和C4C product category hierarchy的可编辑性控制逻辑

    CRM 从ERP导入到CRM系统的Product Hierarchy,在CRM系统切换成编辑模式时,会收到一条提示信息: Hierarchy XXX may only be changed in th ...

  5. Fiddler-1 官网下载及安装

    1 进入Fiddler官网:http://www.telerik.com/fiddler 点击[Free download]:填写一些信息后就可以下载. 2 双击安装包--下一步dinghanhua下 ...

  6. QT5中两个窗体之间传递信息(值)

    一个窗体A调用另一个窗体B: 1)包含窗体B的头文件#include"B.h" 2)在窗体A中增加slots函数: public slots: void infoRecv(QStr ...

  7. 【JavaScript 封装库】BETA 4.0 测试版发布!

    /* 源码作者: 石不易(Louis Shi) 联系方式: http://www.shibuyi.net =============================================== ...

  8. IOS 发布程序(打包上传)

    • 发布程序的主要步骤 登录开发者主页 生成cer证书:cer是一个跟电脑相关联的证书文件,让电脑具备发布程序的功能 添加App ID:发布哪些app? 生成MobileProvision文件:生成一 ...

  9. 二叉树遍历,先序序列+中序序列=后序序列,Poj(2255)

    这里我参考了JHF大神的写法啦,直接把输出写在了建树的过程中了. 思路: 先根据先序序列找到根节点,在找该节点在中序序列中的位置,这样,左右子树有分开了.这里的细节值得注意一下,不然很容易建树出错.( ...

  10. vuejs使用组件的细节点

    is属性 <div id='root'> <table> <tbody> <row></row> <row></row&g ...