Android 天气APP(四)搭建MVP框架与使用

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Android 天气APP(四)搭建MVP框架与使用

4. MVP框架搭建


现在这样固然符合网络请求的标准,结果也得到了,但是这只是一个接口而已,我们用了这么多代码,那假如这个页面上还有好几个接口要请求访问,岂不是多出了很多的重复代码,这一点并不符合现在Android的现状,所以需要封装OKHttp,通过架构或者框架来完成这一步,前期虽然麻烦一些,但是你一旦用习惯了,就停不下来了,接下来我尽量用人话来讲述这个搭建过程。


为了让你有一个清晰的思路,这里创建一个模块,里面搭建MVP框架。


① 创建模块

鼠标右键你的项目名,选择Module

20200402144320790.png

20200402144414454.png

20200402144505165.png


点击Finish


20200402144621103.png


现在模块就创建完成了。


② 配置模块


接下来修改模块的build.gradle

代码如下:


apply plugin: 'com.android.library'
android {
    compileSdkVersion 28
    buildToolsVersion "28.0.3"
    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
  compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    //在模块中添加的依赖若想在项目中使用,则implementation改成api
    //butterknife  绑定视图依赖BindView,告别findById,不过你还得安装一个butterknife插件才行
    api 'com.jakewharton:butterknife:10.1.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
    //Google Material控件,以及迁移到AndroidX下一些控件的依赖
    api 'com.google.android.material:material:1.0.0'
    api 'androidx.lifecycle:lifecycle-extensions:2.1.0'
    api 'androidx.annotation:annotation:1.1.0'
    api 'androidx.legacy:legacy-support-v4:1.0.0'
    //RecyclerView最好的适配器,让你的适配器一目了然,告别代码冗余
    api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'
  //图片加载框架
    api 'com.github.bumptech.glide:glide:4.10.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
    //权限请求框架
    api 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
    api 'io.reactivex.rxjava2:rxandroid:2.0.2'
    api "io.reactivex.rxjava2:rxjava:2.0.0"
    //状态栏
    api 'com.readystatesoftware.systembartint:systembartint:1.0.3'
    //支持okhttp
    api 'com.squareup.okhttp3:okhttp:3.8.1'
    api 'com.squareup.retrofit2:retrofit:2.4.0'
    api 'com.squareup.retrofit2:converter-gson:2.4.0'
    api 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    //阿里巴巴 FastJson
    api 'com.alibaba:fastjson:1.2.57'
    //下拉刷新框架
    api 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-14'
    //没有使用特殊Header,可以不加这行
    api 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0-alpha-14'
}


然后修改项目的build.gradle


2020040217103886.png


implementation project(':mvplibrary')//引入模块  然后将项目里的依赖移动到模块的build.gradle里


然后Sync一下,如果没有出现什么问题就可以进行下一步了。


③ 创建Activity管理


在模块的com.llw.mvplibrary包下新建一个utils包,包下创建一个ActivityManagerl类,管理所有的Activity


20200402151344764.png


代码如下:

package com.llw.mvplibrary.utils;
import android.app.Activity;
import java.util.ArrayList;
import java.util.List;
/**
 * 管理所有的Activity
 */
public class ActivityManager {
    //保存所有创建的Activity
    private List<Activity> allActivities = new ArrayList<>();
    /**
     * 添加Activity到管理器
     *
     * @param activity activity
     */
    public void addActivity(Activity activity) {
        if (activity != null) {
            allActivities.add(activity);
        }
    }
    /**
     * 从管理器移除Activity
     *
     * @param activity activity
     */
    public void removeActivity(Activity activity) {
        if (activity != null) {
            allActivities.remove(activity);
        }
    }
    /**
     * 关闭所有Activity
     */
    public void finishAll() {
        for (Activity activity : allActivities) {
            activity.finish();
        }
    }
    public Activity getTaskTop() {
        return allActivities.get(allActivities.size() - 1);
    }
}

④ 创建BaseApplication


在模块的com.llw.mvplibrary包下新建一个BaseApplication继承Application,作为全局管理


20200402151758373.png


代码如下:


package com.llw.mvplibrary;
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import com.llw.mvplibrary.utils.ActivityManager;
/**
 * 工程管理
 */
public class BaseApplication extends Application {
    private static ActivityManager activityManager;
    private  static BaseApplication application;
    private static Context context;
    @Override
    public void onCreate() {
        super.onCreate();
        //声明Activity管理
        activityManager=new ActivityManager();
        context = getApplicationContext();
        application=this;
    }
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }
    public static ActivityManager getActivityManager() {
        return activityManager;
    }
    //内容提供器
    public static Context getContext(){
        return context;
    }
    public static BaseApplication getApplication() {
        return application;
    }
}

