安卓日记——手把手教你做知乎日报
众所周知知乎日报的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
我这次主要用到了几个获取今日日报,无聊日报,互联网安全,体育日报和每个新闻的详细信息的api
因为这个api返回的是json数据,推荐http://www.bejson.com/json2javapojo/
可将json转换为java类,但是有些只是字符串(生成后有些类是空白的)它都设为一个类,复制到自己java文件时,可以把不要那个类,改为String类型
带着大家简单分析一下api
以最新消息为例
最新消息
-
URL:
http://news-at.zhihu.com/api/4/news/latest
-
响应实例:
{ date: "20140523", stories: [ { title: "中国古代家具发展到今天有两个高峰,一个两宋一个明末(多图)", 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
: 当日新闻-
title
: 新闻标题 -
images
: 图像地址(官方 API 使用数组形式。目前暂未有使用多张图片的情形出现,曾见无images
属性的情况,请在使用中注意 ) -
ga_prefix
: 供 Google Analytics 使用 -
type
: 作用未知 -
id
:url
与share_url
中最后的数字(应为内容的 id) -
multipic
: 消息是否包含多张图片(仅出现在包含多图的新闻中)
-
-
top_stories
: 界面顶部 ViewPager 滚动显示的显示内容(子项格式同上)
-
我们只关注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我们也只关注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;
}
}
我们可以根据stories的id获取到他的详细信息,在详细信息中我们只关注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;
}
}
然后到界面设计,主页是一个ViewPager和PagerSlidingTabStrip,前面已经放了使用教程,我就不详细展开了
然后每个Fragment都只有一个ListView
实现的内容都差不多,所以我们用一个BaseFragment
public class BaseFragment extends Fragment {
private String baseUrl="http://news-at.zhihu.com";//baseUrl一定要设为这个
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跳转到详细页面
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,还不会用的同学请自行百度
每个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根据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的用法,前面已经有教程了,这里用得比较简单
public interface ZhiHuService {
//今日头条
@GET("/api/4/news/latest")
Observable<RootEntity> getLatestNews();
//互联网安全
@GET("/api/4/theme/10")
Observable<RootEntity> getSafety();
//不准无聊
@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);
//左上角出现小箭头
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);
}
}