上路传送眼:

Android练手小项目(KTReader)基于mvp架构(一)

下路传送眼:

Android练手小项目(KTReader)基于mvp架构(三)

GIthub地址: https://github.com/yiuhet/KTReader

上篇文章中我们完成了基类和启动界面。
而这次我们要做的的就是能显示知乎日报内容的fragment。
这次我们使用到了开源框架Rxjava2+Okhttp3+retrofit2实现网络请求,Glide加载图片。

先附上效果图:

效果图

准备工作

  • 首先,添加依赖如下

compile 'com.github.bumptech.glide:glide:3.8.0'
compile 'com.squareup.okhttp3:okhttp:3.8.0'//貌似不用添加,retrofit2封装了okhttp
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'com.google.code.gson:gson:2.8.0' //貌似不用添加,converter-gson中已经封装了gson库
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'

  • 创建一个自定义的MyApplication,用来实现从任意位置获取程序的context
    app.MyApplication.class:
public class MyApplication extends Application {    private static Context sContext ;    private static String sCacheDir;    public static Context getContext() {        return sContext;    }    public static String getAppCacheDir() {        return sCacheDir;    }    @Override    public void onCreate() {        super.onCreate();        sContext = getApplicationContext();        if (getExternalCacheDir() != null && ExistSDCard()){            sCacheDir = getExternalCacheDir().toString();        } else {            sCacheDir = getCacheDir().toString();        }    }    private boolean ExistSDCard() {        return android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);    }}

创建好后别忘了配置AndroidManifest.xml:

在application内添加
android:name=".app.MyApplication"

  • 创建工具类和常量类

utils.NetWorkUtil.class: (判断是否联网的工具类)

public class NetWorkUtil {    private NetWorkUtil(){    };    public static boolean isNetWorkAvailable(Context context) {        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();        return networkInfo != null && networkInfo.isConnected();    }    public static boolean isWifiConnected(Context context) {        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();        return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI;    }}

utils.CommonUtils.class:(目前只有弹toast的功能)

public class CommonUtils {    private static Toast mToast;    public static void ShowTips(Context context, String tips) {        if (mToast == null) {            mToast = Toast.makeText(context,tips,Toast.LENGTH_SHORT);        } else {            mToast.setText(tips);        }        mToast.show();    }}

app.Constant.class :(常量类,目前只有知乎的基本url)

public class Constant {    public static final String ZHIHU_BASE_URL = "http://news-at.zhihu.com/api/4/news/";}

下面用到了retrofit2 + okhttp3 + rxjava3 的知识 附上参考资料

你真的会用Retrofit2吗?Retrofit2完全教程
Android网络编程(六)OkHttp3用法全解析
深入解析OkHttp3
Retrofit2+okhttp3拦截器处理在线和离线缓存
手把手教你使用 RxJava 2.0(一)

  • 创建个RetrofitManager,处理网络请求

utils.RetrofitManager.class:

