个人博客:haichenyi.com。感谢关注
目的
首先先说出,最终的目的是现在主流的MVP+RxJava+Retrofit+OkHttp框架。让大家心里有底
开发工具Android Studio3.0,还在用eclipse的同鞋,强烈推荐你跨出这一步,你会发现一个新的世界。android studio都出来这么久了,你还在远古时代做开发,说句不好听的,你完全与时代脱轨,你不适合做开发(纯属个人观点)
本篇就只有三部分,第一部分就是新建一个Application,第二部分就是BaseActivity,第三部分就是BaseFragment
Application
首先你得有application类,去初始化应用只用初始化一次的内容,继承Application,然后在清单文件里面注册。
package com.haichenyi.myproject;
import android.app.Application;
import com.squareup.leakcanary.LeakCanary;
/**
* Author: 海晨忆
* Date: 2018/2/23
* Desc:
*/
public class MyApplication extends Application {
private static MyApplication instance;
public MyApplication getInstance() {
return instance;
}
private void setInstance(MyApplication instance) {
MyApplication.instance = instance;
}
@Override
public void onCreate() {
super.onCreate();
setInstance(this);
initLeakCanary();
}
/**
* 初始化内存检测工具
*/
private void initLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
如上代码,我这里就初始化了一个全局application单例对象,还初始化square公司出品的一个内存检测工具,用于检测你项目中内存泄漏情况。便于你优化项目。
如上图所示,这个就是清单文件,在application结点下面,添加name标签,内容就是你创建的application的名字。这里你还需要添加两个内存检测的依赖。
如上图所示,首先把你的项目结构视图切换到Project,打开你的app目录下的build.gradle文件,在dependencies结点下面(只要是添加开源库都是在该结点下面,后面就不说了),添加如下两行代码:
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
最后的1.5.4是版本号,你可以在github上面搜索leakcanary,找最新的版本
BaseActivity
创建基类BaseActivity,也就是所有Activity的父类。还有一个基类的接口BaseView,BaseActivity继承刚才添加的依赖的SupportActivity类,实现BaseView接口,并且实现点击事件的接口(选择实现,你要是不乐意在基类里面写,你可以在你自己的子类里面重新实现一遍也是可以的)。代码如下:每个方法注释写的很清楚,就不用一一解释了
package com.haichenyi.myproject.base;
import android.app.AlertDialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.Window;
import android.widget.ProgressBar;
import com.haichenyi.myproject.utils.ToastUtils;
import me.yokeyword.fragmentation.SupportActivity;
/**
* Author: 海晨忆
* Date: 2018/2/23
* Desc:
*/
public abstract class BaseActivity extends SupportActivity implements BaseView {
private AlertDialog loadingDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
/**
* Toast 提示用户
* @param msg 提示内容String
*/
@Override
public void showTipMsg(String msg) {
ToastUtils.showTipMsg(msg);
}
/**
* Toast 提示用户
* @param msg 提示内容res目录下面的String的int值
*/
@Override
public void showTipMsg(int msg) {
ToastUtils.showTipMsg(msg);
}
/**
* 网络请求的时候显示正在加载的对话框
*/
@Override
public void showLoading() {
if (null == loadingDialog) {
loadingDialog = new AlertDialog.Builder(this).setView(new ProgressBar(this)).create();
loadingDialog.setCanceledOnTouchOutside(false);
Window window = loadingDialog.getWindow();
if (null != window) {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
}
if (!loadingDialog.isShowing()) {
loadingDialog.show();
}
}
/**
* 网络请求完成时隐藏加载对话框
*/
@Override
public void hideLoading() {
if (null != loadingDialog) {
if (loadingDialog.isShowing()) {
loadingDialog.dismiss();
}
loadingDialog = null;
}
}
@Override
public void invalidToken() {
//用于检测你当前用户的token是否有效,无效就返回登录界面,具体的业务逻辑你自己实现
//如果需要做到实时检测,推荐用socket长连接,每隔10秒发送一个验证当前登录用户token是否过期的请求
}
/**
* Finish当前页面,最好实现onBackPressedSupport(),这个方法会有一个退栈操作,
* 开源框架实现的,我们不用管
*/
@Override
public void myFinish() {
onBackPressedSupport();
}
@Override
public void onBackPressedSupport() {
super.onBackPressedSupport();
}
}
上面是目前BaseActivity代码,注释写的很清楚,你会发现BaseView你并没有,下面我给出BaseView的代码
package com.haichenyi.myproject.base;
import android.support.annotation.StringRes;
/**
* Author: 海晨忆
* Date: 2018/2/23
* Desc:
*/
public interface BaseView {
void showTipMsg(String msg);
void showTipMsg(@StringRes int msg);
void showLoading();
void hideLoading();
void invalidToken();
void myFinish();
}
BaseView就是一个接口,是所有V层的基类,代码很简单,Toast方法,显示隐藏加载的对话框方法,检验token是否过期的方法,finish当前页面的方法。什么?Toast方法你没有,下面我贴出来我的Toast的工具类
/**
* Author: 海晨忆.
* Date: 2017/12/21
* Desc: 实时更新的Toast工具类
*/
public final class ToastUtils {
private static Toast toast;
private ToastUtils() {
throw new RuntimeException("工具类不允许创建对象");
}
@SuppressWarnings("all")
private static void init() {
if (toast == null) {
toast = Toast.makeText(MyApplication.getInstance(), "", Toast.LENGTH_SHORT);
}
}
public static void showTipMsg(String msg) {
if (null == toast) {
init();
}
toast.setText(msg);
toast.show();
}
public static void showTipMsg(@StringRes int msg) {
if (null == toast) {
init();
}
toast.setText(msg);
toast.show();
}
}
上面我贴出了三个类,这里我要说明的是,我又创建了两个package,一个是base,一个是utils,我把BaseActivity,BaseView,MyApplication放在base包下面,Toast的工具类放在utils包下面
再就是添加一些常用的东西了,这里我没有用黄油刀,用过一段时间之后,感觉他的每个控件都是全局的,有点占内存,就放弃了。我下面贴出BaseActivity新增的伪代码:
/**
* 保存当前activity对象,在OnCreate里面添加,记得在OnDestroy里面移除
* 有什么用呢?
* 比方说有一个需求,让你在任意位置弹出对话框,弹对话框又需要一个context对象,这个时候,
* 你就只用传当前list的最上层的activity对象就可以了
* 当然还有其他需求
*/
public static List<BaseActivity> activities = new ArrayList<>();
private Toolbar toolbar;
private TextView tvToolbarTitle;
private TextView tvToolbarRight;
private TextView tvBack;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activities.add(this);
//强制竖屏(不强制加)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
int layoutId = getLayoutId(savedInstanceState);
View inflate = getLayoutInflater().inflate(R.layout.activity_base, toolbar, false);
LinearLayout rootLinearLayout = inflate.findViewById(R.id.ll_layout_base_activity);
//没有布局的时候传0
if (0 == layoutId) {
setContentView(rootLinearLayout);
} else {
View rootView = getLayoutInflater().inflate(layoutId, rootLinearLayout, true);
setContentView(rootView);
}
stateBar();
initView();
initData();
setOnClick(R.id.tv_back_base_activity);
}
/**
* 设置点击事件.
*
* @param ids 被点击View的ID
* @return {@link BaseActivity}
*/
public BaseActivity setOnClick(@IdRes int... ids) {
View view;
for (int id : ids) {
view = findViewById(id);
if (null != view) {
view.setOnClickListener(this);
}
}
return this;
}
/**
* 设置点击事件.
*
* @param views 被点击View
* @return {@link BaseActivity}
*/
public BaseActivity setOnClick(View... views) {
for (View view : views) {
view.setOnClickListener(this);
}
return this;
}
/**
* 获取当前布局对象
*
* @param savedInstanceState 这个是当前activity保存的数据,最常见的就是横竖屏切换的时候,
* 数据丢失问题
* @return 当前布局的int值
*/
protected abstract int getLayoutId(Bundle savedInstanceState);
@Override
protected void onDestroy() {
activities.remove(this);
super.onDestroy();
}
protected void initData() {
}
protected void initView() {
toolbar = findViewById(R.id.toolbar_base_activity);
tvToolbarTitle = findViewById(R.id.tv_title_base_activity);
tvToolbarRight = findViewById(R.id.tv_right_base_activity);
}
/**
* 设置状态栏背景颜色,不能改变状态栏内容的颜色
*/
private void stateBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
SystemBarTintManager tintManager = new SystemBarTintManager(this);
tintManager.setStatusBarTintEnabled(true);
tintManager.setNavigationBarTintEnabled(true);
tintManager.setTintColor(Color.parseColor("#000000"));
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_back_base_activity:
onBackPressedSupport();
break;
default:
break;
}
}
这里我需要说明的是,新增了一个开源框架,就是设置状态栏背景颜色的systembartint。
implementation 'com.readystatesoftware.systembartint:systembartint:1.0.3'
再就是设置activity标题内容,左边,右边的内容,左边右边可能是文字,也可能是图片。所以,我在用的时候,都是用的TextView,ImageView,不能设置文字。方法如下:
public BaseActivity setTitles(CharSequence title) {
tvToolbarTitle.setText(title);
return this;
}
/**
* 初始化toolbar的内容
* @param isShowToolbar 是否显示toolbar
* @param isShowBack 是否显示左边的TextView
* @param isShowMore 是否显示右边的TextView
* @return 当前activity对象,可以连点
*/
protected BaseActivity initToolbar(boolean isShowToolbar, boolean isShowBack,
boolean isShowMore) {
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (null != actionBar) {
if (isShowToolbar) {
actionBar.show();
tvBack = findViewById(R.id.tv_back_base_activity);
TextView textView = findViewById(R.id.tv_right_base_activity);
if (null != tvBack && null != textView) {
tvBack.setVisibility(isShowBack ? View.VISIBLE : View.INVISIBLE);
textView.setVisibility(isShowMore ? View.VISIBLE : View.INVISIBLE);
}
} else {
actionBar.hide();
}
}
return this;
}
public BaseActivity setToolbarBack(int colorId) {
toolbar.setBackgroundColor(getResources().getColor(colorId));
return this;
}
@SuppressWarnings("unused")
public BaseActivity setMyTitle(String title) {
tvToolbarTitle.setText(title);
return this;
}
public BaseActivity setMyTitle(@StringRes int stringId) {
tvToolbarTitle.setText(stringId);
return this;
}
public void setMoreTitle(String moreTitle) {
tvToolbarRight.setText(moreTitle);
}
public BaseActivity setMoreTitle(@StringRes int stringId) {
tvToolbarRight.setText(stringId);
return this;
}
/**
* 设置左边内容.
*
* @param leftTitle 内容
* @return {@link BaseActivity}
*/
public BaseActivity setLeftTitle(String leftTitle) {
if (tvBack != null) {
tvBack.setBackground(null);
tvBack.setText(leftTitle);
}
return this;
}
/**
* 设置左边内容.
*
* @param leftTitle 内容
*/
public void setLeftTitle(@StringRes int leftTitle) {
if (tvBack != null) {
tvBack.setBackground(null);
tvBack.setText(leftTitle);
}
}
@SuppressWarnings("unused")
protected BaseActivity setMoreBackground(int resId) {
tvToolbarRight.setBackgroundResource(resId);
return this;
}
可以看到上面的方法返回值都是BaseActivity,这样做的目的就只有一个,可以连点,写一个方法之后,可以接着点写下一个方法,不用写一个方法就要加分号,就换一行写下一个方法。
还要加一句,在你的app主题里面添加两个item,也就是你的res目录下面的style:
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
我这里贴出我目前的style的图片
下面有一个LineHorizontal样式,就是你toolbar下面的那个横线
BaseFragment
BaseFragment跟BaseActivity的逻辑是差不多的,我这里就贴出代码
package com.haichenyi.myproject.base;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.haichenyi.myproject.utils.ToastUtils;
import me.yokeyword.fragmentation.SupportFragment;
/**
* Author: 海晨忆
* Date: 2018/2/23
* Desc:
*/
public abstract class BaseFragment extends SupportFragment implements BaseView,
View.OnClickListener {
protected boolean isInit;
private View rootView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
int layoutRes = layoutRes();
if (0 != layoutRes) {
return inflater.inflate(layoutRes, null);
} else {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
rootView = view;
}
@Override
public void onLazyInitView(@Nullable Bundle savedInstanceState) {
super.onLazyInitView(savedInstanceState);
isInit = true;
init();
}
protected <T extends View> T findViewById(@IdRes int id) {
return rootView.findViewById(id);
}
/**
* 设置点击事件.
*
* @param ids 被点击View的ID
* @return {@link BaseFragment}
*/
public BaseFragment setOnClick(@IdRes int... ids) {
for (int id : ids) {
rootView.findViewById(id).setOnClickListener(this);
}
return this;
}
/**
* 设置点击事件.
*
* @param views 被点击View的ID
* @return {@link BaseFragment}
*/
public BaseFragment setOnClick(View... views) {
for (View view : views) {
view.setOnClickListener(this);
}
return this;
}
protected abstract void init();
@Override
public void onDestroy() {
rootView = null;
super.onDestroy();
}
protected abstract int layoutRes();
@Override
public void showTipMsg(String msg) {
ToastUtils.showTipMsg(msg);
}
@Override
public void showTipMsg(int msg) {
ToastUtils.showTipMsg(msg);
}
@Override
public void showLoading() {
BaseActivity activity = (BaseActivity) getActivity();
/*if (activity instanceof BaseMvpActivity) {
activity.showLoading();
}*/
}
@Override
public void hideLoading() {
BaseActivity activity = (BaseActivity) getActivity();
/*if (activity instanceof BaseMvpActivity) {
activity.hideLoading();
}*/
}
@Override
public void invalidToken() {
BaseActivity activity = (BaseActivity) getActivity();
/*if (activity instanceof BaseMvpActivity) {
activity.invalidToken();
}*/
}
@Override
public void onClick(View v) {
}
@Override
public void myFinish() {
onBackPressedSupport();
}
}
两者在布局抽象方法里面有一点区别,Activity的传了Boundle参数,Fragment没有传,因为Fragment可以通过getArguments()方法获取到这个对象,而Activity不能获取到。
总结
到此,一个简单的项目框架就出来了,目前还是框架的第一步,是一个雏形,还不包括MVP,dagger等等,下一篇就加上MVP,我这个人有个好习惯,就是喜欢写注释,我注释写的很清楚,是干什么用的,我也衷心的希望,你能写好注释。