⑤ 创建KnifeKit


接下来创建一个kit包,包下创建一个KnifeKit


20200402152609943.png


代码如下:


package com.llw.mvplibrary.kit;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
 * 绑定视图控件ID
 */
public class KnifeKit {
    //解绑
    public static Unbinder bind(Object target) {
        if (target instanceof Activity) {
            return ButterKnife.bind((Activity) target);
        } else if (target instanceof Dialog) {
            return ButterKnife.bind((Dialog) target);
        } else if (target instanceof View) {
            return ButterKnife.bind((View) target);
        }
        return Unbinder.EMPTY;
    }
    //绑定输入目标资源
    public static Unbinder bind(Object target, Object source) {
        if (source instanceof Activity) {
            return ButterKnife.bind(target, (Activity) source);
        } else if (source instanceof Dialog) {
            return ButterKnife.bind(target, (Dialog) source);
        } else if (source instanceof View) {
            return ButterKnife.bind(target, (View) source);
        }
        return Unbinder.EMPTY;
    }
    //解绑
    public static void unbind(Unbinder unbinder) {
        if (unbinder != Unbinder.EMPTY) {
            unbinder.unbind();
        }
    }
}


⑥ 创建base包(以及包下的类和接口)


接下来新建一个base包,下面创建一个UiCallBack接口


2020040215303833.png


代码如下:

package com.llw.mvplibrary.base;
import android.os.Bundle;
/**
 * UI回调接口
 */
public interface UiCallBack {
    //初始化savedInstanceState
    void initBeforeView(Bundle savedInstanceState);
    //初始化
    void initData(Bundle savedInstanceState);
    //布局
    int getLayoutId();
}


base包,新创建一个BaseView接口


20200402153341604.png


代码如下:


package com.llw.mvplibrary.base;
/**
 * 只是一个接口BaseView ,里面可以自由定制
 */
public interface BaseView {
}


base包下面创建一个BasePresenter


2020040215345428.png


代码如下:


package com.llw.mvplibrary.base;
import com.llw.mvplibrary.base.BaseView;
import java.lang.ref.WeakReference;
/**
 * Presenter基类 操作视图View
 * @param <V>
 */
public class BasePresenter<V extends BaseView> {
    private WeakReference<V> mWeakReference;
    /**
     * 关联view
     * @param v
     */
    public void attach(V v){
        mWeakReference=new WeakReference<V>(v);
    }
    /**
     * 分离view
     * @param v
     */
    public void detach(V v){
        if (mWeakReference!=null){
            mWeakReference.clear();
            mWeakReference=null;
        }
    }
    /**
     * 获取view
     * @return
     */
    public V getView(){
        if (mWeakReference!=null){
            return mWeakReference.get();
        }
        return null;
    }
}


接下来在base包下面创建一个网络请求返回解析基类 BaseResponse


20200402153845524.png


代码如下:


package com.llw.mvplibrary.base;
/**
 * @ClassDest: 网络请求返回解析基类
 */
public class BaseResponse {
    /**
     * code : 200
     * msg : incorrect password
     * data : null
     */
    private int code;
    private String msg;
    private Object data;
    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
}


接下来在base包下面创建BaseActivity ,不需要MVP的Activity普通的Activity直接继承即可使用,这用主要是用于管理Acitivity


20200402155007461.png


代码如下,这个里面还有进一步优化的空间,后面会提到的。


package com.llw.mvplibrary.base;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.llw.mvplibrary.BaseApplication;
import com.llw.mvplibrary.kit.KnifeKit;
import butterknife.Unbinder;
/**
 * 用于不需要请求网络接口的Activity
 */
public abstract class BaseActivity extends AppCompatActivity implements UiCallBack {
    protected Activity context;
    private Unbinder unbinder;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initBeforeView(savedInstanceState);
        this.context = this;
        //添加继承这个BaseActivity的Activity
        BaseApplication.getActivityManager().addActivity(this);
        if (getLayoutId() > 0) {
            setContentView(getLayoutId());
            unbinder = KnifeKit.bind(this);
        }
        initData(savedInstanceState);
    }
    @Override
    public void initBeforeView(Bundle savedInstanceState) {
    }
    @Override
    protected void onStart() {
        super.onStart();
    }
}


既然有了BaseActivity,当然也要有BaseFragment,

在base包下创建BaseFragment意思与BaseActivity接近


