Android 高仿知乎日报(1)

村办蛮喜欢没事看看知乎的,前阵子凑巧也在网上搜到了知乎日报的API,详情见某位开发者在Github上之分享:知乎日报
API
分析

近些年寻找工作,讲一下面试经验之谈。

凭着是,我便举行了一个高仿知乎日报的多少应用

HR电话面

这边描绘图片描述

1.HR来电叩问:章先生自己以XX网上观望您离职了。请问目前考虑换工作也?  

这里描绘图片描述

答:考虑呢。

动态图看起有些流畅,其实真机运行的言辞还是蛮流程的,毕竟这无非是一个纯粹的读类APP,并没有加入什么乱七八糟的功用,且界面主要都是为此Fragment呈现的

HR:那节先生自己生个问题,请问您帅吗?

每当此处就来分享下自己的源代码并记下下出流程吧

答:帅。

相同、工程介绍

工不算是极端复杂,Activity用到了三独而已,包括主界面Activity,文章内容Activity,加上启动界面Activity,其实真正发生意图的呢就是有数独如曾
除却本条之外就是起定义接口有点多,达到了九只。。

此描绘图片描述

说不上,用到之开源库有三个

  • FastJson 用来解析JSON数据,据说解析速度极抢
  • universal-image-loader 用来下载、显示、缓存图片
  • android-async-http 网络访问框架,还是很方便之

这边描绘图片描述

HR:那好章先生,我觉得你非常适合我们的意料的,我管你的资料发放技术部,约一下面试时。

仲、知乎日报API分析

抱最新篇章的接口:http://news-at.zhihu.com/api/4/news/latest

归来的数目为JSON格式的

