TouTiao开源项目 分析笔记19 问答内容
1.真实页面预览
1.1.成果预览
首先是问答列表
然后每个item设置点击事件,进入问答内容列表
然后每一个问答内容也设置点击事件,进入问答详情
1.2.触发事件。
在WendaArticleOneImgViewBinder中,设置item的点击事件,
跳转到WendaContentActivity。
在WendaArticleTextViewBinder中,设置item的点击事件,
跳转到WendaContentActivity。
在WendaArticleThreeImgViewBinder中,设置item的点击事件,
跳转到WendaContentActivity。
2.问答内容的活动页面
2.1.源代码
public class WendaContentActivity extends BaseActivity { private static final String TAG = "WendaContentActivity"; public static void launch(String qid) { InitApp.AppContext.startActivity(new Intent(InitApp.AppContext, WendaContentActivity.class) .putExtra(TAG, qid) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.container); getSupportFragmentManager().beginTransaction() .replace(R.id.container, WendaContentFragment.newInstance(getIntent().getStringExtra(TAG))) .commit(); } }
2.2.需要的布局==>container.xml
<FrameLayout 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:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/viewBackground" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:layout="@layout/fragment_news_tab"/>
2.3.清单中活动配置
<activity android:name=".module.wenda.content.WendaContentActivity" android:configChanges="orientation|screenSize|uiMode" android:theme="@style/AppTheme.NoActionBar.Slidable"/>
2.4.现在还需要问答内容的片段了。
3.问答内容片段
3.1.底层接口==>IWendaContent
public interface IWendaContent { interface View extends IBaseListView<Presenter> { /** * 请求数据 */ void onLoadData(); /** * 刷新 */ void onRefresh(); /** * 设置顶部信息 */ void onSetHeader(WendaContentBean.QuestionBean questionBean); } interface Presenter extends IBasePresenter { /** * 设置顶部信息 */ void doSetHeader(WendaContentBean.QuestionBean questionBean); /** * 请求数据 */ void doLoadData(String qid); /** * 再起请求数据 */ void doLoadMoreData(); /** * 设置适配器 */ void doSetAdapter(List<WendaContentBean.AnsListBean> list); /** * 加载完毕 */ void doShowNoMore(); } }
3.2.源代码
public class WendaContentFragment extends BaseListFragment<IWendaContent.Presenter> implements IWendaContent.View { private static final String TAG = "WendaContentFragment"; private String qid; private String shareTitle; private String shareUrl; private WendaContentBean.QuestionBean WendaContentHeaderBean; public static WendaContentFragment newInstance(String qid) { Bundle args = new Bundle(); args.putString(TAG, qid); WendaContentFragment fragment = new WendaContentFragment(); fragment.setArguments(args); return fragment; } @Override public void setPresenter(IWendaContent.Presenter presenter) { if (null == presenter) { this.presenter = new WendaContentPresenter(this); } } @Override public void onSetAdapter(List<?> list) { Items newItems = new Items(); newItems.add(WendaContentHeaderBean); newItems.addAll(list); newItems.add(new LoadingBean()); DiffCallback.notifyDataSetChanged(oldItems, newItems, DiffCallback.WENDA_CONTENT, adapter); oldItems.clear(); oldItems.addAll(newItems); canLoadMore = true; } @Override protected int attachLayoutId() { return R.layout.fragment_list_toolbar; } @Override protected void initView(View view) { super.initView(view); Toolbar toolbar = view.findViewById(R.id.toolbar); initToolBar(toolbar, true, getString(R.string.title_wenda)); toolbar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { recyclerView.smoothScrollToPosition(0); } }); toolbar.setBackgroundColor(SettingUtil.getInstance().getColor()); adapter = new MultiTypeAdapter(oldItems); Register.registerWendaContentItem(adapter); recyclerView.setAdapter(adapter); recyclerView.addOnScrollListener(new OnLoadMoreListener() { @Override public void onLoadMore() { if (canLoadMore) { canLoadMore = false; presenter.doLoadMoreData(); } } }); setHasOptionsMenu(true); } @Override protected void initData() { this.qid = getArguments().getString(TAG); onLoadData(); } @Override public void onLoadData() { onShowLoading(); presenter.doLoadData(qid); } @Override public void onRefresh() { recyclerView.smoothScrollToPosition(0); presenter.doRefresh(); } @Override public void onSetHeader(WendaContentBean.QuestionBean questionBean) { this.shareTitle = questionBean.getShare_data().getTitle(); this.shareUrl = questionBean.getShare_data().getShare_url(); this.WendaContentHeaderBean = questionBean; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_wenda_content, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_wenda_share) { IntentAction.send(getActivity(), shareTitle + "\n" + shareUrl); } return super.onOptionsItemSelected(item); } @Override public void fetchData() { } }
①新建一个实例,传入一个问答id,返回一个片段。
②重写设置处理器,新建了一个。
③重写视图层的设置适配器,传入一个List,处理新老数据。
④重写初始化视图。
获取toolbar,并设置点击事件。
设置recyclerView的适配器,并设置滑动监听事件。
设置菜单。
⑤重写初始化数据。
从arguments获取问答id。
然后调用加载数据的函数。
⑥重写加载数据的函数。
先显示Loading。
然后调用处理器的加载函数。
⑦重写刷新函数。
先将recyclerView滑到最上面。
然后调用处理器的刷新函数。
⑧重写设置头部。传入一个Bean。获取分享的标题和url。
⑨重写菜单以及菜单的点击事件。
3.3.需要的菜单布局
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_wenda_share" android:icon="@drawable/ic_share_white_24dp" android:title="@string/action_share" app:showAsAction="ifRoom"/> </menu>
预览页面:
4.设置处理器
4.1.处理器源代码
public class WendaContentPresenter implements IWendaContent.Presenter { private static final String TAG = "WendaContentPresenter"; private IWendaContent.View view; private String qid; private int niceOffset = 0; private int normalOffset = 0; private int niceAnsCount = 0; private int normalAnsCount = 0; private List<WendaContentBean.AnsListBean> ansList = new ArrayList<>(); private String title; WendaContentPresenter(IWendaContent.View view) { this.view = view; } public void doRefresh() { if (ansList.size() != 0) { ansList.clear(); niceOffset = 0; normalOffset = 0; } doLoadData(this.qid); } @Override public void doShowNetError() { view.onHideLoading(); view.onShowNetError(); } @Override public void doLoadData(String qid) { this.qid = qid; Log.d(TAG, "doLoadArticle: " + qid); RetrofitFactory.getRetrofit().create(IMobileWendaApi.class).getWendaNiceContent(qid) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<WendaContentBean>() { @Override public void accept(@NonNull WendaContentBean wendaContentBean) throws Exception { doSetHeader(wendaContentBean.getQuestion()); doSetAdapter(wendaContentBean.getAns_list()); niceOffset += 10; } }, new Consumer<Throwable>() { @Override public void accept(@NonNull Throwable throwable) throws Exception { doShowNetError(); ErrorAction.print(throwable); } }); } @Override public void doLoadMoreData() { if (niceOffset < niceAnsCount) { RetrofitFactory.getRetrofit().create(IMobileWendaApi.class) .getWendaNiceContentLoadMore(qid, niceOffset) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<WendaContentBean>() { @Override public void accept(@NonNull WendaContentBean wendaContentBean) throws Exception { doSetAdapter(wendaContentBean.getAns_list()); niceOffset += 10; } }, new Consumer<Throwable>() { @Override public void accept(@NonNull Throwable throwable) throws Exception { doShowNetError(); ErrorAction.print(throwable); } }); } else if (normalOffset < normalAnsCount) { RetrofitFactory.getRetrofit().create(IMobileWendaApi.class) .getWendaNormalContentLoadMore(qid, normalOffset) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<WendaContentBean>() { @Override public void accept(@NonNull WendaContentBean wendaContentBean) throws Exception { doSetAdapter(wendaContentBean.getAns_list()); normalOffset += 10; } }, new Consumer<Throwable>() { @Override public void accept(@NonNull Throwable throwable) throws Exception { doShowNetError(); ErrorAction.print(throwable); } }); } else { doShowNoMore(); } } @Override public void doSetAdapter(List<WendaContentBean.AnsListBean> list) { for (WendaContentBean.AnsListBean bean : list) { bean.setTitle(this.title); bean.setQid(this.qid); } ansList.addAll(list); view.onSetAdapter(ansList); view.onHideLoading(); } @Override public void doSetHeader(WendaContentBean.QuestionBean questionBean) { this.niceAnsCount = questionBean.getNice_ans_count(); this.normalAnsCount = questionBean.getNormal_ans_count(); this.title = questionBean.getTitle(); view.onSetHeader(questionBean); } @Override public void doShowNoMore() { view.onHideLoading(); if (ansList.size() > 0) { view.onShowNoMore(); } } }
4.2.处理器的构造函数。
传入了一个视图层,方便处理视图。
4.3.刷新函数。
将之前的列表清空,然后调用处理器的加载函数即可。
4.4.处理网络异常。
4.5.处理器的加载函数。
传入了一个问答Id。
调用API去请求问答内容。
然后在subscribe进行下一步处理。
4.6.处理器的加载更多函数。
没有传递任何参数。
调用API去请求问答更多内容。
然后在subscribe中进行下一步处理。
4.7.处理器的设置适配器函数。
传递了一个List。
然后调用视图层的设置适配器,并将这个List传给它。
4.8.处理器的设置头部。
传入了一个问题Bean类型。
调用视图层的设置头部信息。
4.9.处理器的没有更多了。
调用视图层的隐藏加载+没有更多了。
5.注册数据类型
5.1.统一管理
/** * 注册问答内容 * @param adapter */ public static void registerWendaContentItem(@NonNull MultiTypeAdapter adapter) { adapter.register(WendaContentBean.QuestionBean.class, new WendaContentHeaderViewBinder()); adapter.register(WendaContentBean.AnsListBean.class, new WendaContentViewBinder()); adapter.register(LoadingBean.class, new LoadingViewBinder()); adapter.register(LoadingEndBean.class, new LoadingEndViewBinder()); }
5.2.问答头部绑定器==>WendaContentHeaderViewBinder
源代码:
public class WendaContentHeaderViewBinder extends ItemViewBinder<WendaContentBean.QuestionBean, WendaContentHeaderViewBinder.ViewHolder> { @NonNull @Override protected WendaContentHeaderViewBinder.ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.item_wenda_content_header, parent, false); return new ViewHolder(view); } @Override protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull final WendaContentBean.QuestionBean item) { try { String tv_title = item.getTitle(); String tv_abstract = item.getContent().getText(); String tv_answer_count = item.getNormal_ans_count() + item.getNice_ans_count() + " 回答"; String tv_follow_count = item.getFollow_count() + " 关注"; holder.tv_title.setText(tv_title); if (!TextUtils.isEmpty(tv_abstract)) { holder.tv_abstract.setText(tv_abstract); } else { holder.tv_abstract.setVisibility(View.GONE); } holder.tv_answer_count.setText(tv_answer_count); holder.tv_follow_count.setText(tv_follow_count); holder.title_view.setBackgroundColor(SettingUtil.getInstance().getColor()); } catch (Exception e) { ErrorAction.print(e); } } public class ViewHolder extends RecyclerView.ViewHolder { private TextView tv_title; private TextView tv_abstract; private TextView tv_answer_count; private TextView tv_follow_count; private LinearLayout title_view; public ViewHolder(View itemView) { super(itemView); this.tv_title = itemView.findViewById(R.id.tv_title); this.tv_abstract = itemView.findViewById(R.id.tv_abstract); this.tv_answer_count = itemView.findViewById(R.id.tv_answer_count); this.tv_follow_count = itemView.findViewById(R.id.tv_follow_count); this.title_view = itemView.findViewById(R.id.title_view); } } }
需要的布局==>item_wenda_content.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"> <LinearLayout android:id="@+id/title_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="8dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="0dp"> <TextView android:id="@+id/tv_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.AppCompat.Title" android:textColor="@color/White" tools:text="都说床头不能朝西,有什么说法吗?"/> </LinearLayout> <LinearLayout android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/title_view" android:background="@color/viewBackground" android:orientation="vertical" android:paddingBottom="8dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="8dp"> <TextView android:id="@+id/tv_abstract" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" tools:text="都说床头不能朝西,可是我家设计的两个卧室都是不得不朝西的啊!有什么不好吗?"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:orientation="horizontal"> <TextView android:id="@+id/tv_answer_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" tools:text="602个回答"/> <TextView android:id="@+id/tv_follow_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginStart="8dp" android:textSize="14sp" tools:text="68关注"/> </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>
预览图片:
5.3.问答列表绑定视图
源代码:
public class WendaContentViewBinder extends ItemViewBinder<WendaContentBean.AnsListBean, WendaContentViewBinder.ViewHolder> { @NonNull @Override protected WendaContentViewBinder.ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.item_wenda_content, parent, false); return new ViewHolder(view); } @Override protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull final WendaContentBean.AnsListBean item) { try { String iv_user_avatar = item.getUser().getAvatar_url(); ImageLoader.loadCenterCrop(holder.itemView.getContext(), iv_user_avatar, holder.iv_user_avatar, R.color.viewBackground); String tv_user_name = item.getUser().getUname(); String tv_like_count = item.getDigg_count() + ""; String tv_abstract = item.getContent_abstract().getText(); holder.tv_user_name.setText(tv_user_name); holder.tv_like_count.setText(tv_like_count); holder.tv_abstract.setText(tv_abstract); RxView.clicks(holder.itemView) .throttleFirst(1, TimeUnit.SECONDS) .subscribe(new Consumer<Object>() { @Override public void accept(@io.reactivex.annotations.NonNull Object o) throws Exception { //WendaDetailActivity.launch(item); } }); } catch (Exception e) { ErrorAction.print(e); } } public class ViewHolder extends RecyclerView.ViewHolder { private CircleImageView iv_user_avatar; private TextView tv_user_name; private TextView tv_like_count; private TextView tv_abstract; public ViewHolder(View itemView) { super(itemView); this.iv_user_avatar = itemView.findViewById(R.id.iv_user_avatar); this.tv_user_name = itemView.findViewById(R.id.tv_user_name); this.tv_like_count = itemView.findViewById(R.id.tv_like_count); this.tv_abstract = itemView.findViewById(R.id.tv_abstract); } } }
需要的item布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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: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:padding="16dp"> <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal"> <com.jasonjan.headnews.widget.CircleImageView android:id="@+id/iv_user_avatar" android:layout_width="22dp" android:layout_height="22dp" android:scaleType="centerCrop"/> <TextView android:id="@+id/tv_user_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginStart="8dp" android:ellipsize="end" android:maxLines="1" android:textAppearance="@style/TextAppearance.AppCompat.Caption" tools:text="用户名"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical"> <ImageView android:id="@+id/iv_like" android:layout_width="16dp" android:layout_height="16dp" android:layout_centerVertical="true" android:layout_marginEnd="4dp" android:layout_marginRight="4dp" android:layout_toLeftOf="@+id/tv_like_count" android:layout_toStartOf="@+id/tv_like_count" app:srcCompat="@drawable/ic_like_gray_24dp" tools:ignore="ContentDescription"/> <TextView android:id="@+id/tv_like_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_centerVertical="true" tools:text="22"/> </RelativeLayout> </LinearLayout> <TextView android:id="@+id/tv_abstract" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:ellipsize="end" android:maxLines="6" tools:text="我去过印度,觉得印度人有时也太可爱了,在他们眼里,印度几乎就是唯一的,他们接受新事物的能力似乎非常的有限。但真心是想不到,印度居然还是IT大国。去过印度的人通常都会从导游那里知道:从新德里出发到阿格拉的泰姬陵之间的一趟列车,时速最高的时候达到了160公里/每小时,被印度人称为当地最快的火车。因为印度人非常的热情,看到中国游客就会用蹩脚的汉语跟中国人搭讪,甚至会问:“中国有没有这样快的火车呀?”,这让人尴尬不已,不知道如何回答是好。我在想如下回答,如何?----对不起,中国没有时速160的火车,只有时速360的动车。----我们中国的火车坐的人少,拉轻,印度的火车超载了,跑不快,所以中国的火车要快一点。----你们印度人是坐在车外面的,所以感觉很快,我们的高铁是坐里面的,所以感觉不到快。"/> </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请求IMobileWendaApi
6.1.获取头条问答优质回答
/** * 获取头条问答优质回答 * http://is.snssdk.com/wenda/v1/question/brow/?iid=10344168417&device_id=36394312781 * * @param qid 问答ID */ @POST("http://is.snssdk.com/wenda/v1/question/brow/?iid=10344168417&device_id=36394312781") @FormUrlEncoded Observable<WendaContentBean> getWendaNiceContent(@Field("qid") String qid);
传进来一个问答Id。
传出去Observable<WendaContentBean>。
6.2.获取头条问答优质回答的加载更多
/** * 获取头条问答优质回答(加载更多) * http://is.snssdk.com/wenda/v1/question/loadmore/?iid=10344168417&device_id=36394312781 * * @param qid 问答ID * @param offset 偏移量 */ @POST("http://is.snssdk.com/wenda/v1/question/loadmore/?iid=10344168417&device_id=36394312781") @FormUrlEncoded Observable<WendaContentBean> getWendaNiceContentLoadMore( @Field("qid") String qid, @Field("offset") int offset);
传进来一个问答Id,一个偏移量。
传出去一个Observable<WendaContentBean>。
6.3.获取头条问答普通回答
/** * 获取头条问答普通回答 * http://is.snssdk.com/wenda/v1/questionother/brow/?iid=10344168417&device_id=36394312781 * * @param qid 问答ID */ @POST("http://is.snssdk.com/wenda/v1/questionother/brow/?iid=10344168417&device_id=36394312781") @FormUrlEncoded Observable<WendaContentBean> getWendaNormalContent(@Field("qid") String qid);
传进来一个问答Id。
传出去一个Observable<WendaContentBean>。
6.4.获取头条问答普通回答的加载更多
/** * 获取头条问答普通回答(加载更多) * http://is.snssdk.com/wenda/v1/questionother/loadmore/?iid=10344168417&device_id=36394312781 * * @param qid 问答ID * @param offset 偏移量 */ @POST("http://is.snssdk.com/wenda/v1/questionother/loadmore/?iid=10344168417&device_id=36394312781") @FormUrlEncoded Observable<WendaContentBean> getWendaNormalContentLoadMore( @Field("qid") String qid, @Field("offset") int offset);
传进来一个问答Id,一个偏移量。
传出去一个Observable<WendaContentBean>。