首页 > 代码库 > Android-MVP+Retrofit+Rxjava实现一个知乎日报客户端

Android-MVP+Retrofit+Rxjava实现一个知乎日报客户端

使用MVP+Retrofit+Rxjava实现一个知乎日报客户端,界面基于Material design,还没有全部完成orz,,放假太懒

效果图


技术分享

开源项目


name introduction
butterknife Annotate fields with @BindView and a view ID for Butter Knife to find and automatically cast the corresponding view in your layout.
MaterialTabs Custom Tabs with Material Design animations for pre-Lollipop devices
materialdatetimepicker Pick a date or time on Android in style
agendacalendarview This library replicates the basic features of the Calendar and Agenda views from the Sunrise Calendar (now Outlook) app, coupled with some small design touch from the Google Calendar app.

使用Material Design


可以参考我的另外一篇文章Android-Material Design的使用

列表使用的为recyclerView,这个就不解释了

tabs标题栏的实现采用了开源项目neokree/MaterialTabs

需和ViewPager一起使用。在ViewPager中添加Fragment
Activity需要 implements MaterialTabListener

fragmentList = new ArrayList<>();
        fragmentList.add(new NewsFragment());
        fragmentList.add(new TestFragment());
        fragmentList.add(new TestFragment());

        tabHost = (MaterialTabHost) this.findViewById(R.id.materialTabHost);
        pager = (ViewPager) this.findViewById(R.id.viewpager);

        // init view pager
        fragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(), fragmentList);
        pager.setAdapter(fragmentPagerAdapter);
        pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                // when user do a swipe the selected tab change
                tabHost.setSelectedNavigationItem(position);
            }
        });

        // insert all tabs from pagerAdapter data
        List<String> tabsNames = new ArrayList<>();
        tabsNames.add("zhihu");
        tabsNames.add("schedule");
        tabsNames.add("timeTable");
        for (int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
            tabHost.addTab(
                    tabHost.newTab()
                            .setText(tabsNames.get(i))
                            .setTabListener(this)
            );
        }

点击fab出现日历来选择时间,用来查询过往的日报

日历的实现来自wdullaer/MaterialDateTimePicker

返回的日期需要格式化,使用在知乎日报的API中

 String date = String.format("%d%02d%02d", year, monthOfYear + 1, dayOfMonth);

知乎日报的数据


来自知乎日报 API 分析

最新消息

URL: http://news-at.zhihu.com/api/4/news/latest

过往消息

URL: http://news-at.zhihu.com/api/4/news/before/20131119

响应为

{
    date: "20140523",
    stories: [
        {
            title: "中国古代家具发展到今天有两个高峰,一个两宋一个明末(多图)",
            ga_prefix: "052321",
            images: [
                "http://p1.zhimg.com/45/b9/45b9f057fc1957ed2c946814342c0f02.jpg"
            ],
            type: 0,
            id: 3930445
        },
    ...
    ],
    ...
}

消息内容获取与离线下载

URL: http://news-at.zhihu.com/api/4/news/{id}

利用Android studio的插件GsonFormat,来解析返回的json数据

MVP+Retrofit+Rxjava


MVP

可以参考
java-mvp模式简单实现
浅谈Andorid开发中的MVP模式

Retrofit

Android网络请求库 - Say hello to retrofit
RxJava 与 Retrofit 结合的最佳实践

Rxjava

给 Android 开发者的 RxJava 详解

定义接口

public interface ZhiHuService {
    @GET("api/4/news/latest")
    Observable<LatestNews> getLatestNews();

    @GET("api/4/news/before/{date}")
    Observable<LatestNews> getBeforeNews(@Path("date") String dateString);

    @GET("api/4/news/{id}")
    Observable<News> getNews(@Path("id") int id);

    @GET("api/4/story/{id}/long-comments")
    Observable<Comment> getComments(@Path("id") int id);

    @GET("api/4/story-extra/{id}")
    Observable<StoryExtra> getStroyExtra(@Path("id") int id);
}

基础url

public class Config {
    public final static String ZHIHU_URL = "http://news-at.zhihu.com/";
}

进行封装

public class ZhiHuApi {

    private static final int DEFAULT_TIMEOUT = 5;
    private ZhiHuService zhiHuService;
    private static ZhiHuApi zhiHuApi;
    private Retrofit retrofit;

    private ZhiHuApi() {
        //设置超时时间
        OkHttpClient.Builder httpcientBuilder = new OkHttpClient.Builder();

        Retrofit retrofit = new Retrofit.Builder()
                .client(httpcientBuilder.build())//
                .baseUrl(Config.ZHIHU_URL)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();

       zhiHuService = retrofit.create(ZhiHuService.class);
    }