20200402155736829.png


代码如下:


package com.llw.mvplibrary.base;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.llw.mvplibrary.kit.KnifeKit;
import butterknife.Unbinder;
/**
 * 用于不需要请求网络接口的BaseFragment
 */
public abstract class BaseFragment extends Fragment implements UiCallBack {
    protected View rootView;
    protected LayoutInflater layoutInflater;
    protected Activity context;
    private Unbinder unbinder;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initBeforeView(savedInstanceState);
    }
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        layoutInflater = inflater;
        if (rootView == null) {
            rootView = inflater.inflate(getLayoutId(), null);
            unbinder = KnifeKit.bind(this, rootView);
        } else {
            ViewGroup viewGroup = (ViewGroup) rootView.getParent();
            if (viewGroup != null) {
                viewGroup.removeView(rootView);
            }
        }
        return rootView;
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initData(savedInstanceState);
    }
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof Activity) {
            this.context = (Activity) context;
        }
    }
    @Override
    public void onDetach() {
        super.onDetach();
        context = null;
    }
    @Override
    public void initBeforeView(Bundle savedInstanceState) {
    }
}


⑦ 创建mvp包(以及包下的Activity和Fragment


base需要的东西已经写完了。接下来创建一个mvp包,包下创建MvpActivity



代码如下:


package com.llw.mvplibrary.mvp;
import android.os.Bundle;
import com.llw.mvplibrary.base.BaseActivity;
import com.llw.mvplibrary.base.BasePresenter;
import com.llw.mvplibrary.base.BaseView;
/**
 * 适用于需要访问网络接口的Activity
 */
public abstract class MvpActivity<P extends BasePresenter> extends BaseActivity {
    protected P mPresent;
    @Override
    public void initBeforeView(Bundle savedInstanceState) {
        mPresent=createPresent();
        mPresent.attach((BaseView) this);
    }
    protected abstract P createPresent();
    @Override
    public void onDestroy() {
        super.onDestroy();
        mPresent.detach((BaseView) this);
    }
}


同样在mvp包下创建MvpFragment



代码如下:


package com.llw.mvplibrary.mvp;
import android.os.Bundle;
import com.llw.mvplibrary.base.BaseFragment;
import com.llw.mvplibrary.base.BasePresenter;
import com.llw.mvplibrary.base.BaseView;
/**
 * 适用于需要访问网络接口的Fragment
 */
public abstract class MvpFragment<P extends BasePresenter> extends BaseFragment {
    protected P mPresent;
    @Override
    public void initBeforeView(Bundle savedInstanceState) {
        mPresent=createPresent();
        mPresent.attach((BaseView) this);
    }
    @Override
    public void onDetach() {
        super.onDetach();
        if (mPresent!=null){
            mPresent.detach((BaseView) this);
        }
    }
    protected abstract P createPresent();
}


⑧ 创建net包(封装OKHttp,重写CallBack)


mvp包下的内容写完了,接下来配置网络访问

先创建一个net包 ,在这个包下新建一个ServiceGenerator


20200402161310108.png


代码如下:


package com.llw.mvplibrary.net;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
 * 服务构建器 API服务设置在里面
 */
public class ServiceGenerator {
    //https://free-api.heweather.net/s6/weather/now?key=3086e91d66c04ce588a7f538f917c7f4&location=深圳
    //将上方的API接口地址进行拆分得到不变的一部分,实际开发中可以将这一部分作为服务器的ip访问地址
    public static String BASE_URL = "https://free-api.heweather.net";//地址
    //创建服务  参数就是API服务
    public static <T> T createService(Class<T> serviceClass) {
        //创建OkHttpClient构建器对象
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
        //设置请求超时的时间,这里是10秒
        okHttpClientBuilder.connectTimeout(10000, TimeUnit.MILLISECONDS);
        //消息拦截器  因为有时候接口不同在排错的时候 需要先从接口的响应中做分析。利用了消息拦截器可以清楚的看到接口返回的所有内容
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        //setlevel用来设置日志打印的级别,共包括了四个级别:NONE,BASIC,HEADER,BODY
        //BASEIC:请求/响应行
        //HEADER:请求/响应行 + 头
        //BODY:请求/响应航 + 头 + 体
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        //为OkHttp添加消息拦截器
        okHttpClientBuilder.addInterceptor(httpLoggingInterceptor);
        //在Retrofit中设置httpclient
        Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)//设置地址  就是上面的固定地址,如果你是本地访问的话,可以拼接上端口号  例如 +":8080"
                .addConverterFactory(GsonConverterFactory.create())//用Gson把服务端返回的json数据解析成实体
                .client(okHttpClientBuilder.build())//放入OKHttp,之前说过retrofit是对OkHttp的进一步封装
                .build();
        return retrofit.create(serviceClass);//返回这个创建好的API服务
    }
}


