2017安卓主流框架搭建?看這篇就夠了(上)

  • 前言:記得那是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)緩解一下木有圖的尷尬吧!

SM-G9350_20170920211104.gif

缺省頁(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)度適配:
多dimens.png

如圖,簡(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看一下

W360.png

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

S7edge.png

按照公式像素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)看看

w420.png

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

joke.png

其實(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à),那你一定是處女座!處女座!!

truth.png

處女座追求完美也沒(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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容