public class RetrofitManager {    private static RetrofitManager retrofitManager;    private RetrofitManager() {    }    // 无论有无网络都读取缓存。(有时间限制) 把拦截器设置到addNetworkOnterceptor    private static Interceptor netInterceptor1 = new Interceptor() {        @Override        public Response intercept(Chain chain) throws IOException {            Request request = chain.request();            Response response = chain.proceed(request);            int maxAge = 60;  //60s为缓存的有效时间,60s内获取的是缓存数据,超过60S我们就去网络重新请求数据            return response                    .newBuilder()                    .removeHeader("Pragma")                    .removeHeader("Cache-Control")                    .header("Cache-Control", "public,max-age=" + maxAge)                    .build();        }    };    //有网络读取网络的数据,没有网络读取缓存。    private static class netInterceptor2 implements Interceptor{        @Override        public Response intercept(Chain chain) throws IOException {            Request request = chain.request();            //没有网络时强制使用缓存数据            if (!NetWorkUtil.isNetWorkAvailable(MyApplication.getContext())) {                request = request.newBuilder()                        //强制使用缓存数据                        .cacheControl(CacheControl.FORCE_CACHE)                        .build();            }            Response originalResponse = chain.proceed(request);            if (true) {                return originalResponse .newBuilder()                        .removeHeader("Pragma")                        .header("Cache-Control", "public,max-age=" + 0) //0为不进行缓存                        .build();            } else {                int maxAge =  4 * 24 * 60 * 60; //缓存保存时间                return originalResponse .newBuilder()                        .removeHeader("Pragma")                        .header("Cache-Control", "public, only-if-cached, max-age=" + maxAge)                        .build();            }        }    };    //缓存位置    private static File cacheFile = new File(MyApplication.getAppCacheDir(), "caheData_zhihu");    //设置缓存大小    private static int DEFAULT_DIR_CACHE = 10 * 1024 * 1024;    private static Cache cache = new Cache(cacheFile, DEFAULT_DIR_CACHE);    private static OkHttpClient client = new OkHttpClient.Builder()            .addInterceptor(new netInterceptor2())            .addNetworkInterceptor(new netInterceptor2())            .cache(cache)            .build();    public static RetrofitManager getInstence() {        if (retrofitManager == null) {            synchronized (RetrofitManager.class) {                if (retrofitManager == null) {                    retrofitManager = new RetrofitManager();                }            }        }        return retrofitManager;    }    private Retrofit retrofit;    public Retrofit getRetrofit(String url) {        if (retrofit == null) {            retrofit = new Retrofit.Builder()                    .baseUrl(url) //必须以‘/’结尾                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//使用RxJava2作为CallAdapter                    .client(client)//如果没有添加,那么retrofit2会自动给我们添加了一个。                    .addConverterFactory(GsonConverterFactory.create())//Retrofit2可以帮我们自动解析返回数据,                    .build();        }        return retrofit;    }}

api.ZhihuApi:

public interface ZhihuApi {    @GET("latest")    Observable getZhihuLatest();    @GET("before/{date}")    Observable getBefore(@Path("date") String date);}

Model层 :

  • 模型实体类ZhihuLatest直接使用GsonFormat工具快速生成(model.entity.ZhihuLatest)

  • 知乎日报Model接口
    model.ZhihuLatestModel:

public interface ZhihuLatestModel {    void loadZhihuLatest(OnZhihuLatestListener listener);    void loadMore(OnZhihuLatestListener listener);}
  • 获取知乎日报数据的Model实现
    model.impq.ZhihuLatestModelImp1.class:
public class ZhihuLatestModelImp1 implements ZhihuLatestModel {    /*获取知乎日报数据的Model实现*/    private ZhihuApi mZhihuApiService; //请求服务    private List mZhihuLatestList; //储存entity的list。    private String date; //网络请求的url参数,首次加载数据获取,调用getmore方法时,date-1.    public ZhihuLatestModelImp1 () {        mZhihuLatestList = new ArrayList<>();        mZhihuApiService = RetrofitManager                .getInstence()                .getRetrofit("http://news-at.zhihu.com/api/4/news/")                .create(ZhihuApi.class); //创建请求服务    }    public List getmZhihuLatestList(){        return mZhihuLatestList;    }    @Override    public void loadZhihuLatest(final OnZhihuLatestListener listener) {        mZhihuLatestList.clear();        //数据层的操作,网络请求数据        if (mZhihuApiService != null) {            mZhihuApiService.getZhihuLatest()                    .subscribeOn(Schedulers.io())                    .observeOn(AndroidSchedulers.mainThread())                    .subscribe(new Observer() {                        @Override                        public void onSubscribe(@NonNull Disposable d) {                        }                        @Override                        public void onNext(@NonNull ZhihuLatest zhihuLatest) {                            date = zhihuLatest.date;                            for (int i =0;i < zhihuLatest.stories.size(); i++) {                                mZhihuLatestList.add(zhihuLatest.stories.get(i));                            }                            listener.onLoadZhihuLatestSuccess(); //加载成功时 回调接口方法。                        }                        @Override                        public void onError(@NonNull Throwable e) {                            listener.onLoadDataError(e.toString());//加载失败时 回调接口方法。                        }                        @Override                        public void onComplete() {                        }                    });        }    }    @Override    public void loadMore(final OnZhihuLatestListener listener) {       // date = String.valueOf(Integer.parseInt(date) - 1);   2333,之前犯傻直接减1就当求前一天了        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");        Calendar calendar = new GregorianCalendar();;//获取日历实例        try {            calendar.setTime(sdf.parse(date));            calendar.add(Calendar.HOUR_OF_DAY, -1);  //设置为前一天            date = sdf.format(calendar.getTime());//获得前一天        } catch (ParseException e) {            e.printStackTrace();        }        //数据层的操作,网络请求数据        if (mZhihuApiService != null) {            mZhihuApiService.getBefore(date)                    .subscribeOn(Schedulers.io())                    .observeOn(AndroidSchedulers.mainThread())                    .subscribe(new Observer() {                        @Override                        public void onSubscribe(@NonNull Disposable d) {                        }                        @Override                        public void onNext(@NonNull ZhihuLatest zhihuLatest) {                            for (int i =0;i < zhihuLatest.stories.size(); i++) {                                mZhihuLatestList.add(zhihuLatest.stories.get(i));                            }                            listener.onLoadMoreSuccess();//加载成功时 回调接口方法。                        }                        @Override                        public void onError(@NonNull Throwable e) {                            listener.onLoadDataError(e.toString());//加载失败时 回调接口方法。                        }                        @Override                        public void onComplete() {                        }                    });        }    }}

View层

首先我们先确定需要实现的功能