接下来重写Callback,在,net包下新建NetCallBack


20200402161802290.png


代码如下:


package com.llw.mvplibrary.net;
import android.util.Log;
import com.google.gson.Gson;
import com.llw.mvplibrary.base.BaseResponse;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
 * 网络请求回调
 * @param <T>
 */
public abstract class NetCallBack<T> implements Callback<T> {//这里实现了retrofit2.Callback
    //访问成功回调
    @Override
    public void onResponse(Call<T> call, Response<T> response) {//数据返回
        if (response != null && response.body() != null && response.isSuccessful()) {
            BaseResponse baseResponse = new Gson().fromJson(new Gson().toJson(response.body()), BaseResponse.class);
            if (baseResponse.getCode() == 404) {//404
                Log.e("Warn",baseResponse.getData().toString());
            }else if(baseResponse.getCode() == 500) {//500
                Log.e("Warn",baseResponse.getData().toString());
            } else {//无异常则返回数据
                onSuccess(call, response);
                Log.e("Warn","其他情况");
            }
        } else {
            onFailed();
        }
    }
    //访问失败回调
    @Override
    public void onFailure(Call<T> call, Throwable t) {
        onFailed();
    }
    //数据返回
    public abstract void onSuccess(Call<T> call, Response<T> response);
    //失败异常
    public abstract void onFailed();
}


5. app使用MVP


至此,MVP框架就搭建完成了,接下来回到app项目中在com.llw.goodweather包下创建一个api包,在这个包下新建一个ApiService接口


① 创建API管理服务接口ApiService


20200402163816842.png

代码如下:

package com.llw.goodweather.api;
import com.llw.goodweather.bean.TodayResponse;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
 * API服务接口
 */
public interface ApiService {
    /**
     * 当天天气查询
     * https://free-api.heweather.net/s6/weather/now?key=3086e91d66c04ce588a7f538f917c7f4&location=深圳
     *   将地址进一步拆分,将可变的一部分放在注解@GET的地址里面,其中
     *   /s6/weather/now?key=3086e91d66c04ce588a7f538f917c7f4 这一部分在这个接口中又是不变的,变的是location的值
     *   所以将location的参数放入@Query里面,因为是使用的GET请求,所以里面的内容会拼接到地址后面,并且自动会加上 & 符号
     *   Call是retrofit2框架里面的,这个框架是对OKHttp的进一步封装,会让你的使用更加简洁明了,里面放入之前通过接口返回
     *   的JSON字符串生成返回数据实体Bean,Retrofit支持Gson解析实体类,所以,后面的返回值就不用做解析了。
     *   getTodayWeather是这个接口的方法名。这样说应该很清楚了吧
     * @param location  区/县
     * @return
     */
    @GET("/s6/weather/now?key=3086e91d66c04ce588a7f538f917c7f4")
    Call<TodayResponse> getTodayWeather(@Query("location") String location);
}


这里你要注意一点key的值用你自己的应用的KEY


② 订阅接口服务,处理API请求返回数据


接下来新建contract包,创建一个订阅器WeatherContract


20200402164825732.png


代码如下:


package com.llw.goodweather.contract;
import android.content.Context;
import com.llw.goodweather.api.ApiService;
import com.llw.goodweather.bean.TodayResponse;
import com.llw.mvplibrary.base.BasePresenter;
import com.llw.mvplibrary.base.BaseView;
import com.llw.mvplibrary.net.NetCallBack;
import com.llw.mvplibrary.net.ServiceGenerator;
import retrofit2.Call;
import retrofit2.Response;
/**
 * 天气订阅器
 */
public class WeatherContract {
    public static class WeatherPresenter extends BasePresenter<IWeatherView> {
        /**
         * 当日天气
         * @param context
         * @param location  区/县
         */
        public void todayWeather(final Context context, String location) {
            //得到构建之后的网络请求服务,这里的地址已经拼接完成,只差一个location了
            ApiService service = ServiceGenerator.createService(ApiService.class);
            //设置请求回调  NetCallBack是重写请求回调
            service.getTodayWeather(location).enqueue(new NetCallBack<TodayResponse>() {
                //成功回调
                @Override
                public void onSuccess(Call<TodayResponse> call, Response<TodayResponse> response) {
                    if (getView() != null) {//当视图不会空时返回请求数据
                        getView().getTodayWeatherResult(response);
                    }
                }
                //失败回调
                @Override
                public void onFailed() {
                    if (getView() != null) {//当视图不会空时获取错误信息
                        getView().getDataFailed();
                    }
                }
            });
        }
    }
    public interface IWeatherView extends BaseView {
        //将数据放入实体
        void getTodayWeatherResult(Response<TodayResponse> response);
        //错误返回
        void getDataFailed();
    }
}


