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

2021-01-18 11:10:23 浏览数 (1)

前言

  其实之前我是写过相关的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类,里面的代码如下:

代码语言:txt复制
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();
        }
    }
}

2. 创建基类Application

在com.llw.mvplibrary下新建一个BaseApplication类,里面的代码如下:

代码语言:txt复制
package com.llw.mvplibrary;

import android.app.Application;
import android.content.Context;

/**
 * 基类Application
 * @author llw
 */
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;
    }


    public static BaseApplication getApplication() {
        return application;
    }

    public static Context getContext() {
        return context;
    }

    public static ActivityManager getActivityManager() {
        return activityManager;
    }
}

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

在com.llw.mvplibrary下新建一个base包,然后在这个包下面新建一个IUiCallback接口,里面代码如下:

代码语言:txt复制
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接口,这是一个空接口。

代码语言:txt复制
package com.llw.mvplibrary.base;

/**
 * 基类View,可以根据实际情况写方法
 * @author llw
 */
public interface BaseView {
    
}

然后创建一个BasePresenter,在这里可以操作View,这是MVP中的核心思想,通过P层控制M和V,从而减低M和V的耦合,甚至让它们不存在直接关联。

代码语言:txt复制
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,重写里面的方法即可。

代码语言:txt复制
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类,里面的代码如下:

代码语言:txt复制
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,里面的代码如下:

代码语言:txt复制
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,里面的布局代码如下:

代码语言:txt复制
<?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,里面的代码如下:

代码语言:txt复制
<?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中新增如下两个方法。

代码语言:txt复制
	//弹窗
    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;
    }

然后再增加页面返回的处理。

代码语言:txt复制
	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了。

代码语言:txt复制
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);
        initBeforeView(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;
    }


}

现在这个base包下的内容就写完了,该到mvp包了。

4. 创建mvp包(以及包下的Activity和Fragment)

在com.llw.mvplibrary下创建一个mvp包,在这个包下创建一个MvpActivity抽象类,代码如下:

代码语言:txt复制
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抽象类

代码语言:txt复制
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运行时的一些信息,里面的代码如下:

代码语言:txt复制
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类

代码语言:txt复制
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接口

代码语言:txt复制
package com.llw.mvpdemo.api;


import com.llw.mvpdemo.bean.GankResponse;

import io.reactivex.Observable;
import retrofit2.http.GET;


/**
 * ApiService接口 统一管理应用所有的接口
 * @author llw
 */
public interface ApiService {

    /**
     * 获取数据列表
     * @return GankResponse
     */
    @GET("/api/v2/data/category/Girl/type/Girl/page/1/count/10")
    Observable<GankResponse> getList();
}

现在你的这个GankResponse肯定是报红的,因为没有所以需要创建一个,这个实体Bean的意思就是当你请求网络接口,会将返回的数据解析成这样的一个实体,而Observable则是用来出来OkHttp的返回数据的。如果你使用Retrofit来处理返回这里就要改成Call,这种方式你可以参考我在天气APP中的网络写法看看。

下面创建一个这样返回数据类。这里最好的方式是在com.llw.mvpdemo下再建一个bean包或者model包,看个人习惯了,我个人习惯建bean包,然后包下新建一个GankResponse类,类的代码如下:

代码语言:txt复制
package com.llw.mvpdemo.bean;

import java.util.List;

/**
 * Gank返回数据
 * @author llw
 */
public class GankResponse {

    private int page;
    private int page_count;
    private int status;
    private int total_counts;
    private List<DataBean> data;

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public int getPage_count() {
        return page_count;
    }

    public void setPage_count(int page_count) {
        this.page_count = page_count;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public int getTotal_counts() {
        return total_counts;
    }

    public void setTotal_counts(int total_counts) {
        this.total_counts = total_counts;
    }

    public List<DataBean> getData() {
        return data;
    }

    public void setData(List<DataBean> data) {
        this.data = data;
    }

    public static class DataBean {

        private String _id;
        private String author;
        private String category;
        private String createdAt;
        private String desc;
        private int likeCounts;
        private String publishedAt;
        private int stars;
        private String title;
        private String type;
        private String url;
        private int views;
        private List<String> images;

        public String get_id() {
            return _id;
        }

        public void set_id(String _id) {
            this._id = _id;
        }

        public String getAuthor() {
            return author;
        }

        public void setAuthor(String author) {
            this.author = author;
        }

        public String getCategory() {
            return category;
        }

        public void setCategory(String category) {
            this.category = category;
        }

        public String getCreatedAt() {
            return createdAt;
        }

        public void setCreatedAt(String createdAt) {
            this.createdAt = createdAt;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public int getLikeCounts() {
            return likeCounts;
        }

        public void setLikeCounts(int likeCounts) {
            this.likeCounts = likeCounts;
        }

        public String getPublishedAt() {
            return publishedAt;
        }

        public void setPublishedAt(String publishedAt) {
            this.publishedAt = publishedAt;
        }

        public int getStars() {
            return stars;
        }

        public void setStars(int stars) {
            this.stars = stars;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public int getViews() {
            return views;
        }

        public void setViews(int views) {
            this.views = views;
        }

        public List<String> getImages() {
            return images;
        }

        public void setImages(List<String> images) {
            this.images = images;
        }
    }
}

下面就该到主角登场了,那就是一个可以把M、V、P结合起来使用的东西。

3. 创建订阅合同

在com.llw.mvpdemo下新建一个contract包,这个包下新建一个MainContract类,里面的代码如下:

代码语言:txt复制
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 getGankList(){
            ApiService service  = NetworkApi.createService(ApiService.class);
            service.getList().compose(NetworkApi.applySchedulers(new BaseObserver<GankResponse>() {
                @Override
                public void onSuccess(GankResponse gankResponse) {
                    if (getView() != null) {
                        getView().getListResult(gankResponse);
                    }
                }

                @Override
                public void onFailure(Throwable e) {
                    if (getView() != null) {
                        getView().getListFailed(e);
                    }
                }
            }));
        }
    }

    public interface IMainView extends BaseView {
        //返回列表结果
        void getListResult(GankResponse gankResponse);
        //获取列表失败返回
        void getListFailed(Throwable e);
    }
}

请仔细的看看这些代码,你就明白这个类做了什么。

4. 数据渲染

下面要显示数据了,不过这个数据是要显示在列表上的,如果有列表,自然要有适配器了,适配器是需要绑定控件的,那么首先创建一个列表的item布局。

在layout下新建一个item_list.xml,里面的代码如下:

代码语言:txt复制
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:background="@color/colorPrimaryDark"
    android:layout_marginBottom="12dp"
    android:padding="12dp">

