@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 mLoadingViewSoftReference;
private float mDegrees = 0f;
public LoadingRunnable(LoadingView loadingView) {
mLoadingViewSoftReference = new SoftReference(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” />
还需要在valuse下新建一个styles.xml,里面的代码如下:
<?xml version="1.0" encoding="utf-8"?>然后在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
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
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框架依赖模块的过程,目前就已经完成了,那么接下来就到了使用阶段,既然是使用那么自然而然就是在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 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 vertical;
public List getVertical() {
return vertical;
}
public void setVertical(List 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 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 getCid() {
return cid;
}
public void setCid(List 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 {
@SuppressLint(“CheckResult”)
public void getWallPaper(){
ApiService service = NetworkApi.createService(ApiService.class);
service.getWallPaper().compose(NetworkApi.applySchedulers(new BaseObserver() {
@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” />
下面终于到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