4. MVP框架搭建
现在这样固然符合网络请求的标准,结果也得到了,但是这只是一个接口而已,我们用了这么多代码,那假如这个页面上还有好几个接口要请求访问,岂不是多出了很多的重复代码,这一点并不符合现在Android的现状,所以需要封装OKHttp,通过架构或者框架来完成这一步,前期虽然麻烦一些,但是你一旦用习惯了,就停不下来了,接下来我尽量用人话来讲述这个搭建过程。
为了让你有一个清晰的思路,这里创建一个模块,里面搭建MVP框架。
① 创建模块
鼠标右键你的项目名,选择Module
点击Finish
现在模块就创建完成了。
② 配置模块
接下来修改模块的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
implementation project(':mvplibrary')//引入模块 然后将项目里的依赖移动到模块的build.gradle里
然后Sync一下,如果没有出现什么问题就可以进行下一步了。
③ 创建Activity管理
在模块的com.llw.mvplibrary包下新建一个utils包,包下创建一个ActivityManagerl类,管理所有的Activity
代码如下:
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,作为全局管理
代码如下:
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类
代码如下:
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接口
代码如下:
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接口
代码如下:
package com.llw.mvplibrary.base; /** * 只是一个接口BaseView ,里面可以自由定制 */ public interface BaseView { }
base包下面创建一个BasePresenter类
代码如下:
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
代码如下:
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
代码如下,这个里面还有进一步优化的空间,后面会提到的。
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接近
代码如下:
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类
代码如下:
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类
代码如下:
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
代码如下:
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类
代码如下:
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
代码如下:
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
由于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文件中配置
现在你可以运行一下,看你的项目有没有问题,早出现问题早解决。
现在框架已经搭好了,不过页面布局还没有写好的,所以要写一下页面了。
⑤ 编辑布局文件
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>
背景图
修改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。
删除即可
然后再绑定布局中控件
@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 } }
写完之后就可以直接运行了,运行效果图如下:
可以看到,已经得到天气数据了,只不过美中不足,上面的状态栏是原生的颜色,原谅绿,这个颜色不吉利啊。我们换一下。这个时候就可以用到透明状态栏,这种东西了,在utils包下新建一个StatusBarUtil工具类
工具类代码如下:
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中调用即可
StatusBarUtil.transparencyBar(context);//透明状态栏
然后再运行一下
不管怎么说,都比原谅绿好看。
现在查询当天的天气是可以了,但是都说是天气预报了,当然也要有啊,否则不就是骗人了吗?OK