Android MVP框架搭建与使用(含源码)

简介: Android MVP框架搭建与使用(含源码)

前言


  其实之前我是写过相关的MVP文章的,只不过当时在是天气APP中,而这里是单独拉出来讲,因此会有一些不同。


正文


先在Android Studio中创建一个名为MvpDemo的项目。20210113171119319.png

创建好如下图所示


20210113171321349.png


一、创建mvp模块


再创建一个依赖模块,File → New → New Module…


20210113171403584.png


选择Android Library


20210113171435869.png


Next,修改一下模块名字


20210113171602109.png


点击Finish,你的mvplibrary模块就创建完成了。

下面在app模块中依赖mvplibrary模块。


20210113171859704.png


在AS的右上角工具栏中找到上图中的图标按钮,点击进入如下页面。


20210113172135516.png


然后通过上面的操作,到最后一步点击这个Module Dependency会出现如下页面


20210113172312115.png


勾选上,点击OK。


20210113172354691.png


你会发现这里多了一个依赖,然后点击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下添加两个图片,这两个图片有一些特殊,建议你直接在我的源码里面复制出来,因为我现在贴出来你拿过去直接用是达不到实际的效果的。


20210114112259632.png


然后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


20210114154849248.png


下载到本地,然后解压。


2021011415495542.png


然后打开到最下面的network包


20210114155233416.png


将这个文件夹复制到com.llw.mvplibrary下。如下图所示


20210114155622255.png


然后找到res下这四个文件夹,全部复制


20210114155445936.png



粘贴到你项目的res下


20210114155712699.png


然后修改一下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方法中,完成了对环境的配置


e0961f8c4e474d10b4971eae0762d09f.png


你如果你对这一块并不了解,你可以先看看Android OkHttp+Retrofit+RxJava搭建网络访问框架这篇文章,相信你就明白了,因为内容实在比较多,因此写到一起你可能不太有耐心看完。


刚才写了MyApplication,自然要在AndroidManifest.xml中配置才行,如果你不配置则就是使用系统的Application。


20210115102326765.png


这样就配置好了


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


这个可以用浏览器访问一下:


0f4ad6e026fd472daff4901ee104b417.png


可以拿到结果,这说明这个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方法了。


20210115110736548.png


/**
 * @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();
    }

18c0b5d13706447eb010eb1026f095e9.gif


我听见雨滴落在青青草地,要想代码过得去,UI就得带点绿。

效果很明显了,这样就搞定了。


四、源码


源码地址:MvpDemo


尾声


 不知道你现在了解MVP这个模式没有,当然我写博客从来都是注重实操的,你讲一大堆理论放在那里没有实际的项目或者搭建过程,别人是不容易上手的,希望看这篇文章的朋友能够,把里面的每一行代码都敲一遍,这样你的理解就更深。


相关文章
|
3月前
|
Ubuntu 开发工具 Android开发
Repo下载AOSP源码:基于ubuntu22.04 环境配置,android-12.0.0_r32
本文介绍了在基于Ubuntu 22.04的环境下配置Python 3.9、安装repo工具、下载和同步AOSP源码包以及处理repo同步错误的详细步骤。
183 0
Repo下载AOSP源码:基于ubuntu22.04 环境配置,android-12.0.0_r32
|
3月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
4月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台框架解析
在移动应用开发的广阔舞台上,安卓和iOS一直是两大主角。随着技术的进步,开发者们渴望能有一种方式,让他们的应用能同时在这两大平台上运行,而不必为每一个平台单独编写代码。这就是跨平台框架诞生的背景。本文将探讨几种流行的跨平台框架,包括它们的优势、局限性,以及如何根据项目需求选择合适的框架。我们将从技术的深度和广度两个维度,对这些框架进行比较分析,旨在为开发者提供一个清晰的指南,帮助他们在安卓和iOS的开发旅程中,做出明智的选择。
|
3天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
17 1
|
22天前
|
前端开发 Java 测试技术
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
14 2
|
2月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
243 3
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
65 8
|
3月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
3月前
|
开发工具 git 索引
repo sync 更新源码 android-12.0.0_r34, fatal: 不能重置索引文件至版本 ‘v2.27^0‘。
本文描述了在更新AOSP 12源码时遇到的repo同步错误,并提供了通过手动git pull更新repo工具来解决这一问题的方法。
106 1
|
3月前
|
Android开发 Docker 容器
docker中编译android aosp源码,出现Build sandboxing disabled due to nsjail error
在使用Docker编译Android AOSP源码时,如果遇到"Build sandboxing disabled due to nsjail error"的错误,可以通过在docker run命令中添加`--privileged`参数来解决权限不足的问题。
415 1