  1. 从知乎日报上拉取数据 ( 知乎日报 API 分析)
  1. 当屏幕拉到底部时加载更多数据
  • 创建回调接口

view.ZhihuView:

public interface ZhihuView {   void onStartGetData();   void onGetZhihuLatestSuccess();   void onGetMoreSuccess();   void onGetDataFailed(String error);}
  • 在创建ZhiHuFragment之前,我们要先创建一个组件和adapter

widget.ZhihuItem:

public class ZhihuItem extends RelativeLayout {    private Context mContext;    @BindView(R.id.zhihu_iv)    ImageView mZhihuIv;    @BindView(R.id.zhihu_title)    TextView mZhihuTitle;    public ZhihuItem(Context context) {        this(context, null);    }    public ZhihuItem(Context context, AttributeSet attrs) {        super(context, attrs);        mContext = context;        init();    }    private void init() {        LayoutInflater.from(getContext()).inflate(R.layout.view_zhihu_item, this);        ButterKnife.bind(this, this);    }    public void bindView(ZhihuLatest.StoriesEntity zhihuLatest) {        mZhihuTitle.setText(zhihuLatest.title);        String url = zhihuLatest.images.get(0).toString();        //Glide 获取图片        Glide.with(mContext)                .load(url)                .placeholder(R.drawable.loading) //占位图片                .error(R.drawable.error) //错误图片                .into(mZhihuIv);    }}

组件的布局文件:
view_zhihu_item.xml:

<?xml version="1.0" encoding="utf-8"?>        

创建的ZhihuAdapter自己写了点击监听器接口,会在fragment里添加监听事件。

adapter.ZhihuAdapter:

public class ZhihuAdapter extends RecyclerView.Adapter {    private Context mContext;    List mZhihuLatestList;    private OnItemClickListener mItemClickListener;    public ZhihuAdapter(Context context, List zhihuLatestList) {        mContext =context;        mZhihuLatestList = zhihuLatestList;    }    @Override    public ZhihuViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        ZhihuItem zhihuItem = new ZhihuItem(mContext);        return new ZhihuViewHolder(zhihuItem);    }    @Override    public void onBindViewHolder(ZhihuViewHolder holder, int position) {        final ZhihuLatest.StoriesEntity zhihuLatest = mZhihuLatestList.get(position);        holder.zhihuItem.bindView(zhihuLatest);        holder.zhihuItem.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (mItemClickListener != null) {                    mItemClickListener.onItemClick(zhihuLatest.id);                }            }        });    }    @Override    public int getItemCount() {        return mZhihuLatestList.size();    }    public class ZhihuViewHolder extends RecyclerView.ViewHolder {        public ZhihuItem zhihuItem;        public ZhihuViewHolder(ZhihuItem itemView) {            super(itemView);            zhihuItem = itemView;        }    }    public interface OnItemClickListener {        void onItemClick(int id);    }    public void setOnItemClickListener(OnItemClickListener listener) {        mItemClickListener = listener;    }}
  • 创建ZhiHuFragment
    ui.fragment.ZhiHuFragment:
public class ZhiHuFragment extends BaseFragment implements ZhihuView {    @BindView(R.id.recycle_zhihu)    RecyclerView mRecycleZhihu;    Unbinder unbinder;    @BindView(R.id.prograss)    ProgressBar mPrograss;    private ZhihuAdapter mZhihuAdapter;    @Override    public void onStartGetZhihuLatest() {        mPrograss.setVisibility(View.VISIBLE);    }    @Override    public void onGetZhihuLatestSuccess() {        mPrograss.setVisibility(View.GONE);        mZhihuAdapter.notifyDataSetChanged();    }    @Override    public void onGetZhihuLatestFailed(String error) {        mPrograss.setVisibility(View.GONE);        toast(error);    }    @Override    public void onStartGetMore() {        mPrograss.setVisibility(View.VISIBLE);    }    @Override    public void onGetMoreSuccess() {        mPrograss.setVisibility(View.GONE);        mZhihuAdapter.notifyDataSetChanged();    }    @Override    public void onGetMoreFailed(String error) {        mPrograss.setVisibility(View.GONE);        toast(error);    }    @Override    protected int getLayoutRes() {        return R.layout.fragment_zhihu;    }    @Override    protected ZhihuPresenterImp1 createPresenter() {        return new ZhihuPresenterImp1(this);    }    @Override    public void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {        // TODO: inflate a fragment view        View rootView = super.onCreateView(inflater, container, savedInstanceState);        unbinder = ButterKnife.bind(this, rootView);        init();        mPresenter.getLatest();        return rootView;    }    private void init() {        mRecycleZhihu.setLayoutManager(new LinearLayoutManager(getContext()));        mRecycleZhihu.setHasFixedSize(true);        mRecycleZhihu.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));        mRecycleZhihu.setItemAnimator(new DefaultItemAnimator());        mRecycleZhihu.addOnScrollListener(new RecyclerView.OnScrollListener() {            @Override            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {                super.onScrollStateChanged(recyclerView, newState);            }            @Override            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {                super.onScrolled(recyclerView, dx, dy);                if (isSlideToBottom(recyclerView)) {                    mPresenter.getMore();                }            }        });        mZhihuAdapter = new ZhihuAdapter(getContext(), mPresenter.getmZhihuLatestList());        mZhihuAdapter.setOnItemClickListener(mOnItemClickListener);        mRecycleZhihu.setAdapter(mZhihuAdapter);    }    public static boolean isSlideToBottom(RecyclerView recyclerView) {        if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset()                >= recyclerView.computeVerticalScrollRange())            return true;        return false;    }    private ZhihuAdapter.OnItemClickListener mOnItemClickListener = new ZhihuAdapter.OnItemClickListener() {        @Override        public void onItemClick(int id) {            toast(Constant.ZHIHU_BASE_URL + String.valueOf(id));        }    };    @Override    public void onDestroyView() {        super.onDestroyView();        unbinder.unbind();    }}

Presenter层

在ZhihuPresenterImp1类里实现数据和视图的绑定

  • 先写一个回调接口:
    (在Presenter层实现,给Model层回调,更改View层的状态,确保Model层不直接操作View层)
    presenter.OnZhihuLatestListener:
public interface OnZhihuLatestListener {    /**     * 成功时回调     */    void onLoadZhihuLatestSuccess();    void onLoadMoreSuccess();    /**     * 失败时回调     */    void onLoadDataError(String error);}
  • 再写一个presenter接口:
    presenter.ZhihuPresenter :
public interface ZhihuPresenter {    void getLatest();    void getMore();}
  • 最后写Prestener实现类:
    presenter.imp1.ZhihuPresenterImp1.class:
public class ZhihuPresenterImp1 extends BasePresenter implements ZhihuPresenter,OnZhihuLatestListener{    /*Presenter作为中间层,持有View和Model的引用*/    private ZhihuView mZhihuView;    private ZhihuLatestModelImp1 zhihuLatestModelImp1;    public ZhihuPresenterImp1(ZhihuView zhihuView) {        mZhihuView = zhihuView;        zhihuLatestModelImp1 = new ZhihuLatestModelImp1();    }    public List getmZhihuLatestList() {        return zhihuLatestModelImp1.getmZhihuLatestList();    }    @Override    public void getLatest() {        mZhihuView.onStartGetData();        zhihuLatestModelImp1.loadZhihuLatest(this);    }    @Override    public void getMore() {        mZhihuView.onStartGetData();        zhihuLatestModelImp1.loadMore(this);    }    @Override    public void onLoadZhihuLatestSuccess() {        mZhihuView.onGetZhihuLatestSuccess();    }    @Override    public void onLoadMoreSuccess() {        mZhihuView.onGetMoreSuccess();    }    @Override    public void onLoadDataError(String error) {        mZhihuView.onGetDataFailed(error);    }}

最后,创建一个带有侧滑菜单的MainActivity

