- 前言:記得那是2014年8月份13號(hào),在親戚的鼓勵(lì)下,C#轉(zhuǎn)android,自學(xué)了15天網(wǎng)上下載的“黑馬視頻”后,懷著忐忑不安的心情被帶來(lái)了深圳,趕鴨子上架般接手了人生中的第一個(gè)android項(xiàng)目“橙果新聞”。當(dāng)時(shí)毫無(wú)框架的概念,listview也沒(méi)有viewholder優(yōu)化,整個(gè)app可以說(shuō)各種卡頓!然鵝,,,對(duì)于初學(xué)的我來(lái)講,當(dāng)時(shí)的追求也只是app不閃退就好,哈哈。
轉(zhuǎn)眼三年有余,前段時(shí)間新公司app項(xiàng)目重構(gòu),采用的主框架正好是當(dāng)下比較熱門(mén)的MVP+RXJava2+Retrofit2.0,當(dāng)時(shí)由于時(shí)間緊任務(wù)重,粗略的看了看使用方法就開(kāi)始編碼了,這段時(shí)間項(xiàng)目小版本迭代空閑時(shí)間多,于是決定將別人整理,封裝的框架自己再整理完善一下,以后開(kāi)發(fā)項(xiàng)目都可以用這一套熟練的進(jìn)行開(kāi)發(fā)了!
構(gòu)建順序:
1.常用基礎(chǔ)工具類(lèi)(包括File,Bitmap,字符串處理,圖片加載等,UI輔助等等)
2.基類(lèi)BaseActivity,BaseFragment的構(gòu)建(titlebar,statebar,loadingview,defalutview四大模塊的封裝)
3.屏幕適配(圖片,長(zhǎng)度)
4.mvp模式(使用MVPPluge插件,自動(dòng)生成MVP的類(lèi)文件以及該插件的改裝)
5.網(wǎng)絡(luò)框架的接入(RXJava2+Retrofit2.0)
6.gradle多環(huán)境配置(測(cè)試,預(yù)發(fā)布,正式)
7.CI自動(dòng)化打包上傳(jenkins+git+碼云or蒲公英)
*其中1,2,3跟業(yè)務(wù)邏輯關(guān)系不大,我抽取出來(lái)作為一個(gè)lib,下載過(guò)去直接應(yīng)用就行。
本次系列文章,本人一改之前懶得傳代碼的尿性,完整的demo項(xiàng)目地址將會(huì)在系列結(jié)束后貼上(目前還在整理中),恩,拿去就用!你值得clone
一.常用工具類(lèi)清單:
Base64Util:圖片文件與Base64互轉(zhuǎn)。
BaseBitmapUtil:處理圖片的壓縮,縮放,裁剪,旋轉(zhuǎn),并且含有代碼創(chuàng)建一些Drawable XML的方法
BaseConstant:定義一下常量
BaseFileUtil:文件的常用操作方法
BasePackageUtil:獲取包信息,檢查包名應(yīng)用是否安裝等pack相關(guān)方法
BaseStringUtil:常規(guī)例如:手機(jī)號(hào),郵箱等的正則檢驗(yàn),全半角轉(zhuǎn)換,等字符串處理方法
DateUtil:給我時(shí)間戳~給你各種日期,時(shí)間
KeyboardHelper:專(zhuān)業(yè)處理軟鍵盤(pán)遮擋,軟鍵盤(pán)隱藏,顯示等
NetworkUtil:獲取網(wǎng)絡(luò)狀態(tài)等方法
PreferUtil:專(zhuān)業(yè)處理shareperder數(shù)據(jù)
UiUtil:.提供常見(jiàn)的獲取屏幕寬高、獲取各種資源等方法,px dp轉(zhuǎn)換,提供延時(shí)處理的Handle
這部分沒(méi)啥好說(shuō),都是一些常用的方法,開(kāi)發(fā)必備,當(dāng)然也不那么完善,根據(jù)需求,后期再添加
二.(重點(diǎn)來(lái)了)基類(lèi)BaseActivity,BaseFragment的構(gòu)建:
一個(gè)功能完善,封裝優(yōu)雅的基類(lèi)無(wú)疑可以很大程度上減少重復(fù)代碼,使得開(kāi)發(fā)時(shí)可以專(zhuān)注于業(yè)務(wù)邏輯,而不是在什么導(dǎo)航欄啊,缺省頁(yè)啊,加載框啊之類(lèi)的東西上反復(fù)花時(shí)間!BaseActivity里面封裝了titlebar,statebar,loadingview,defalutview四大模塊,開(kāi)發(fā)時(shí)界面Activity的相關(guān)的UI直接幾行代碼配置即可,十分方面。
且BaseFragment里面會(huì)獲取父容器activity,然后直接復(fù)用BaseActivity的各種方法。既然是重點(diǎn),那么下面就來(lái)詳細(xì)講講四大模塊的封裝。
TitleBar
導(dǎo)航欄,toolbar有自身的一些缺陷,還是感覺(jué)不夠靈活,所以自己封裝一個(gè)公用的TitleBar是很有必要的。
封裝過(guò)程:
1.畫(huà)一個(gè)導(dǎo)航欄布局layout,明確導(dǎo)航欄的基本組成,這里我是直接封裝了三個(gè)Textview,由于Textview有一個(gè)drawableX屬性,這就使得每個(gè)Textview既能做純文本,又能圖文混合, 左右中,三個(gè)Textview,基本夠用了。
2.定義一個(gè)BaseTitleBar接口,里面定義好常用方法:
/*設(shè)置整體背景色*/
BaseTitleBar setBgColor(int color);
/*標(biāo)題欄相關(guān)*/
BaseTitleBar setTitle(@StringRes int StringResId);
BaseTitleBar setTitle(String text);
BaseTitleBar setTitleIcon(@DrawableRes int drawableId);
BaseTitleBar setTitleTextColor(int color);
/*右側(cè)文本或按鈕相關(guān)*/
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);
/*左側(cè)文本或按鈕相關(guān)*/
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,機(jī)智如你,一看這些方法名字就懂了吧。
3.創(chuàng)建TitleBar,繼承FrameLayout,實(shí)現(xiàn)BaseTitleBar,具體的代碼就不貼了,之后自己看。
StateBar
關(guān)于狀態(tài)欄,網(wǎng)上有太多教程,太多方法,太多框架了,這里針對(duì)SDK>Build.VERSION_CODES.KITKAT,統(tǒng)一隱藏狀態(tài)欄,然后建個(gè)StateBar去覆蓋,StateBar的顏色自定義,還可以隱藏,效果感覺(jué)還不錯(cuò),就不去使用框架了,如果您對(duì)狀態(tài)欄要求比較高,一定要多種效果,一定要適配側(cè)滑等等 那你可以略過(guò)StateBar,自己去封裝下就好。
封裝過(guò)程:
1.定義接口BaseStateBar,明確需要提供的方法
void hide();
void show();
void setBackgroundColor(int color);
void setBackgroundDrawable(@DrawableRes int resId);
View getView();
int getId();
boolean isEnabled();
2.創(chuàng)建StateBar,實(shí)現(xiàn)BaseStateBar接口
至于具體在BaseActivity中如何去初始化StateBar,可以詳見(jiàn)BaseActivity代碼
Loadingview
終于到了Loadingview,想想還有點(diǎn)小激動(dòng)!因?yàn)檫@次,再也不用gif,不用幀動(dòng)畫(huà),不同一張破圖旋啊轉(zhuǎn),用上了大名鼎鼎的lottie,然如您還沒(méi)有聽(tīng)說(shuō)過(guò)或者使用過(guò)lottie(好low啊,掩面偷笑中..)可以看看這篇簡(jiǎn)單的介紹,內(nèi)有大量免費(fèi)炫酷示例,down一下就進(jìn)自己的app了,,這X裝的豪不費(fèi)功夫有木有?
http://www.itdecent.cn/p/15c18049f642
LoadingView的封裝相對(duì)簡(jiǎn)單,畫(huà)個(gè)xml,在baseactivity中提供兩個(gè)方法show,hide 你懂的,重點(diǎn)就是引入了lottie,炫酷不止一點(diǎn)點(diǎn)~打了好多字,這里放一段demo里用lottie實(shí)現(xiàn)的啟動(dòng)動(dòng)畫(huà)來(lái)緩解一下木有圖的尷尬吧!