③ 继承mvplibrary中的BaseApplication


接下来,在项目的com.llw.goodweather包下,新建一个WeatherApplication类继承模块中BaseApplication


20200402165057417.png


代码如下:


package com.llw.goodweather;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.webkit.WebView;
import com.llw.mvplibrary.BaseApplication;
import com.llw.mvplibrary.utils.ActivityManager;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.DefaultRefreshFooterCreator;
import com.scwang.smartrefresh.layout.api.DefaultRefreshHeaderCreator;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.footer.ClassicsFooter;
import com.scwang.smartrefresh.layout.header.ClassicsHeader;
public class WeatherApplication extends BaseApplication {
    /**
     * 应用实例
     */
    public static WeatherApplication weatherApplication;
    private static Context context;
    private static ActivityManager activityManager;
    private static Activity sActivity;
    public static Context getMyContext() {
        return weatherApplication == null ? null : weatherApplication.getApplicationContext();
    }
    private Handler myHandler;
    public Handler getMyHandler() {
        return myHandler;
    }
    public void setMyHandler(Handler handler) {
        myHandler = handler;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        activityManager = new ActivityManager();
        context = getApplicationContext();
        weatherApplication = this;
        this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }
            @Override
            public void onActivityStarted(Activity activity) {
                sActivity = activity;
            }
            @Override
            public void onActivityResumed(Activity activity) {
            }
            @Override
            public void onActivityPaused(Activity activity) {
            }
            @Override
            public void onActivityStopped(Activity activity) {
            }
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
            @Override
            public void onActivityDestroyed(Activity activity) {
            }
        });
    }
    public static ActivityManager getActivityManager() {
        return activityManager;
    }
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }
    //static 代码段可以防止内存泄露
    static {
        //设置全局的Header构建器
        SmartRefreshLayout.setDefaultRefreshHeaderCreator(new DefaultRefreshHeaderCreator() {
            @Override
            public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) {
                layout.setPrimaryColorsId(android.R.color.darker_gray, android.R.color.black);//全局设置主题颜色
                return new ClassicsHeader(context);//.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header
            }
        });
        //设置全局的Footer构建器
        SmartRefreshLayout.setDefaultRefreshFooterCreator(new DefaultRefreshFooterCreator() {
            @Override
            public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) {
                //指定为经典Footer,默认是 BallPulseFooter
                return new ClassicsFooter(context).setDrawableSize(20);
            }
        });
    }
}


④ 配置AndroidManifest.xml文件


接下来在AndroidManifest.xml文件中配置WeatherApplication


20200402165840782.png


由于Android9.0以后网络访问默认是https了,导致访问http类型的API接口访问不了,所以要配置项目允许访问http,所以在res文件下面新建一个xml的文件夹,在这个文件夹下新建名为的network_security_config.xml的网络配置文件,里面的配置代码如下:


<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>


接下来在AndroidManifest.xml文件中配置


2020040217035521.png


现在你可以运行一下,看你的项目有没有问题,早出现问题早解决。


现在框架已经搭好了,不过页面布局还没有写好的,所以要写一下页面了。


⑤ 编辑布局文件


activity_main.xml