  • 暂且只在其内部添加一个ZhiHuFragment。
  • 侧滑菜单具体功能之后会实现。
  • 双击返回键退出

ui.activity.MainActivity.class:

public class MainActivity extends BaseActivity        implements NavigationView.OnNavigationItemSelectedListener {    @BindView(R.id.toolbar)    Toolbar mToolbar;    @BindView(R.id.fragment_main)    FrameLayout fragmentMain;    @BindView(R.id.nav_view)    NavigationView mNavView;    @BindView(R.id.drawer_layout)    DrawerLayout mDrawerLayout;    private long exitTime = 0;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ButterKnife.bind(this);        initView();        getSupportFragmentManager().beginTransaction().add(R.id.fragment_main, new ZhiHuFragment()).commit();    }    private void initView() {        setSupportActionBar(mToolbar);        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(                this, mDrawerLayout, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);        mDrawerLayout.addDrawerListener(toggle);        toggle.syncState();        mNavView.setNavigationItemSelectedListener(this);    }    @Override    protected int getLayoutRes() {        return R.layout.activity_main;    }    @Override    public void onBackPressed() {        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);        if (drawer.isDrawerOpen(GravityCompat.START)) {            drawer.closeDrawer(GravityCompat.START);        } else {            if ((System.currentTimeMillis() - exitTime) > 2000) {                CommonUtils.ShowTips(MainActivity.this, "再点一次,退出");                exitTime = System.currentTimeMillis();            } else {                super.onBackPressed();            }        }    }    @Override    public boolean onCreateOptionsMenu(Menu menu) {        getMenuInflater().inflate(R.menu.main, menu);        return true;    }    @Override    public boolean onOptionsItemSelected(MenuItem item) {        int id = item.getItemId();        if (id == R.id.action_settings) {            return true;        }        return super.onOptionsItemSelected(item);    }    @SuppressWarnings("StatementWithEmptyBody")    @Override    public boolean onNavigationItemSelected(MenuItem item) {        int id = item.getItemId();        if (id == R.id.nav_camera) {            // Handle the camera action        }         mDrawerLayout.closeDrawer(GravityCompat.START);        return true;    }

修改布局文件
activity_main.xml:

                                    

app_bar_main.xml:

<?xml version="1.0" encoding="utf-8"?>    

更多相关文章

  1. android Content Provider的使用
  2. SQlite字段类型升级
  3. Android(安卓)TTS学习——TTS初体验
  4. Android深入讲解WebView——下
  5. android parcelable 详细介绍
  6. OpenCV4Android开发之旅(一)----OpenCV2.4简介及 app通过Java接
  7. AIDL——Android接口描述语言
  8. 在android中使用OrmLite数据库框架
  9. 关于Android使用新浪API的一些说明

随机推荐

  1. android AOP实现之AspectJ
  2. Android(安卓)Vold 分析(一)--system/vold/
  3. Android逆向之旅---Android中锁屏密码算
  4. 領航桌面App一秒鐘把你的 Android 手機換
  5. Android(安卓)DependencyResolveDetails
  6. android 仿ios 对话框已封装成工具类
  7. 【Android 内存优化】使用 Memory Analyz
  8. 老于的开发经历
  9. Android开发 第五课 Android的几种布局方
  10. 电商应用开发实例分享:《凡客移动应用之An