缺省頁(yè)Defaultview
總結(jié)起來(lái),app中的缺省頁(yè)其實(shí)無(wú)外乎以下幾種:
1.無(wú)網(wǎng)絡(luò)缺省頁(yè)面
2.網(wǎng)絡(luò)請(qǐng)求錯(cuò)誤缺省頁(yè)
3.空數(shù)據(jù)缺省頁(yè)
另外除了缺省頁(yè)有時(shí)候只是toast一下,并不需要缺省頁(yè),具體如何,得看業(yè)務(wù),得聽(tīng)產(chǎn)品的!哈哈
這里針對(duì)最為復(fù)雜的情況做一下封裝,其他簡(jiǎn)單情況自然好處理
復(fù)雜情況:
進(jìn)入頁(yè)面一瞬掉咔嚓斷網(wǎng),顯示帶有““”刷新看看”按鈕的無(wú)網(wǎng)絡(luò)缺省頁(yè)
點(diǎn)擊刷新看看請(qǐng)求網(wǎng)絡(luò)后服務(wù)錯(cuò)拋出錯(cuò)誤,顯示網(wǎng)絡(luò)錯(cuò)誤缺省頁(yè),并且?guī)в邪粹o“再試試"
點(diǎn)擊再試試,請(qǐng)求正常了,可是沒(méi)有業(yè)務(wù)數(shù)據(jù),顯示空數(shù)據(jù)缺省頁(yè),帶有按鈕“XXXX”
點(diǎn)擊按鈕XXXX,響應(yīng)別的業(yè)務(wù)邏輯
思路有兩種:
1.多個(gè)defaultview實(shí)例,分別控制各個(gè)view的層級(jí),顯示狀態(tài)
2.一個(gè)實(shí)例,根據(jù)需要?jiǎng)討B(tài)變換view中的文本,按鈕,圖片
對(duì)比一下就明白,思路1處理起來(lái)會(huì)比較麻煩,萬(wàn)一有更復(fù)雜的情況需要繼續(xù)添加不同的defaultview
所以這次的框架中采用思路2,一個(gè)defaultview,各種變換!
封裝過(guò)程和loading大同小異,只不過(guò)提供的方法會(huì)多一些,三個(gè)核心view
圖片Image,文本Textview,按鈕Textview
BaseActivty中需要封裝的四個(gè)模塊已經(jīng)分析完,那么他們?nèi)绾畏庋b進(jìn)BaseActivity里呢?
大家都知道,ViewGroup有個(gè)Addview方法可以添加子類(lèi),那么在BaseActivity中設(shè)置一個(gè)根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在需要的時(shí)候直接addView進(jìn)根RelativeLayout,不用addrule。
到此為止,BaseActivity的封裝基本完成。至于BaseFragment中就更簡(jiǎn)單了,除了DefultView之外,其他的地方直接:
public void XXXXX() {
BaseActivity baseActivity = getBaseActivity();
if (baseActivity != null) {
baseActivity.XXXXX();
}
}
意思就是任何BaseActivity里面封裝過(guò)調(diào)度UI的方法在BaseFragment里直接通過(guò)獲取父容器Activity后復(fù)用一下。 可以是對(duì)于DefaultVie卻不能這么用,這是為神馬呢?哈哈,留個(gè)課后習(xí)題,歡迎留言里回答~
三.屏幕適配
網(wǎng)上關(guān)于anroid適配的文章太多太多,這里就不去復(fù)制了,直接說(shuō)簡(jiǎn)單處理方法
適配圖片:使用mipmap系列文件夾放圖片,mipmap系統(tǒng)會(huì)在縮放上提供一定的性能優(yōu)化,
讓UI切一套720P的圖(或者用ios 750的,如果UI太懶,你又搞不定他),放入mipmap-xhdpi文件夾 這一套其實(shí)就夠了,不同分辨率手機(jī)上系統(tǒng)會(huì)自動(dòng)去縮放,如果擔(dān)心高分辨率圖片變的模糊可以再適配一套xxx的,反正我的S7edge上基本看不出差別。
長(zhǎng)度適配:

如圖,簡(jiǎn)單解釋一下,w300dp表示手機(jī) 分辨率和手機(jī)屏幕密度經(jīng)過(guò)計(jì)算后得出該手機(jī)寬度300dp
框架中設(shè)置了300-420范圍的寬度文件夾,絕對(duì)涵蓋了96%+主流的手機(jī)寬度。
像素px =dp* (屏幕密度/160)
一個(gè)720px的手機(jī),如果屏幕密度是320,那么他的寬度用dp表示就是360dp,會(huì)使用w360里面的dimen,而點(diǎn)開(kāi)W360里面的deimens看一下

我們?cè)O(shè)置的dp1正好也是1dp,那么如果UI按照720P給你標(biāo)注,你直接按標(biāo)注的px除以2用dp就好。
你可能會(huì)問(wèn),如果我的手機(jī)不是W360的呢? 例如去年的機(jī)皇S7edge:

按照公式像素px =dp* (屏幕密度/160)
算出 S7的屏幕寬度是1440/(534/160)= w431.46
系統(tǒng)會(huì)根據(jù)手機(jī)的寬度去選擇接近的尺寸文件夾(聽(tīng)說(shuō)是向下取,431的手機(jī)還是會(huì)用w420,不會(huì)用w440?未實(shí)測(cè)哦),如果UI按照720P給你標(biāo)注一個(gè)頭像Image的寬度是100px, 你還是除以2,用50dp
<ImageView
android:layout_width="@dimen/dp50"
android:layout_height="@dimen/dp50"
/>
注意,這時(shí)候是W420文件夾里的dp50哦,同樣 打開(kāi)看看