<?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:gravity="center"
    android:fitsSystemWindows="true"
    android:background="@drawable/pic_bg"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <!--相对布局-->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!--透明度为0.3的黑色背景-->
        <LinearLayout
            android:background="#000"
            android:alpha="0.3"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        <!--主要的布局文件-->
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <!--标题 沉浸式-->
            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:contentInsetLeft="16dp"
                app:popupTheme="@style/AppTheme.PopupOverlay">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:textSize="16sp"
                    android:textColor="#FFF"
                    android:text="城市天气" />
            </androidx.appcompat.widget.Toolbar>
            <!--天气和所在城市 -->
            <LinearLayout
                android:gravity="center_horizontal"
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <!--天气状况-->
                <TextView
                    android:paddingLeft="16dp"
                    android:paddingTop="12dp"
                    android:id="@+id/tv_info"
                    android:textColor="#FFF"
                    android:textSize="18sp"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
                <!--温度-->
                <LinearLayout
                    android:gravity="top|center_horizontal"
                    android:layout_marginTop="20dp"
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">
                    <TextView
                        android:id="@+id/tv_temperature"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="0"
                        android:textColor="#FFF"
                        android:textSize="60sp" />
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:text="℃"
                        android:textColor="#FFF"
                        android:textSize="24sp" />
                </LinearLayout>
                <!--最高温和最低温-->
                <TextView
                    android:layout_marginTop="12dp"
                    android:id="@+id/tv_low_height"
                    android:textColor="#FFF"
                    android:textSize="@dimen/sp_14"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
                <!--城市-->
                <TextView
                    android:layout_marginTop="20dp"
                    android:id="@+id/tv_city"
                    android:textColor="#FFF"
                    android:text="城市"
                    android:textSize="20sp"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
                <!--上一次更新时间-->
                <TextView
                    android:layout_marginTop="8dp"
                    android:id="@+id/tv_old_time"
                    android:textColor="#FFF"
                    android:text="上次更新时间:"
                    android:textSize="@dimen/sp_12"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
            </LinearLayout>
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

背景图

20200402173855541.png


修改res文件下styles.xml文件


<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>


这样你的布局文件应该就没有报错的红线了。


然后看到MainActivity.java中的这个TextView报错,因为布局文件中已经去掉了这个TextView。


20200402174831300.png


20200402175117218.png

删除即可


然后再绑定布局中控件


  @BindView(R.id.tv_info)
    TextView tvInfo;//天气状况
    @BindView(R.id.tv_temperature)
    TextView tvTemperature;//温度
    @BindView(R.id.tv_low_height)
    TextView tvLowHeight;//最高温和最低温
    @BindView(R.id.tv_city)
    TextView tvCity;//城市
    @BindView(R.id.tv_old_time)
    TextView tvOldTime;//最近更新时间


⑥ 天气查询(使用MVPActivity实现数据请求与数据渲染显示)


接下来进行使用MVP框架数据请求,删除getTodayWeather()方法。修改后的MainActivity代码如下所示


package com.llw.goodweather;
import android.Manifest;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.llw.goodweather.bean.TodayResponse;
import com.llw.goodweather.contract.WeatherContract;
import com.llw.goodweather.utils.ToastUtils;
import com.llw.mvplibrary.mvp.MvpActivity;
import com.tbruyelle.rxpermissions2.RxPermissions;
import java.io.IOException;
import butterknife.BindView;
import butterknife.ButterKnife;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import retrofit2.Response;
public class MainActivity extends MvpActivity<WeatherContract.WeatherPresenter> implements WeatherContract.IWeatherView {
    @BindView(R.id.tv_info)
    TextView tvInfo;//天气状况
    @BindView(R.id.tv_temperature)
    TextView tvTemperature;//温度
    @BindView(R.id.tv_low_height)
    TextView tvLowHeight;//最高温和最低温
    @BindView(R.id.tv_city)
    TextView tvCity;//城市
    @BindView(R.id.tv_old_time)
    TextView tvOldTime;//最近更新时间
    private RxPermissions rxPermissions;//权限请求框架
    //定位器
    public LocationClient mLocationClient = null;
    private MyLocationListener myListener = new MyLocationListener();
    //数据初始化  主线程,onCreate方法可以删除了,把里面的代码移动这个initData下面
    @Override
    public void initData(Bundle savedInstanceState) {
        //因为这个框架里面已经放入了绑定,所以这行代码可以注释掉了。
        //ButterKnife.bind(this);
        rxPermissions = new RxPermissions(this);//实例化这个权限请求框架,否则会报错
        permissionVersion();//权限判断
    }
    //绑定布局文件
    @Override
    public int getLayoutId() {
        return R.layout.activity_main;
    }
    //绑定Presenter ,这里不绑定会报错
    @Override
    protected WeatherContract.WeatherPresenter createPresent() {
        return new WeatherContract.WeatherPresenter();
    }
    //权限判断
    private void permissionVersion() {
        if (Build.VERSION.SDK_INT >= 23) {//6.0或6.0以上
            //动态权限申请
            permissionsRequest();
        } else {//6.0以下
            //发现只要权限在AndroidManifest.xml中注册过,均会认为该权限granted  提示一下即可
            ToastUtils.showShortToast(this, "你的版本在Android6.0以下,不需要动态申请权限。");
        }
    }
    //动态权限申请
    private void permissionsRequest() {//使用这个框架需要制定JDK版本,建议用1.8
        rxPermissions.request(Manifest.permission.ACCESS_FINE_LOCATION)
                .subscribe(granted -> {
                    if (granted) {//申请成功
                        //得到权限之后开始定位
                        startLocation();
                    } else {//申请失败
                        ToastUtils.showShortToast(this, "权限未开启");
                    }
                });
    }
    //定位
    private void startLocation() {
        //声明LocationClient类
        mLocationClient = new LocationClient(this);
        //注册监听函数
        mLocationClient.registerLocationListener(myListener);
        LocationClientOption option = new LocationClientOption();
        //如果开发者需要获得当前点的地址信息,此处必须为true
        option.setIsNeedAddress(true);
        //可选,设置是否需要最新版本的地址信息。默认不需要,即参数为false
        option.setNeedNewVersionRgc(true);
        //mLocationClient为第二步初始化过的LocationClient对象
        //需将配置好的LocationClientOption对象,通过setLocOption方法传递给LocationClient对象使用
        mLocationClient.setLocOption(option);
        //启动定位
        mLocationClient.start();
    }
    /**
     * 定位结果返回
     */
    private class MyLocationListener extends BDAbstractLocationListener {
        @Override
        public void onReceiveLocation(BDLocation location) {
            //获取区/县
            String district = location.getDistrict();
            //获取今天的天气数据
            mPresent.todayWeather(context,district);
        }
    }
    //查询当天天气,请求成功后的数据返回
    @Override
    public void getTodayWeatherResult(Response<TodayResponse> response) {
        //数据返回后关闭定位
        mLocationClient.stop();
        if (response.body().getHeWeather6().get(0).getBasic() != null) {//得到数据不为空则进行数据显示
            //数据渲染显示出来
            tvTemperature.setText(response.body().getHeWeather6().get(0).getNow().getTmp());//温度
            tvCity.setText(response.body().getHeWeather6().get(0).getBasic().getLocation());//城市
            tvInfo.setText(response.body().getHeWeather6().get(0).getNow().getCond_txt());//天气状况
            tvOldTime.setText("上次更新时间:" + response.body().getHeWeather6().get(0).getUpdate().getLoc());
        } else {
            ToastUtils.showShortToast(context, response.body().getHeWeather6().get(0).getStatus());
        }
    }
    //数据请求失败返回
    @Override
    public void getDataFailed() {
        ToastUtils.showShortToast(context,"网络异常");//这里的context是框架中封装好的,等同于this
    }
}


