Android练手小项目(KTReader)基于mvp架构(三)
2017-05-30 本文已影响209人
yiuhet
上路传送眼:
Android练手小项目(KTReader)基于mvp架构(二)
下路传送眼:
Android练手小项目(KTReader)基于mvp架构(四)
GIthub地址: https://github.com/yiuhet/KTReader
上篇文章中我们完成了知乎日报内容的fragment。
而这次我们要做的的就是知乎日报详情页。
惯例先上效果图:
效果图
准备工作
- 添加依赖
compile 'com.jude:swipebackhelper:3.1.2' //右滑关闭详情页
-
工具类
utils.WebUtil.class(返回html结果的工具类):
public class WebUtil {
private WebUtil() {
}
public static final String BASE_URL = "file:///android_asset/";
public static final String MIME_TYPE = "text/html";
public static final String ENCODING = "utf-8";
public static final String FAIL_URL = "http//:daily.zhihu.com/";
private static final String CSS_LINK_PATTERN = " <link href=\"%s\" type=\"text/css\" rel=\"stylesheet\" />";
private static final String DIV_IMAGE_PLACE_HOLDER = "class=\"img-place-holder\"";
public static String buildHtmlWithCss(String html, List<String> cssUrls) {
StringBuilder result = new StringBuilder();
for (String cssUrl : cssUrls) {
result.append(String.format(CSS_LINK_PATTERN, cssUrl));
}
result.append(html.replace(DIV_IMAGE_PLACE_HOLDER, ""));
return result.toString();
}
}
Model层
-
模型实体类ZhihuDetail直接使用GsonFormat工具快速生成
(model.entity.ZhihuDetail) -
知乎日报Model接口
model.ZhihuDetailModel:
public interface ZhihuDetailModel {
void loadDetail(String id, OnZhihuDetailListener listener);
}
- 获取日报详情的Model实现
public class ZhihuDetailModelImp1 implements ZhihuDetailModel {
// /*获取日报详情的Model实现*/
private ZhihuApi mZhihuApiService; //请求服务
private ZhihuDetail mZhihuDetail;
public ZhihuDetailModelImp1() {
mZhihuDetail = new ZhihuDetail();
mZhihuApiService = RetrofitManager
.getInstence()
.getRetrofit("http://news-at.zhihu.com/api/4/news/")
.create(ZhihuApi.class); //创建请求服务
}
public ZhihuDetail getDetail() {
return mZhihuDetail;
}
@Override
public void loadDetail(String id, final OnZhihuDetailListener listener) {
if (mZhihuApiService != null) {
mZhihuApiService.getDetail(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ZhihuDetail>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ZhihuDetail zhihuDetail) {
mZhihuDetail = zhihuDetail;
listener.onLoadZhihuDetailSuccess(zhihuDetail);//加载成功时 回调接口方法。
}
@Override
public void onError(@NonNull Throwable e) {
listener.onLoadDataError(e.toString());//加载失败时 回调接口方法。
}
@Override
public void onComplete() {
}
});
}
}
}
View层
-
创建回调接口
view.ZhihuDetailView:
public interface ZhihuDetailView {
void onStartGetData();
void onGetDetailSuccess(ZhihuDetail zhihuDetail);
void onGetDetailFailed(String error);
}
-
创建ZhihuDetailActivity
详情页包含的新知识点全在ZhihuDetailActivity上,所涵盖的知识有:- WebView的使用
- 右滑关闭activity(也不算知识点,因为使用别人的开源,以后要改写成自己的)
- CollapsingToolbarLayout 和NestedScrollView的使用
附上一些资料:
WebView·开车指南
看,这个工具栏能伸缩折叠——Android CollapsingToolbarLayout使用
[Jude95/SwipeBackHelper]
具体解释全都在代码注释里。
直接上代码
ui.activity.ZhihuDetailActivity .class:
public class ZhihuDetailActivity extends MVPBaseActivity<ZhihuDetailView, ZhihuDetailPresenterImp1> implements ZhihuDetailView {
@BindView(R.id.toolbar)
Toolbar mToolbar;
@BindView(R.id.toolbar_layout)
CollapsingToolbarLayout mToolbarLayout;
@BindView(R.id.prograss)
ProgressBar mPrograss;
@BindView(R.id.wv_zhihu)
WebView mWvZhihu;
@BindView(R.id.fab)
FloatingActionButton mFab;
@BindView(R.id.iv_title)
ImageView mIvTitle;
private String mZhihuId;
@Override
protected ZhihuDetailPresenterImp1 createPresenter() {
return new ZhihuDetailPresenterImp1(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SwipeBackHelper.onCreate(this);
ButterKnife.bind(this);
initToolbar();
initView();
}
private void initToolbar() {
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
private void initView() {
mZhihuId = getIntent().getStringExtra("ZHIHUID");
mPresenter.getDetail(mZhihuId);
mFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "已添加进收藏夹(待做功能)", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
mWvZhihu.setVerticalScrollBarEnabled(true);
mWvZhihu.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
WebSettings settings = mWvZhihu.getSettings();
//设置应用缓存路径,这个路径必须是可以让app写入文件的。该方法应该只被调用一次,重复调用会被无视~
settings.setAppCachePath(getCacheDir().getAbsolutePath() + "/webViewCache");
settings.setAppCacheEnabled(true); //启用应用缓存。
settings.setDatabaseEnabled(true); //启用数据库缓存。
settings.setDomStorageEnabled(true); //开启DOM缓存
//用来设置WebView的缓存模式(这里使用的是 只要缓存可用就加载缓存)
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
settings.setJavaScriptEnabled(true); //设置WebView可以运行JavaScript。
settings.setBuiltInZoomControls(true);//显示或不显示缩放按钮(wap网页不支持)。
//指定WebView的页面布局显示形式,调用该方法会引起页面重绘
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
mWvZhihu.setWebChromeClient(new WebChromeClient());
}
@Override
protected int getLayoutRes() {
return R.layout.activity_zhihu_detail;
}
@Override
public void onStartGetData() {
mPrograss.setVisibility(View.VISIBLE);
}
@Override
public void onGetDetailSuccess(ZhihuDetail zhihuDetail) {
mPrograss.setVisibility(View.GONE);
mToolbarLayout.setTitle(zhihuDetail.title);
//在较为特殊的情况下,知乎日报可能将某个主题日报的站外文章推送至知乎日报首页。
if (zhihuDetail.body == null) {
mWvZhihu.loadUrl(zhihuDetail.shareUrl);
} else {
Glide.with(this).load(zhihuDetail.image).into(mIvTitle);
String data = WebUtil.buildHtmlWithCss(zhihuDetail.body, zhihuDetail.css);
mWvZhihu.loadDataWithBaseURL(WebUtil.BASE_URL, data, WebUtil.MIME_TYPE, WebUtil.ENCODING, WebUtil.FAIL_URL);
}
}
@Override
public void onGetDetailFailed(String error) {
mPrograss.setVisibility(View.GONE);
toast(error);
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
SwipeBackHelper.onPostCreate(this);
}
@Override
protected void onDestroy() {
if (mWvZhihu != null) {
//webview内存泄露
((ViewGroup) mWvZhihu.getParent()).removeView(mWvZhihu);
mWvZhihu.destroy();
mWvZhihu = null;
}
super.onDestroy();
SwipeBackHelper.onDestroy(this);
}
}
另附上ZhihuDetailActivity的布局文件:
activity_zhihu_detail.xml:
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.yiuhet.ktreader.ui.activity.ZhihuDetailActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginBottom="16dp"
app:expandedTitleMarginStart="24dp"
app:expandedTitleTextAppearance="@style/TextAppearance.AppCompat.Title"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/iv_title"
android:layout_width="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5"
android:fitsSystemWindows="true"
android:layout_height="match_parent"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom"
android:background="@color/title_mask"></FrameLayout>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_zhihu_detail" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
content_zhihu_detail.xml:
<android.support.v4.widget.NestedScrollView 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="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.example.yiuhet.ktreader.ui.activity.ZhihuDetailActivity"
tools:showIn="@layout/activity_zhihu_detail">
<WebView
android:id="@+id/wv_zhihu"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/prograss"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:indeterminateTint="?android:colorAccent"/>
</WebView>
</android.support.v4.widget.NestedScrollView>
ZhihuDetailActivity里的fab点击事件预想是添加入收藏栏,等之后写到收藏栏功能时会完成该功能
ps:从效果图中,可以看出CollapsingToolbarLayout的title显示不全,目前还没找到解决办法(貌似可以写theme解决,回头再实验下),如果有知道的,请告知,谢谢。
Presenter层
在ZhihuPresenterImp1类里实现数据和视图的绑定
-
先写一个回调接口:
(在Presenter层实现,给Model层回调,更改View层的状态,确保Model层不直接操作View层)
presenter.OnZhihuDetailListener :
public interface OnZhihuDetailListener {
void onLoadZhihuDetailSuccess(ZhihuDetail zhihuDetail);
void onLoadDataError(String error);
}
-
再写一个presenter接口:
presenter.ZhihuDetailPresenter :
public interface ZhihuDetailPresenter {
void getDetail(String id);
}
-
最后写Prestener实现类:
presenter.imp1.ZhihuDetailPresenterImp1 .class:
public class ZhihuDetailPresenterImp1 extends BasePresenter<ZhihuDetailView> implements ZhihuDetailPresenter,OnZhihuDetailListener{
/*Presenter作为中间层,持有View和Model的引用*/
private ZhihuDetailView mZhihuDetailView;
private ZhihuDetailModelImp1 zhihuDetailModelImp1;
String id;
public ZhihuDetailPresenterImp1(ZhihuDetailView zhihuDetailView) {
mZhihuDetailView = zhihuDetailView;
zhihuDetailModelImp1 = new ZhihuDetailModelImp1();
}
@Override
public void getDetail(String id) {
mZhihuDetailView.onStartGetData();
zhihuDetailModelImp1.loadDetail(id, this);
}
@Override
public void onLoadZhihuDetailSuccess(ZhihuDetail zhihuDetail) {
mZhihuDetailView.onGetDetailSuccess(zhihuDetail);
}
@Override
public void onLoadDataError(String error) {
mZhihuDetailView.onGetDetailFailed(error);
}
}