Android练手小项目(KTReader)基于mvp架构(三)

上路传送眼:

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);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容