    public static ZhiHuApi getInstance(){
        if (zhiHuApi == null) {
            synchronized (ZhiHuApi.class){
                if (zhiHuApi == null){
                   zhiHuApi = new ZhiHuApi();
                }
            }
        }
        return zhiHuApi;
    }

    public void getLatestNews(Subscriber<LatestNews> subscriber){
        zhiHuService.getLatestNews()
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }
...
}

把列表的界面作为例子

定义Contract接口

public interface NewsContract {

    interface View{
        void refreshRecyclerVew(List<LatestNews.StoriesBean> storiesList);
    }

    interface Presenter{
        void getBeforeNews(String date);
        void getLatestNews();
    }

    interface model{
        void getBeforeNews(CallBackLatestNews callback, String date);
        void getLatestNews(CallBackLatestNews callback);
    }

}

View层

public class NewsFragment extends Fragment implements NewsContract.View, DatePickerDialog.OnDateSetListener {

    private static final String TAG = "NewsFragment";
    RecyclerView recyclerView;
    private NewsContract.Presenter presenter;

    public NewsFragment() {
        presenter = new NewsPresenter(this);
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_zhihu_daily, container, false);

        FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab);
        fab.setImageResource(R.drawable.add);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //显示日历选项
                Calendar now = Calendar.getInstance();
                DatePickerDialog dpd = DatePickerDialog.newInstance(
                        NewsFragment.this,
                        now.get(Calendar.YEAR),
                        now.get(Calendar.MONTH),
                        now.get(Calendar.DAY_OF_MONTH)
                );
                dpd.show(getActivity().getFragmentManager(), "Datepickerdialog");
            }
        });

        recyclerView = (RecyclerView) view.findViewById(R.id.latest_news_recyclerview);
        //StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        recyclerView.setLayoutManager(layoutManager);
       presenter.getLatestNews();

        return view;
    }

    @Override
    public void refreshRecyclerVew(List<LatestNews.StoriesBean> storiesList) {
        Log.d(TAG, "refreshRecyclerVew: ");
        NewsSummaryAdapter adapter = new NewsSummaryAdapter(storiesList);
        recyclerView.setAdapter(adapter);
    }


    @Override
    public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
        //String date = "You picked the following date: "+dayOfMonth+"/"+(monthOfYear+1)+"/"+year;
        String date = String.format("%d%02d%02d", year, monthOfYear + 1, dayOfMonth);
        presenter.getBeforeNews(date);
    }
}

定义一个回调接口

public interface CallBackLatestNews {
    public void result(List<LatestNews.StoriesBean> list);
}

Presenter层

public class NewsPresenter implements NewsContract.Presenter {

    private static final String TAG = "NewsPresenter";
    private NewsContract.View view;
    private NewsContract.model model;

    public NewsPresenter(NewsContract.View view) {
        this.view = view;
        model = new NewsModel();
    }


    @Override
    public void getBeforeNews(String date) {
        model.getBeforeNews(new CallBackLatestNews() {
            @Override
            public void result(List<LatestNews.StoriesBean> list) {
                view.refreshRecyclerVew(list);
            }
        }, date);
    }

    @Override
    public void getLatestNews() {
        Log.d(TAG, "getLatestNews: ");
        model.getLatestNews(new CallBackLatestNews() {
            @Override
            public void result(List<LatestNews.StoriesBean> list) {
                view.refreshRecyclerVew(list);
            }
        });
    }
}

model层

public class NewsModel implements NewsContract.model {

    private static final String TAG = "NewsModel";


    @Override
    public void getBeforeNews(final CallBackLatestNews callback, String date) {
        Subscriber subscriber = new Subscriber<LatestNews>() {

            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted: ");
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(LatestNews latestNews) {
                callback.result(latestNews.getStories());
            }
        };
        ZhiHuApi.getInstance().getBeforeNews(subscriber, date);
    }

    @Override
    public void getLatestNews(final CallBackLatestNews callback) {
        Subscriber subscriber = new Subscriber<LatestNews>() {

            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted: ");
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(LatestNews latestNews) {
                Log.d(TAG, "onNext: ");
               callback.result(latestNews.getStories());
            }
        };
        ZhiHuApi.getInstance().getLatestNews(subscriber);
    }

}

暂时只实现了知乎日报的功能,schedule和timetable还未实现。

github:https://github.com/linsawako/oneDay

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android-MVP+Retrofit+Rxjava实现一个知乎日报客户端