先扯兩句
最近在開發(fā)一個項目,算算時間也有一周沒有繼續(xù)寫《一個android工程的從零開始》了填抬,先跟大家道個歉翻默。當(dāng)然,這段時間真正實(shí)際操作中抒痒,也發(fā)現(xiàn)了自己的Base封裝中有一些Bug幌绍,正好在這次開發(fā)過程中找出來,并且予以改正故响,特此發(fā)一篇博客說明一下傀广,之前博客對應(yīng)的部分會給予提示,部分內(nèi)容就不予以修改了彩届,算是為大家也為自己在出錯的時候提供一個可查找對照的方向伪冰。錯誤的部分為大家使用過程中帶來的不便十分抱歉。
閑言少敘樟蠕,老規(guī)矩還是先上我的Git庫贮聂,然后開始正文。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
正文
這部分主要分為BaseActivity封裝修改寨辩、BaseFragment封裝修改吓懈、Retrofit header動態(tài)添加封裝三部分。
BaseActivity封裝修改
BaseActivity布局
BaseActivity中靡狞,布局修改的部分是ScrollView布局的部分耻警,當(dāng)然,這部分使用不會報錯甸怕,只是無法實(shí)現(xiàn)我們要求甘穿,具體會出現(xiàn)什么問題,我創(chuàng)建了一個測試Activity蕾各,布局文件相當(dāng)簡單:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context="com.iyubatestapplication.ui.TestActivity">
<TextView
android:id="@+id/textView"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="大家注意看標(biāo)題"
android:textSize="100sp" />
</LinearLayout>
而java部分也是簡單異常:
public class TestActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setBaseConten tView(R.layout.activity_test);
setTitle("Test");
}
}
那么我們來看看效果是什么樣的:
大家可以看到扒磁,Title部分竟然也隨著滑動了,這明顯不是工程中我們想要的效果式曲。
至于造成這個現(xiàn)象的原因妨托,其實(shí)很簡單缸榛,那就是之前集成的時候,我將ScrollView放在了最外層兰伤,也就是放在了Title的外面内颗,那么ScrollView內(nèi)的都會滑動,自然也就會出現(xiàn)Title隨之滑動的現(xiàn)象了敦腔,既然好的了問題均澳,那么解決起來自然就建簡單了,只要將ScrollView放到Title下面一層不就好了嘛符衔。不過真的這么簡單嗎找前?
這是京東的布局,如果套用我們剛剛的設(shè)想判族,那么大家思考一下躺盛,下方的工具欄在我們滑動的過程中會出現(xiàn)什么效果?沒錯形帮,就如同上面的Title一樣槽惫,會出現(xiàn)隨著滑動的情況。當(dāng)然辩撑,這是首頁所特有的界斜,想必大多數(shù)APP只會有一個首頁,而且就功能而言合冀,也就是“我的”版塊才會用到ScrollView各薇,而其他版塊基本都會有RecyclerView(或者ListView、GridView君躺、VLayout等)得糜,所以說這個部分我們可以單獨(dú)搭建。
可是晰洒,如果當(dāng)前的Activity中需要創(chuàng)建帶有Title或者底部固定位置布局的Fragment,那么前面Title的問題就會再次出現(xiàn)啥箭,作為一個一直將偷懶作為人生準(zhǔn)則的人谍珊,當(dāng)然想要弄一個都考慮到的情況,所以這部分的布局代碼就被我修改成了這樣一副樣子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.banshouweng.mybaseapplication.base.BaseActivity">
<include
android:id="@+id/base_title_layout"
layout="@layout/title_layout"></include>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:id="@+id/base_main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"></LinearLayout>
<ScrollView
android:id="@+id/base_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"></ScrollView>
</FrameLayout>
</LinearLayout>
將LinearLayout與ScrollView放在一起急侥,那么就可以想用哪里選哪里砌滞,當(dāng)然,我們的setBaseContentView方法也要做一定的調(diào)整坏怪,成為了兩個方法贝润,setBaseContentView與setBaseScrollContentView,分別對應(yīng)沒有ScrollView和有ScrollView兩種情況:
/**
* 引用頭部布局
*
* @param layoutId 布局id
*/
public void setBaseContentView(int layoutId) {
LinearLayout layout = (LinearLayout) findViewById(R.id.base_main_layout);
//獲取布局铝宵,并在BaseActivity基礎(chǔ)上顯示
final View view = getLayoutInflater().inflate(layoutId, null);
//關(guān)閉鍵盤
hideKeyBoard();
//給EditText的父控件設(shè)置焦點(diǎn)打掘,防止鍵盤自動彈出
view.setFocusable(true);
view.setFocusableInTouchMode(true);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
layout.addView(view, params);
layout.setVisibility(View.VISIBLE);
}
/**
* 引用頭部布局且當(dāng)前頁面基于ScrollView
*
* @param layoutId 布局id
*/
public void setBaseScrollContentView(int layoutId) {
ScrollView layout = (ScrollView) findViewById(R.id.base_scroll_view);
//當(dāng)子布局高度值不足ScrollView時华畏,用這個方法可以充滿ScrollView,防止布局無法顯示
layout.setFillViewport(true);
//獲取布局尊蚁,并在BaseActivity基礎(chǔ)上顯示
final View view = getLayoutInflater().inflate(layoutId, null);
//關(guān)閉鍵盤
hideKeyBoard();
//給EditText的父控件設(shè)置焦點(diǎn)亡笑,防止鍵盤自動彈出
view.setFocusable(true);
view.setFocusableInTouchMode(true);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
layout.addView(view, params);
layout.setVisibility(View.VISIBLE);
}
大家可以看得出來,我這里給方法的注釋是“引用頭部布局”也就是說横朋,以上的所有討論都是基于會引用我們的Title的情況下的仑乌,畢竟如果不使用Title的時候,我們可以直接使用setContentView方法進(jìn)行布局初始化琴锭,當(dāng)然晰甚,看過我之前博客的也會知道,我創(chuàng)建了一個hideTitle方法决帖,用于處理Title的顯隱厕九。不過hideTitle + setBaseContentView就是setContentView,只有這種情況下古瓤,就建議大家直接使用setContentView止剖,而hideTitle + setBaseScrollContentView則相當(dāng)于在在setContentView的基礎(chǔ)上嵌套了一層ScrollView,如果真的有需要落君,這個組合還是可以使用的穿香。
當(dāng)然,如果我們的布局中嵌套了ScrollView的情況下绎速,在使用ListView與GridView的時候皮获,會出現(xiàn)顯示不全的情況等,這個部分想必大家都已經(jīng)清楚了纹冤,而解決方法就是需要我們自定義一個自己的ListView洒宝,一般都為其命名為MyListView,而重寫的方法也很簡單萌京,只需要繼承ListView雁歌,并重寫以下方法即可:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
} catch (Exception e) {
}
}
不過好在我最近使用RecyclerView的時候,故意沒有重寫一下知残,簡單做了個測試靠瞎,并沒有發(fā)現(xiàn)ListView想同的問題,所以暫時就直接用了求妹,如果大家對這部分有什么其他的發(fā)現(xiàn)乏盐,歡迎一起溝通。
ps:在工程開發(fā)過程中制恍,尤其是電商APP的產(chǎn)品瀏覽父能,經(jīng)常會上設(shè)置一鍵返回頭部的按鈕,這個按鈕就很顯然就不適合在ScrollView布局的內(nèi)層净神,所以我們可以考慮將其集成在BaseActivity與Title一層何吝,當(dāng)然記得將父布局改成RelativeLayout溉委,并在下方FrameLayout中添加屬性android:layout_below=“@+id/base_title_layout”
ps的ps:以上建議是在本框架基礎(chǔ)上進(jìn)行的操作,當(dāng)然也可以脫離框架自行編寫一個布局岔霸,而且商品瀏覽一把都是有列表布局的薛躬,因此也不需要使用ScrollView,非要在上下添加其他布局的情況呆细,RecyclerView可以判斷onCreateViewHolder中的viewType做對應(yīng)適配型宝,ListView、GridView通用的方法就是添加Header絮爷,至于RecyclerView添加header的方法趴酣,大家可以看看張鴻洋大神的Android 優(yōu)雅的為RecyclerView添加HeaderView和FooterView,或許會有幫助坑夯。
BaseActivity方法修改####
前面說了布局岖寞,是會影響到我們使用的,下面說的方法柜蜈,呃仗谆,一部分也影響到我們使用,先上方法吧淑履。
/**
* 最右側(cè)文本功能鍵設(shè)置方法
*
* @param text 文本信息
* @param clickListener 點(diǎn)擊事件
* @return 將當(dāng)前TextView返回方便進(jìn)一步處理
*/
public TextView setBaseRightText(String text, View.OnClickListener clickListener) {
TextView baseRightText = (TextView) findViewById(R.id.base_right_text);
baseRightText.setText(text);
baseRightText.setVisibility(View.VISIBLE);
baseRightText.setOnClickListener(clickListener);
return baseRightText;
}
上面這個方法看過我前面博客的應(yīng)該比較熟悉隶垮,那就是我在最右側(cè)設(shè)置了一個文本功能鍵的使用方法,包括設(shè)置文本秘噪、設(shè)置點(diǎn)擊時間等狸吞,不過若是細(xì)看的話,很簡單就會發(fā)現(xiàn)與之前的不同指煎。
第一:創(chuàng)建的baseRightText從全局變量變成了如今的局部變量蹋偏,也就是說當(dāng)我們不使用這個功能鍵的時候,雖然解析布局的時候依然會解析base_right_text對應(yīng)id的TextView至壤,不過至少(TextView) findViewById方法可以偷懶不執(zhí)行了威始。
第二:TextView baseRightText = (TextView) findViewById(R.id.base_right_text),很顯然這并不是ButterKnife的控件綁定方法像街,至于為什么這么寫字逗,主要還是因為我們無法在BaseActivity和集成它的Activity中同時添加ButterKnife.bind(this);,不然會出現(xiàn)如下錯誤:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.banshouweng.mybaseapplication/com.banshouweng.mybaseapplication.ui.activity.MainActivity}: java.lang.IllegalStateException: Required view 'address_list' with ID 2131427418 for field 'addressList' was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.
...
Caused by: java.lang.IllegalStateException: Required view 'address_list' with ID 2131427418 for field 'addressList' was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.
原因就是在集成BaseActivity的Activity(以MainActivity為例)的onCreate宅广,會有super.onCreate(savedInstanceState);方法,也就是說MainActivity的onCreate執(zhí)行之前我們會先執(zhí)行BaseActivity的onCreate方法些举,而BaseActivity中有ButtKnife.bind(this);方法跟狱,看過我之前的博客的會知道,我在BaseActivity中有這么一段代碼:
if (!(this instanceof MainActivity)) {
activities.add(this);
}
先把我查的this是什么貼出來:
this確實(shí)是當(dāng)前activity的指針户魏,它可以傳給Context是因為Activity是Context的一個子類
也就是說當(dāng)MainActivity繼承BaseActivity時驶臊,this就是MainActivity挪挤,所以綁定控件的時候,就會以BaseActivity的布局文件作為參照关翎,而這部分自然是沒有MainActivity中對應(yīng)的布局文件的扛门,自然就會報上述錯誤。
反之纵寝,如果是在MainActivity中添加ButtKnife.bind(this);方法论寨,而不再BaseActivity中添加,我們可以在下圖位置查找到MainActivity_ViewBinding類:
在其中可以看到:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.addressList = Utils.findRequiredViewAsType(source, R.id.address_list, "field 'addressList'", RecyclerView.class);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.addressList = null;
}
}
也就是說考阱,其中只有我在MainActivity中的addressList控件猾愿,并沒有BaseActivity中的控件猎醇,所以很遺憾,這樣依然會報上述錯誤火焰,因此,在BaseActivity中只能使用TextView baseRightText = (TextView) findViewById(R.id.base_right_text);關(guān)聯(lián)控件與布局文件胧沫。
其三:@return 將當(dāng)前TextView返回方便進(jìn)一步處理
這個部分也比較好理解昌简,有很多情況下,我們需要做界面的復(fù)用绒怨,也就是說右上角的圖片功能鍵纯赎、文本功能鍵都不是固定的,需要修改資源窖逗,或者隱藏掉已經(jīng)顯示出來的功能鍵址否,而如果每次都創(chuàng)建一個對應(yīng)的方法就得不償失了,所以這里我們便將對應(yīng)的功能鍵控件返回到調(diào)用的Activity中碎紊,需要處理的時候佑附,處理對應(yīng)返回的控件即可。
其四:baseRightText.setOnClickListener(clickListener);
這里不再創(chuàng)建對應(yīng)的新接口仗考,而是統(tǒng)一使用OnClickListener音同,當(dāng)然,作為懶人秃嗜,之前我一直懶得記每個對應(yīng)控件的Id权均,不過第三點(diǎn)已經(jīng)說了,我們將對應(yīng)的控件返回了回來锅锨,所以只需要調(diào)用對應(yīng)控件的getId()方法就可以很優(yōu)雅的解決掉我們的偷懶問題叽赊。
BaseFragment封裝修改
前面BaseActivity寫了那么多,作為一個懶人必搞,大家就別指望我在這里也會寫那么多了必指,這里我只總結(jié)了一句話,那就是參見“BaseActivity封裝修改”恕洲,對應(yīng)做修改即可——和諧社會塔橡,不能打人的梅割。
Retrofit header動態(tài)添加封裝
這個部分呢,是最近有這方面需求葛家,所以我就查看了一下户辞,首先還是先感謝zhuhai__yizhi的Retrofit添加header參數(shù)的幾種方法。幫了個大忙癞谒。
大家可以通過zhuhai__yizhi了解一下對應(yīng)的三種方法底燎,而作為一個懶人的我,實(shí)在是太懶了扯俱,而且是集成的Retrofit2.3.0的我书蚪,就稍微嘗試了一下,結(jié)果發(fā)現(xiàn)了一個更好的偷懶方法:
//Post
public interface RetrofitPostService {
@FormUrlEncoded
@POST("{action}")
Observable<ResponseBody> postResult(@Path("action") String action, @HeaderMap Map<String, String> headerParams, @FieldMap Map<String, String> params);
}
//Get
public interface RetrofitGetService {
@GET("{action}")
Observable<ResponseBody> getResult(@Path("action") String action, @HeaderMap Map<String, String> headerParams, @QueryMap Map<String, String> params);
}
大家應(yīng)該看出來了迅栅,那就是@HeaderMap Map<String, String> headerParams參數(shù)殊校,用法就是Key是字段,Value是我們要傳遞的值读存,當(dāng)然現(xiàn)階段我接觸到的为流,需要傳遞Header的還是比較少的,但是多了解一下總歸是沒有壞處的嘛让簿。