美文网首页Android优秀开源安卓开发Android项目
安卓日记——手把手教你做知乎日报

安卓日记——手把手教你做知乎日报

作者: 饥渴计科极客杰铿 | 来源:发表于2016-05-30 13:43 被阅读2151次

    众所周知知乎日报的api是公开的,所以我就想做个自己的知乎日报来玩一下

    主页
    详细页面

    主要用到下列库

    首先要看一下知乎日报的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 : urlshare_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;
        }
    
    }
    

    然后到界面设计,主页是一个ViewPagerPagerSlidingTabStrip,前面已经放了使用教程,我就不详细展开了

    然后每个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);
        }
    }
    
    

    那两个css是在assets里的,我这里就不展示了,所有文件我都放在github里了

    源码下载

    相关文章

      网友评论

      • 潜鸟:想问下,如果是前端页面能自己写,可是数据怎么获取呢
      • 4d2600b0f244:导入之后一直没法运行,新手小白,好苦恼Error:(27, 0) Could not find method android() for arguments [build_5lws6f889f7m792b1s6ulq6vl$_run_closure3@86755de] on root project 'zhihuDaily-master' of type org.gradle.api.Project.
        <a href="openFile:D:\spaceAS\zhihuDaily-master\build.gradle">Open File</a>
      • nbpzjy:这个我也喜欢
      • hui朝晖:讲真,还是详情页面的做法。
      • ae12:怎么或获取的?我已经在看源码了。在HtmlUtils.structHtml()里的css样式是自己加的吗?
        assets 里的2个css文件里的代码,是怎么来的?感觉好复杂
        饥渴计科极客杰铿:@Liqing_1938 不用管,其实是别人写好的,会用就行了
      • ae12:生成Html代码的那个,是自己写的?还是用什么工具生成的?

        饥渴计科极客杰铿:@Liqing_1938 获取的啊,然后自己要加一些css样式,可以下载源码看看
      • ae12:Git上有很多源码,但是看了之后完全不知道怎么自己动手写,看了你的分享,又感觉了
        饥渴计科极客杰铿:@Liqing_1938 我的写法是尽量简单,尽量适合初学者去学习,只有简单地学会做一样东西,才会有兴趣是深究
      • 饥渴计科极客杰铿:谢谢支持,以后会有更多好的文章,简书会和我的csdn同步,欢迎关注http://blog.csdn.net/qq_32198277

      本文标题:安卓日记——手把手教你做知乎日报

      本文链接:https://www.haomeiwen.com/subject/retcdttx.html