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 + "
" + 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();
    }

}
View Code

  ①首先是启动函数,传入一个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.说明一下

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

  所以将这个类封装起来

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



原文地址:https://www.cnblogs.com/Jason-Jan/p/8267731.html