TouTiao开源项目 分析笔记18 视频详情页面
1.效果预览
1.1.需要做到的真实效果
1.2.触发的点击事件
在MediaArticleVideoViewBinder的每一个item点击事件中:
VideoContentActivity.launch(bean);
在NewsArticleVideoViewViewBinder的每一个item点击事件中:
VideoContentActivity.launch(item);
2.视频详情的活动
2.1.首先看一下第三方库==>视频播放==>jiecaovideoplayer的使用
github地址:https://github.com/wlsh/JieCaoVideoPlayer/
参考博客:http://blog.csdn.net/w_l_s/article/details/53132179
这里就不详细了解了。
然后这里需要一个节操播放器的一个自定义帮助器
public class MyJCVideoPlayerStandard extends JCVideoPlayerStandard { public static onClickFullScreenListener onClickFullScreenListener; public MyJCVideoPlayerStandard(Context context) { super(context); } public MyJCVideoPlayerStandard(Context context, AttributeSet attrs) { super(context, attrs); } public static void setOnClickFullScreenListener(onClickFullScreenListener listener) { onClickFullScreenListener = listener; } @Override public void onClick(View v) { super.onClick(v); int id = v.getId(); if (id == R.id.fullscreen) { if (onClickFullScreenListener != null) { onClickFullScreenListener.onClickFullScreen(); } } } public interface onClickFullScreenListener { void onClickFullScreen(); } }
主要工作就是设置了一个全屏监听器。
2.2.需要实现的底层接口==>IVideoContent.View
public interface IVideoContent { interface View extends INewsComment.View { /** * 设置播放器 */ void onSetVideoPlay(String url); } interface Presenter extends INewsComment.Presenter { /** * 请求数据 */ void doLoadVideoData(String videoid); } }
针对新闻评论页面底层接口的改进。
其实就是在新闻评论页面接口中添加了额外的一个方法而已。
2.3.然后就是重头戏,活动的源代码了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.jasonjan.headnews.module.video.content; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.ColorStateList; import android.graphics.PorterDuff; import android.os.Build; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.widget.ContentLoadingProgressBar; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import com.jasonjan.headnews.R; import com.jasonjan.headnews.adapter.DiffCallback; import com.jasonjan.headnews.bean.common.LoadingBean; import com.jasonjan.headnews.bean.common.LoadingEndBean; import com.jasonjan.headnews.bean.news.MultiNewsArticleDataBean; import com.jasonjan.headnews.global.InitApp; import com.jasonjan.headnews.main.ErrorAction; import com.jasonjan.headnews.main.IntentAction; import com.jasonjan.headnews.main.Register; import com.jasonjan.headnews.module.base.BaseActivity; import com.jasonjan.headnews.module.news.comment.INewsComment; import com.jasonjan.headnews.util.ImageLoader; import com.jasonjan.headnews.util.OnLoadMoreListener; import com.jasonjan.headnews.util.SettingUtil; import com.jasonjan.headnews.widget.MyJCVideoPlayerStandard; import com.trello.rxlifecycle2.LifecycleTransformer; import com.trello.rxlifecycle2.android.ActivityEvent; import java.util.List; import fm.jiecao.jcvideoplayer_lib.JCUserAction; import fm.jiecao.jcvideoplayer_lib.JCUserActionStandard; import fm.jiecao.jcvideoplayer_lib.JCVideoPlayer; import fm.jiecao.jcvideoplayer_lib.JCVideoPlayerStandard; import me.drakeet.multitype.Items; import me.drakeet.multitype.MultiTypeAdapter; public class VideoContentActivity extends BaseActivity implements IVideoContent.View { public static final String TAG = "VideoContentActivity"; protected boolean canLoadMore = false; protected MultiTypeAdapter adapter; private String groupId; private String itemId; private String videoId; private String videoTitle; private String shareUrl; private MultiNewsArticleDataBean dataBean; private Items oldItems = new Items(); private RecyclerView recyclerView; private ContentLoadingProgressBar progressBar; private FloatingActionButton fab; private MyJCVideoPlayerStandard jcVideo; private IVideoContent.Presenter presenter; private int currentAction; private SwipeRefreshLayout swipeRefreshLayout; public static void launch(MultiNewsArticleDataBean bean) { InitApp.AppContext.startActivity(new Intent(InitApp.AppContext, VideoContentActivity.class) .putExtra(VideoContentActivity.TAG, bean) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } setContentView(R.layout.fragment_video_content_new); presenter = new VideoContentPresenter(this); initView(); initData(); onLoadData(); } private void initData() { Intent intent = getIntent(); try { dataBean = intent.getParcelableExtra(TAG); if (null != dataBean.getVideo_detail_info()) { if (null != dataBean.getVideo_detail_info().getDetail_video_large_image()) { String image = dataBean.getVideo_detail_info().getDetail_video_large_image().getUrl(); if (!TextUtils.isEmpty(image)) { ImageLoader.loadCenterCrop(this, image, jcVideo.thumbImageView, R.color.viewBackground, R.mipmap.error_image); } } } this.groupId = dataBean.getGroup_id() + ""; this.itemId = dataBean.getItem_id() + ""; this.videoId = dataBean.getVideo_id(); this.videoTitle = dataBean.getTitle(); this.shareUrl = dataBean.getDisplay_url(); oldItems.add(dataBean); } catch (NullPointerException e) { ErrorAction.print(e); } } private void initView() { recyclerView = (RecyclerView) findViewById(R.id.recycler_view); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(this)); adapter = new MultiTypeAdapter(oldItems); Register.registerVideoContentItem(adapter); recyclerView.setAdapter(adapter); recyclerView.addOnScrollListener(new OnLoadMoreListener() { @Override public void onLoadMore() { if (canLoadMore) { canLoadMore = false; presenter.doLoadMoreData(); } } }); MyJCVideoPlayerStandard.setOnClickFullScreenListener(new MyJCVideoPlayerStandard.onClickFullScreenListener() { @Override public void onClickFullScreen() { if (currentAction == JCUserAction.ON_ENTER_FULLSCREEN && SettingUtil.getInstance().getIsVideoForceLandscape()) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } } }); progressBar = (ContentLoadingProgressBar) findViewById(R.id.pb_progress); int color = SettingUtil.getInstance().getColor(); progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.MULTIPLY); progressBar.show(); swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_layout); swipeRefreshLayout.setColorSchemeColors(SettingUtil.getInstance().getColor()); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { swipeRefreshLayout.post(new Runnable() { @Override public void run() { swipeRefreshLayout.setRefreshing(true); } }); onLoadData(); } }); fab = (FloatingActionButton) findViewById(R.id.fab); fab.setBackgroundTintList(ColorStateList.valueOf(SettingUtil.getInstance().getColor())); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { IntentAction.send(VideoContentActivity.this, videoTitle + "\n" + shareUrl); } }); jcVideo = (MyJCVideoPlayerStandard) findViewById(R.id.jc_video); jcVideo.thumbImageView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { fab.setVisibility(View.GONE); return false; } }); } @Override public void onLoadData() { presenter.doLoadData(groupId, itemId); presenter.doLoadVideoData(videoId); } @Override public void onSetAdapter(final List<?> list) { Items newItems = new Items(); newItems.add(dataBean); newItems.addAll(list); newItems.add(new LoadingBean()); DiffCallback.notifyDataSetChanged(newItems, newItems, DiffCallback.NEWS_COMMENT, adapter); oldItems.clear(); oldItems.addAll(newItems); canLoadMore = true; } @Override public void onShowLoading() { progressBar.show(); } @Override public void onHideLoading() { progressBar.hide(); swipeRefreshLayout.post(new Runnable() { @Override public void run() { swipeRefreshLayout.setRefreshing(false); } }); } @Override public void onShowNetError() { Snackbar.make(recyclerView, R.string.network_error, Snackbar.LENGTH_SHORT).show(); } @Override public void setPresenter(INewsComment.Presenter presenter) { } @Override public <T> LifecycleTransformer<T> bindToLife() { return this.bindUntilEvent(ActivityEvent.DESTROY); } @Override public void onShowNoMore() { runOnUiThread(new Runnable() { @Override public void run() { if (oldItems.size() > 1) { Items newItems = new Items(oldItems); newItems.remove(newItems.size() - 1); newItems.add(new LoadingEndBean()); adapter.setItems(newItems); adapter.notifyDataSetChanged(); } else if (oldItems.size() == 0) { oldItems.add(new LoadingEndBean()); adapter.setItems(oldItems); adapter.notifyDataSetChanged(); } canLoadMore = false; } }); } @Override public void onSetVideoPlay(String urls) { jcVideo.setUp(urls, JCVideoPlayerStandard.SCREEN_LAYOUT_NORMAL, videoTitle); if (SettingUtil.getInstance().getIsVideoAutoPlay()) { jcVideo.startButton.performClick(); fab.setVisibility(View.GONE); } // 设置监听事件 判断是否进入全屏 JCVideoPlayer.setJcUserAction(new JCUserAction() { @Override public void onEvent(int type, String url, int screen, Object... objects) { if (type == JCUserActionStandard.ON_CLICK_START_THUMB || type == JCUserAction.ON_CLICK_START_ICON || type == JCUserAction.ON_CLICK_RESUME || type == JCUserAction.ON_CLICK_START_AUTO_COMPLETE) { fab.setVisibility(View.GONE); } if (type == JCUserAction.ON_CLICK_PAUSE || type == JCUserAction.ON_AUTO_COMPLETE) { fab.setVisibility(View.VISIBLE); } if (type == JCUserAction.ON_ENTER_FULLSCREEN) { currentAction = JCUserAction.ON_ENTER_FULLSCREEN; View decorView = getWindow().getDecorView(); int uiOptions = 0; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; } else { uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } decorView.setSystemUiVisibility(uiOptions); if (slidrInterface != null) { slidrInterface.lock(); } } if (type == JCUserAction.ON_QUIT_FULLSCREEN) { currentAction = JCUserAction.ON_QUIT_FULLSCREEN; View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility(0); if (slidrInterface != null) { slidrInterface.unlock(); } } } }); } @Override protected void onPause() { super.onPause(); JCVideoPlayer.releaseAllVideos(); } @Override public void onBackPressed() { if (JCVideoPlayer.backPress()) { return; } super.onBackPressed(); } }
①首先是启动函数,传入一个MultiNewsArticleDataBean类
因为新闻页面也有视频类型的,这是通用Bean类。
可以知道是哪一条新闻,有哪些视频链接。
将数据封装成一个Bean,然后放在意图里面。
②然后是一个onCreate函数。
这里处理一下沉浸式标题。
加载布局,生成处理器。
初始化视图,初始化数据。
加载数据。
③然后是初始化视图的函数。
首先获得recyclerView,设置适配器+recyclerView滑动监听事件。
然后处理节操播放器的全屏点击事件。
然后获得progressBar进度条,设置颜色。
然后获得swipeRefreshLayout,设置颜色+刷新监听事件。
然后活得分享浮动按钮,设置点击事件。
然后获得节操播放器的布局,设置点击缩略图事件。
④然后是加载数据。
调用处理器的加载数据函数。
调用处理器的加载视频数据函数。
⑤然后设置适配器函数。
传入一个List。
然后处理新老数据。
⑥然后重写展示加载函数。
调用了加载圈的show函数。
⑦然后隐藏加载函数。
先调用加载圈的隐藏函数。
然后调用了刷新圈的隐藏函数。
⑧然后重写网络错误函数。
⑨然后重写设置处理器,这是一个空函数。
⑩然后重写绑定生命周期。
⑪然后是重写没有更多了。
⑫然后重写接口函数设置视频播放。
这里面设置监听事件,判断是否进入全屏。
⑬然后重写暂停生命周期,设置释放所有资源。
⑭然后重写活动返回。
2.4.活动需要的布局==>fragment_video_content_new.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/windowBackground" android:fitsSystemWindows="true"> <com.jasonjan.headnews.widget.MyJCVideoPlayerStandard android:id="@+id/jc_video" android:layout_width="match_parent" android:layout_height="220dp" android:fitsSystemWindows="true"/> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="196dp"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"/> </android.support.v4.widget.SwipeRefreshLayout> <android.support.v4.widget.ContentLoadingProgressBar android:id="@+id/pb_progress" style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"/> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="56dp" android:layout_height="56dp" android:layout_margin="16dp" app:elevation="16dp" app:layout_anchor="@id/jc_video" app:layout_anchorGravity="bottom|end" app:layout_behavior="com.meiji.toutiao.widget.behavior.ScrollAwareFABBehavior" app:srcCompat="@drawable/ic_share_white_24dp"/> </android.support.design.widget.CoordinatorLayout>
预览图片:
2.5.活动清单配置
<activity android:name=".module.video.content.VideoContentActivity" android:configChanges="orientation|screenSize|uiMode" android:label="@string/title_video_content" android:theme="@style/AppTheme.NoActionBar.Slidable" />
2.6.配置浮动按钮行为
首先需要新建一个行为类==>ScrollAwareFABBehavior
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) { return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } @Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { child.setVisibility(View.INVISIBLE); } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { child.show(); } } }
然后是在布局中配置行为:
<android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="56dp" android:layout_height="56dp" android:layout_margin="16dp" app:elevation="16dp" app:layout_anchor="@id/jc_video" app:layout_anchorGravity="bottom|end" app:layout_behavior="com.jasonjan.headnews.widget.ScrollAwareFABBehavior" app:srcCompat="@drawable/ic_share_white_24dp"/>
3.视频详情处理器
3.1.源代码
public class VideoContentPresenter extends NewsCommentPresenter implements IVideoContent.Presenter { private static final String TAG = "VideoContentPresenter"; private IVideoContent.View view; VideoContentPresenter(IVideoContent.View view) { super(view); this.view = view; } private static String getVideoContentApi(String videoid) { String VIDEO_HOST = "http://ib.365yg.com"; String VIDEO_URL = "/video/urls/v/1/toutiao/mp4/%s?r=%s"; String r = getRandom(); String s = String.format(VIDEO_URL, videoid, r); // 将/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()} 进行crc32加密 CRC32 crc32 = new CRC32(); crc32.update(s.getBytes()); String crcString = crc32.getValue() + ""; String url = VIDEO_HOST + s + "&s=" + crcString; return url; } private static String getRandom() { Random random = new Random(); StringBuilder result = new StringBuilder(); for (int i = 0; i < 16; i++) { result.append(random.nextInt(10)); } return result.toString(); } @Override public void doLoadVideoData(String videoid) { String url = getVideoContentApi(videoid); RetrofitFactory.getRetrofit().create(IVideoApi.class).getVideoContent(url) .subscribeOn(Schedulers.io()) .map(new Function<VideoContentBean, String>() { @Override public String apply(@NonNull VideoContentBean videoContentBean) throws Exception { VideoContentBean.DataBean.VideoListBean videoList = videoContentBean.getData().getVideo_list(); if (videoList.getVideo_3() != null) { String base64 = videoList.getVideo_3().getMain_url(); String url = (new String(Base64.decode(base64.getBytes(), Base64.DEFAULT))); Log.d(TAG, "getVideoUrls: " + url); return url; } if (videoList.getVideo_2() != null) { String base64 = videoList.getVideo_2().getMain_url(); String url = (new String(Base64.decode(base64.getBytes(), Base64.DEFAULT))); Log.d(TAG, "getVideoUrls: " + url); return url; } if (videoList.getVideo_1() != null) { String base64 = videoList.getVideo_1().getMain_url(); String url = (new String(Base64.decode(base64.getBytes(), Base64.DEFAULT))); Log.d(TAG, "getVideoUrls: " + url); return url; } return null; } }) .compose(view.<String>bindToLife()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<String>() { @Override public void accept(@NonNull String s) throws Exception { view.onSetVideoPlay(s); view.onHideLoading(); } }, new Consumer<Throwable>() { @Override public void accept(@NonNull Throwable throwable) throws Exception { view.onShowNetError(); view.onHideLoading(); ErrorAction.print(throwable); } }); } }
3.2.构造函数。传进来一个视图层。
3.3.一个静态函数,获取视频详情的API。
传进来一个视频Id,传出去一个url。
3.4.一个获取随机值得函数。获取API需要加密,然后调用这个随机函数即可。
3.5.请求视频数据的函数。
传进来一个视频id。
调用API获取视频详情,返回一个Observable<VideoContentBean>。
然后将String转换成自己想要的类。
然后在subscribe中处理之后的事件,比如视图层设置播放器等。
4.API请求
4.1.源代码
public interface IVideoApi { /** * 获取视频标题等信息 * http://toutiao.com/api/article/recent/?source=2&category=类型&as=A105177907376A5&cp=5797C7865AD54E1&count=20" */ @GET("api/article/recent/?source=2&as=A105177907376A5&cp=5797C7865AD54E1&count=30") Observable<ResponseBody> getVideoArticle( @Query("category") String category, @Query("_") String time); /** * 获取视频信息 * Api 生成较复杂 详情查看 * http://ib.365yg.com/video/urls/v/1/toutiao/mp4/视频ID?r=17位随机数&s=加密结果 */ @GET Observable<VideoContentBean> getVideoContent(@Url String url); }
4.2.获取视频标题等信息的请求接口。
传进去一个类型,一个时间。
传出来一个Observable<ResponseBody>
4.3.获取视频详情信息。
传进去一个url。
传出来一个Observable<VideoContentBean>。
5.注册视频详情类型
5.1.调用的地方
在视频详情活动页面的initView中
Register.registerVideoContentItem(adapter);
5.2.然后在Register中注册视频详情类型
/** * 注册视频详情类型 * @param adapter */ public static void registerVideoContentItem(@NonNull MultiTypeAdapter adapter) { adapter.register(MultiNewsArticleDataBean.class, new VideoContentHeaderViewBinder()); adapter.register(NewsCommentBean.DataBean.CommentBean.class, new NewsCommentViewBinder()); adapter.register(LoadingBean.class, new LoadingViewBinder()); adapter.register(LoadingEndBean.class, new LoadingEndViewBinder()); }
5.3.需要注册一个视频内容头部的绑定器
public class VideoContentHeaderViewBinder extends ItemViewBinder<MultiNewsArticleDataBean, VideoContentHeaderViewBinder.ViewHolder> { @NonNull @Override protected VideoContentHeaderViewBinder.ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.item_video_content_header, parent, false); return new ViewHolder(view); } @Override protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull MultiNewsArticleDataBean item) { try { String media_avatar_url = item.getMedia_info().getAvatar_url(); if (!TextUtils.isEmpty(media_avatar_url)) { ImageLoader.loadCenterCrop(holder.itemView.getContext(), media_avatar_url, holder.iv_media_avatar_url, R.color.viewBackground); } String title = item.getTitle(); String abstractX = item.getAbstractX(); String source = item.getSource(); int video_duration = item.getVideo_duration(); String min = String.valueOf(video_duration / 60); String second = String.valueOf(video_duration % 10); if (Integer.parseInt(second) < 10) { second = "0" + second; } String tv_video_time = min + ":" + second; String tv_comment_count = item.getComment_count() + ""; final String media_id = item.getMedia_info().getMedia_id(); holder.tv_title.setText(title); holder.tv_tv_video_duration_str.setText("时长 " + tv_video_time + " | " + tv_comment_count + "评论"); holder.tv_abstract.setText(abstractX); holder.tv_source.setText(source); RxView.clicks(holder.itemView) .throttleFirst(1, TimeUnit.SECONDS) .subscribe(new Consumer<Object>() { @Override public void accept(@io.reactivex.annotations.NonNull Object o) throws Exception { MediaHomeActivity.launch(media_id); } }); } catch (Exception e) { ErrorAction.print(e); } } public class ViewHolder extends RecyclerView.ViewHolder { private TextView tv_title; private TextView tv_tv_video_duration_str; private TextView tv_abstract; private TextView tv_source; private CircleImageView iv_media_avatar_url; private LinearLayout media_layout; public ViewHolder(View itemView) { super(itemView); this.tv_title = itemView.findViewById(R.id.tv_title); this.tv_tv_video_duration_str = itemView.findViewById(R.id.tv_tv_video_duration_str); this.tv_abstract = itemView.findViewById(R.id.tv_abstract); this.tv_source = itemView.findViewById(R.id.tv_extra); this.iv_media_avatar_url = itemView.findViewById(R.id.iv_media_avatar_url); this.media_layout = itemView.findViewById(R.id.media_layout); } } }
5.4.还需要头部每一个item的布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/tv_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="8dp" android:textSize="20sp" tools:text="毒舌马丁催泪讲述中国式父亲,母亲曾给他下跪,现场观众感动落泪"/> <TextView android:id="@+id/tv_tv_video_duration_str" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingBottom="8dp" android:paddingTop="8dp" tools:text="时长 3:35"/> <TextView android:id="@+id/tv_abstract" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" tools:text="97年驻港部队第一次进驻香港,万人空巷欢迎,场面壮观"/> <LinearLayout android:id="@+id/media_layout" android:layout_width="wrap_content" android:layout_height="?attr/actionBarSize" android:background="?attr/selectableItemBackground" android:foreground="?attr/selectableItemBackground" android:gravity="center_vertical" android:orientation="horizontal" android:padding="8dp"> <com.jasonjan.headnews.widget.CircleImageView android:id="@+id/iv_media_avatar_url" android:layout_width="36dp" android:layout_height="36dp" android:scaleType="centerCrop" app:srcCompat="@color/viewBackground" tools:ignore="ContentDescription"/> <TextView android:id="@+id/tv_extra" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="1" android:paddingLeft="8dp" android:paddingRight="8dp" android:textStyle="bold" tools:text="龙猫公社"/> </LinearLayout> </LinearLayout>
效果预览:
6.处理新老数据
6.1.源代码
package com.jasonjan.headnews.adapter; import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import com.jasonjan.headnews.bean.joke.JokeCommentBean; import com.jasonjan.headnews.bean.joke.JokeContentBean; import com.jasonjan.headnews.bean.media.MediaWendaBean; import com.jasonjan.headnews.bean.media.MultiMediaArticleBean; import com.jasonjan.headnews.bean.news.MultiNewsArticleDataBean; import com.jasonjan.headnews.bean.news.NewsCommentBean; import com.jasonjan.headnews.bean.photo.PhotoArticleBean; import com.jasonjan.headnews.bean.wenda.WendaArticleDataBean; import com.jasonjan.headnews.bean.wenda.WendaContentBean; import java.util.List; /** * Created by JasonJan on 2017/12/6. */ public class DiffCallback extends DiffUtil.Callback { public static final int JOKE = 1; public static final int PHOTO = 2; public static final int NEWS_COMMENT = 5; public static final int JOKE_COMMENT = 6; public static final int MUlTI_NEWS = 7; public static final int WENDA_ARTICLE = 8; public static final int WENDA_CONTENT = 9; public static final int SEARCH = 10; public static final int MUlTI_MEDIA = 11; public static final int MEDIA_WENDA = 12; private List oldList, newList; private int type; public DiffCallback(List oldList, List newList, int type) { this.oldList = oldList; this.newList = newList; this.type = type; } public static void notifyDataSetChanged(List oldList, List newList, int type, RecyclerView.Adapter adapter) { DiffCallback diffCallback = new DiffCallback(oldList, newList, type); DiffUtil.DiffResult result = DiffUtil.calculateDiff(diffCallback, true); result.dispatchUpdatesTo(adapter); } @Override public int getOldListSize() { return oldList != null ? oldList.size() : 0; } @Override public int getNewListSize() { return newList != null ? newList.size() : 0; } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { try { switch (type) { case JOKE: return ((JokeContentBean.DataBean.GroupBean) oldList.get(oldItemPosition)).getContent().equals( ((JokeContentBean.DataBean.GroupBean) newList.get(newItemPosition)).getContent()); case PHOTO: return ((PhotoArticleBean.DataBean) oldList.get(oldItemPosition)).getTitle().equals( ((PhotoArticleBean.DataBean) newList.get(newItemPosition)).getTitle()); case NEWS_COMMENT: return ((NewsCommentBean.DataBean.CommentBean) oldList.get(oldItemPosition)).getText().equals( ((NewsCommentBean.DataBean.CommentBean) newList.get(newItemPosition)).getText()); case JOKE_COMMENT: return ((JokeCommentBean.DataBean.RecentCommentsBean) oldList.get(oldItemPosition)).getText().equals( ((JokeCommentBean.DataBean.RecentCommentsBean) newList.get(newItemPosition)).getText()); case MUlTI_NEWS: return ((MultiNewsArticleDataBean) oldList.get(oldItemPosition)).getTitle().equals( ((MultiNewsArticleDataBean) newList.get(newItemPosition)).getTitle()); case WENDA_ARTICLE: return ((WendaArticleDataBean) oldList.get(oldItemPosition)).getQuestionBean().getTitle().equals( ((WendaArticleDataBean) newList.get(newItemPosition)).getQuestionBean().getTitle()); case WENDA_CONTENT: return ((WendaContentBean.AnsListBean) oldList.get(oldItemPosition)).getAnsid().equals( ((WendaContentBean.AnsListBean) newList.get(newItemPosition)).getAnsid()); case MUlTI_MEDIA: return ((MultiMediaArticleBean.DataBean) oldList.get(oldItemPosition)).getTitle().equals( ((MultiMediaArticleBean.DataBean) newList.get(newItemPosition)).getTitle()); case MEDIA_WENDA: return ((MediaWendaBean.AnswerQuestionBean) oldList.get(oldItemPosition)).getQuestion().getTitle().equals( ((MediaWendaBean.AnswerQuestionBean) newList.get(newItemPosition)).getQuestion().getTitle()); } } catch (Exception e) { // ErrorAction.print(e); } return false; } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { try { switch (type) { case JOKE: return ((JokeContentBean.DataBean.GroupBean) oldList.get(oldItemPosition)).getShare_url().equals( ((JokeContentBean.DataBean.GroupBean) newList.get(newItemPosition)).getShare_url()); case PHOTO: return ((PhotoArticleBean.DataBean) oldList.get(oldItemPosition)).getSource_url().equals( ((PhotoArticleBean.DataBean) newList.get(newItemPosition)).getSource_url()); case NEWS_COMMENT: return ((NewsCommentBean.DataBean.CommentBean) oldList.get(oldItemPosition)).getUser_name().equals( ((NewsCommentBean.DataBean.CommentBean) newList.get(newItemPosition)).getUser_name()); case JOKE_COMMENT: return ((JokeCommentBean.DataBean.RecentCommentsBean) oldList.get(oldItemPosition)).getId() == ((JokeCommentBean.DataBean.RecentCommentsBean) newList.get(newItemPosition)).getId(); case MUlTI_NEWS: return ((MultiNewsArticleDataBean) oldList.get(oldItemPosition)).getItem_id() == ((MultiNewsArticleDataBean) newList.get(newItemPosition)).getItem_id(); case WENDA_ARTICLE: return ((WendaArticleDataBean) oldList.get(oldItemPosition)).getQuestionBean().getContent().equals( ((WendaArticleDataBean) newList.get(newItemPosition)).getQuestionBean().getContent()); case WENDA_CONTENT: return ((WendaContentBean.AnsListBean) oldList.get(oldItemPosition)).getAns_url().equals( ((WendaContentBean.AnsListBean) newList.get(newItemPosition)).getAns_url()); case MUlTI_MEDIA: return ((MultiMediaArticleBean.DataBean) oldList.get(oldItemPosition)).getAbstractX().equals( ((MultiMediaArticleBean.DataBean) newList.get(newItemPosition)).getAbstractX()); case MEDIA_WENDA: return ((MediaWendaBean.AnswerQuestionBean) oldList.get(oldItemPosition)).getAnswer().getAnsid().equals( ((MediaWendaBean.AnswerQuestionBean) newList.get(newItemPosition)).getAnswer().getAnsid()); } } catch (Exception e) { // ErrorAction.print(e); } return false; } }
6.2.说明一下
因为处理新老数据代码很相似
所以将这个类封装起来
按照不同的参数区分不同的类型。