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); } }); }