• TouTiao开源项目 分析笔记16 新闻评论



    1.要达到的效果

    1.1.主要效果图

      

      点击了标题栏的消息图标后,然后会跳转到评论详情的页面。

    1.2.触发的点击事件

      在新闻详情的片段中的菜单点击事件中

      设置上方标题栏的消息标的监听事件

     case R.id.action_open_comment:
                    NewsCommentActivity.launch(bean.getGroup_id() + "", bean.getItem_id() + "");
                    break;

      bean就是某一个新闻的一些属性,从最前面item中传递过来的。


    2.新闻评论详情活动

    2.1.源代码

    class NewsCommentActivity : BaseActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.container)
            val intent = intent
            supportFragmentManager.beginTransaction()
                    .replace(R.id.container,
                            NewsCommentFragment.newInstance(intent.getStringExtra(ARG_GROUPID), intent.getStringExtra(ARG_ITEMID)))
                    .commit()
        }
    
        companion object {
    
            private val TAG = "NewsCommentActivity"
            private val ARG_GROUPID = "groupId"
            private val ARG_ITEMID = "itemId"
    
            fun launch(groupId: String, itemId: String) {
                InitApp.AppContext.startActivity(Intent(InitApp.AppContext, NewsCommentActivity::class.java)
                        .putExtra(ARG_GROUPID, groupId)
                        .putExtra(ARG_ITEMID, itemId)
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
            }
        }
    }

    2.2.外部启动这个评论活动的一个静态launch函数。

      这里需要将两个关键的参数保存起来,之后在评论中会用到。  

    2.3.然后是一个onCreate函数,这个活动优先执行。

      将container替换成片段的布局。

    2.4.在清单中配置这个活动 

     <activity
                android:name=".module.news.comment.NewsCommentActivity"
                android:configChanges="orientation|screenSize|uiMode"
                android:label="@string/title_comment"
                android:theme="@style/AppTheme.NoActionBar.Slidable"/>


    3.新闻评论的片段

    3.1.底层接口==>INewsComment

    public interface INewsComment {
    
        interface View extends IBaseListView<Presenter> {
    
            /**
             * 请求数据
             */
            void onLoadData();
        }
    
        interface Presenter extends IBasePresenter {
    
            /**
             * 请求数据
             */
            void doLoadData(String... groupId_ItemId);
    
            /**
             * 再起请求数据
             */
            void doLoadMoreData();
    
            /**
             * 设置适配器
             */
            void doSetAdapter(List<NewsCommentBean.DataBean.CommentBean> list);
    
            /**
             * 加载完毕
             */
            void doShowNoMore();
        }
    }

    3.2.新闻评论片段源代码  

    public class NewsCommentFragment extends BaseListFragment<INewsComment.Presenter> implements INewsComment.View{
        private static final String GROUP_ID = "groupId";
        private static final String ITEM_ID = "itemId";
        private static final String TAG = "NewsCommentFragment";
        private String groupId;
        private String itemId;
    
        public static NewsCommentFragment newInstance(String groupId, String itemId) {
            NewsCommentFragment instance = new NewsCommentFragment();
            Bundle bundle = new Bundle();
            bundle.putString(GROUP_ID, groupId);
            bundle.putString(ITEM_ID, itemId);
            instance.setArguments(bundle);
            return instance;
        }
    
        @Override
        protected int attachLayoutId() {
            return R.layout.fragment_list_toolbar;
        }
    
        @Override
        protected void initData() {
            Bundle arguments = getArguments();
            groupId = arguments.getString(GROUP_ID);
            itemId = arguments.getString(ITEM_ID);
            onLoadData();
        }
    
        @Override
        public void onLoadData() {
            onShowLoading();
            presenter.doLoadData(groupId, itemId);
        }
    
        @Override
        public void onRefresh() {
            presenter.doRefresh();
        }
    
        @Override
        protected void initView(View view) {
            super.initView(view);
            Toolbar toolbar = view.findViewById(R.id.toolbar);
            initToolBar(toolbar, true, getString(R.string.title_comment));
            toolbar.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    recyclerView.smoothScrollToPosition(0);
                }
            });
            toolbar.setBackgroundColor(SettingUtil.getInstance().getColor());
    
            adapter = new MultiTypeAdapter(oldItems);
            Register.registerNewsCommentItem(adapter);
            recyclerView.setAdapter(adapter);
            recyclerView.addOnScrollListener(new OnLoadMoreListener() {
                @Override
                public void onLoadMore() {
                    if (canLoadMore) {
                        canLoadMore = false;
                        presenter.doLoadMoreData();
                    }
                }
            });
            setHasOptionsMenu(true);
        }
    
        @Override
        public void onSetAdapter(final List<?> list) {
            Items newItems = new Items(list);
            newItems.add(new LoadingBean());
            DiffCallback.notifyDataSetChanged(oldItems, newItems, DiffCallback.NEWS_COMMENT, adapter);
            oldItems.clear();
            oldItems.addAll(newItems);
            canLoadMore = true;
        }
    
        @Override
        public void setPresenter(INewsComment.Presenter presenter) {
            if (null == presenter) {
                this.presenter = new NewsCommentPresenter(this);
            }
        }
    
        @Override
        public void fetchData() {
    
        }
    }

    3.3.新建一个实例,供外部调用。

      传进来两个参数,一个groupId,一个itemId。

      传出去一个片段Fragment。

    3.4.重写返回片段的布局==>fragment_list_toolbar.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"
        android:orientation="vertical">
    
        <include layout="@layout/toolbar"/>
    
        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/refresh_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fadeScrollbars="true"
                android:scrollbarFadeDuration="1"
                android:scrollbars="vertical"
                app:layoutManager="LinearLayoutManager">
            </android.support.v7.widget.RecyclerView>
    
        </android.support.v4.widget.SwipeRefreshLayout>
    
    </android.support.design.widget.CoordinatorLayout>
    

      预览页面:

      

    3.5.初始化视图initView。

      传进去一个view。

      获取toolbar+点击事件。

      新建一个adapter,给recycleView设置适配器+滑动监听事件。

      设置菜单。

    3.6.初始化数据initData。

      获取存放在bundle中的两个信息。

      然后调用处理器来加载数据。

    3.7.重写onRefresh函数。

      调用处理器的刷新。

    3.8.重写加载函数onLoadData。

      显示视图的加载圈。

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

    3.9.重写设置适配器。

      传入一个List。

      比较新老数据,动态变化数据。

    3.10.重写设置处理器。

      传入一个底层接口中定义的一个处理器。

      将这个处理器保存起来以后用。

      

    3.11.重写填充数据的fetchData。

      里面是空的。这里不做任何事情。   


    4.新闻评论的处理器

    4.1.源代码 

    package com.jasonjan.headnews.module.news.comment;
    
    import com.jasonjan.headnews.api.IMobileNewsApi;
    import com.jasonjan.headnews.bean.news.NewsCommentBean;
    import com.jasonjan.headnews.main.ErrorAction;
    import com.jasonjan.headnews.main.RetrofitFactory;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import io.reactivex.android.schedulers.AndroidSchedulers;
    import io.reactivex.annotations.NonNull;
    import io.reactivex.functions.Consumer;
    import io.reactivex.functions.Function;
    import io.reactivex.schedulers.Schedulers;
    
    /**
     * Created by JasonJan on 2018/1/9.
     */
    
    public class NewsCommentPresenter implements INewsComment.Presenter{
        private static final String TAG = "NewsCommentPresenter";
        private INewsComment.View view;
        private String groupId;
        private String itemId;
        private int offset = 0;
        private List<NewsCommentBean.DataBean.CommentBean> commentsBeanList = new ArrayList<>();
    
        public NewsCommentPresenter(INewsComment.View view) {
            this.view = view;
        }
    
        @Override
        public void doLoadData(String... groupId_ItemId){
            try {
                if (null == this.groupId) {
                    this.groupId = groupId_ItemId[0];
                }
                if (null == this.itemId) {
                    this.itemId = groupId_ItemId[1];
                }
            } catch (Exception e) {
                ErrorAction.print(e);
            }
    
            RetrofitFactory.getRetrofit().create(IMobileNewsApi.class)
                    .getNewsComment(groupId, offset)
                    .subscribeOn(Schedulers.io())
                    .map(new Function<NewsCommentBean, List<NewsCommentBean.DataBean.CommentBean>>() {
                        @Override
                        public List<NewsCommentBean.DataBean.CommentBean> apply(@NonNull NewsCommentBean newsCommentBean) throws Exception {
                            List<NewsCommentBean.DataBean.CommentBean> data = new ArrayList<>();
                            for (NewsCommentBean.DataBean bean : newsCommentBean.getData()) {
                                data.add(bean.getComment());
                            }
                            return data;
                        }
                    })
                    .compose(view.<List<NewsCommentBean.DataBean.CommentBean>>bindToLife())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<List<NewsCommentBean.DataBean.CommentBean>>() {
                        @Override
                        public void accept(@NonNull List<NewsCommentBean.DataBean.CommentBean> list) throws Exception {
                            if (null != list && list.size() > 0) {
                                doSetAdapter(list);
                            } else {
                                doShowNoMore();
                            }
                        }
                    }, new Consumer<Throwable>() {
                        @Override
                        public void accept(@NonNull Throwable throwable) throws Exception {
                            doShowNetError();
                            ErrorAction.print(throwable);
                        }
                    });
        }
    
        @Override
        public void doLoadMoreData() {
            offset += 20;
            doLoadData();
        }
    
        @Override
        public void doSetAdapter(List<NewsCommentBean.DataBean.CommentBean> list) {
            commentsBeanList.addAll(list);
            view.onSetAdapter(commentsBeanList);
            view.onHideLoading();
        }
    
        @Override
        public void doRefresh() {
            if (commentsBeanList.size() != 0) {
                commentsBeanList.clear();
                offset = 0;
            }
            doLoadData();
        }
    
        @Override
        public void doShowNetError() {
            view.onHideLoading();
            view.onShowNetError();
        }
    
        @Override
        public void doShowNoMore() {
            view.onHideLoading();
            view.onShowNoMore();
        }
    }

    4.2.一个构造函数。

      传进去一个底层接口中定义的一个View。

      保存这个View,以后再用。

    4.3.重写doLoadData函数。

      传进去一个String...类型,类似于数组。

      然后调用API请求。

    4.4.重写加载更多doLoadMoreData函数。

      偏移量增加20即可。

      然后再调用doLoadData函数。

    4.5.重写设置适配器doSetAdapter函数。

      传进去一个List。

      然后调用视图层的onSetAdapter函数。

      然后调用试图层的onHideLoading函数。

    4.6.重写刷新。

      将List清空,设置偏移量为0。

      然后调用doLoadData。

    4.7.重写网络错误。  

      调用视图层的隐藏加载函数。

      调用视图层的显示网络错误。

    4.8.重写没有更多。

      调用视图层的隐藏加载函数。

      调用视图层的显示没有更多函数。


    5.注册数据类型

    5.1.首先在新闻评论片段中给适配器添加数据类型 

    adapter = new MultiTypeAdapter(oldItems);
            Register.registerNewsCommentItem(adapter);
            recyclerView.setAdapter(adapter);

     

    5.2.然后在自定义类Register统一注册这个页面需要的类型 

    public static void registerNewsCommentItem(@NonNull MultiTypeAdapter adapter) {
            adapter.register(NewsCommentBean.DataBean.CommentBean.class, new NewsCommentViewBinder());
            adapter.register(LoadingBean.class, new LoadingViewBinder());
            adapter.register(LoadingEndBean.class, new LoadingEndViewBinder());
        }

      

    5.3.然后看一下这个新闻评论的视图绑定类==>NewsCommentViewBinder

    package com.jasonjan.headnews.binder.news;
    
    import android.content.ClipData;
    import android.content.ClipboardManager;
    import android.content.Context;
    import android.support.annotation.NonNull;
    import android.support.design.widget.Snackbar;
    import android.support.v7.widget.RecyclerView;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    import com.jasonjan.headnews.R;
    import com.jasonjan.headnews.bean.news.NewsCommentBean;
    import com.jasonjan.headnews.main.ErrorAction;
    import com.jasonjan.headnews.main.IntentAction;
    import com.jasonjan.headnews.module.base.BaseActivity;
    import com.jasonjan.headnews.util.ImageLoader;
    import com.jasonjan.headnews.widget.BottomSheetDialogFixed;
    
    import me.drakeet.multitype.ItemViewBinder;
    
    /**
     * Created by JasonJan on 2018/1/9.
     */
    
    public class NewsCommentViewBinder extends ItemViewBinder<NewsCommentBean.DataBean.CommentBean,NewsCommentViewBinder.ViewHolder> {
    
        @NonNull
        @Override
        protected NewsCommentViewBinder.ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
            View view = inflater.inflate(R.layout.item_news_comment, parent, false);
            return new ViewHolder(view);
        }
    
        @Override
        protected void onBindViewHolder(@NonNull final ViewHolder holder, @NonNull final NewsCommentBean.DataBean.CommentBean item) {
    
            final Context context = holder.itemView.getContext();
    
            try {
                String iv_avatar = item.getUser_profile_image_url();
                String tv_username = item.getUser_name();
                String tv_text = item.getText();
                int tv_likes = item.getDigg_count();
    
                ImageLoader.loadCenterCrop(context, iv_avatar, holder.iv_avatar, R.color.viewBackground);
                holder.tv_username.setText(tv_username);
                holder.tv_text.setText(tv_text);
                holder.tv_likes.setText(tv_likes + "赞");
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        final String content = item.getText();
                        final BottomSheetDialogFixed dialog = new BottomSheetDialogFixed(context);
                        dialog.setOwnerActivity((BaseActivity) context);
                        View view = ((BaseActivity) context).getLayoutInflater().inflate(R.layout.item_comment_action_sheet, null);
                        view.findViewById(R.id.layout_copy_text).setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View view) {
                                ClipboardManager copy = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
                                ClipData clipData = ClipData.newPlainText("text", content);
                                copy.setPrimaryClip(clipData);
                                Snackbar.make(holder.itemView, R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show();
                                dialog.dismiss();
                            }
                        });
                        view.findViewById(R.id.layout_share_text).setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View view) {
                                IntentAction.send(context, content);
                                dialog.dismiss();
                            }
                        });
                        dialog.setContentView(view);
                        dialog.show();
                    }
                });
            } catch (Exception e) {
                ErrorAction.print(e);
            }
        }
    
        public class ViewHolder extends RecyclerView.ViewHolder {
    
            private ImageView iv_avatar;
            private TextView tv_username;
            private TextView tv_text;
            private TextView tv_likes;
    
            public ViewHolder(View itemView) {
                super(itemView);
                this.iv_avatar = itemView.findViewById(R.id.iv_avatar);
                this.tv_username = itemView.findViewById(R.id.tv_username);
                this.tv_text = itemView.findViewById(R.id.tv_text);
                this.tv_likes = itemView.findViewById(R.id.tv_likes);
            }
        }
    }

     

    5.4.需要一个新闻评论每一个item的布局==>item_news_comment.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/viewBackground">
    
        <LinearLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/selectableItemBackground"
            android:foreground="?attr/selectableItemBackground"
            android:orientation="vertical"
            android:paddingBottom="8dp"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:paddingTop="8dp">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
    
                <com.meiji.toutiao.widget.CircleImageView
                    android:id="@+id/iv_avatar"
                    android:layout_width="22dp"
                    android:layout_height="22dp"
                    android:layout_gravity="center"/>
    
                <TextView
                    android:id="@+id/tv_username"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginLeft="8dp"
                    android:layout_marginStart="8dp"
                    android:ellipsize="end"
                    android:maxLines="1"
                    tools:text="小恢恢的帽子"/>
    
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:orientation="vertical">
    
                <TextView
                    android:id="@+id/tv_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    tools:text="光看个开头就笑的不行了,咱们中国有个传统,就是家里来客人了,要到门口迎一下,如果手里还带着礼物,那要先接过来,因为人家大老远一路带过来的,已经很累了,更何况老美不远万里带过来的呢,如果不接过来那也太不像话了,会让国际笑话中国,也有失大国风范!这也是一种礼貌,更是中华民族的传统美德!"/>
    
                <TextView
                    android:id="@+id/tv_likes"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="2dp"
                    android:gravity="end"
                    android:maxLines="1"
                    tools:text="4832赞"/>
    
            </LinearLayout>
        </LinearLayout>
    
        <View
            android:id="@+id/divider"
            android:layout_width="match_parent"
            android:layout_height="1px"
            android:layout_below="@+id/content"
            android:background="@color/line_divider"/>
    
    </RelativeLayout>

      预览页面:

      


    6.API请求

    6.1.在IMobieNewsApi中写这个函数

     /**
         * 获取新闻评论
         * 按热度排序
         * http://is.snssdk.com/article/v53/tab_comments/?group_id=6314103921648926977&offset=0&tab_index=0
         * 按时间排序
         * http://is.snssdk.com/article/v53/tab_comments/?group_id=6314103921648926977&offset=0&tab_index=1
         *
         * @param groupId 新闻ID
         * @param offset  偏移量
         */
        @GET("http://is.snssdk.com/article/v53/tab_comments/")
        Observable<NewsCommentBean> getNewsComment(
                @Query("group_id") String groupId,
                @Query("offset") int offset);

      传递进来两个参数,一个是新闻Id,一个是偏移量(就是获取那些评论)。

      传出去Observable<NewsCommentBean>

    6.2.调用方式==>在处理器的加载数据中执行 

    RetrofitFactory.getRetrofit().create(IMobileNewsApi.class)
                    .getNewsComment(groupId, offset)
                    .subscribeOn(Schedulers.io())
                    .map(new Function<NewsCommentBean, List<NewsCommentBean.DataBean.CommentBean>>() {
                        @Override
                        public List<NewsCommentBean.DataBean.CommentBean> apply(@NonNull NewsCommentBean newsCommentBean) throws Exception {
                            List<NewsCommentBean.DataBean.CommentBean> data = new ArrayList<>();
                            for (NewsCommentBean.DataBean bean : newsCommentBean.getData()) {
                                data.add(bean.getComment());
                            }
                            return data;
                        }
                    })
                    .compose(view.<List<NewsCommentBean.DataBean.CommentBean>>bindToLife())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<List<NewsCommentBean.DataBean.CommentBean>>() {
                        @Override
                        public void accept(@NonNull List<NewsCommentBean.DataBean.CommentBean> list) throws Exception {
                            if (null != list && list.size() > 0) {
                                doSetAdapter(list);
                            } else {
                                doShowNoMore();
                            }
                        }
                    }, new Consumer<Throwable>() {
                        @Override
                        public void accept(@NonNull Throwable throwable) throws Exception {
                            doShowNetError();
                            ErrorAction.print(throwable);
                        }
                    });
        }


  • 相关阅读:
    北京南天软件java工程师面试题
    祝福自己
    致青春——IT之路
    PL/SQL devloper 常用设置
    CENTOS LINUX查询内存大小、频率
    centOS安装openoffice
    echo > 和 echo >>的区别
    sqoop job 增量导入
    sqoop job从创建到执行
    sqoop导入增量数据
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/8250612.html
Copyright © 2020-2023  润新知