上路傳送眼:
Android練手小項目(KTReader)基于mvp架構(gòu)(二)
下路傳送眼:
Android練手小項目(KTReader)基于mvp架構(gòu)(四)
GIthub地址: https://github.com/yiuhet/KTReader
上篇文章中我們完成了知乎日報內(nèi)容的fragment割按。
而這次我們要做的的就是知乎日報詳情頁。
慣例先上效果圖:
準(zhǔn)備工作
- 添加依賴
compile 'com.jude:swipebackhelper:3.1.2' //右滑關(guān)閉詳情頁
-
工具類
utils.WebUtil.class(返回html結(jié)果的工具類):
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實現(xiàn)
public class ZhihuDetailModelImp1 implements ZhihuDetailModel {
// /*獲取日報詳情的Model實現(xiàn)*/
private ZhihuApi mZhihuApiService; //請求服務(wù)
private ZhihuDetail mZhihuDetail;
public ZhihuDetailModelImp1() {
mZhihuDetail = new ZhihuDetail();
mZhihuApiService = RetrofitManager
.getInstence()
.getRetrofit("http://news-at.zhihu.com/api/4/news/")
.create(ZhihuApi.class); //創(chuàng)建請求服務(wù)
}
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);//加載成功時 回調(diào)接口方法柴罐。
}
@Override
public void onError(@NonNull Throwable e) {
listener.onLoadDataError(e.toString());//加載失敗時 回調(diào)接口方法芹枷。
}
@Override
public void onComplete() {
}
});
}
}
}
View層
-
創(chuàng)建回調(diào)接口
view.ZhihuDetailView:
public interface ZhihuDetailView {
void onStartGetData();
void onGetDetailSuccess(ZhihuDetail zhihuDetail);
void onGetDetailFailed(String error);
}
-
創(chuàng)建ZhihuDetailActivity
詳情頁包含的新知識點全在ZhihuDetailActivity上,所涵蓋的知識有:- WebView的使用
- 右滑關(guān)閉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();
//設(shè)置應(yīng)用緩存路徑衰伯,這個路徑必須是可以讓app寫入文件的铡羡。該方法應(yīng)該只被調(diào)用一次,重復(fù)調(diào)用會被無視~
settings.setAppCachePath(getCacheDir().getAbsolutePath() + "/webViewCache");
settings.setAppCacheEnabled(true); //啟用應(yīng)用緩存意鲸。
settings.setDatabaseEnabled(true); //啟用數(shù)據(jù)庫緩存烦周。
settings.setDomStorageEnabled(true); //開啟DOM緩存
//用來設(shè)置WebView的緩存模式(這里使用的是 只要緩存可用就加載緩存)
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
settings.setJavaScriptEnabled(true); //設(shè)置WebView可以運行JavaScript。
settings.setBuiltInZoomControls(true);//顯示或不顯示縮放按鈕(wap網(wǎng)頁不支持)临扮。
//指定WebView的頁面布局顯示形式论矾,調(diào)用該方法會引起頁面重繪
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內(nèi)存泄露
((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點擊事件預(yù)想是添加入收藏欄贪壳,等之后寫到收藏欄功能時會完成該功能
ps:從效果圖中,可以看出CollapsingToolbarLayout的title顯示不全蚜退,目前還沒找到解決辦法(貌似可以寫theme解決闰靴,回頭再實驗下),如果有知道的钻注,請告知蚂且,謝謝。
Presenter層
在ZhihuPresenterImp1類里實現(xiàn)數(shù)據(jù)和視圖的綁定
-
先寫一個回調(diào)接口:
(在Presenter層實現(xiàn)幅恋,給Model層回調(diào)杏死,更改View層的狀態(tài),確保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實現(xiàn)類:
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);
}
}