1.效果预览

1.1.效果预览,从问答列表开始

  TouTiao开源项目 分析笔记20 问答详情-LMLPHP

  前面实现了从列表到内容。

  这里主要讲解从内容到详情。

  点击每一个回答内容,进入回答详情页面。

1.2.触发的点击事件

  在WendaContentViewBinder中,设置item点击事件:

  WendaDetailActivity.lauch(bean);

2.问答详情的活动页面

2.1.源代码

public class WendaDetailActivity extends BaseActivity {

    private static final String TAG = "WendaDetailActivity";

    public static void launch(WendaContentBean.AnsListBean bean) {
InitApp.AppContext.startActivity(new Intent(InitApp.AppContext, WendaDetailActivity.class)
.putExtra(TAG, bean)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wenda_content_view);
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, WendaDetailFragment.newInstance(getIntent().getParcelableExtra(TAG)))
.commit();
}
}

2.2.需要的布局

<?xml version="1.0" encoding="utf-8"?>
<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"
> </FrameLayout>

2.3.清单中活动配置

<activity
android:name=".module.wenda.detail.WendaDetailActivity"
android:configChanges="orientation|screenSize|uiMode"
android:label="@string/title_wenda_detail"
android:theme="@style/AppTheme.NoActionBar.Slidable"/>

3.问答详情页面片段

3.1.需要实现的底层接口

public interface IWendaDetail {
interface View extends IBaseListView<Presenter> { /**
* 加载网页
*/
void onSetWebView(String baseUrl, boolean flag); /**
* 请求数据
*/
void onLoadData();
} interface Presenter extends IBasePresenter { /**
* 请求数据
*/
void doLoadData(String url); /**
* 加载评论
*/
void doLoadComment(String... ansId); /**
* 加载更多评论
*/
void doLoadMoreComment(); /**
* 设置适配器
*/
void doSetAdapter(List<NewsCommentBean.DataBean.CommentBean> list); /**
* 加载完毕
*/
void doShowNoMore();
}
}

3.2.片段源代码 

public class WendaDetailFragment extends BaseFragment<IWendaDetail.Presenter> implements IWendaDetail.View{

