1.要达到的效果

1.1.主要效果图

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

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

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>

  预览页面:

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

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>

  预览页面:

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

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);
}
});
}
05-26 17:27