{
    "date": "20160804",
    "stories": [
        {
            "images": [
                "http://pic4.zhimg.com/b247609f382ec5d097c51d468975fd1b.jpg"
            ],
            "type": 0,
            "id": 8644697,
            "ga_prefix": "080409",
            "title": "以现有的技术,能不能把地沟油检测出来?"
        },
        {
            "images": [
                "http://pic4.zhimg.com/b556013584ac5f190f1a343aad2b75bb.jpg"
            ],
            "type": 0,
            "id": 8645106,
            "ga_prefix": "080408",
            "title": "写给产品 / 市场 / 运营的数据抓取黑科技教程"
        },
        {
            "images": [
                "http://pic2.zhimg.com/eaf3fb69ddfe636c6c4c40c2c76029c5.jpg"
            ],
            "type": 0,
            "id": 8644252,
            "ga_prefix": "080407",
            "title": "里约奥运已经开始啦~这里是一份熬夜与早起的时间表"
        }
}

中,date代表当日时空,stories为一个JSON数组,包含了同样组文章多少,如文章标题,文章配图,文章ID等

倚得到的章ID,再长取得文章详细内容的接口http://news-at.zhihu.com/api/4/news/8643890就是可以获取数据了,其中8643890啊文章ID
抱到之多少格式为:

{"body":"<div class=\"main-wrap content-wrap\">\n<div class=\"headline\">\n\n<div class=\"img-place-holder\"><\/div>\n\n\n\n<\/div>\n\n<div class=\"content-inner\">\n\n\n\n\n<div class=\"question\">\n<h2 class=\"question-title\"><\/h2>\n\n<div class=\"answer\">\n\n<div class=\"meta\">\n<img class=\"avatar\" src=\"http:\/\/pic4.zhimg.com\/4ac31ef63_is.jpg\">\n刘明,<\/span>非典型法律人<\/span>\n<\/div>\n\n<div class=\"content\">\n<p>网络用户协议的订约方式决定了,如果没有外部力量干涉,网络用户协议中会存在大量不利于网络用户的&ldquo;霸王条款&rdquo;。总体看来,这些不公平可以分为程序不公平和实体不公平两类,在此简单列举一二具有共同性的例子:<\/p>\r\n<p><strong>程序不公平<\/strong><\/p>\r\n<p>1.以超链接方式展示合同条款,使网络用户很容易忽略用户协议,以及在用户协议中嵌套的其他协议。<\/p>\r\n<p>2.一揽子同意。很多网站的用户协议实际上是由多份协议共同组成的,但网络用户通常只需要点击一次同意,就被视为已经一揽子的同意了所有协议,而实际上由于这些协议大多通过超链接方式提供,所以用户可能根本就没注意到它们的存在。<\/p>\r\n<p>3.对网络服务提供者权利保留条款和限制网络用户权利的条款缺少明显标注,网络用户即使浏览用户协议,也很可能忽略这些条款。<\/p>\r\n<p><strong>实体不公平<\/strong><\/p>\r\n<p>1.网络服务提供者权利保留条款。如网络游戏运营商保留网络游戏中游戏人物和装备的所有权,玩家只有使用权。<\/p>\r\n<p>2.个人信息授权收集条款。如很多用户协议中都有约定,网络用户同意网络服务提供者在很广的范围内收集其在使用网络服务过程中产生的个人信息,并同意将这些信息交给第三方使用。<\/p>\r\n<p>3.限制网络用户的处分权利。如某公司禁止网络用户交易聊天软件号码,网游运营商也经常禁止用户交易其网络游戏中的人物和装备。<\/p>\r\n<p>4.随时更改、终止合同的权利。几乎所有用户协议中都会有这条。<\/p>\r\n<p>5.网络服务提供者对技术故障免责。几乎所有用户协议中都会有这条,约定无论是否因不可抗力引起技术故障,一律免责。<\/p>\r\n<p>6.纠纷解决条款。将网络用户与网络服务提供者之间法律纠纷的管辖权约定在方便网络服务提供者应诉的地方,可能给网络用户通过司法渠道主张权利造成困难。<\/p>\r\n<p>除此之外,不同的网络服务提供者还会根据自己的经营需要,制定一些特殊的不公平条款。例如某网络专车的服务条款在很长一段时间以来,都约定乘客与司机之间是个人劳务关系,而非运输合同关系,一旦出现交通事故导致车外人受伤,实际上应由雇主,也就是乘客来承担赔偿责任。当然,在《网络预约出租车经营服务管理暂行办法》出台后,专车平台需要承担承运人责任,这种约定也就不存在了。<\/p>\n<\/div>\n<\/div>\n\n\n<div class=\"view-more\"><a href=\"http:\/\/www.zhihu.com\/question\/26978264\">查看知乎讨论<\/span><\/a><\/div>\n\n<\/div>\n\n\n<\/div>\n<\/div>","image_source":"Yestone.com 版权图片库","title":"软件安装、网站注册的用户协议里有哪些著名的陷阱条款?","image":"http:\/\/pic2.zhimg.com\/9f1fc77ede31203a3117b205a7120239.jpg","share_url":"http:\/\/daily.zhihu.com\/story\/8643890","js":[],"ga_prefix":"080407","images":["http:\/\/pic1.zhimg.com\/dd1487cd8011ec69b3ca45c0faa87bc4.jpg"],"type":0,"id":8643890,"css":["http:\/\/news-at.zhihu.com\/css\/news_qa.auto.css?v=4b3e3"]}

纵使为html格式的字符串,文章详情页的界面就之所以WebView来显现的,毕竟数据格式千整个万家,用WebView是太简单易行且呈现效果太好的主意
内部尚富含了CSS文件的地点:"css":["http:\/\/news-at.zhihu.com\/css\/news_qa.auto.css?v=4b3e3"]
此需要下充斥到地头然后导入工程被,因为CSS文件是连以的且一般不见面一再更改的,所以可以一直作为本土资源,不欲用户下载

任何接口的含义可自行查看

答:好的。

三、代码

主界面包含下拉刷新功能,且产生侧滑菜单,这个还因此合法提供的支撑库即可,activity_main.xml的布局文件如下:

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/sr"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimaryDark"
                android:fitsSystemWindows="true"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

            <FrameLayout
                android:id="@+id/fl_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

        </LinearLayout>
    </android.support.v4.widget.SwipeRefreshLayout>

    <fragment
        android:id="@+id/menuFragment"
        android:name="czy.kankan.myapplication.Fragment.MenuFragment"
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:layout_gravity="left" />

</android.support.v4.widget.DrawerLayout>

当中,id为fl_content的FrameLayout即用来盛Fragment

 <FrameLayout
   android:id="@+id/fl_content"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

出于动态图可以见到,程序除了主界面外,还连多只主题分类,如果每次都设开动个Activity来回切换的话,未免太为难了,所以这里用Fragment来展现
此首先新建个BaseFragment作为具有Fragmnet的父类,并定义有通用的函数

public abstract class BaseFragment extends Fragment {

    protected Activity mActivity;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mActivity = getActivity();
        return initView(inflater, container, savedInstanceState);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initData();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mActivity = null;
    }

    protected abstract View initView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);

    protected void initData() {

    }

    protected void hint(View view, String content, int color) {
        Snackbar snackbar = Snackbar.make(view, content, Snackbar.LENGTH_SHORT);
        snackbar.getView().setBackgroundColor(color);
        snackbar.show();
    }

    public MainActivity getRootActivity() {
        return (MainActivity) mActivity;
    }

}

