WanAndroid实战——首页Banner

写在前面的话

之前一直在学习任玉刚老师的《Android开发艺术探索》,并且也有通过笔记的形式记录下自己认为符合自己需要的内容,一是加深记忆,二是方便以后查找复习。目前的工作内容主要是android原生应用的定制修改,基本上涉及不到使用网络的情况,但是现在的第三方app基本上无一例外的都需要使用网络,因此这方面的知识就需要弥补上来。这里要感谢我的同事王远志的帮助,大家也可以去看他的主页milovetingting,有问题也可以给他留言。

准备工作

项目使用的API由玩Android 开放API提供。创建项目,首先将需要用到的开源项目导入。在dependencies里面加入如下内容。

    //butterknife
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    //banner
    implementation 'com.youth.banner:banner:1.4.10'
    //retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
    //rx
    implementation 'io.reactivex.rxjava2:rxjava:2.2.7'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    //glide
    implementation 'com.github.bumptech.glide:glide:4.9.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'

这里指定使用java 8来进行编译,方便使用lambda表达式,在android里增加如下内容


compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

搭建MVP框架

目前主流的app开发基本都是使用MVP模式来进行的,因此也去学习了一下MVP的相关知识,这里就直接使用。对于MVP模式的理解,可以这样来看,我们部门包括部长,组长,我,现在有一个开发任务,部长告诉组长,组长安排我来去完成,我完成开发任务之后告诉组长,组长再告诉部长,部长就可以说任务完成了。这里的我相当于M层(真正做事情的,获取数据等耗时操作),组长相当于P层(用来传递消息,在V和M之间的桥梁),部长相当于V层(用来展示数据),这样便于理解,现实中也是如此,尽量不要越级沟通。

1.创建base文件夹,里面为基类BasePresenter和BaseActivity

BasePresenter.java


package com.tom.wanandroid.base;

import java.lang.ref.WeakReference;

import io.reactivex.disposables.CompositeDisposable;

/**
 * 

Title: BasePresenter

*

Description:

* * @author tom * @date 2019/3/7 09:59 **/ public abstract class BasePresenter { WeakReference mViewRef; //防止内存泄漏 protected CompositeDisposable mCompositeDisposable = new CompositeDisposable(); void attachView(V view) { mViewRef = new WeakReference(view); } protected V getView() { return mViewRef == null ? null : mViewRef.get(); } protected boolean isViewAttached() { return mViewRef != null && mViewRef.get() != null; } void detachView() { if (mViewRef != null) { mViewRef.clear(); mViewRef = null; } mCompositeDisposable.clear(); } }

BaseActivity.java


package com.tom.wanandroid.base;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * 

Title: BaseActivity

*

Description:

* * @author tom * @date 2019/3/7 10:04 **/ public abstract class BaseActivity> extends AppCompatActivity { protected P mPresenter; protected Unbinder unbinder; /** * 获取布局id * * @return 布局id */ protected abstract int getContentViewId(); /** * 初始化 * * @param savedInstanceState bundle */ protected abstract void init(Bundle savedInstanceState); /** * 创建Presenter * * @return p */ protected abstract P createPresenter(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getContentViewId()); unbinder = ButterKnife.bind(this); mPresenter = createPresenter(); mPresenter.attachView((V) this); init(savedInstanceState); } @Override public void onDestroy() { super.onDestroy(); unbinder.unbind(); mPresenter.detachView(); } }

这两个基类基本在每次使用的时候就可以直接复制粘贴了,避免每次都要重写。

2.创建Contract.java类,在contract包下,这个类里面主要定义接口,接口里面需要包含哪些方法时根据具体的根据业务来定义的,基本框架如下。

Contract.java


package com.tom.wanandroid.contract;

/**
 * 

Title: Contract

*

Description:

* * @author tom * @date 2019/3/7 10:13 **/ public class Contract { public interface IMainModel{ } public interface IMainView{ } public interface IMainPresenter{ } }

3.因为我们要获取到banner图的数据,所以需要有一个bean,数据根据API返回的数据来定义,API返回的是一个json串,因此我们可以创建一个对应的bean,也可以使用插件,我这里使用了GsonFormat插件。网页介绍如下图所示


网页介绍

可以使用http://www.wanandroid.com/banner/json查看示例。

通过GsonFormat插件生成的类没有toString方法,这里把toString方法补上,方便调试。

BannerBean.java


package com.tom.wanandroid.bean;

import java.util.List;