    private static final String TAG = "WendaDetailFragment";
private WendaContentBean.AnsListBean bean = null;
private String url;
private String title;
private String shareTitle; private WebView webView;
private NestedScrollView scrollView;
private ContentLoadingProgressBar progressBar;
private TextView tv_title;
private CircleImageView iv_user_avatar;
private TextView tv_user_name;
private RecyclerView recyclerView;
private LinearLayout header_layout;
private SwipeRefreshLayout swipeRefreshLayout; private MultiTypeAdapter adapter;
private boolean canLoadMore;
private Items oldItems = new Items(); public static WendaDetailFragment newInstance(Parcelable bean) {
Bundle args = new Bundle();
args.putParcelable(TAG, bean);
WendaDetailFragment fragment = new WendaDetailFragment();
fragment.setArguments(args);
return fragment;
} @Override
protected void initData() {
bean = getArguments().getParcelable(TAG);
if (null == this.bean) {
onShowNetError();
return;
}
url = bean.getShare_data().getShare_url();
onLoadData(); ImageLoader.loadCenterCrop(getActivity(), bean.getUser().getAvatar_url(), iv_user_avatar, R.color.viewBackground);
tv_title.setText(bean.getTitle());
tv_user_name.setText(bean.getUser().getUname());
shareTitle = bean.getShare_data().getTitle();
header_layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
WendaContentActivity.launch(bean.getQid());
}
});
} @Override
public void onLoadData() {
presenter.doLoadData(url);
} @Override
public void setPresenter(IWendaDetail.Presenter presenter) {
if (null == presenter) {
this.presenter = new WendaDetailPresenter(this);
}
} @Override
public void onSetAdapter(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;
recyclerView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
} @Override
protected int attachLayoutId() {
return R.layout.fragment_wenda_detail;
} @Override
protected void initView(View view) {
Toolbar toolbar = view.findViewById(R.id.toolbar);
initToolBar(toolbar, true, getString(R.string.title_wenda_detail));
toolbar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
scrollView.smoothScrollTo(0, 0);
}
}); webView = view.findViewById(R.id.webview);
initWebClient(); header_layout = view.findViewById(R.id.header_layout);
header_layout.setBackgroundColor(SettingUtil.getInstance().getColor()); tv_title = view.findViewById(R.id.tv_title);
iv_user_avatar = view.findViewById(R.id.iv_user_avatar);
tv_user_name = view.findViewById(R.id.tv_user_name); scrollView = view.findViewById(R.id.scrollView);
scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
onHideLoading();
}
});
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
View view = scrollView.getChildAt(scrollView.getChildCount() - 1);
int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));
if (diff == 0) {
canLoadMore = false;
presenter.doLoadMoreComment();
}
}
}); swipeRefreshLayout = view.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);
}
});
presenter.doLoadData(url);
}
}); progressBar = view.findViewById(R.id.pb_progress);
int color = SettingUtil.getInstance().getColor();
progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
progressBar.show(); recyclerView = view.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
// 禁止嵌套滚动
recyclerView.setNestedScrollingEnabled(false);
adapter = new MultiTypeAdapter(oldItems);
Register.registerNewsCommentItem(adapter);
recyclerView.setAdapter(adapter); setHasOptionsMenu(true);
} @Override
public void onSetWebView(String url, boolean flag) {
// 是否解析网页成功
if (flag) {
webView.loadDataWithBaseURL(null, url, "text/html", "utf-8", null);
presenter.doLoadComment(bean.getAnsid());
} else {
webView.loadUrl(url);
}
} @SuppressLint("SetJavaScriptEnabled")
private void initWebClient() {
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
// 缩放,设置为不能缩放可以防止页面上出现放大和缩小的图标
settings.setBuiltInZoomControls(false);
// 缓存
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
// 开启DOM storage API功能
settings.setDomStorageEnabled(true);
// 开启application Cache功能
settings.setAppCacheEnabled(true);
// 判断是否为无图模式
settings.setBlockNetworkImage(SettingUtil.getInstance().getIsNoPhotoMode());
// 不调用第三方浏览器即可进行页面反应
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!TextUtils.isEmpty(url)) {
view.loadUrl(url);
}
return true;
} @Override
public void onPageFinished(WebView view, String url) {
onHideLoading();
super.onPageFinished(view, url);
}
}); webView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if ((keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
webView.goBack();
return true;
}
return false;
}
}); webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (newProgress >= 90) {
onHideLoading();
} else {
onShowLoading();
}
}
});
} @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_wenda_detail, menu);
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_wenda_share) {
IntentAction.send(getActivity(), shareTitle + "\n" + url);
}
return super.onOptionsItemSelected(item);
} @Override
public void onShowNoMore() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (oldItems.size() > 0) {
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 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(scrollView, R.string.network_error, Snackbar.LENGTH_SHORT).show();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.setItems(new Items());
adapter.notifyDataSetChanged();
canLoadMore = false;
}
});
}
}

3.3.新建实例。

  传进去一个bean类。

  传出去一个片段。

3.4.初始化视图。

  获取toolbar,并进行相关设置。

  获取webview,设置一些交互属性。

  获取头部布局,设置颜色。

  获取scrollView,设置滑动监听,以及设置观察树。

  获取刷新圈,设置刷新事件。

  获取进度条,设置颜色。

  获取recyclerView,设置适配器。

  设置菜单。

3.5.重写设置WebView。

  判断是否解析网页成功。

3.6.初始化webClient,设置webView的配置。

3.7.设置菜单。

3.8.重写没有更多了,重写显示加载,重写隐藏加载,重写网络错误。

3.9.重写设置适配器和设置处理器。