主界面的章列表均是用RecycleView呈现,包括顶部的大循环播放图片的Banner均是RecycleView的一个子项
故此,MainFragment只要包含一个RecycleView和一个FloatingActionButton即可
activity_ article_list.xml文件如下:

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/articleList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#e2dedf" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/top"
        app:backgroundTint="#bae9eff1" />

</FrameLayout>

FloatingActionButton用来当点击后滑动到顶部

既然是以RecycleView,也就算需一个ArticleListAdapter继承于RecyclerView.Adapter<RecyclerView.ViewHolder>

public class ArticleListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<Stories> storiesList;

    private LayoutInflater inflater;

    private Context context;

    private final int TYPE_TOP = 0;

    private final int TYPE_ARTICLE = 1;

    private final int TYPE_FOOTER = 2;

    public OnLoadTopArticleListener loadTopArticleListener;

    private OnSlideToTheBottomListener slideListener;

    private OnArticleItemClickListener clickListener;

    private ArticleListTopHolder articleListTopHolder;

    public ArticleListAdapter(Context context) {
        this.context = context;
        init();
    }

    private void init() {
        inflater = LayoutInflater.from(context);
        storiesList = new ArrayList<>();
        //文章列表点击事件监听
        clickListener = new OnArticleItemClickListener() {
            @Override
            public void OnItemClickListener(int position) {
                int id = storiesList.get(position - 1).getId();
                Intent intent = new Intent(context, ArticleContentActivity.class);
                Bundle bundle = new Bundle();
                bundle.putInt("ID", id);
                intent.putExtras(bundle);
                context.startActivity(intent);
            }
        };
        //加载banner文章事件监听
        loadTopArticleListener = new OnLoadTopArticleListener() {
            @Override
            public void onSuccess(List<TopStories> topStoriesList) {
                if (articleListTopHolder != null) {
                    articleListTopHolder.banner.update(topStoriesList);
                    articleListTopHolder.banner.startPlay();
                    notifyDataSetChanged();
                }
            }

            @Override
            public void onFailure() {

            }
        };
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0) {
            return TYPE_TOP;
        }
        if (position + 1 == getItemCount()) {
            return TYPE_FOOTER;
        }
        return TYPE_ARTICLE;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;
        if (viewType == TYPE_ARTICLE) {
            view = inflater.inflate(R.layout.article_list_item, parent, false);
            return new ArticleListHolder(view);
        } else if (viewType == TYPE_FOOTER) {
            view = inflater.inflate(R.layout.fooder, parent, false);
            return new ArticleListFooterHolder(view);
        }
        view = inflater.inflate(R.layout.banner_layout, parent, false);
        return new ArticleListTopHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        switch (getItemViewType(position)) {
            case TYPE_ARTICLE:
                ArticleListHolder articleListHolder = (ArticleListHolder) holder;
                Constant.getImageLoader().displayImage(storiesList.get(position - 1).getImages().get(0),
                        articleListHolder.articleImage, Constant.getDisplayImageOptions());
                articleListHolder.articleTitle.setText(storiesList.get(position - 1).getTitle());
                articleListHolder.setItemClickListener(clickListener);
                break;
            case TYPE_FOOTER:
                //只有当文章数量不为零时才启用事件监听
                if (slideListener != null && storiesList != null && storiesList.size() > 0) {
                    slideListener.onSlideToTheBottom();
                }
                break;
            case TYPE_TOP:
                articleListTopHolder = (ArticleListTopHolder) holder;
                break;
        }
    }

    @Override
    public int getItemCount() {
        return storiesList.size() + 1;
    }

    //当刷新文章列表时使用
    public void setData(ArticleLatest articleLatest) {
        storiesList.clear();
        storiesList.addAll(articleLatest.getStories());
    }

    //当加载下一页文章内容时使用
    public void addData(List<Stories> storiesList) {
        this.storiesList.addAll(storiesList);
    }

    public void setSlideToTheBottomListener(OnSlideToTheBottomListener slideListener) {
        this.slideListener = slideListener;
    }

}

函数getItemViewType(int position)之所以来赢得子项的类,这里分为三栽,即顶部Banner,文章列表,底部用来显现“正在加载”字样的TextView
内用到了大多单回调函数,例如Banner数据加载回调,文章列表点击事件监听等

假如MainFragment就要来开展实际的数额加载操作了,当数码也空时显示默认数据,当数获得成功后,则刷新Adapter

