前言
其实之前我是写过相关的MVP文章的,只不过当时在是天气APP中,而这里是单独拉出来讲,因此会有一些不同。
正文
先在Android Studio中创建一个名为MvpDemo的项目。
创建好如下图所示
一、创建mvp模块
再创建一个依赖模块,File → New → New Module…
选择Android Library
Next,修改一下模块名字
点击Finish,你的mvplibrary模块就创建完成了。
下面在app模块中依赖mvplibrary模块。
在AS的右上角工具栏中找到上图中的图标按钮,点击进入如下页面。
然后通过上面的操作,到最后一步点击这个Module Dependency会出现如下页面
勾选上,点击OK。
你会发现这里多了一个依赖,然后点击OK即可。现在已经依赖好了,下面就是MVP框架的搭建了,搭建过程中都是在mvplibrary中,与app模块无关。
二、搭建MVP框架
1. 创建Activity管理
首先在com.llw.mvplibrary下新建一个ActivityManager类,里面的代码如下:
package com.llw.mvplibrary; import android.app.Activity; import java.util.ArrayList; import java.util.List; /** * 管理Activity * @author llw */ public class ActivityManager { //保存所有创建的Activity private List<Activity> activityList = new ArrayList<>(); /** * 添加Activity * @param activity */ public void addActivity(Activity activity){ if(activity != null){ activityList.add(activity); } } /** * 移除Activity * @param activity */ public void removeActivity(Activity activity){ if(activity != null){ activityList.remove(activity); } } /** * 关闭所有Activity */ public void finishAllActivity(){ for (Activity activity : activityList) { activity.finish(); } } }
3. 创建base包(以及包下的类和接口)
在com.llw.mvplibrary下新建一个base包,然后在这个包下面新建一个IUiCallback接口,里面代码如下:
package com.llw.mvplibrary.base; import android.os.Bundle; /** * UI回调接口 * @author llw */ public interface IUiCallback { void initBeforeView(Bundle savedInstanceState); //初始化视图 void initData(Bundle savedInstanceState); //获取布局Id int getLayoutId(); }
之后再新建一个BaseView接口,这是一个空接口。
package com.llw.mvplibrary.base; /** * 基类View,可以根据实际情况写方法 * @author llw */ public interface BaseView { }
然后创建一个BasePresenter,在这里可以操作View,这是MVP中的核心思想,通过P层控制M和V,从而减低M和V的耦合,甚至让它们不存在直接关联。
package com.llw.mvplibrary.base; import java.lang.ref.WeakReference; /** * Presenter基类 操作视图View * * @param <V> * @author llw */ public class BasePresenter<V extends BaseView> { //弱引用View protected WeakReference<V> mWeakReference; private V mView; /** * 绑定View * * @param view */ public void attachView(V view) { mView = view; mWeakReference = new WeakReference<V>(view); } /** * 解绑View */ public void detachView() { mView = null; if (mWeakReference != null) { mWeakReference.clear(); mWeakReference = null; } } /** * 获取view * * @return */ public V getView() { if (mWeakReference != null) { return mWeakReference.get(); } return null; } /** * View是否绑定 * * @return */ public boolean isViewAttached() { return mView != null; } }
下面写BaseActivity,一般的Activity只要继承这个BaseActivity,重写里面的方法即可。
package com.llw.mvplibrary.base; import android.app.Activity; import android.app.Dialog; import android.os.Bundle; import android.view.View; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import com.llw.mvplibrary.R; import java.util.Objects; /** * 基类Activity,普通Activity继承即可。 * * @author llw */ public abstract class BaseActivity extends AppCompatActivity implements IUiCallback { //Activity 上下文 protected Activity context; //弹窗 private Dialog mDialog; private static final int FAST_CLICK_DELAY_TIME = 500; private static long lastClickTime; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //绑定视图 initBeforeView(savedInstanceState); //获取Activity的上下文 this.context = this; BaseApplication.getActivityManager().addActivity(this); //绑定视图XML if (getLayoutId() > 0) { setContentView(getLayoutId()); } initData(savedInstanceState); } /** * Toast消息提示 字符 * @param llw */ protected void showMsg(CharSequence llw) { Toast.makeText(context, llw, Toast.LENGTH_SHORT).show(); } /** * Toast消息提示 资源ID * @param resourceId */ protected void showMsg(int resourceId){ Toast.makeText(context, resourceId, Toast.LENGTH_SHORT).show(); } /** * 弹窗出现 */ protected void showLoadingDialog() { if (mDialog == null) { mDialog = new Dialog(context, R.style.loading_dialog); } mDialog.setContentView(R.layout.dialog_loading); mDialog.setCancelable(false); Objects.requireNonNull(mDialog.getWindow()).setBackgroundDrawableResource(android.R.color.transparent); mDialog.show(); } /** * 弹窗隐藏 */ protected void hideLoadingDialog() { if (mDialog != null) { mDialog.dismiss(); } mDialog = null; } /** * 返回 不需要参数 */ protected void Back(){ context.finish(); if(!isFastClick()){ context.finish(); } } /** * 返回 toolbar控件点击 * * @param toolbar */ protected void Back(Toolbar toolbar) { toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { context.finish(); if (!isFastClick()) { context.finish(); } } }); } /** * 两次点击间隔不能少于500ms * * @return flag */ protected static boolean isFastClick() { boolean flag = true; long currentClickTime = System.currentTimeMillis(); if ((currentClickTime - lastClickTime) >= FAST_CLICK_DELAY_TIME) { flag = false; } lastClickTime = currentClickTime; return flag; } }
一般来说这样就可以了,但是以我的开发习惯来说我还会加上这个页面加载的弹窗和页面销毁的控制,那么下午我给加上。
首先需要在drawable下添加两个图片,这两个图片有一些特殊,建议你直接在我的源码里面复制出来,因为我现在贴出来你拿过去直接用是达不到实际的效果的。
然后com.llw.mvplibrary下新建一个view包,然后新建一个LoadingView类,里面的代码如下:
package com.llw.mvplibrary.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.util.AttributeSet; import androidx.annotation.Nullable; import com.llw.mvplibrary.R; import java.lang.ref.SoftReference; /** * 加载框 * @author llw */ public class LoadingView extends androidx.appcompat.widget.AppCompatImageView { private int mCenterRotateX;//图片旋转点x private int mCenterRotateY;//图片旋转点y private LoadingRunnable mRunnable; public LoadingView(Context context) { this(context, null); } public LoadingView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { setScaleType(ScaleType.MATRIX); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_loading); setImageBitmap(bitmap); mCenterRotateX = bitmap.getWidth() / 2; mCenterRotateY = bitmap.getHeight() / 2; } /** * onDraw()之前调用 */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mRunnable==null){ mRunnable=new LoadingRunnable(this); } if (!mRunnable.isLoading){ mRunnable.start(); } } /** * view销毁时调用 */ @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mRunnable!=null){ mRunnable.stop(); } mRunnable=null; } class LoadingRunnable implements Runnable { private boolean isLoading; private Matrix mMatrix; private SoftReference<LoadingView> mLoadingViewSoftReference; private float mDegrees = 0f; public LoadingRunnable(LoadingView loadingView) { mLoadingViewSoftReference = new SoftReference<LoadingView>(loadingView); mMatrix = new Matrix(); } @Override public void run() { if (mLoadingViewSoftReference.get().mRunnable != null && mMatrix != null) { mDegrees += 30f; mMatrix.setRotate(mDegrees, mCenterRotateX, mCenterRotateY); mLoadingViewSoftReference.get().setImageMatrix(mMatrix); if (mDegrees==360){ mDegrees=0f; } if (isLoading) { mLoadingViewSoftReference.get().postDelayed(mLoadingViewSoftReference.get().mRunnable, 100); } } } public void stop() { isLoading = false; } public void start() { isLoading = true; if (mLoadingViewSoftReference.get().mRunnable != null && mMatrix != null) { mLoadingViewSoftReference.get().postDelayed(mLoadingViewSoftReference.get().mRunnable, 100); } } } }
再新建一个LoadingTextView,里面的代码如下:
package com.llw.mvplibrary.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Shader; import android.util.AttributeSet; import androidx.appcompat.widget.AppCompatTextView; /** * 颜色波浪TextView * @author llw */ public class LoadingTextView extends AppCompatTextView { private LinearGradient mLinearGradient; private Matrix mGradientMatrix; private Paint mPaint; private int mViewWidth = 0; private int mTranslate = 0; private boolean mAnimating = true; public LoadingTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mViewWidth == 0) { mViewWidth = getMeasuredWidth(); if (mViewWidth > 0) { mPaint = getPaint(); mLinearGradient = new LinearGradient(-mViewWidth, 0, 0, 0, new int[]{0x33ffffff, 0xffd81e06, 0x33ffffff}, new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP); mPaint.setShader(mLinearGradient); mGradientMatrix = new Matrix(); } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mAnimating && mGradientMatrix != null) { mTranslate += mViewWidth / 10; if (mTranslate > 2 * mViewWidth) { mTranslate = -mViewWidth; } mGradientMatrix.setTranslate(mTranslate, 0); mLinearGradient.setLocalMatrix(mGradientMatrix); postInvalidateDelayed(20); } } }
然后在res下新建一个layout文件夹,在这个文件夹下新建一个dialog_loading.xml,里面的布局代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout_loading" android:layout_width="120dp" android:layout_height="120dp" android:layout_gravity="center" android:background="@drawable/ic_loading_bg" android:gravity="center" android:orientation="vertical"> <!--旋转的图--> <com.llw.mvplibrary.view.LoadingView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <!--变色的字--> <com.llw.mvplibrary.view.LoadingTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="Loading" android:textColor="#fff" android:textSize="@dimen/sp_14" /> </LinearLayout>
还需要在valuse下新建一个styles.xml,里面的代码如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <!--加载弹窗的样式--> <style name="loading_dialog" parent="@android:style/Theme.Dialog"> <item name="android:windowFrame">@null</item> <item name="android:windowIsFloating">true</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowNoTitle">true</item> <item name="android:background">@null</item> <item name="android:windowBackground">@null</item> <item name="android:backgroundDimEnabled">false</item> </style> </resources>
然后在BaseActivity中新增如下两个方法。
//弹窗 private Dialog mDialog; /** * 弹窗出现 */ protected void showLoadingDialog() { if (mDialog == null) { mDialog = new Dialog(context, R.style.loading_dialog); } mDialog.setContentView(R.layout.dialog_loading); mDialog.setCancelable(false); Objects.requireNonNull(mDialog.getWindow()).setBackgroundDrawableResource(android.R.color.transparent); mDialog.show(); } /** * 弹窗隐藏 */ protected void hideLoadingDialog() { if (mDialog != null) { mDialog.dismiss(); } mDialog = null; }
然后再增加页面返回的处理。
private static final int FAST_CLICK_DELAY_TIME = 500; private static long lastClickTime; /** * 返回 不需要参数 */ protected void Back(){ context.finish(); if(!isFastClick()){ context.finish(); } } /** * 返回 toolbar控件点击 * * @param toolbar */ protected void Back(Toolbar toolbar) { toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { context.finish(); if (!isFastClick()) { context.finish(); } } }); } /** * 两次点击间隔不能少于500ms * * @return flag */ protected static boolean isFastClick() { boolean flag = true; long currentClickTime = System.currentTimeMillis(); if ((currentClickTime - lastClickTime) >= FAST_CLICK_DELAY_TIME) { flag = false; } lastClickTime = currentClickTime; return flag; }
这一步为止,这个BaseActivity就写完了,下面该写BaseFragment了。
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.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; /** * 基类Fragment,普通Fragment继承即可。 * @author llw */ public abstract class BaseFragment extends Fragment implements IUiCallback { protected View rootView; protected LayoutInflater layoutInflater; protected Activity context; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initBeforeView(savedInstanceState); } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { layoutInflater = inflater; if(rootView == null){ rootView = inflater.inflate(getLayoutId(),null); }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); } /** * 绑定 * @param context */ @Override public void onAttach(@NonNull 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) { } }
现在这个base包下的内容就写完了,该到mvp包了。
4. 创建mvp包(以及包下的Activity和Fragment)
在com.llw.mvplibrary下创建一个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 * * @author llw */ public abstract class MvpActivity<P extends BasePresenter> extends BaseActivity { protected P mPresenter; /** * 创建Presenter */ protected abstract P createPresenter(); @Override public void initBeforeView(Bundle savedInstanceState) { //创建 mPresenter = createPresenter(); //绑定View mPresenter.attachView((BaseView) this); } /** * 页面销毁时解除绑定 */ @Override protected void onDestroy() { super.onDestroy(); mPresenter.detachView(); } }
然后创建一个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 * * @author llw */ public abstract class MvpFragment<P extends BasePresenter> extends BaseFragment { protected P mPresenter; /** * 创建Presenter */ protected abstract P createPresent(); @Override public void initBeforeView(Bundle savedInstanceState) { mPresenter = createPresent(); mPresenter.attachView((BaseView) this); } @Override public void onDestroy() { super.onDestroy(); mPresenter.detachView(); } }
它里面的代码其实和MvpActivity差不多,唯一区别就是继承的父类不同。
mvp包中的代码就写完了,下面就到网络请求的使用了,这里我会采用我之前写的一个网络访问框架,把它融合到这个Mvp框架中,成为一体,如果你还没有了解过网络访问框架的话,不妨看看这一篇文章Android OkHttp+Retrofit+RxJava搭建网络访问框架,相信对你有所帮助,那么为了避免一些麻烦我直接去Github上面把源码下载下来。
5. 结合网络访问框架
进入源码地址:NetworkFrameWorkDemo
下载到本地,然后解压。
然后打开到最下面的network包
将这个文件夹复制到com.llw.mvplibrary下。如下图所示
然后找到res下这四个文件夹,全部复制
粘贴到你项目的res下
然后修改一下network包下一些类的包异常问题,建议你把这个里面的每一个类或者接口都打开一次,报红的就是里面的包路径需要修改的。
然后可以在app模块中使用了,使用过程可能一开始有一些麻烦,但是当你熟悉之后就好了。
三、使用MVP框架
通过上面的一系列搭建MVP框架依赖模块的过程,目前就已经完成了,那么接下来就到了使用阶段,既然是使用那么自然而然就是在app模块中了,当前这个模块中只有一个MainActivity。
1. 创建application包以及下面的类
在com.llw.mvpdemo下新建一个application包,在这个包下创建一个NetworkRequiredInfo类,里面实现network包下的INetworkRequiredInfo接口,目的就是获取APP运行时的一些信息,里面的代码如下:
package com.llw.mvpdemo.application; import android.app.Application; import com.llw.mvplibrary.BuildConfig; import com.llw.mvplibrary.network.INetworkRequiredInfo; /** * 网络访问信息 * @author llw */ public class NetworkRequiredInfo implements INetworkRequiredInfo { private Application application; public NetworkRequiredInfo(Application application){ this.application = application; } /** * 版本名 */ @Override public String getAppVersionName() { return BuildConfig.VERSION_NAME; } /** * 版本号 */ @Override public String getAppVersionCode() { return String.valueOf(BuildConfig.VERSION_CODE); } /** * 是否为debug */ @Override public boolean isDebug() { return BuildConfig.DEBUG; } /** * 应用全局上下文 */ @Override public Application getApplicationContext() { return application; } }
然后同样在这个application包下新建一个MyApplication类
package com.llw.mvpdemo.application; import com.llw.mvplibrary.BaseApplication; import com.llw.mvplibrary.network.NetworkApi; /** * 自定义Application * @author llw */ public class MyApplication extends BaseApplication { @Override public void onCreate() { super.onCreate(); //初始化 NetworkApi.init(new NetworkRequiredInfo(this)); } }
这里面主要完成对网络的初始化,在这个init方法中,完成了对环境的配置
你如果你对这一块并不了解,你可以先看看Android OkHttp+Retrofit+RxJava搭建网络访问框架这篇文章,相信你就明白了,因为内容实在比较多,因此写到一起你可能不太有耐心看完。
刚才写了MyApplication,自然要在AndroidManifest.xml中配置才行,如果你不配置则就是使用系统的Application。
这样就配置好了
2. 创建ApiService接口
最好有一个地方可以集中写一些接口,因为在实际开发中,一个服务器中不可能就一个接口,因此前面的地址和后面的参数是可以分开的。
下面在com.llw.mvpdemo下新建一个api包,这个包下新建一个ApiService接口
package com.llw.mvpdemo.api; import com.llw.mvpdemo.bean.WallPaperResponse; import io.reactivex.Observable; import retrofit2.http.GET; /** * ApiService接口 统一管理应用所有的接口 * @author llw */ public interface ApiService { /** * 获取热门壁纸列表 */ @GET("/v1/vertical/vertical?limit=30&skip=180&adult=false&first=0&order=hot") Observable<WallPaperResponse> getWallPaper(); }
现在你的这个WallPaperResponse肯定是报红的,因为没有所以需要创建一个,这个实体Bean的意思就是当你请求网络接口,会将返回的数据解析成这样的一个实体,而Observable则是用来出来OkHttp的返回数据的。如果你使用Retrofit来处理返回这里就要改成Call,这种方式你可以参考我在天气APP中的网络写法看看。
之前的请求地址现在已经访问不了了,也有读者和我反映这个情况,痛定思痛之后,我决定更改一个访问API,将返回的数据进行一次修改。新的访问地址如下:
http://service.picasso.adesk.com/v1/vertical/vertical?limit=30&skip=180&adult=false&first=0&order=hot
这个可以用浏览器访问一下:
可以拿到结果,这说明这个API目前还是可以使用的,那么下面增加一个新的返回实体Bean。
下面创建一个这样返回数据类。这里最好的方式是在com.llw.mvpdemo下再建一个bean包或者model包,看个人习惯了,我个人习惯建bean包,然后包下新建一个WallPaperResponse类,类的代码如下:
public class WallPaperResponse { private String msg; private ResBean res; private int code; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public ResBean getRes() { return res; } public void setRes(ResBean res) { this.res = res; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public static class ResBean { private List<VerticalBean> vertical; public List<VerticalBean> getVertical() { return vertical; } public void setVertical(List<VerticalBean> vertical) { this.vertical = vertical; } public static class VerticalBean { private String preview; private String thumb; private String img; private int views; private String rule; private int ncos; private int rank; private String source_type; private String wp; private boolean xr; private boolean cr; private int favs; private double atime; private String id; private String store; private String desc; private List<String> cid; private List<?> tag; private List<?> url; public String getPreview() { return preview; } public void setPreview(String preview) { this.preview = preview; } public String getThumb() { return thumb; } public void setThumb(String thumb) { this.thumb = thumb; } public String getImg() { return img; } public void setImg(String img) { this.img = img; } public int getViews() { return views; } public void setViews(int views) { this.views = views; } public String getRule() { return rule; } public void setRule(String rule) { this.rule = rule; } public int getNcos() { return ncos; } public void setNcos(int ncos) { this.ncos = ncos; } public int getRank() { return rank; } public void setRank(int rank) { this.rank = rank; } public String getSource_type() { return source_type; } public void setSource_type(String source_type) { this.source_type = source_type; } public String getWp() { return wp; } public void setWp(String wp) { this.wp = wp; } public boolean isXr() { return xr; } public void setXr(boolean xr) { this.xr = xr; } public boolean isCr() { return cr; } public void setCr(boolean cr) { this.cr = cr; } public int getFavs() { return favs; } public void setFavs(int favs) { this.favs = favs; } public double getAtime() { return atime; } public void setAtime(double atime) { this.atime = atime; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getStore() { return store; } public void setStore(String store) { this.store = store; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public List<String> getCid() { return cid; } public void setCid(List<String> cid) { this.cid = cid; } public List<?> getTag() { return tag; } public void setTag(List<?> tag) { this.tag = tag; } public List<?> getUrl() { return url; } public void setUrl(List<?> url) { this.url = url; } } } }
下面就该到主角登场了,那就是一个可以把M、V、P结合起来使用的东西。
3. 创建订阅合同
在com.llw.mvpdemo下新建一个contract包,这个包下新建一个MainContract类,里面的代码如下:
package com.llw.mvpdemo.contract; import android.annotation.SuppressLint; import com.llw.mvpdemo.api.ApiService; import com.llw.mvpdemo.bean.GankResponse; import com.llw.mvplibrary.base.BasePresenter; import com.llw.mvplibrary.base.BaseView; import com.llw.mvplibrary.network.NetworkApi; import com.llw.mvplibrary.network.observer.BaseObserver; /** * 将V与M订阅起来 * @author llw */ public class MainContract { public static class MainPresenter extends BasePresenter<IMainView> { @SuppressLint("CheckResult") public void getWallPaper(){ ApiService service = NetworkApi.createService(ApiService.class); service.getWallPaper().compose(NetworkApi.applySchedulers(new BaseObserver<WallPaperResponse>() { @Override public void onSuccess(WallPaperResponse wallPaperResponse) { if (getView() != null) { getView().getWallPaper(wallPaperResponse); } } @Override public void onFailure(Throwable e) { if (getView() != null) { getView().getWallPaperFailed(e); } } })); } } public interface IMainView extends BaseView { void getWallPaper(WallPaperResponse wallPaperResponse); //获取列表失败返回 void getWallPaperFailed(Throwable e); } }
请仔细的看看这些代码,你就明白这个类做了什么。
4. 数据渲染
下面要显示数据了,不过这个数据是要显示在列表上的,如果有列表,自然要有适配器了,适配器是需要绑定控件的,那么首先创建一个列表的item布局。
在layout下新建一个item_wallpaper.xml,里面的代码如下:
<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/image" android:layout_width="match_parent" android:layout_height="300dp" android:layout_margin="5dp" />
然后写适配器,在com.llw.mvpdemo下新建一个adapter包。在这个包下新建一个WallPaperAdapter类,里面的代码如下:
public class WallPaperAdapter extends BaseQuickAdapter<WallPaperResponse.ResBean.VerticalBean, BaseViewHolder> { public WallPaperAdapter(List<WallPaperResponse.ResBean.VerticalBean> data) { super(R.layout.item_wallpaper, data); } @Override protected void convert(BaseViewHolder helper, WallPaperResponse.ResBean.VerticalBean item) { ImageView imageView = helper.getView(R.id.image); Glide.with(mContext).load(item.getImg()).into(imageView); } }
里面的代码还是比较简单了,当然前提是你熟悉BaseQuickAdapter,个人觉得比Android自带适配器要好用很多。
适配器和item的布局都写好了,下面修改一下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:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimary" tools:context=".MainActivity"> <!--列表--> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="match_parent" android:overScrollMode="never" android:padding="5dp" android:scrollbars="none" /> </LinearLayout>
下面终于到MainActivity了,先继承MvpActivity,传入P,然后实现MainContract.IMainView,可以删掉原来onCreate方法了。
/** * @author llw */ public class MainActivity extends MvpActivity<MainContract.MainPresenter> implements MainContract.IMainView { @Override public void initData(Bundle savedInstanceState) { } @Override public int getLayoutId() { return R.layout.activity_main; } @Override protected MainContract.MainPresenter createPresenter() { return new MainContract.MainPresenter(); } /** * 获取壁纸返回 * * @param wallPaperResponse */ @Override public void getWallPaper(WallPaperResponse wallPaperResponse) { } /** * 获取列表数据异常 * * @param e */ @Override public void getWallPaperFailed(Throwable e) { } }
下面先创建一些成员变量
private static final String TAG = "MainActivity"; private final List<WallPaperResponse.ResBean.VerticalBean> mList = new ArrayList<>(); private WallPaperAdapter mAdapter;
初始化列表
/** * 初始化列表 */ private void initList() { RecyclerView rv = findViewById(R.id.rv); //配置rv mAdapter = new WallPaperAdapter(mList); rv.setLayoutManager(new GridLayoutManager(context,2)); rv.setAdapter(mAdapter); //请求列表数据 mPresenter.getWallPaper(); }
网络请求的正常和异常返回
/** * 获取壁纸返回 * * @param wallPaperResponse */ @Override public void getWallPaper(WallPaperResponse wallPaperResponse) { List<WallPaperResponse.ResBean.VerticalBean> vertical = wallPaperResponse.getRes().getVertical(); if (vertical != null && vertical.size() > 0) { mList.clear(); mList.addAll(vertical); mAdapter.notifyDataSetChanged(); } else { showMsg("数据为空"); } hideLoadingDialog(); } /** * 获取列表数据异常 * * @param e */ @Override public void getWallPaperFailed(Throwable e) { KLog.e(TAG,e.toString()); showMsg("获取列表数据异常,具体日志信息请查看日志"); hideLoadingDialog(); }
最后在initData中调用初始化方法即可,下面运行一下:
@Override public void initData(Bundle savedInstanceState) { //显示加载弹窗 showLoadingDialog(); //初始化列表 initList(); }
我听见雨滴落在青青草地,要想代码过得去,UI就得带点绿。
效果很明显了,这样就搞定了。
四、源码
源码地址:MvpDemo
尾声
不知道你现在了解MVP这个模式没有,当然我写博客从来都是注重实操的,你讲一大堆理论放在那里没有实际的项目或者搭建过程,别人是不容易上手的,希望看这篇文章的朋友能够,把里面的每一行代码都敲一遍,这样你的理解就更深。