眾所周知知乎日報的api是公開的,所以我就想做個自己的知乎日報來玩一下
主要用到下列庫
rxandroid:1.1.0
rxjava:1.1.3
retrofit:2.0.0-beta1
retrofit:adapter-rxjava:2.0.0-beta2
retrofit:converter-gson:2.0.0-beta2
pagerslidingtabstrip:1.0.1
glide:3.5.2
如果不熟悉rxjava怎么用的話可以先到這里看一下(我這次只用到map)http://blog.csdn.net/lzyzsd/article/details/41833541/
如果不熟悉retrofit怎么用的話可以先到這里看一下(我這次只用到Get和Path)http://www.tuicool.com/articles/fQju2uQ
如果不熟悉pagerslidingtabstrip怎么用的話可以先到這里看一下http://blog.csdn.net/qq_32198277/article/details/51503156
首先要看一下知乎日報的api
我這次主要用到了幾個獲取今日日報,無聊日報,互聯(lián)網(wǎng)安全,體育日報和每個新聞的詳細信息的api
因為這個api返回的是json數(shù)據(jù),推薦http://www.bejson.com/json2javapojo/
可將json轉(zhuǎn)換為java類,但是有些只是字符串(生成后有些類是空白的)它都設(shè)為一個類,復(fù)制到自己java文件時,可以把不要那個類,改為String類型
帶著大家簡單分析一下api
以最新消息為例
最新消息
URL:
http://news-at.zhihu.com/api/4/news/latest-
響應(yīng)實例:
{ date: "20140523", stories: [ { title: "中國古代家具發(fā)展到今天有兩個高峰,一個兩宋一個明末(多圖)", ga_prefix: "052321", images: [ "http://p1.zhimg.com/45/b9/45b9f057fc1957ed2c946814342c0f02.jpg" ], type: 0, id: 3930445 }, ... ], top_stories: [ { title: "商場和很多人家里,竹制家具越來越多(多圖)", image: "http://p2.zhimg.com/9a/15/9a1570bb9e5fa53ae9fb9269a56ee019.jpg", ga_prefix: "052315", type: 0, id: 3930883 }, ... ] } -
分析:
-
date: 日期 -
stories: 當(dāng)日新聞-
title: 新聞標(biāo)題 -
images: 圖像地址(官方 API 使用數(shù)組形式。目前暫未有使用多張圖片的情形出現(xiàn),曾見無images屬性的情況,請在使用中注意 ) -
ga_prefix: 供 Google Analytics 使用 -
type: 作用未知 -
id:url與share_url中最后的數(shù)字(應(yīng)為內(nèi)容的 id) -
multipic: 消息是否包含多張圖片(僅出現(xiàn)在包含多圖的新聞中)
-
-
top_stories: 界面頂部 ViewPager 滾動顯示的顯示內(nèi)容(子項格式同上)
-
我們只關(guān)注story所以新建一個類時只需新建一個類是只需有一個stories的ArrayList
并生成他的getter和setter
public class RootEntity {
private ArrayList<StoriesEntity> stories ;
public void setStories(ArrayList<StoriesEntity> stories){
this.stories = stories;
}
public ArrayList<StoriesEntity> getStories(){
return this.stories;
}
}
stories我們也只關(guān)注id,title和images(images是一個ArrayList但我們只需顯示第一個就好了,但有時是空的,使用時要記得判空),新建類時可以這樣新建
public class StoriesEntity {
private int id;
private String title;
private List<String> images;
public void setId(int id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setImages(List<String> images) {
this.images = images;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public List<String> getImages() {
return images;
}
}
我們可以根據(jù)stories的id獲取到他的詳細信息,在詳細信息中我們只關(guān)注body,iamge,image_source和title,生成如下類
public class StoryDetailsEntity {
private String body;
private String image_source;
private String title;
private String image;
public void setBody(String body) {
this.body = body;
}
public void setImage_source(String image_source) {
this.image_source = image_source;
}
public void setTitle(String title) {
this.title = title;
}
public void setImage(String image) {
this.image = image;
}
public String getBody() {
return body;
}
public String getImage_source() {
return image_source;
}
public String getTitle() {
return title;
}
public String getImage() {
return image;
}
}
然后到界面設(shè)計,主頁是一個ViewPager和PagerSlidingTabStrip,前面已經(jīng)放了使用教程,我就不詳細展開了
然后每個Fragment都只有一個ListView
實現(xiàn)的內(nèi)容都差不多,所以我們用一個BaseFragment
public class BaseFragment extends Fragment {
private String baseUrl="http://news-at.zhihu.com";//baseUrl一定要設(shè)為這個
public ZhiHuService service;//要靠他來獲取消息,子Fragment都要用
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
service=getService();
return inflater.inflate(R.layout.fragment_base, container, false);
}
public ZhiHuService getService() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
service=retrofit.create(ZhiHuService.class);
return service;
}
public void loadDataSetLis(Observable<RootEntity> rootEntityObservable, final ListView listView){
rootEntityObservable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.map(new Func1<RootEntity, ArrayList<StoriesEntity>>() {
@Override
public ArrayList<StoriesEntity> call(RootEntity rootEntity) {
return rootEntity.getStories();
}
})
.subscribe(new Subscriber<ArrayList<StoriesEntity>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(final ArrayList<StoriesEntity> storiesEntities) {
listView.setAdapter(new NewsAdapter(storiesEntities,getContext()));
//點擊item跳轉(zhuǎn)到詳細頁面
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent=new Intent(getActivity(),StoryDetailActivity.class);
intent.putExtra("id",storiesEntities.get(position).getId());
startActivity(intent);
}
});
}
});
}
}
其他Fragment都繼承自這個BaseFragment,以最新消息為例
public class InterestFragment extends BaseFragment {
private ListView lv;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
lv= (ListView) view.findViewById(R.id.lvnews);
loadDataSetLis(service.getInterest(),lv);
}
}
每個ListView的Adapter是繼承自BaseAdapter,還不會用的同學(xué)請自行百度
每個item的布局只有一個TextView和ImageView
代碼如下
public class NewsAdapter extends BaseAdapter {
private List<StoriesEntity> newsList;
private LayoutInflater mInflater;
private Context context;
public NewsAdapter(ArrayList<StoriesEntity> newsList, Context context){
this.newsList=newsList;
this.context=context;
mInflater=LayoutInflater.from(context);
}
@Override
public int getCount() {
return newsList.size();
}
@Override
public Object getItem(int position) {
return newsList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder=null;
if (convertView==null){
convertView=mInflater.inflate(R.layout.news_item,null);
holder=new Holder(convertView);
convertView.setTag(holder);
}
holder= (Holder) convertView.getTag();
StoriesEntity news=newsList.get(position);
TextView tvnews=holder.tvnews;
ImageView ivnews=holder.ivnews;
tvnews.setText(news.getTitle());
//記得判空,不然后會空指針異常
if (news.getImages()==null){
ivnews.setVisibility(View.GONE);
}else {
ivnews.setVisibility(View.VISIBLE);
//用Glide根據(jù)URL加載圖片
Glide.with(context).load(news.getImages().get(0)).into(ivnews);
}
return convertView;
}
private class Holder {
ImageView ivnews;
TextView tvnews;
public Holder(View view){
ivnews= (ImageView) view.findViewById(R.id.ivnews);
tvnews= (TextView) view.findViewById(R.id.tvnews);
}
}
}
再來看看ZhihuService是怎么寫的,只是retrofit的用法,前面已經(jīng)有教程了,這里用得比較簡單
public interface ZhiHuService {
//今日頭條
@GET("/api/4/news/latest")
Observable<RootEntity> getLatestNews();
//互聯(lián)網(wǎng)安全
@GET("/api/4/theme/10")
Observable<RootEntity> getSafety();
//不準(zhǔn)無聊
@GET("/api/4/theme/11")
Observable<RootEntity> getInterest();
//體育日報
@GET("/api/4/theme/8")
Observable<RootEntity> getSport();
//傳入id查看詳細信息
@GET("/api/4/news/{id}")
Observable<StoryDetailsEntity> getNewsDetails(@Path("id") int id);
}
最后就是詳細頁面
詳細頁面只是一個webview,可里面的css來自assets,不然的話頁面會很難看
還需要一個HtmlUtil來生成完成的html代碼
代碼如下
public class HtmlUtils {
public static String structHtml(StoryDetailsEntity storyDetailsEntity) {
StringBuilder sb = new StringBuilder();
sb.append("<div class=\"img-wrap\">")
.append("<h1 class=\"headline-title\">")
.append(storyDetailsEntity.getTitle()).append("</h1>")
.append("<span class=\"img-source\">")
.append(storyDetailsEntity.getImage_source()).append("</span>")
.append("<img src=\"").append(storyDetailsEntity.getImage())
.append("\" alt=\"\">")
.append("<div class=\"img-mask\"></div>");
//news_content_style.css和news_header_style.css都是在assets里的
String mNewsContent = "<link rel=\"stylesheet\" type=\"text/css\" href=\"news_content_style.css\"/>"
+ "<link rel=\"stylesheet\" type=\"text/css\" href=\"news_header_style.css\"/>"
+ storyDetailsEntity.getBody().replace("<div class=\"img-place-holder\">", sb.toString());
return mNewsContent;
}
}
接下來是詳細頁面的activity的代碼
public class StoryDetailActivity extends AppCompatActivity {
private WebView wv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_story_detail);
//左上角出現(xiàn)小箭頭
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
wv= (WebView) findViewById(R.id.webView);
Intent intent=getIntent();
String baseUrl="http://news-at.zhihu.com";
int id=intent.getIntExtra("id",0);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
ZhiHuService service=retrofit.create(ZhiHuService.class);
service.getNewsDetails(id)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.map(new Func1<StoryDetailsEntity, String>() {
@Override
public String call(StoryDetailsEntity storyDetailsEntity) {
return HtmlUtils.structHtml(storyDetailsEntity);
}
})
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Log.e("error",e.toString());
}
@Override
public void onNext(String s) {
//加載asset里的css
wv.loadDataWithBaseURL("file:///android_asset/", s, "text/html", "UTF-8", null);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
//點擊小箭頭返回
if(item.getItemId() == android.R.id.home)
{
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}