public class MainFragment extends BaseFragment {

    //文章列表
    private RecyclerView recyclerView;

    private FloatingActionButton floatingActionButton;

    private ArticleListAdapter adapter;

    private OnLoadLatestArticleListener latestListener;

    private OnLoadBeforeArticleListener beforeListener;

    private boolean flag;

    @Override
    protected View initView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.activity_article_list, container, false);
        recyclerView = (RecyclerView) view.findViewById(R.id.articleList);
        floatingActionButton = (FloatingActionButton) view.findViewById(R.id.fab);
        floatingActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                recyclerView.smoothScrollToPosition(0);
            }
        });
        recyclerView.setLayoutManager(new LinearLayoutManager(mActivity, LinearLayoutManager.VERTICAL, false));
        return view;
    }

    @Override
    protected void initData() {
        adapter = new ArticleListAdapter(mActivity);
        recyclerView.setAdapter(adapter);
        //加载最新文章事件监听
        latestListener = new OnLoadLatestArticleListener() {
            @Override
            public void onSuccess(ArticleLatest articleLatest) {
                adapter.setData(articleLatest);
                getRootActivity().setDate(articleLatest.getDate());
                List<TopStories> topStoriesList = articleLatest.getTop_stories();
                if (adapter.loadTopArticleListener != null) {
                    adapter.loadTopArticleListener.onSuccess(topStoriesList);
                }
                stopRefresh();
                if (!flag) {
                    flag = true;
                } else {
                    hint(recyclerView, "已经是最新文章啦", Color.parseColor("#0099CC"));
                }
                //加载最新文章成功后在后台再加载下一页
                getBeforeArticleList();
            }

            @Override
            public void onFailure() {
                if (mActivity != null) {

                    hint(recyclerView, "好奇怪,文章加载不来", Color.parseColor("#0099CC"));

                }
                stopRefresh();
            }
        };
        //加载过去文章事件监听
        beforeListener = new OnLoadBeforeArticleListener() {
            @Override
            public void onSuccess(ArticleBefore articleBefore) {
                adapter.addData(articleBefore.getStories());
                adapter.notifyDataSetChanged();
                getRootActivity().setDate(articleBefore.getDate());
            }

            @Override
            public void onFailure() {
                if (mActivity != null) {
                    hint(recyclerView, "好奇怪,文章加载不来", Color.parseColor("#0099CC"));

                }
            }
        };
        //滑动到底部事件监听
        OnSlideToTheBottomListener slideListener = new OnSlideToTheBottomListener() {
            @Override
            public void onSlideToTheBottom() {
                getBeforeArticleList();
            }
        };
        adapter.setSlideToTheBottomListener(slideListener);
        getLatestArticleList();
    }

    public void getLatestArticleList() {
        if (!HttpUtil.isNetworkConnected(mActivity)) {
            hint(recyclerView, "似乎没有连接网络?", Color.parseColor("#0099CC"));
            stopRefresh();
            return;
        }
        HttpUtil.getLatestArticleList(latestListener);
    }

    public void getBeforeArticleList() {
        if (!HttpUtil.isNetworkConnected(mActivity)) {
            hint(recyclerView, "似乎没有连接网络?", Color.parseColor("#0099CC"));
            return;
        }
        HttpUtil.getBeforeArticleList(getRootActivity().getDate(), beforeListener);
    }

    public void stopRefresh() {
        if (getRootActivity() != null) {
            getRootActivity().setRefresh(false);
        }
    }

}

以上多只地方因此到了HttpUtil当中的静态方法,该网络要是异步的,当数获得到晚采取回调函数执行多少刷新即可

技术面(简称IT)

IT:我靠,这男真帅啊。

我:嗯。

IT:IT给自家看下代码。

本身将给他一如既往看

IT:这代码太NM帅了。

我:嗯。

IT:技术给过了,我错过探寻咱总监。您稍等

总监面(CTO)

CTO:你以为咱们公司如何?

我:挺好的。

CTO:你怎么评价您协调?

我:一个字“帅”

CEO:你当自家哪些?

我:帅。

CEO:你是个仗义的人数,你小子也漂亮。我去寻觅HR谈薪酬。

HR面

HR远远过来,对干的红颜说:瞧,那男真帅。

HR:你这么可以为什么选择我们企业

自身:我欢喜跟帅哥美女在协同坐班。

HR:别的不说了,开单价格吧。我们而了

自:你们随便。我只有想平静的坐美男子。

HR:定了。下周入住!

一个真实的故事

葡京网上娱乐场 1