可以看到,同樣取dp50,這時(shí)候設(shè)置的是58.3, 然后你再算算
58.3/50 是不是 約= 420/360
至于明明是w431,可是取的是w420,或者一個(gè)w359的設(shè)備向下取到w340怎么辦?

其實(shí)不用太糾結(jié),正常設(shè)備寬度350+,359和340差了屏幕寬度的1/17,也就是說(shuō)如果50dp最多相差3dp,基本可以忽略。如果你不能接受這個(gè)說(shuō)法,那么我會(huì)繼續(xù)說(shuō)服你,359-340也是極端情況了,哪里去找正好359的設(shè)備呢? 如果你還不服,那你去建立一個(gè)w350,甚至w355的吧,誤差可以控制在1dp。
如果你真的打算這么做的話(huà),那你一定是處女座!處女座!!

處女座追求完美也沒(méi)有錯(cuò),至于w350,甚至w430+的dimens怎么產(chǎn)生的 其實(shí)很簡(jiǎn)單,網(wǎng)上有腳本,找來(lái)跑一下,或者直接一個(gè)for循環(huán)啊,比如wXXX的
for(int i=1:i<500;i++){
float value=XXX/360*i;
Logger.d("<dimen name="dp"+i+">"+value+"dp</dimen>")
}
好了,篇幅原因,上篇到此為止,可能文中有些不準(zhǔn)確或者錯(cuò)誤的地方歡迎指出,大家一起進(jìn)步!下個(gè)月25號(hào)見(jiàn)。