- 前言:记得那是2014年8月份13号,在亲戚的鼓励下,C#转android,自学了15天网上下载的“黑马视频”后,怀着忐忑不安的心情被带来了深圳,赶鸭子上架般接手了人生中的第一个android项目“橙果新闻”。当时毫无框架的概念,listview也没有viewholder优化,整个app可以说各种卡顿!然鹅,,,对于初学的我来讲,当时的追求也只是app不闪退就好,哈哈。
转眼三年有余,前段时间新公司app项目重构,采用的主框架正好是当下比较热门的MVP+RXJava2+Retrofit2.0,当时由于时间紧任务重,粗略的看了看使用方法就开始编码了,这段时间项目小版本迭代空闲时间多,于是决定将别人整理,封装的框架自己再整理完善一下,以后开发项目都可以用这一套熟练的进行开发了!
构建顺序:
1.常用基础工具类(包括File,Bitmap,字符串处理,图片加载等,UI辅助等等)
2.基类BaseActivity,BaseFragment的构建(titlebar,statebar,loadingview,defalutview四大模块的封装)
3.屏幕适配(图片,长度)
4.mvp模式(使用MVPPluge插件,自动生成MVP的类文件以及该插件的改装)
5.网络框架的接入(RXJava2+Retrofit2.0)
6.gradle多环境配置(测试,预发布,正式)
7.CI自动化打包上传(jenkins+git+码云or蒲公英)
*其中1,2,3跟业务逻辑关系不大,我抽取出来作为一个lib,下载过去直接应用就行。
本次系列文章,本人一改之前懒得传代码的尿性,完整的demo项目地址将会在系列结束后贴上(目前还在整理中),恩,拿去就用!你值得clone
一.常用工具类清单:
Base64Util:图片文件与Base64互转。
BaseBitmapUtil:处理图片的压缩,缩放,裁剪,旋转,并且含有代码创建一些Drawable XML的方法
BaseConstant:定义一下常量
BaseFileUtil:文件的常用操作方法
BasePackageUtil:获取包信息,检查包名应用是否安装等pack相关方法
BaseStringUtil:常规例如:手机号,邮箱等的正则检验,全半角转换,等字符串处理方法
DateUtil:给我时间戳~给你各种日期,时间
KeyboardHelper:专业处理软键盘遮挡,软键盘隐藏,显示等
NetworkUtil:获取网络状态等方法
PreferUtil:专业处理shareperder数据
UiUtil:.提供常见的获取屏幕宽高、获取各种资源等方法,px dp转换,提供延时处理的Handle
这部分没啥好说,都是一些常用的方法,开发必备,当然也不那么完善,根据需求,后期再添加
二.(重点来了)基类BaseActivity,BaseFragment的构建:
一个功能完善,封装优雅的基类无疑可以很大程度上减少重复代码,使得开发时可以专注于业务逻辑,而不是在什么导航栏啊,缺省页啊,加载框啊之类的东西上反复花时间!BaseActivity里面封装了titlebar,statebar,loadingview,defalutview四大模块,开发时界面Activity的相关的UI直接几行代码配置即可,十分方面。
且BaseFragment里面会获取父容器activity,然后直接复用BaseActivity的各种方法。既然是重点,那么下面就来详细讲讲四大模块的封装。
TitleBar
导航栏,toolbar有自身的一些缺陷,还是感觉不够灵活,所以自己封装一个公用的TitleBar是很有必要的。
封装过程:
1.画一个导航栏布局layout,明确导航栏的基本组成,这里我是直接封装了三个Textview,由于Textview有一个drawableX属性,这就使得每个Textview既能做纯文本,又能图文混合, 左右中,三个Textview,基本够用了。
2.定义一个BaseTitleBar接口,里面定义好常用方法:
/*设置整体背景色*/
BaseTitleBar setBgColor(int color);
/*标题栏相关*/
BaseTitleBar setTitle(@StringRes int StringResId);
BaseTitleBar setTitle(String text);
BaseTitleBar setTitleIcon(@DrawableRes int drawableId);
BaseTitleBar setTitleTextColor(int color);
/*右侧文本或按钮相关*/
BaseTitleBar setRightText(String text);
BaseTitleBar setRightText(@StringRes int stringResId);
BaseTitleBar setRightIcon(@DrawableRes int drawableId);
BaseTitleBar showRightTextView();
BaseTitleBar hideRightTextView();
BaseTitleBar setRightTextColor(int color);
BaseTitleBar setRightTextClickListener(View.OnClickListener listener);
/*左侧文本或按钮相关*/
BaseTitleBar setLeftText(String text);
BaseTitleBar setLeftText(@StringRes int stringResId);
BaseTitleBar setLeftIcon(@DrawableRes int drawableId);
BaseTitleBar showLeftTextView();
BaseTitleBar hideLeftTextVeiw();
BaseTitleBar setLeftTextColor(int color);
BaseTitleBar setLeftTextClickListener(View.OnClickListener listener);
int getId();
OK,机智如你,一看这些方法名字就懂了吧。
3.创建TitleBar,继承FrameLayout,实现BaseTitleBar,具体的代码就不贴了,之后自己看。
StateBar
关于状态栏,网上有太多教程,太多方法,太多框架了,这里针对SDK>Build.VERSION_CODES.KITKAT,统一隐藏状态栏,然后建个StateBar去覆盖,StateBar的颜色自定义,还可以隐藏,效果感觉还不错,就不去使用框架了,如果您对状态栏要求比较高,一定要多种效果,一定要适配侧滑等等 那你可以略过StateBar,自己去封装下就好。
封装过程:
1.定义接口BaseStateBar,明确需要提供的方法
void hide();
void show();
void setBackgroundColor(int color);
void setBackgroundDrawable(@DrawableRes int resId);
View getView();
int getId();
boolean isEnabled();
2.创建StateBar,实现BaseStateBar接口
至于具体在BaseActivity中如何去初始化StateBar,可以详见BaseActivity代码
Loadingview
终于到了Loadingview,想想还有点小激动!因为这次,再也不用gif,不用帧动画,不同一张破图旋啊转,用上了大名鼎鼎的lottie,然如您还没有听说过或者使用过lottie(好low啊,掩面偷笑中..)可以看看这篇简单的介绍,内有大量免费炫酷示例,down一下就进自己的app了,,这X装的豪不费功夫有木有?
//www.greatytc.com/p/15c18049f642
LoadingView的封装相对简单,画个xml,在baseactivity中提供两个方法show,hide 你懂的,重点就是引入了lottie,炫酷不止一点点~打了好多字,这里放一段demo里用lottie实现的启动动画来缓解一下木有图的尴尬吧!
缺省页Defaultview
总结起来,app中的缺省页其实无外乎以下几种:
1.无网络缺省页面
2.网络请求错误缺省页
3.空数据缺省页
另外除了缺省页有时候只是toast一下,并不需要缺省页,具体如何,得看业务,得听产品的!哈哈
这里针对最为复杂的情况做一下封装,其他简单情况自然好处理
复杂情况:
进入页面一瞬掉咔嚓断网,显示带有““”刷新看看”按钮的无网络缺省页
点击刷新看看请求网络后服务错抛出错误,显示网络错误缺省页,并且带有按钮“再试试"
点击再试试,请求正常了,可是没有业务数据,显示空数据缺省页,带有按钮“XXXX”
点击按钮XXXX,响应别的业务逻辑
思路有两种:
1.多个defaultview实例,分别控制各个view的层级,显示状态
2.一个实例,根据需要动态变换view中的文本,按钮,图片
对比一下就明白,思路1处理起来会比较麻烦,万一有更复杂的情况需要继续添加不同的defaultview
所以这次的框架中采用思路2,一个defaultview,各种变换!
封装过程和loading大同小异,只不过提供的方法会多一些,三个核心view
图片Image,文本Textview,按钮Textview
BaseActivty中需要封装的四个模块已经分析完,那么他们如何封装进BaseActivity里呢?
大家都知道,ViewGroup有个Addview方法可以添加子类,那么在BaseActivity中设置一个根RelativeLayout,初始化添加StateBar和TitleBar后,将子界面的contentview添加到TitleBar下方:
//mContainer为根RelativeLayout
mContainer.addView(view, getLayoutParams());
private RelativeLayout.LayoutParams getLayoutParams() {
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
if (titleBar != null) {
lp.addRule(RelativeLayout.BELOW, titleBar.getId());
} else if (stateBar.isEnabled()) {
lp.addRule(RelativeLayout.BELOW, stateBar.getId());
}
return lp;
}
而LoadingView和DefaultVie在需要的时候直接addView进根RelativeLayout,不用addrule。
到此为止,BaseActivity的封装基本完成。至于BaseFragment中就更简单了,除了DefultView之外,其他的地方直接:
public void XXXXX() {
BaseActivity baseActivity = getBaseActivity();
if (baseActivity != null) {
baseActivity.XXXXX();
}
}
意思就是任何BaseActivity里面封装过调度UI的方法在BaseFragment里直接通过获取父容器Activity后复用一下。 可以是对于DefaultVie却不能这么用,这是为神马呢?哈哈,留个课后习题,欢迎留言里回答~
三.屏幕适配
网上关于anroid适配的文章太多太多,这里就不去复制了,直接说简单处理方法
适配图片:使用mipmap系列文件夹放图片,mipmap系统会在缩放上提供一定的性能优化,
让UI切一套720P的图(或者用ios 750的,如果UI太懒,你又搞不定他),放入mipmap-xhdpi文件夹 这一套其实就够了,不同分辨率手机上系统会自动去缩放,如果担心高分辨率图片变的模糊可以再适配一套xxx的,反正我的S7edge上基本看不出差别。
长度适配:
如图,简单解释一下,w300dp表示手机 分辨率和手机屏幕密度经过计算后得出该手机宽度300dp
框架中设置了300-420范围的宽度文件夹,绝对涵盖了96%+主流的手机宽度。
像素px =dp* (屏幕密度/160)
一个720px的手机,如果屏幕密度是320,那么他的宽度用dp表示就是360dp,会使用w360里面的dimen,而点开W360里面的deimens看一下
我们设置的dp1正好也是1dp,那么如果UI按照720P给你标注,你直接按标注的px除以2用dp就好。
你可能会问,如果我的手机不是W360的呢? 例如去年的机皇S7edge:
按照公式像素px =dp* (屏幕密度/160)
算出 S7的屏幕宽度是1440/(534/160)= w431.46
系统会根据手机的宽度去选择接近的尺寸文件夹(听说是向下取,431的手机还是会用w420,不会用w440?未实测哦),如果UI按照720P给你标注一个头像Image的宽度是100px, 你还是除以2,用50dp
<ImageView
android:layout_width="@dimen/dp50"
android:layout_height="@dimen/dp50"
/>
注意,这时候是W420文件夹里的dp50哦,同样 打开看看
可以看到,同样取dp50,这时候设置的是58.3, 然后你再算算
58.3/50 是不是 约= 420/360
至于明明是w431,可是取的是w420,或者一个w359的设备向下取到w340怎么办?
其实不用太纠结,正常设备宽度350+,359和340差了屏幕宽度的1/17,也就是说如果50dp最多相差3dp,基本可以忽略。如果你不能接受这个说法,那么我会继续说服你,359-340也是极端情况了,哪里去找正好359的设备呢? 如果你还不服,那你去建立一个w350,甚至w355的吧,误差可以控制在1dp。
如果你真的打算这么做的话,那你一定是处女座!处女座!!
处女座追求完美也没有错,至于w350,甚至w430+的dimens怎么产生的 其实很简单,网上有脚本,找来跑一下,或者直接一个for循环啊,比如wXXX的
for(int i=1:i<500;i++){
float value=XXX/360*i;
Logger.d("<dimen name="dp"+i+">"+value+"dp</dimen>")
}
好了,篇幅原因,上篇到此为止,可能文中有些不准确或者错误的地方欢迎指出,大家一起进步!下个月25号见。