第一次尝试——使用Retrofit+Dagger架构一个拿来就可以使用的Android空项目
2016-11-19 11:19
501 查看
刚开始来公司的时候,接手了两个项目,两个项目的架构是用的同一个,虽然做了一些封装,但在我这个菜鸟的眼里,也觉得不好,单其中一个项目在不久后就上线了,因为百分之八十以上的页面在我来之前就写好了,而且之前也上传了几个版本了,在这种情况下,虽然后来的扩展和修改bug让我很头疼,也还是没有想着做一些改变。另外一个项目至今没有上线,还有一些东西没有完成,而另一个开发者又离职了,UI给我列出来一大堆要改的页面和逻辑,在缝缝补补做了半个月的时候,我终于终于没办法忍受,于是毅然决定重构整个项目,哦,不对,是重新做这个项目。
那么,既然重新做,就不能用之前的架构了,缝缝补补太痛苦,所以开始了这篇博客的主题所指,当然,在我这个阅历下,能想到的封装思路和能够封装的程度都是有限的,只是给初级的开发者一些参考,大神,请绕步。。。
一、网络加载
首先,选择网络加载框架,恩,之前就一直听说Retrofit,是最近非常流行的一个框架,但是一直没有机会自己来好好玩一次,所以在研究了一段时间之后毅然决定就它了。因为自己研究的程度目前还不能给大家分析,所以,详情请看下后面的代码吧!如果你都还没听说过,就去看看官方文档,这个不是今天的重点。
二、图片加载
然后就是图片加载框架,现在流行的有三大图片框架:
1) Picasso
2) Glide
3) Fresco
当然,ImageLoader是历史舞台的大佬,不过我好久没用过了。。。
先简单的介绍下这三个框架:
Picasso :和Square的网络库一起能发挥最大作用,因为Picasso可以选择将网络请求的缓存部分交给了okhttp实现。
Glide:模仿了Picasso的API,而且在他的基础上加了很多的扩展(比如gif等支持),Glide默认的Bitmap格式是RGB_565,比 Picasso默认的ARGB_8888格式的内存开销要小一半;Picasso缓存的是全尺寸的(只缓存一种),而Glide缓存的是跟ImageView尺寸相同的(即56*56和128*128是两个缓存)
。
FB的图片加载框架Fresco:最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。为什么说是5.0以下,因为在5.0以后系统默认就是存储在Ashmem区了。
再看下三者的比较:
Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多如果项目中网络请求本身用的就是okhttp或者retrofit(本质还是okhttp),那么建议用Picasso,体积会小很多(Square全家桶的干活)。Glide的好处是大型的图片流,比如gif、Video,如果你们是做美拍、爱拍这种视频类应用,建议使用。
Fresco在5.0以下的内存优化非常好,代价就是体积也非常的大,按体积算Fresco>Glide>Picasso,不过在使用起来也有些不便(小建议:他只能用内置的一个ImageView来实现这些功能,用起来比较麻烦,我们通常是根据Fresco自己改改,直接使用他的Bitmap层)
因为使用了retrofit,再加上本应用对图片的要求不高,所以我们这里最完美的选择就是Picasso了,那就决定了,就用它。
三、其他
做完了这两个选择后,接下来要考虑的事情是对基类的封装。当然最基础的基类有BaseApplication,BaseActivity和BaseFragment三个,对于BaseApplication的话,我没有做太多的处理,现在想到一个对网络状态进行监听的方法,然后在当网络由不可用转为确实可用的时候,发送一个消息给所有加载失败的页面,重新加载数据,这个在我写完这篇博客后会加上去的。
BaseActivity和的ParentActivity封装
至于ParentActivity是干嘛的?因为我需要在BaseActivity中封装好对错误页面,未加载完成的页面,加载成功后的页面和对标题栏等的一些页面的处理,但是很多页面其实是不需要加载网络的,所以不需要加载错误,未加载完成的页面,他只需要直接显示出来一些界面就好,所以ParentActivity就诞生了,在这个类里面,就是单单的做了除了与View有关的其他所有操作。
ParentActivity.java
在上面的代码中,首先定义了一个List<ParentActivity> mActivities的集合,用于存储所有的已打开的Activtiy,便于以后统一的去处理和查询;还定义了一个public
static boolean isKitkat;这个是用来记录当前Android版本支不支持沉浸式状态栏,如果支持,直接使用沉浸式状态栏;还有一个抽象方法initView(null);是用来暴露给用户,让他去处理View相关的事情,但是由于ParentActivity没有处理View相关的操作,所以传入了null;在onDestroy方法里面,移除掉了当前的Activity,并且ButterKnife.unbind(this);由于整个架构默认使用ButterKnife来对View进行绑定,所以在退出的时候主动unbind掉,免得每个子类都要各自去处理。
在BaseActivity里面,就需要处理上面ParentActivity没有能处理的事情,所以BaseActivity是继承自ParentActivity的。
具体如下:
BaseActivity.java
layouy_base_activity.xml:
先看上面的布局文件,分为两个大的部分,一个是标题栏,一个是内容区域,标题栏左边有一个默认的返回ImageView,中间是一个TextView,用于显示标题,右边有一个TextView和一个ImageView,TextView用来显示比如修改的操作,ImageView用来显示设置什么的图标,默认左边的返回按钮个标题栏可见状态,右边两个都是隐藏的,因为用到他们的机会还是挺少的。在内用区域,有一个显示内容的布局,到时候用来显示需要加载的布局,还有一个正在加载的布局,用来显示正在加载中。。这个可自己去根据需求定制,这个页面也可以作为加载失败的页面,根据需求来就可以,当这个界面的网络加载完成之后,就可以主动的隐藏掉这个正在加载的页面就可以了,用户体验相对也会好一点。
然后来看下BaseActivity中的操作,首先,我们避免写findViewById的操作,所以我们在BaseActivity中封装好了对ButterKnife的bind和unbind操作,这样在子类中就只需要简单的操作就行,不需要再bind和unbind了,然后提供了一系列的对标题栏自定义的操作,比如设置标题内容,文字大小颜色,设置标题栏右边的图标的资源。。等等,这些根据需求,不够的还可以自己再自行添加就好;还有在前面我们判断了Android版本是否支持沉浸式状态栏,如果支持的话,我在BaseActivity中也对标题栏进行了setPadding的操作,因为不设置的话,标题会跟状态栏的文字重叠,就会比较难看,这种操作不需要子类来理会,所以封装出来会比较好;另外我还提供了一个比较方便的的方法,就是showDialog和stopDialog的操作,当页面需要二次请求网路的时候,显示showDialog,加载完就stopDialog即可,这样用户体验就会好一点;还有一个很大的点就是对网络请求的封装,这个因为我现在对Retrofit还不是非常熟练,所以只进行了简单的封装,到时候经过再研究之后,再来进行完善!在目前的代码中,我写了一个抽象方法,在抽象方法中对请求的响应做出一个初级的判断,如果请求是成功的,并且数据的返回也是正常的,那么回调给调用者,否则直接显示网络加载错误的页面,统一的对网络错误进行处理,不过,这个要能真正完美的使用,需要后台不要太差。。不然老是返回乱七八糟的错误给你,就不太好统一处理了。。到此,BaseActivity的处理也要到此为止了。
BaseFragment的封装
对于BaseFragment来说,其封装跟BaseActtivity是一模一样的,本质的差别也就是一个是Activity一个是Fragment,所以就不再赘述了。
BaseFragmentActivity和BackFragmentActivity的封装
为什么会有这两个类的存在?我现在比较喜欢的一种加载界面方式是使用Fragment碎片化的方式,就是开一个Activity,这个Activity就是承载Fragment的一个容器,所以这个Activity啥都不做,就只作用于添加Fragment,显然,这个Activity是不需要加载网络和一些其他操作的,所以我把他们俩都是集成自ParentActivity,这个具体看下项目中的操作。看这两个类的名字也很清楚,继承BaseFragmentActivity的时候,代表要加载的Fragment是不需要假如回退栈的,继承BackFragmentActivity的时候,代表要加载的Fragment是需要假如回退栈的,当然,在计入Fragment的时候,也可以使用动画,具体的使用,看Demo就好,在此不再赘述了,代码什么的还是要自己看,理解才快。
工具类
最后再来说下项目中默认添加使用的一些其他好用的东西:
本地数据的存储:Hawk
这个工具是当年一个叫Calvin的大哥告诉我的,一用起来就爱不释手。
Hawk 是一个非常便捷的数据库 . 操作数据库只需一行代码 , 能存任何数据类型 .
github 地址: https://github.com/orhanobut/hawk
Hawk 是一个简单的 key-value 数据库
它使用:
AES 加密
能选择使用SharedPreferences 或者 SQLite
Gson解析
提供:
安全数据持久化
能存储任何类型
具体的使用,大家去Github上去看吧,相信只要你用过你就会喜欢上他的,信我。
Android事件总线:EventBus
这个不多说,已经非常成熟了。简单介绍下
实际项目开发过程中,经常遇到如下场景:不同的应用程序组件的控件间具有一定的相互关联性,其中用户对后者进行的某种操作会引起前者的相应改变。举一个具体的场景:以糗事百科为例,在糗事列表页和详情页页,对于每个糗事而言,布局基本一致,在详情页点击了个赞,赞的数量增加,同时赞的图标发生了变化,此时返回到列表页,此糗事上的赞图标以及数量与刚刚详情页的需要保持一致。在举一个例子,对于多个底部导航tab下的资讯类阅读app,在咨询详情页点击了收藏,然后收藏成功,此时回到底部tab中的个人中心,假如个人中心中有我的收藏,同时后面显示的是收藏数量,此时此收藏数量需要同于于刚刚用户所进行的收藏/取消收藏而即时更改数字。凡此种种,类似需求场景非常常见。
有时候,当此类需求相对简单时,通过接口以实现回调等方式可以完成,但是当不同组件/控件之间的关系纷繁复杂时,基于接口的方案不仅使得代码非常繁琐,同时是的程序逻辑很混乱,基于此,EventBus,为此类需求的实现提供了非常方便的方案。
butterknife注解框架的偷懒插件
这个也不多说,已经非常成熟了。简单介绍下
事实上这是个Android Studio的plugin,他可以让你在添加Butterkinfe注解时偷偷懒,直接点击几下鼠标既可以完成注解的增加,同时还是图形化的操作,可以说,大大的减轻了开发负担。尤其是当你的layout中有很多很多的view需要通过findviewbyid来获得引用时。实际上如果不用这个插件而通过手打加ButtefKnife注解的方式,要是view很多启示也挺麻烦的,不是吗?
到此这篇博客也就到了尾声了,可能有很多地方还不是很准确,希望大家多提提意见,而且,这个封装也还很基础,希望大佬有时间看下,多提提意见和建议,再次谢过了。如果觉得不是太烂,记得Star一下哦。
我是Cretin,一个可爱的小男孩。
下面是Github的地址:
https://github.com/MZCretin/CreateNewProjectDemo
那么,既然重新做,就不能用之前的架构了,缝缝补补太痛苦,所以开始了这篇博客的主题所指,当然,在我这个阅历下,能想到的封装思路和能够封装的程度都是有限的,只是给初级的开发者一些参考,大神,请绕步。。。
一、网络加载
首先,选择网络加载框架,恩,之前就一直听说Retrofit,是最近非常流行的一个框架,但是一直没有机会自己来好好玩一次,所以在研究了一段时间之后毅然决定就它了。因为自己研究的程度目前还不能给大家分析,所以,详情请看下后面的代码吧!如果你都还没听说过,就去看看官方文档,这个不是今天的重点。
二、图片加载
然后就是图片加载框架,现在流行的有三大图片框架:
1) Picasso
2) Glide
3) Fresco
当然,ImageLoader是历史舞台的大佬,不过我好久没用过了。。。
先简单的介绍下这三个框架:
Picasso :和Square的网络库一起能发挥最大作用,因为Picasso可以选择将网络请求的缓存部分交给了okhttp实现。
Glide:模仿了Picasso的API,而且在他的基础上加了很多的扩展(比如gif等支持),Glide默认的Bitmap格式是RGB_565,比 Picasso默认的ARGB_8888格式的内存开销要小一半;Picasso缓存的是全尺寸的(只缓存一种),而Glide缓存的是跟ImageView尺寸相同的(即56*56和128*128是两个缓存)
。
FB的图片加载框架Fresco:最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。为什么说是5.0以下,因为在5.0以后系统默认就是存储在Ashmem区了。
再看下三者的比较:
Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多如果项目中网络请求本身用的就是okhttp或者retrofit(本质还是okhttp),那么建议用Picasso,体积会小很多(Square全家桶的干活)。Glide的好处是大型的图片流,比如gif、Video,如果你们是做美拍、爱拍这种视频类应用,建议使用。
Fresco在5.0以下的内存优化非常好,代价就是体积也非常的大,按体积算Fresco>Glide>Picasso,不过在使用起来也有些不便(小建议:他只能用内置的一个ImageView来实现这些功能,用起来比较麻烦,我们通常是根据Fresco自己改改,直接使用他的Bitmap层)
因为使用了retrofit,再加上本应用对图片的要求不高,所以我们这里最完美的选择就是Picasso了,那就决定了,就用它。
三、其他
做完了这两个选择后,接下来要考虑的事情是对基类的封装。当然最基础的基类有BaseApplication,BaseActivity和BaseFragment三个,对于BaseApplication的话,我没有做太多的处理,现在想到一个对网络状态进行监听的方法,然后在当网络由不可用转为确实可用的时候,发送一个消息给所有加载失败的页面,重新加载数据,这个在我写完这篇博客后会加上去的。
BaseActivity和的ParentActivity封装
至于ParentActivity是干嘛的?因为我需要在BaseActivity中封装好对错误页面,未加载完成的页面,加载成功后的页面和对标题栏等的一些页面的处理,但是很多页面其实是不需要加载网络的,所以不需要加载错误,未加载完成的页面,他只需要直接显示出来一些界面就好,所以ParentActivity就诞生了,在这个类里面,就是单单的做了除了与View有关的其他所有操作。
ParentActivity.java
/**
* Created by cretin on 16/10/27.
*/
public abstract class ParentActivity extends AppCompatActivity {
//记录下所有的Activity
public final static List<ParentActivity> mActivities = new LinkedList<ParentActivity>();
public static boolean isKitkat;
public static ParentActivity mActivity;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
synchronized (mActivities) {
mActivities.add(this);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
isKitkat = true;
}
initView(null);
}
protected abstract void initView(View view);
@Override
protected void onResume() {
super.onResume();
mActivity = this;
}
@Override
protected void onPause() {
super.onPause();
mActivity = null;
}
@Override
protected void onDestroy() {
super.onDestroy();
synchronized (mActivities) {
mActivities.remove(this);
}
ButterKnife.unbind(this);
}
}
在上面的代码中,首先定义了一个List<ParentActivity> mActivities的集合,用于存储所有的已打开的Activtiy,便于以后统一的去处理和查询;还定义了一个public
static boolean isKitkat;这个是用来记录当前Android版本支不支持沉浸式状态栏,如果支持,直接使用沉浸式状态栏;还有一个抽象方法initView(null);是用来暴露给用户,让他去处理View相关的事情,但是由于ParentActivity没有处理View相关的操作,所以传入了null;在onDestroy方法里面,移除掉了当前的Activity,并且ButterKnife.unbind(this);由于整个架构默认使用ButterKnife来对View进行绑定,所以在退出的时候主动unbind掉,免得每个子类都要各自去处理。
在BaseActivity里面,就需要处理上面ParentActivity没有能处理的事情,所以BaseActivity是继承自ParentActivity的。
具体如下:
BaseActivity.java
package com.cretin.www.createnewprojectdemo.base;
import android.graphics.Color;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.cretin.www.createnewprojectdemo.R;
import com.cretin.www.createnewprojectdemo.utils.ViewUtils;
import com.cretin.www.createnewprojectdemo.view.CustomProgressDialog;
import butterknife.ButterKnife;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import rx.Subscription;
import rx.subscriptions.CompositeSubscription;
/**
* Created by cretin on 16/10/27.
* 如果是需要处理自己的逻辑 则继承这个
* 如果只是给加载Fragment提供一个容器 则继承ParentActivity
*/
public abstract class BaseActivity extends ParentActivity {
private CustomProgressDialog dialog;
private OnTitleAreaCliclkListener onTitleAreaCliclkListener;
private TextView tvMainTitle;
private ImageView ivMainBack;
private ImageView ivMainRight;
private TextView tvMainRight;
private RelativeLayout relaLoadContainer;
private TextView tvLoadingMsg;
private ImageView ivBack;
private CompositeSubscription mCompositeSubscription;
protected void addSubscription(Subscription s) {
if ( this.mCompositeSubscription == null ) {
this.mCompositeSubscription = new CompositeSubscription();
}
this.mCompositeSubscription.add(s);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = getLayoutInflater().inflate(R.layout.layout_base_activity, null);
setContentView(view);
initHeadView(view);
initContentView(view);
if ( isKitkat ) {
view.findViewById(R.id.ll_main_title).setPadding(0, ViewUtils.getStatusBarHeights(), 0, 0);
}
initData();
initEvent();
}
private AnimationDrawable animationDrawable;
private void initContentView(View view) {
RelativeLayout container = ( RelativeLayout ) view.findViewById(R.id.main_container);
relaLoadContainer = ( RelativeLayout ) view.findViewById(R.id.load_container);
tvLoadingMsg = ( TextView ) view.findViewById(R.id.loading_msg);
ImageView imageView = ( ImageView ) view
.findViewById(R.id.loading_image);
animationDrawable = ( AnimationDrawable ) imageView
.getBackground();
animationDrawable.start();
View v = getLayoutInflater().inflate(getContentViewId(), null);
ButterKnife.bind(this, v);
container.addView(v);
initView(v);
}
//onResponse子类去实现
public abstract class ResultCall<T> implements Callback<T> {
@Override
public void onResponse(Call<T> call, Response<T> response) {
hidProgressView();
onResponse(response);
}
protected abstract void onResponse(Response<T> response);
@Override
public void onFailure(Call<T> call, Throwable t) {
showErrorView();
onError(call, t);
}
protected abstract void onError(Call<T> call, Throwable t);
}
//隐藏正在加载视图
public void hidProgressView() {
if ( relaLoadContainer != null )
relaLoadContainer.setVisibility(View.GONE);
if ( animationDrawable != null )
animationDrawable.stop();
}
//显示正在加载视图
public void showProgressView() {
if ( relaLoadContainer != null && relaLoadContainer.getVisibility() == View.GONE )
relaLoadContainer.setVisibility(View.VISIBLE);
if ( animationDrawable != null )
animationDrawable.start();
}
//隐藏返回按钮
public void hidBackIv() {
if ( ivBack != null && ivBack.getVisibility() == View.VISIBLE )
ivBack.setVisibility(View.GONE);
}
//显示加载错误
public void showErrorView() {
if ( relaLoadContainer != null && relaLoadContainer.getVisibility() == View.GONE )
relaLoadContainer.setVisibility(View.VISIBLE);
if ( animationDrawable != null )
animationDrawable.stop();
tvLoadingMsg.setText("加载错误");
}
//初始化头部视图
private void initHeadView(View view) {
tvMainTitle = ( TextView ) view.findViewById(R.id.tv_title_info);
ivMainBack = ( ImageView ) view.findViewById(R.id.iv_back);
ivMainRight = ( ImageView ) view.findViewById(R.id.iv_right);
tvMainRight = ( TextView ) view.findViewById(R.id.tv_right);
ivBack = ( ImageView ) view.findViewById(R.id.iv_back);
ivMainBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
if ( onTitleAreaCliclkListener != null )
onTitleAreaCliclkListener.onTitleAreaClickListener(v);
}
});
ivMainRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if ( onTitleAreaCliclkListener != null )
onTitleAreaCliclkListener.onTitleAreaClickListener(v);
}
});
tvMainRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if ( onTitleAreaCliclkListener != null )
onTitleAreaCliclkListener.onTitleAreaClickListener(v);
}
});
}
/**
* 显示加载对话框
*
* @param msg
*/
public void showDialog(String msg) {
if ( dialog == null ) {
dialog = CustomProgressDialog.createDialog(this);
if ( msg != null && !msg.equals("") ) {
dialog.setMessage(msg);
}
}
dialog.show();
}
/**
* 关闭对话框
*/
public void stopDialog() {
if ( dialog != null && dialog.isShowing() ) {
dialog.dismiss();
}
}
public void setOnTitleAreaCliclkListener(OnTitleAreaCliclkListener onTitleAreaCliclkListener) {
this.onTitleAreaCliclkListener = onTitleAreaCliclkListener;
}
//设置Title
protected void setMainTitle(String title) {
if ( !TextUtils.isEmpty(title) )
tvMainTitle.setText(title);
}
//设置TitleColor
protected void setMainTitleColor(String titleColor) {
if ( !TextUtils.isEmpty(titleColor) )
setMainTitleColor(Color.parseColor(titleColor));
}
//设置TitleColor
protected void setMainTitleColor(int titleColor) {
tvMainTitle.setTextColor(titleColor);
}
//设置右边TextView颜色
protected void setMainTitleRightColor(int tvRightColor) {
tvMainRight.setTextColor(tvRightColor);
}
//设置右边TextView颜色
protected void setMainTitleRightColor(String tvRightColor) {
if ( !TextUtils.isEmpty(tvRightColor) )
setMainTitleRightColor(Color.parseColor(tvRightColor));
}
//设置右边TextView大小
protected void setMainTitleRightSize(int size) {
tvMainRight.setTextSize(size);
}
//设置右边TextView内容
protected void setMainTitleRightContent(String content) {
if ( !TextUtils.isEmpty(content) ) {
if ( tvMainRight.getVisibility() == View.GONE )
tvMainRight.setVisibility(View.VISIBLE);
tvMainRight.setText(content);
}
}
//设置左边ImageView资源
protected void setMainLeftIvRes(int res) {
if ( ivMainBack.getVisibility() == View.GONE )
ivMainBack.setVisibility(View.VISIBLE);
ivMainBack.setImageResource(res);
}
//设置又边ImageView资源
protected void setMainRightIvRes(int res) {
if ( ivMainRight.getVisibility() == View.GONE )
ivMainRight.setVisibility(View.VISIBLE);
ivMainRight.setImageResource(res);
}
interface OnTitleAreaCliclkListener {
void onTitleAreaClickListener(View view);
}
protected abstract void initData();
protected abstract int getContentViewId();
protected void initEvent() {
}
@Override
protected void onDestroy() {
super.onDestroy();
ButterKnife.unbind(this);
if ( this.mCompositeSubscription != null ) {
this.mCompositeSubscription.unsubscribe();
}
}
}
layouy_base_activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_main_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:clickable="true"
android:src="@mipmap/arrow_left"/>
<TextView
android:id="@+id/tv_title_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="13dp"
android:textColor="@color/font_black3"
android:textSize="@dimen/text_size_17"
tools:text="标题" />
<TextView
android:id="@+id/tv_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:layout_marginRight="5dp"
android:clickable="true"
android:drawablePadding="5dp"
android:textColor="@color/font_black3"
android:textSize="@dimen/text_size_17"
android:visibility="gone"
tools:text="修改" />
<ImageView
android:id="@+id/iv_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:visibility="gone"
tools:src="@mipmap/ic_launcher" />
</RelativeLayout>
<include layout="@layout/line" />
</LinearLayout>
<RelativeLayout
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/ll_main_title" />
<RelativeLayout
android:id="@+id/load_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/ll_main_title"
android:background="@color/white">
<LinearLayout
android:layout_width="120dip"
android:layout_height="120dip"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/loading_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:background="@drawable/load_progress_bar" />
<TextView
android:id="@+id/loading_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:padding="10dip"
android:text="正在加载..."
android:textSize="12sp" />
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
先看上面的布局文件,分为两个大的部分,一个是标题栏,一个是内容区域,标题栏左边有一个默认的返回ImageView,中间是一个TextView,用于显示标题,右边有一个TextView和一个ImageView,TextView用来显示比如修改的操作,ImageView用来显示设置什么的图标,默认左边的返回按钮个标题栏可见状态,右边两个都是隐藏的,因为用到他们的机会还是挺少的。在内用区域,有一个显示内容的布局,到时候用来显示需要加载的布局,还有一个正在加载的布局,用来显示正在加载中。。这个可自己去根据需求定制,这个页面也可以作为加载失败的页面,根据需求来就可以,当这个界面的网络加载完成之后,就可以主动的隐藏掉这个正在加载的页面就可以了,用户体验相对也会好一点。
然后来看下BaseActivity中的操作,首先,我们避免写findViewById的操作,所以我们在BaseActivity中封装好了对ButterKnife的bind和unbind操作,这样在子类中就只需要简单的操作就行,不需要再bind和unbind了,然后提供了一系列的对标题栏自定义的操作,比如设置标题内容,文字大小颜色,设置标题栏右边的图标的资源。。等等,这些根据需求,不够的还可以自己再自行添加就好;还有在前面我们判断了Android版本是否支持沉浸式状态栏,如果支持的话,我在BaseActivity中也对标题栏进行了setPadding的操作,因为不设置的话,标题会跟状态栏的文字重叠,就会比较难看,这种操作不需要子类来理会,所以封装出来会比较好;另外我还提供了一个比较方便的的方法,就是showDialog和stopDialog的操作,当页面需要二次请求网路的时候,显示showDialog,加载完就stopDialog即可,这样用户体验就会好一点;还有一个很大的点就是对网络请求的封装,这个因为我现在对Retrofit还不是非常熟练,所以只进行了简单的封装,到时候经过再研究之后,再来进行完善!在目前的代码中,我写了一个抽象方法,在抽象方法中对请求的响应做出一个初级的判断,如果请求是成功的,并且数据的返回也是正常的,那么回调给调用者,否则直接显示网络加载错误的页面,统一的对网络错误进行处理,不过,这个要能真正完美的使用,需要后台不要太差。。不然老是返回乱七八糟的错误给你,就不太好统一处理了。。到此,BaseActivity的处理也要到此为止了。
BaseFragment的封装
对于BaseFragment来说,其封装跟BaseActtivity是一模一样的,本质的差别也就是一个是Activity一个是Fragment,所以就不再赘述了。
BaseFragmentActivity和BackFragmentActivity的封装
为什么会有这两个类的存在?我现在比较喜欢的一种加载界面方式是使用Fragment碎片化的方式,就是开一个Activity,这个Activity就是承载Fragment的一个容器,所以这个Activity啥都不做,就只作用于添加Fragment,显然,这个Activity是不需要加载网络和一些其他操作的,所以我把他们俩都是集成自ParentActivity,这个具体看下项目中的操作。看这两个类的名字也很清楚,继承BaseFragmentActivity的时候,代表要加载的Fragment是不需要假如回退栈的,继承BackFragmentActivity的时候,代表要加载的Fragment是需要假如回退栈的,当然,在计入Fragment的时候,也可以使用动画,具体的使用,看Demo就好,在此不再赘述了,代码什么的还是要自己看,理解才快。
工具类
最后再来说下项目中默认添加使用的一些其他好用的东西:
本地数据的存储:Hawk
这个工具是当年一个叫Calvin的大哥告诉我的,一用起来就爱不释手。
Hawk 是一个非常便捷的数据库 . 操作数据库只需一行代码 , 能存任何数据类型 .
github 地址: https://github.com/orhanobut/hawk
Hawk 是一个简单的 key-value 数据库
它使用:
AES 加密
能选择使用SharedPreferences 或者 SQLite
Gson解析
提供:
安全数据持久化
能存储任何类型
具体的使用,大家去Github上去看吧,相信只要你用过你就会喜欢上他的,信我。
Android事件总线:EventBus
这个不多说,已经非常成熟了。简单介绍下
实际项目开发过程中,经常遇到如下场景:不同的应用程序组件的控件间具有一定的相互关联性,其中用户对后者进行的某种操作会引起前者的相应改变。举一个具体的场景:以糗事百科为例,在糗事列表页和详情页页,对于每个糗事而言,布局基本一致,在详情页点击了个赞,赞的数量增加,同时赞的图标发生了变化,此时返回到列表页,此糗事上的赞图标以及数量与刚刚详情页的需要保持一致。在举一个例子,对于多个底部导航tab下的资讯类阅读app,在咨询详情页点击了收藏,然后收藏成功,此时回到底部tab中的个人中心,假如个人中心中有我的收藏,同时后面显示的是收藏数量,此时此收藏数量需要同于于刚刚用户所进行的收藏/取消收藏而即时更改数字。凡此种种,类似需求场景非常常见。
有时候,当此类需求相对简单时,通过接口以实现回调等方式可以完成,但是当不同组件/控件之间的关系纷繁复杂时,基于接口的方案不仅使得代码非常繁琐,同时是的程序逻辑很混乱,基于此,EventBus,为此类需求的实现提供了非常方便的方案。
butterknife注解框架的偷懒插件
这个也不多说,已经非常成熟了。简单介绍下
事实上这是个Android Studio的plugin,他可以让你在添加Butterkinfe注解时偷偷懒,直接点击几下鼠标既可以完成注解的增加,同时还是图形化的操作,可以说,大大的减轻了开发负担。尤其是当你的layout中有很多很多的view需要通过findviewbyid来获得引用时。实际上如果不用这个插件而通过手打加ButtefKnife注解的方式,要是view很多启示也挺麻烦的,不是吗?
到此这篇博客也就到了尾声了,可能有很多地方还不是很准确,希望大家多提提意见,而且,这个封装也还很基础,希望大佬有时间看下,多提提意见和建议,再次谢过了。如果觉得不是太烂,记得Star一下哦。
我是Cretin,一个可爱的小男孩。
下面是Github的地址:
https://github.com/MZCretin/CreateNewProjectDemo
相关文章推荐
- spring中提供了一个Filter过滤器,我们可以直接拿来使用
- 安装了MVC1后,仍然不能加载MVC项目,可以尝试使用devenv /setup命令。
- 在一个大型B2C项目中,如果存在大量重复查询,可以通过使用ThreadLocal来解决
- Android --- 一个项目只使用一个Activity初探
- 横向图片轮播(如果一个项目里面只需用这一次,可以用这个插件,多次则不建议使用)
- android中,如果使用imageButton可以在drawable 中设置一个selector,但是imageView设置不起作用
- android中,如果使用imageButton可以在drawable 中设置一个selector,但是imageView设置不起作用
- 一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构、性能的要求都很简单,随着互联网业务的不断丰富,网站
- 【Maven实用技巧】01. 命令行新建一个Android项目,并使用Maven管理
- Android可以直接拿来用的开源项目
- ubuntu下使用命令行创建一个android项目
- 使用手动命令创建一个Android项目
- 将一个Android项目修改为能够使用Maven进行构建
- 使用Volley来写一个List列表(Valley可以解决很大一部分android请求server的问题)
- 【android学习】_如何创建一个android下可以使用的数据库
- android,viewpage实现应用程序员宝典;扩展包不管什么android版本都可以用;第一次安装使用从封面到提示帮助界面
- Android 使用库项目时的一个特殊tip
- gitHub上边android studio开发的开源项目转为Eclipse中的项目,个人调试过的可以使用
- 第十八周项目三(2):使用枚举类型设计函数,可以按指定的方式输出一个平面点的对称点
- 一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目