/**
 * 

Title: BannerBean

*

Description:

* * @author tom * @date 2019/3/7 10:28 **/ public class BannerBean { /** * data : [{"desc":"一起来做个App吧","id":10,"imagePath":"http://www.wanandroid * .com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":3,"title":"一起来做个App吧","type":0, * "url":"http://www.wanandroid.com/blog/show/2"},{"desc":"","id":4,"imagePath":"http://www.wanandroid * .com/blogimgs/ab17e8f9-6b79-450b-8079-0f2287eb6f0f.png","isVisible":1,"order":0,"title":"看看别人的面经,搞定面试~", * "type":1,"url":"http://www.wanandroid.com/article/list/0?cid=73"},{"desc":"","id":3,"imagePath":"http://www * .wanandroid.com/blogimgs/fb0ea461-e00a-482b-814f-4faca5761427.png","isVisible":1,"order":1, * "title":"兄弟,要不要挑个项目学习下?","type":1,"url":"http://www.wanandroid.com/project"},{"desc":"","id":6, * "imagePath":"http://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png","isVisible":1, * "order":1,"title":"我们新增了一个常用导航Tab~","type":1,"url":"http://www.wanandroid.com/navi"},{"desc":"","id":18, * "imagePath":"http://www.wanandroid.com/blogimgs/00f83f1d-3c50-439f-b705-54a49fc3d90d.jpg","isVisible":1, * "order":1,"title":"公众号文章列表强势上线","type":1,"url":"http://www.wanandroid.com/wxarticle/list/408/1"},{"desc":"", * "id":2,"imagePath":"http://www.wanandroid.com/blogimgs/90cf8c40-9489-4f9d-8936-02c9ebae31f0.png", * "isVisible":1,"order":2,"title":"JSON工具","type":1,"url":"http://www.wanandroid.com/tools/bejson"},{"desc":"", * "id":5,"imagePath":"http://www.wanandroid.com/blogimgs/acc23063-1884-4925-bdf8-0b0364a7243e.png", * "isVisible":1,"order":3,"title":"微信文章合集","type":1,"url":"http://www.wanandroid.com/blog/show/6"}] * errorCode : 0 * errorMsg : */ private int errorCode; private String errorMsg; private List data; public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public List getData() { return data; } public void setData(List data) { this.data = data; } public static class DataBean { /** * desc : 一起来做个App吧 * id : 10 * imagePath : http://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png * isVisible : 1 * order : 3 * title : 一起来做个App吧 * type : 0 * url : http://www.wanandroid.com/blog/show/2 */ private String desc; private int id; private String imagePath; private int isVisible; private int order; private String title; private int type; private String url; public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getImagePath() { return imagePath; } public void setImagePath(String imagePath) { this.imagePath = imagePath; } public int getIsVisible() { return isVisible; } public void setIsVisible(int isVisible) { this.isVisible = isVisible; } public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } @Override public String toString() { return "DataBean{" + "desc='" + desc + '\'' + ", id=" + id + ", imagePath='" + imagePath + '\'' + ", isVisible=" + isVisible + ", order=" + order + ", title='" + title + '\'' + ", type=" + type + ", url='" + url + '\'' + '}'; } } @Override public String toString() { return "BannerBean{" + "errorCode=" + errorCode + ", errorMsg='" + errorMsg + '\'' + ", data=" + data + '}'; } }

4.填充Contract.java类里面的接口

根据实际的业务,这里要获取banner的数据,因此增加如下内容。

Contract.java


package com.tom.wanandroid.contract;

import com.tom.wanandroid.bean.BannerBean;

import io.reactivex.Observable;

/**
 * 

Title: Contract

*

Description:

* * @author tom * @date 2019/3/7 10:13 **/ public class Contract { public interface IMainModel { /** *

获取banner数据

* @return banner数据 */ Observable loadBanner(); } public interface IMainView { /** *

View 获取到数据后进行显示

* @param bean banner的数据 */ void loadBanner(BannerBean bean); } public interface IMainPresenter{ /** *

P层接口

*/ void loadBanner(); } }

5.接口已经定义好了,接下来就是分别创建MVP对应的类,来实现这些接口。先创建Model层,这里创建MainModel类,在包model下,实现IMainModel接口,主要作用是来获取数据,真正干活的地方。

MainModel.java


package com.tom.wanandroid.model;

import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;

import io.reactivex.Observable;

/**
 * 

Title: MainModel

*

Description:

* * @author tom * @date 2019/3/7 10:54 **/ public class MainModel implements Contract.IMainModel { @Override public Observable loadBanner() { return null; } }

接着创建MainPresenter.java,继承BasePresenter,实现IMainPresenter接口,用来传话的地方。

MainPresenter.java


package com.tom.wanandroid.presenter;

import com.tom.wanandroid.base.BasePresenter;
import com.tom.wanandroid.contract.Contract;

/**
 * 

Title: MainPresenter

*

Description:

* * @author tom * @date 2019/3/7 10:59 **/ public class MainPresenter extends BasePresenter implements Contract.IMainPresenter { @Override public void loadBanner() { } }

6.数据获取,获取banner数据是从服务器上获取的,这里使用retrofit开源框架来获取,retrofit的使用需要自己学习。因为网站开放API介绍的是使用get的无参方法,因此创建retrofit接口,用于使用该框架。

IRetrofitData.java


package com.tom.wanandroid.retrofit;

import com.tom.wanandroid.bean.BannerBean;

import io.reactivex.Observable;
import retrofit2.http.GET;

/**
 * 

Title: IRetrofitData

*

Description:

* * @author tom * @date 2019/3/7 11:34 **/ public interface IRetrofitData { @GET("banner/json") Observable loadBanner(); }

