Android 高仿知乎日報(1)
個人蠻喜歡沒事看看知乎的,前陣子湊巧也在網上搜到了知乎日報的API,詳情見某位開發者在Github上的分享:知乎日報 API 分析
靠著這個,我就做了一個高仿知乎日報的小應用
動態圖看起來不怎麼流暢,其實真機執行的話還是很流程的,畢竟這只是一個純粹的閱讀類APP,並沒有加入什麼亂七八糟的功能,且介面主要都是用Fragment呈現的
在這裡就來分享下我的原始碼並記錄下開發流程吧
一、工程介紹
工程不算太複雜,Activity用到了三個而已,包括主介面Activity,文章內容Activity,加上啟動介面Activity,其實真正有作用的也就兩個而已
除此以外就是自定義介面有點多,達到了九個。。
其次,用到的開源庫有三個
- FastJson 用來解析JSON資料,據說解析速度最快
- universal-image-loader 用來下載、顯示、快取圖片
- android-async-http 網路訪問框架,還是挺方便的
二、知乎日報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 class=\"author\">劉明,<\/span><span class=\"bio\">非典型法律人<\/span>\n<\/div>\n\n<div class=\"content\">\n<p>網路使用者協議的訂約方式決定了,如果沒有外部力量干涉,網路使用者協議中會存在大量不利於網路使用者的“霸王條款”。總體看來,這些不公平可以分為程式不公平和實體不公平兩類,在此簡單列舉一二具有共同性的例子:<\/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 class=\"js-question-holder\"><\/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當中的靜態方法,該網路請求是非同步的,當資料獲取到後利用回撥函式執行資料重新整理即可
相關推薦
Android 高仿知乎日報(1)
個人蠻喜歡沒事看看知乎的,前陣子湊巧也在網上搜到了知乎日報的API,詳情見某位開發者在Github上的分享:知乎日報 API 分析 靠著這個,我就做了一個高仿知乎日報的小應用 動態圖看起來不怎麼流暢,其實真機執行的話還是很流程的,畢竟這只是一個純
微信小程式日記——高仿知乎日報(上)
該小程式的作者是Oopsguy,我也參與小功能的開發和完善,希望大家能支援一下 本人對知乎日報是情有獨鍾,看我的部落格和github就知道了,寫了幾個不同技術型別的知乎日報APP 要做微信小程式首先要對html,css,js有一定的基礎,還有對微信小
【專案原始碼】- 【模仿知乎日報二】吐血高仿知乎日報
對之前的模仿做品進行了改善改善。。。再改善。。。(僅供學習) 多說無益。。。。上圖才是王道: 這個東西越模仿發現他的東西就越多,離上次的模仿時間已經過去好久了,這一版本的介面看似好很多,但還是
高仿知乎日報(一)
之前寫的高仿知乎日報的程式碼是用eclipse寫的,匯入AndroidStudio之後雖然改改也能跑起來,但是格式很怪異,而且由於時間長了,很多東西都忘了,所以準備用AndroidStudio重寫一遍,順便記錄下過程並將很多需要優化的地方都完成。 首先是介面的
android高仿微信佈局(二)
前言 這期我們講講怎麼實現微信的左右滑動的效果和底部的tabs欄 廢話不多說,開工吧! 首先我們先看看總體佈局是怎麼寫的。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout
仿知乎日報(1)_緒論
匯入庫與專案結構分析 1、匯入庫 開源的迷人之處正在於“不用重複的造輪子”,使用開源庫能夠一定程度上加快我們開發的速度,推進軟體開發更快的發展。 在筆者的專案中使用了以下幾個開源庫 compile 'com.android.support:desig
仿知乎日報(3)_MainActivity分析
MainActivity分析 1、結構 2、介面分析 首先第一張圖片是主頁面,是一個Fragment。第二張圖片是左側滑選單,是一個ListView。整體介面是使用MaterialUI中的DrawableLayout佈局。關於Drawa
【iOS】仿知乎日報,RxSwift-Part2-詳情頁的搭建
前言 在上一篇,我們搭建了首頁。而這篇,我們將開始搭建話題詳情頁。 分析 還是先來看下演示gif { "body": "<div class=\"main-wrap content-wrap\">\n<div cla
vue低仿知乎日報
概述 一個基於vue的仿知乎日報的前端專案。 關於知乎日報: 知乎日報是一款擁有千萬使用者的資訊類客戶端,每日提供來自知乎社群的精選問答,還有國內一流媒體的專欄特稿。 主要功能 每天更新好文章,包括權威的時事解讀、有趣的生活建議 更符合使
【Android】仿知乎夜間模式的實現
1.簡介 目前很多App都有夜間模式的功能,網上教程也是很多,最近專案不忙,抽空學習了下,在這做下記錄,希望能幫到正在看部落格的你,我們先來看下知乎的效果: 看我的效果: 臥槽,好像啊,哈
android之使用百度地圖(1)
baidu man ports cte public phone sch lis stat Activity_main.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmln
Android鎖屏勒索病毒分析(1)BWM線上
1.樣本概況 1.1 基本資訊 樣本名稱: 刷贊. 所屬家族: 鎖屏勒索病毒(a.rogue.SimpleLocker.a) MD5值: 7626090b69cd1e2e5671a022712808eb 包名: com.binge.mohe 入口: MainActiv
Android原始碼之Gallery專題研究(1)
前言 時光飛逝,從事Android系統開發已經兩年了,總想寫點什麼來安慰自己。思考了很久總是無法下筆,覺得沒什麼好寫的。現在終於決定寫一些符合大多數人需求的東西,想必使用過Android手機的人們一定對“相簿”(以下簡稱Gallery)這個應用非常熟悉。在Android市場
Tensorflow:Android呼叫Tensorflow Mobile版本API(1)-訓練一個網路
在這裡,我們訓練一個網路來擬合 y=x^2+1 程式碼如下: # y = x^2 + 1 import tensorflow as tf import numpy as np import r
Android系統原理與原始碼分析(1):利用Java反射技術阻止通過按鈕關閉對話方塊
本文為原創,如需轉載,請註明作者和出處,謝謝! 眾所周知,AlertDialog類用於顯示對話方塊。關於AlertDialog的基本用法在這裡就不詳細介紹了,網上有很多,讀者可以自己搜尋。那
Android ALSA音訊系統架構分析(1)----從Loopback瞭解Audio
/************************************ Author:劉江明 * Environment:MTK Android 6.0* Date:2017年05月25日***********************************/
Android Media Player 框架分析-Nuplayer(1)
由於工作崗位調整,開始接觸Media相關部分的程式碼,起初希望在網路上找一下大神們分析軟文學習一下就好,由於Google在新的Android版本上修改了Nuplayer的地位,原本NuPlayer主要在系統中負責流媒體的播放,但現在Android基本已經全面棄用Awesom
python: 知乎大規模(34k)使用者爬蟲
前些天學習python,完成了python練習冊的大部分習題:https://github.com/Show-Me-the-Code/python(我的github上有習題程式碼,歡迎自取)。之後看到@salamer的一個python爬蟲專案,覺得很不錯。於是自己花了4天的
Android 事件分發機制詳解(1)
Android事件分發機制詳解(一) 所謂Android事件分發機制,其實也就是View的事件分發機制,在介紹事件的傳遞規則之前,首先我們要明白這裡需要分析的物件MotionEvent。 MotionEvent類 在手指接觸屏幕後所產生的事件封裝成了Mo
是男人就下100層【第一層】——高仿微信介面(7)
在上一篇《是男人就下100層【第一層】——高仿微信介面(6)》中我們已經對主介面的的各個選單進行了簡單實現,接下來我們完成兩個比較有趣的功能,一個是上部的下彈式選單,另一個是搖一搖功能。效果如下圖:我們先做 一個位於右上方的對話方塊樣子,佈局程式碼很簡單,外面是一個相對佈局,