Android开发玩android项目Android开发

WanAndroid实战——首页Banner

2019-03-07  本文已影响19人  Tom_Ji

写在前面的话

之前一直在学习任玉刚老师的《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;

/**
 * <p>Title: BasePresenter</p>
 * <p>Description: </p>
 *
 * @author tom
 * @date 2019/3/7 09:59
 **/
public abstract class BasePresenter<V> {
    WeakReference<V> mViewRef;
    //防止内存泄漏
    protected CompositeDisposable mCompositeDisposable = new CompositeDisposable();

    void attachView(V view) {
        mViewRef = new WeakReference<V>(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;

/**
 * <p>Title: BaseActivity</p>
 * <p>Description: </p>
 *
 * @author tom
 * @date 2019/3/7 10:04
 **/
public abstract class BaseActivity<V, P extends BasePresenter<V>> 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;

/**
 * <p>Title: Contract</p>
 * <p>Description: </p>
 *
 * @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;

/**
 * <p>Title: BannerBean</p>
 * <p>Description: </p>
 *
 * @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<DataBean> 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<DataBean> getData() {
        return data;
    }

    public void setData(List<DataBean> 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;

/**
 * <p>Title: Contract</p>
 * <p>Description: </p>
 *
 * @author tom
 * @date 2019/3/7 10:13
 **/
public class Contract {

    public interface IMainModel {

        /**
         * <p>获取banner数据</p>
         * @return banner数据 
         */
        Observable<BannerBean> loadBanner();


    }

    public interface IMainView {
        /**
         * <p>View 获取到数据后进行显示</p>
         * @param bean banner的数据
         */
        void loadBanner(BannerBean bean);

    }

    public interface IMainPresenter{

        /**
         * <p>P层接口</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;

/**
 * <p>Title: MainModel</p>
 * <p>Description: </p>
 *
 * @author tom
 * @date 2019/3/7 10:54
 **/
public class MainModel implements Contract.IMainModel {
    @Override
    public Observable<BannerBean> 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;

/**
 * <p>Title: MainPresenter</p>
 * <p>Description: </p>
 *
 * @author tom
 * @date 2019/3/7 10:59
 **/
public class MainPresenter extends BasePresenter<Contract.IMainView> 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;

/**
 * <p>Title: IRetrofitData</p>
 * <p>Description: </p>
 *
 * @author tom
 * @date 2019/3/7 11:34
 **/
public interface IRetrofitData {
    @GET("banner/json")
    Observable<BannerBean> 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;

/**
 * <p>Title: MainModel</p>
 * <p>Description: </p>
 *
 * @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<BannerBean> 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;

/**
 * <p>Title: MainPresenter</p>
 * <p>Description: </p>
 *
 * @author tom
 * @date 2019/3/7 10:59
 **/
public class MainPresenter extends BasePresenter<Contract.IMainView> 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<BannerBean>() {
                    @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;

/**
 * <p>Title: GlideImageLoader</p>
 * <p>Description: 重写图片加载器 </p>
 *
 * @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。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.MainActivity"
    android:orientation="vertical">

    <com.youth.banner.Banner
        android:id="@+id/main_banner"
        android:layout_width="match_parent"
        android:layout_height="@dimen/banner_height"/>

</LinearLayout>

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<Contract.IMainView, MainPresenter> 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<String> images = bean.getData()
                    .stream()
                    .map(BannerBean.DataBean::getImagePath)
                    .collect(Collectors.toList());

            mMainBanner.setImages(images);

            //获取title

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

            mMainBanner.setBannerTitles(titles);


            mMainBanner.start();
        }

    }


}

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

<uses-permission android:name="android.permission.INTERNET"/>

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

            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

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

banner效果图.gif

至此,本节内容告一段落,接下来要继续实现后续部分。

上一篇下一篇

猜你喜欢

热点阅读