3.10.重写布局。

  需要的布局==>fragment_wenda_detail.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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/news_content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"> <include layout="@layout/toolbar"/> <RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadeScrollbars="true"
android:scrollbarFadeDuration="1"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"> <android.support.v4.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <LinearLayout
android:id="@+id/header_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:foreground="?attr/selectableItemBackground"
android:orientation="vertical"> <TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:padding="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:textColor="@color/White"
tools:text="都说床头不能朝西,有什么说法吗?"/> </LinearLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/viewBackground"
android:gravity="center_vertical"
android:padding="8dp"> <com.jasonjan.headnews.widget.CircleImageView
android:id="@+id/iv_user_avatar"
android:layout_width="36dp"
android:layout_height="36dp"
android:scaleType="centerCrop"
android:src="@color/Black" /> <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"
tools:text="用户名"/>
</LinearLayout> <View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/line_divider"/> <WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/> <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"/>
</LinearLayout> </android.support.v4.widget.NestedScrollView> </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_centerInParent="true"/> </RelativeLayout> </android.support.design.widget.CoordinatorLayout>

  预览图片:

  TouTiao开源项目 分析笔记20 问答详情-LMLPHP

4.问答详情处理器

4.1.需要第三方库Jsoup的支持

  请参考这篇文章:使用Jsoup解析HTML

  在build.gradle中这样配置:

// 解析HTML
implementation 'org.jsoup:jsoup:1.10.2'

  参考的官方网站:http://jsoup.org/

  

4.2.源代码 