7.填充M层的方法,获取banner数据。

MainModel.java


package com.tom.wanandroid.model;

import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import com.tom.wanandroid.retrofit.IRetrofitData;

import io.reactivex.Observable;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * 

Title: MainModel

*

Description:

* * @author tom * @date 2019/3/7 10:54 **/ public class MainModel implements Contract.IMainModel { private static final String BASE_URL = "http://www.wanandroid.com"; @Override public Observable loadBanner() { Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); IRetrofitData retrofitData = retrofit.create(IRetrofitData.class); return retrofitData.loadBanner(); } }

8.填充P层方法,将获取到的数据设置给V层,让View去显示数据。

MainPresenter.java


package com.tom.wanandroid.presenter;

import com.tom.wanandroid.base.BasePresenter;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import com.tom.wanandroid.model.MainModel;

import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

/**
 * 

Title: MainPresenter

*

Description:

* * @author tom * @date 2019/3/7 10:59 **/ public class MainPresenter extends BasePresenter implements Contract.IMainPresenter { Contract.IMainModel mModel; public MainPresenter() { mModel = new MainModel(); } @Override public void loadBanner() { mModel.loadBanner() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer() { @Override public void onSubscribe(Disposable d) { mCompositeDisposable.add(d); } @Override public void onNext(BannerBean bean) { if (isViewAttached()) { getView().loadBanner(bean); } } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onComplete() { } }); } }

9.数据已经准备好并且传递给了View层,接下来就需要在View来展示数据,新建用来展示数据的Activity,继承BaseActivity,实现IMainView接口,轮播图的显示使用开源框架Banner,图片使用Glide框架。Banner使用

这里我把重写的图片加载器放到了utils包里。

GlideImageLoader.java


package com.tom.wanandroid.utils;

import android.content.Context;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.youth.banner.loader.ImageLoader;

/**
 * 

Title: GlideImageLoader

*

Description: 重写图片加载器

* * @author tom * @date 2019/3/7 14:21 **/ public class GlideImageLoader extends ImageLoader { @Override public void displayImage(Context context, Object path, ImageView imageView) { Glide.with(context).load(path).into(imageView); } }

activity_main.xml 高度值banner_height我设置了200dp。





    


Constant.java

/**
 * 

Title: Constant

*

Description: 使用到的常量

* * @author tom * @date 2019/3/7 14:23 **/ public class Constant { /** * banner获取成功 */ public static final int BANNER_SUCCESS = 0; /** * 文章title的key */ public static final String ARTICLE_TITLE = "title"; /** * 文章url的key */ public static final String ARTICLE_URL = "url"; }

MainActivity.java


package com.tom.wanandroid.view;

import android.os.Bundle;

import com.tom.wanandroid.Constant;
import com.tom.wanandroid.R;
import com.tom.wanandroid.base.BaseActivity;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import com.tom.wanandroid.presenter.MainPresenter;
import com.tom.wanandroid.utils.GlideImageLoader;
import com.youth.banner.Banner;
import com.youth.banner.BannerConfig;

import java.util.List;
import java.util.stream.Collectors;

import butterknife.BindView;

public class MainActivity extends BaseActivity implements Contract.IMainView {

    @BindView(R.id.main_banner)
    Banner mMainBanner;

    @Override
    protected int getContentViewId() {
        return R.layout.activity_main;
    }

    @Override
    protected void init(Bundle savedInstanceState) {

        mPresenter.loadBanner();
    }

    @Override
    protected MainPresenter createPresenter() {
        return new MainPresenter();
    }


    @Override
    public void loadBanner(BannerBean bean) {


        mMainBanner.setImageLoader(new GlideImageLoader());

        if (bean.getErrorCode() == Constant.BANNER_SUCCESS) {

            //设置banner样式
            mMainBanner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR_TITLE_INSIDE);

            //获取图片路径
            List images = bean.getData()
                    .stream()
                    .map(BannerBean.DataBean::getImagePath)
                    .collect(Collectors.toList());

            mMainBanner.setImages(images);

            //获取title

            List titles = bean.getData()
                    .stream()
                    .map(BannerBean.DataBean::getTitle)
                    .collect(Collectors.toList());

            mMainBanner.setBannerTitles(titles);


            mMainBanner.start();
        }

    }


}

10.添加权限,添加使用开源库所需要的权限,因为目前只涉及到网络权限,所以只申请网络权限。在AndroidManifest.xml文件中添加。


至此,基本内容都写完了,跑一下看看,发现提示 "default Activity not found",检查发现AndroidManifest.xml中没有写默认启动的activity,因为目前只有一个Activity,添加如下内容,即可。

            
                

                
            

在此运行,符合续期效果。运行效果如图:

banner效果图.gif

至此,本节内容告一段落,接下来要继续实现后续部分。
2.WanAndroid实战——首页文章
3.WanAndroid实战——内容显示
4.WanAndroid实战——刷新加载
5.WanAndroid实战——网络判断

你可能感兴趣的:(WanAndroid实战——首页Banner)