    <ImageView
        android:id="@ id/image"
        android:layout_width="120dp"
        android:layout_height="160dp" />

    <TextView
        android:id="@ id/desc"
        android:layout_width="0dp"
        android:textColor="#FFF"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center" />

</LinearLayout>

然后写适配器,在com.llw.mvpdemo下新建一个adapter包。在这个包下新建一个GankListAdapter类,里面的代码如下:

代码语言:txt复制
package com.llw.mvpdemo.adapter;

import android.widget.ImageView;

import androidx.annotation.Nullable;

import com.bumptech.glide.Glide;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.mvpdemo.R;
import com.llw.mvpdemo.bean.GankResponse;

import java.util.List;

/**
 * 列表适配器
 *
 * @author llw
 */
public class GankListAdapter extends BaseQuickAdapter<GankResponse.DataBean, BaseViewHolder> {

    public GankListAdapter(int layoutResId, @Nullable List<GankResponse.DataBean> data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, GankResponse.DataBean item) {
        String imgUrl = item.getImages().get(0);

        Glide.with(mContext).load((imgUrl != null && !imgUrl.isEmpty()) ? imgUrl : "").into((ImageView) helper.getView(R.id.image));
        helper.setText(R.id.desc, item.getDesc());
    }
}

里面的代码还是比较简单了,当然前提是你熟悉BaseQuickAdapter,个人觉得比Android自带适配器要好用很多。

适配器和item的布局都写好了,下面修改一下activity_main.xml的布局

代码语言:txt复制
<?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:background="@color/colorPrimary"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!--列表-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@ id/rv"
        android:scrollbars="none"
        android:overScrollMode="never"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

下面终于到MainActivity了,先继承MvpActivity,传入P,然后实现MainContract.IMainView,可以删掉原来onCreate方法了。

在这里插入图片描述在这里插入图片描述
代码语言:txt复制
/**
 * @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 gankResponse
     */
    @Override
    public void getListResult(GankResponse gankResponse) {
        
    }

    /**
     * 获取列表数据异常
     *
     * @param e
     */
    @Override
    public void getListFailed(Throwable e) {
        
    }


}

下面先创建一些成员变量

代码语言:txt复制
	private RecyclerView rv;

    private static final String TAG = "MainActivity";

    private List<GankResponse.DataBean> mList = new ArrayList<>();
    private GankListAdapter mAdapter;

初始化列表

代码语言:txt复制
	/**
     * 初始化列表
     */
    private void initList() {
        rv = findViewById(R.id.rv);
        //配置rv
        mAdapter = new GankListAdapter(R.layout.item_list, mList);
        rv.setLayoutManager(new LinearLayoutManager(context));
        rv.setAdapter(mAdapter);

        //请求列表数据
        mPresenter.getGankList();
    }

网络请求的正常和异常返回

代码语言:txt复制
	/**
     * 获取列表数据返回
     *
     * @param gankResponse
     */
    @Override
    public void getListResult(GankResponse gankResponse) {
        if (gankResponse.getData() != null && gankResponse.getData().size() > 0) {
            mList.clear();
            mList.addAll(gankResponse.getData());
            mAdapter.notifyDataSetChanged();
            hideLoadingDialog();
        } else {
            showMsg("数据为空");
            hideLoadingDialog();
        }
    }

    /**
     * 获取列表数据异常
     *
     * @param e
     */
    @Override
    public void getListFailed(Throwable e) {
        KLog.e(TAG, e.toString());
        showMsg("获取列表数据异常,具体日志信息请查看日志");
        hideLoadingDialog();
    }

最后在initData中调用初始化方法即可,下面运行一下:

代码语言:txt复制
	@Override
    public void initData(Bundle savedInstanceState) {
        //显示加载弹窗
        showLoadingDialog();
        //初始化列表
        initList();
    }
在这里插入图片描述在这里插入图片描述

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

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

四、源码

源码地址:MvpDemo

尾声

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

0 人点赞