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.然后就是重头戏,活动的源代码了
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.说明一下
因为处理新老数据代码很相似
所以将这个类封装起来
按照不同的参数区分不同的类型。
TouTiao开源项目 分析笔记18 视频详情页面的更多相关文章
- TouTiao开源项目 分析笔记20 问答详情
1.效果预览 1.1.效果预览,从问答列表开始 前面实现了从列表到内容. 这里主要讲解从内容到详情. 点击每一个回答内容,进入回答详情页面. 1.2.触发的点击事件 在WendaContentView ...
- TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现
1.预览效果 1.1.首先看一下需要实现的效果. 第一种,文字类型新闻. 第二种,图片类型新闻. 1.2.在NewsArticleTextViewBinder中设置了点击事件 RxView.click ...
- TouTiao开源项目 分析笔记2
1.Constant常量定义类 1.1.源代码 public class Constant { public static final String USER_AGENT_MOBILE = " ...
- TouTiao开源项目 分析笔记14 段子评论
1.段子页面详情 1.1.先看看预览界面吧 左边的页面已经实现了,现在的目的就是要实现点击左侧的每一个item 然后跳转到右边相应的段子详情页面. 1.2.首先肯定有右侧这个活动==>JokeC ...
- TouTiao开源项目 分析笔记12 从总体到局部 构建视频主页面
1.构建视频主列表的整体碎片VideoTabLayout 1.1.首先创建一个VideoTabLayout package com.jasonjan.headnews.module.video; im ...
- TouTiao开源项目 分析笔记17 新闻媒体专栏
1.效果预览 1.1.要实现的效果 1.2.如何调转到新闻媒体专栏 点击右上角的用户图标. 在新闻详情页面的Fragment的菜单点击事件中触发. case R.id.action_open_medi ...
- TouTiao开源项目 分析笔记10 实现通用普通文章片段页面
1.RxJava的Observable数据操作符总结 1.1.Map操作符 Map操作符对原始Observable发射的没一项数据应用一个你选择的函数, 然后返回一个发射这些结果的Observable ...
- TouTiao开源项目 分析笔记6
1.NewsChannelBean简单类笔记 1.1.Comparable接口的实现和使用 参考文章:Comparable接口的实现和使用. 因为NewsChannelBean实现了Comparabl ...
- TouTiao开源项目 分析笔记4==>一个简单APP 整体常用框架
1.效果预览 1.1.如下图所以,到目前为止所有的功能. 2.从InitApp开始->SplashActivity->MainActivity 2.1.InitApp源代码.这是整个项目的 ...
随机推荐
- 【Android学习入门】Android中activity的启动模式
启动模式简单地说就是Activity启动时的策略,在Androidmanifest.xml文件中的标签android:launchMode属性设置,在Android中Activity共有四种启动模式分 ...
- ScrollView监听滑动到顶部和底部的方法
不需要监听滑动位置,只需要重写ScrollView的onOverScrolled和stopNestedScroll方法就可以了 public class ReadScrollView extends ...
- android studio gradle统一管理版本
创建config.gradle ext { android = [ compileSdkVersion : 26, buildToolsVersion : "26.0.2", mi ...
- u-boot分析(九)----nand flash初始化|nand flash读写分析
u-boot分析(九) 上篇博文我们按照210的启动流程,分析到了初始化串口,由于接下来的取消存储保护不是很重要,所以我们今天按照u-boot的启动流程对nand flash初始化进行分析. 今天我们 ...
- Java—字符串
字符串 在java中,字符串被作为String类型的对象处理.String类位于java.lang包中,默认情况下,该包被自动导入所有的程序. 创建String对象的方法: String s1 = & ...
- Eclipse导入web项目后,run列表中没有run on server?
Eclipse导入web项目,没有run列表中run on server? 首先确保正确安装Tomcat和JDK .找到对于web项目的文件夹,打开文件夹下.project文件 <?xml ve ...
- 2017年10月31日结束Outlook 2007与Office 365的连接
2017 年10月31日 ,微软即将推出 Office 365中Exchange Online邮箱将需要Outlook for Windows的连接,即通过HTTP Over MAPI方式,传统使用R ...
- ring0 SSDTHook
SSDT 的全称是 System Services Descriptor Table,系统服务描述符表.这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来. ...
- leetcode: 数组
1. longest-consecutive-sequence Given an unsorted array of integers, find the length of the longest ...
- 1.08 在select语句使用条件逻辑
问题:要在select语句中,对数值执行if-else操作.例如,要产生一个结果集,如果一个员工工资小于等于2000美金,就返回消息”underpaid”:如果大于等于4000美金:就返回消息”ove ...