写完之后就可以直接运行了,运行效果图如下:

20200403145630378.png


可以看到,已经得到天气数据了,只不过美中不足,上面的状态栏是原生的颜色,原谅绿,这个颜色不吉利啊。我们换一下。这个时候就可以用到透明状态栏,这种东西了,在utils包下新建一个StatusBarUtil工具类


2020040315044563.png


工具类代码如下:


package com.llw.goodweather.utils;
import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import com.readystatesoftware.systembartint.SystemBarTintManager;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
 * 状态栏工具类
 */
public class StatusBarUtil {
    /**
     * 修改状态栏为全透明
     *
     * @param activity
     */
    @TargetApi(19)
    public static void transparencyBar(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Window window = activity.getWindow();
            window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }
    /**
     * 修改状态栏颜色,支持4.4以上版本
     *
     * @param activity
     * @param colorId
     */
    public static void setStatusBarColor(Activity activity, int colorId) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
//      window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(activity.getResources().getColor(colorId));
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明
            transparencyBar(activity);
            SystemBarTintManager tintManager = new SystemBarTintManager(activity);
            tintManager.setStatusBarTintEnabled(true);
            tintManager.setStatusBarTintResource(colorId);
        }
    }
    /**
     * 状态栏亮色模式,设置状态栏黑色文字、图标,
     * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
     *
     * @param activity
     * @return 1:MIUUI 2:Flyme 3:android6.0
     */
    public static int StatusBarLightMode(Activity activity) {
        int result = 0;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (MIUISetStatusBarLightMode(activity, true)) {
                result = 1;
            } else if (FlymeSetStatusBarLightMode(activity.getWindow(), true)) {
                result = 2;
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                result = 3;
            }
        }
        return result;
    }
    /**
     * 已知系统类型时,设置状态栏黑色文字、图标。
     * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
     *
     * @param activity
     * @param type     1:MIUUI 2:Flyme 3:android6.0
     */
    public static void StatusBarLightMode(Activity activity, int type) {
        if (type == 1) {
            MIUISetStatusBarLightMode(activity, true);
        } else if (type == 2) {
            FlymeSetStatusBarLightMode(activity.getWindow(), true);
        } else if (type == 3) {
            activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }
    }
    /**
     * 状态栏暗色模式,清除MIUI、flyme或6.0以上版本状态栏黑色文字、图标
     */
    public static void StatusBarDarkMode(Activity activity, int type) {
        if (type == 1) {
            MIUISetStatusBarLightMode(activity, false);
        } else if (type == 2) {
            FlymeSetStatusBarLightMode(activity.getWindow(), false);
        } else if (type == 3) {
            activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
        }
    }
    /**
     * 设置状态栏图标为深色和魅族特定的文字风格
     * 可以用来判断是否为Flyme用户
     *
     * @param window 需要设置的窗口
     * @param dark   是否把状态栏文字及图标颜色设置为深色
     * @return boolean 成功执行返回true
     */
    public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
        boolean result = false;
        if (window != null) {
            try {
                WindowManager.LayoutParams lp = window.getAttributes();
                Field darkFlag = WindowManager.LayoutParams.class
                        .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
                Field meizuFlags = WindowManager.LayoutParams.class
                        .getDeclaredField("meizuFlags");
                darkFlag.setAccessible(true);
                meizuFlags.setAccessible(true);
                int bit = darkFlag.getInt(null);
                int value = meizuFlags.getInt(lp);
                if (dark) {
                    value |= bit;
                } else {
                    value &= ~bit;
                }
                meizuFlags.setInt(lp, value);
                window.setAttributes(lp);
                result = true;
            } catch (Exception e) {
            }
        }
        return result;
    }
    /**
     * 需要MIUIV6以上
     *
     * @param activity
     * @param dark     是否把状态栏文字及图标颜色设置为深色
     * @return boolean 成功执行返回true
     */
    public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) {
        boolean result = false;
        Window window = activity.getWindow();
        if (window != null) {
            Class clazz = window.getClass();
            try {
                int darkModeFlag = 0;
                Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
                Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
                darkModeFlag = field.getInt(layoutParams);
                Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
                if (dark) {
                    extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//状态栏透明且黑色字体
                } else {
                    extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
                }
                result = true;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    //开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上
                    if (dark) {
                        activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                    } else {
                        activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
                    }
                }
            } catch (Exception e) {
            }
        }
        return result;
    }
}