public class WendaDetailPresenter implements IWendaDetail.Presenter {
private IWendaDetail.View view;
private String groupId;
private int offset = 0;
private List<NewsCommentBean.DataBean.CommentBean> commentsBeanList = new ArrayList<>(); WendaDetailPresenter(IWendaDetail.View view) {
this.view = view;
} @Override
public void doRefresh() {
if (commentsBeanList.size() != 0) {
commentsBeanList.clear();
offset = 0;
}
view.onLoadData();
} @Override
public void doLoadData(String url) {
RetrofitFactory.getRetrofit().create(IMobileWendaApi.class)
.getWendaAnsDetail(url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ResponseBody>() {
@Override
public void accept(@NonNull ResponseBody responseBody) throws Exception {
String result = getHTML(responseBody.string());
if (result != null) {
view.onSetWebView(result, true);
} else {
view.onSetWebView(null, false);
}
view.onHideLoading();
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
view.onSetWebView(null, false);
view.onHideLoading();
ErrorAction.print(throwable);
}
});
} private String getHTML(String response) {
Document doc = Jsoup.parse(response, "UTF-8");
Elements elements = doc.getElementsByClass("con-words");
String content = null;
for (Element element : elements) {
content = element.toString();
break;
}
if (content != null) { String css = "<link rel=\"stylesheet\" href=\"file:///android_asset/toutiao_light.css\" type=\"text/css\">";
if (SettingUtil.getInstance().getIsNightMode()) {
css = css.replace("toutiao_light", "toutiao_dark");
} String html = "<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">" +
css +
"<body>\n" +
"<article class=\"article-container\">\n" +
" <div class=\"article__content article-content\">" +
content +
" </div>\n" +
"</article>\n" +
"</body>\n" +
"</html>"; return html;
} else {
return null;
}
} @Override
public void doLoadComment (String...ansId){ try {
if (null == groupId) {
this.groupId = ansId[0];
}
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} RetrofitFactory.getRetrofit().create(IMobileNewsApi.class)
.getNewsComment(groupId, offset)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.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())
.subscribe(new Consumer<List<NewsCommentBean.DataBean.CommentBean>>() {
@Override
public void accept(@NonNull List<NewsCommentBean.DataBean.CommentBean> list) throws Exception {
if (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 doLoadMoreComment () {
offset += 10;
doLoadComment();
} @Override
public void doSetAdapter (List < NewsCommentBean.DataBean.CommentBean > list) {
commentsBeanList.addAll(list);
view.onSetAdapter(commentsBeanList);
view.onHideLoading();
} @Override
public void doShowNetError () {
view.onHideLoading();
view.onShowNetError();
} @Override
public void doShowNoMore () {
view.onHideLoading();
if (commentsBeanList.size() > 0) {
view.onShowNoMore();
}
} }

4.3.构造函数,传入一个视图层。

4.4.重写刷新函数,调用视图层的加载。

4.5.重写加载数据,调用API。

4.6.获取HTML的一个函数。

  传进去一个response的字符串。

  利用Jsoup解析这个字符串,然后得到一个html。

  方法源代码: 

private String getHTML(String response) {
Document doc = Jsoup.parse(response, "UTF-8");
Elements elements = doc.getElementsByClass("con-words");
String content = null;
for (Element element : elements) {
content = element.toString();
break;
}
if (content != null) { String css = "<link rel=\"stylesheet\" href=\"file:///android_asset/toutiao_light.css\" type=\"text/css\">";
if (SettingUtil.getInstance().getIsNightMode()) {
css = css.replace("toutiao_light", "toutiao_dark");
} String html = "<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">" +
css +
"<body>\n" +
"<article class=\"article-container\">\n" +
" <div class=\"article__content article-content\">" +
content +
" </div>\n" +
"</article>\n" +
"</body>\n" +
"</html>"; return html;
} else {
return null;
}
}

  

4.7.重写加载评论。

  调用API,请求获取问答详情的评论。

  

4.8.重写加载更多评论。

4.9.重写设置适配器。

  传进去一个List。

4.10.重写网络异常,没有更多了。

5.问答详情评论的视图绑定

5.1.这里用的依旧是新闻页面的视图绑定

  在WendaDetailFragment中的一个initView中引用。

 Register.registerNewsCommentItem(adapter);

  在Register中注册数据类型。

/**
* 新闻评论
* @param adapter
*/
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.2.回顾一下新闻评论的视图绑定

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

  需要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.jasonjan.headnews.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开源项目 分析笔记20 问答详情-LMLPHP

6.API请求

6.1.获取头条回答正文

 /**
* 获取头条问答回答正文
*/
@GET
@Headers("User-Agent:" + Constant.USER_AGENT_MOBILE)
Observable<ResponseBody> getWendaAnsDetail(@Url String url);

  传进去一个url。

  传出来一个Observable<ResponseBody>。

6.2.整体问答API请求

package com.jasonjan.headnews.api;

import com.jasonjan.headnews.bean.wenda.WendaArticleBean;
import com.jasonjan.headnews.bean.wenda.WendaContentBean;
import com.jasonjan.headnews.global.Constant; import io.reactivex.Observable;
import okhttp3.ResponseBody;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.Query;
import retrofit2.http.Url; /**
* Created by JasonJan on 2017/12/11.
*/ public interface IMobileWendaApi {
/**
* 获取头条问答标题等信息
* http://is.snssdk.com/wenda/v1/native/feedbrow/?category=question_and_answer&wd_version=5&count=20&max_behot_time=1495245397?iid=10344168417&device_id=36394312781
*
* @param maxBehotTime 时间轴
*/
@GET("http://is.snssdk.com/wenda/v1/native/feedbrow/?iid=10344168417&device_id=36394312781&category=question_and_answer&wd_version=5&count=20&aid=13")
Observable<WendaArticleBean> getWendaArticle(
@Query("max_behot_time") String maxBehotTime); /**
* 获取头条问答优质回答
* 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); /**
* 获取头条问答优质回答(加载更多)
* 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); /**
* 获取头条问答普通回答
* 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); /**
* 获取头条问答普通回答(加载更多)
* 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); /**
* 获取头条问答回答正文
*/
@GET
@Headers("User-Agent:" + Constant.USER_AGENT_MOBILE)
Observable<ResponseBody> getWendaAnsDetail(@Url String url);
}
05-15 04:17