接下来,在MainActivity.java中调用即可


20200403151119618.png


StatusBarUtil.transparencyBar(context);//透明状态栏

然后再运行一下


20200403151249241.png


不管怎么说,都比原谅绿好看。


现在查询当天的天气是可以了,但是都说是天气预报了,当然也要有啊,否则不就是骗人了吗?OK


相关文章
|
1月前
|
XML Java 数据库
安卓项目:app注册/登录界面设计
本文介绍了如何设计一个Android应用的注册/登录界面,包括布局文件的创建、登录和注册逻辑的实现,以及运行效果的展示。
151 0
安卓项目:app注册/登录界面设计
|
17天前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
63 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
131 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
21天前
|
前端开发 Java 测试技术
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
30 0
|
1月前
|
前端开发 Java 测试技术
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
22 2
|
2月前
|
存储 开发工具 Android开发
使用.NET MAUI开发第一个安卓APP
【9月更文挑战第24天】使用.NET MAUI开发首个安卓APP需完成以下步骤:首先,安装Visual Studio 2022并勾选“.NET Multi-platform App UI development”工作负载;接着,安装Android SDK。然后,创建新项目时选择“.NET Multi-platform App (MAUI)”模板,并仅针对Android平台进行配置。了解项目结构,包括`.csproj`配置文件、`Properties`配置文件夹、平台特定代码及共享代码等。
187 2
|
2月前
|
XML Android开发 数据格式
🌐Android国际化与本地化全攻略!让你的App走遍全球无障碍!🌍
在全球化背景下,实现Android应用的国际化与本地化至关重要。本文以一款旅游指南App为例,详细介绍如何通过资源文件拆分与命名、适配布局与方向、处理日期时间及货币格式、考虑文化习俗等步骤,完成多语言支持和本地化调整。通过邀请用户测试并收集反馈,确保应用能无缝融入不同市场,提升用户体验与满意度。
107 3
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
74 10
|
1月前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
246 0
|
2月前
|
XML 数据库 Android开发
10分钟手把手教你用Android手撸一个简易的个人记账App
该文章提供了使用Android Studio从零开始创建一个简单的个人记账应用的详细步骤,包括项目搭建、界面设计、数据库处理及各功能模块的实现方法。
下一篇
无影云桌面