最近自己編寫下拉刷新的時候,發現了一個問題,就是有一個需求是這樣的:要求頁面中是一個Tab切換界面,一個界面有底部操作欄,不可下拉刷新,另一個界面沒有底部操作欄,但可以下拉刷新。
按照平常的做法,我在xml文件中使用了RelativeLayout,聲明下拉刷新組件的layout_above為底部操作欄,然后在測試的時候發現一個奇怪的現象:如果一開始設置底部操作欄可見,在另一個運行下拉刷新的界面在下拉的時候就會出現和底部操作欄同樣位置,同樣大小,但顏色采用系統默認的布局,如果設置為不可見,則不會出現這個問題。
<BottomView android:id="@+id/bottom" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentBottom="true"/> <RefreshListView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/bottom" />
經過調試,底部操作欄的確是在下拉刷新的時候被設為不可見,所以這個迷之物體並不是底部操作欄。
查了一些資料,雖然並沒有找到和我一樣的問題,但最大的可能就是在RelativeLayout中,如果layout_above對應的組件被gone掉,有可能產生布局錯亂的問題。
既然這樣,就換一種寫法:聲明底部操作欄的layout_bellow為下拉刷新組件。可惜,同樣的問題還是會產生。
好吧,既然有可能是因為組件的相對關系產生問題,那么思考是否能夠動態的添加對應關系。
Android依然可以提供這種方案,可以通過view.getLayoutParams來獲取組件的LayoutParams,然后通過addRule和removeRule方法來動態的添加和刪除相對關系。但可惜的是,removeRule和addRule這兩個方法並不是成套出售的,removeRule需要更高的API才能支持。
想想,為了考慮兼容性問題,還是放棄這種做法。
結合現象,可能的問題就是下拉刷新需要一個下拉區域,而這個區域在View剛被繪制的時候就已經確定好,如果后面的對應關系被打亂了,可能系統也會產生默認的布局來明確這個區域。
更好的解決方法就出來了:將底部操作欄也作為下拉刷新的區域,也就是和下拉刷新組件一起作為一個布局。
問題是解決了,但我覺得不是很舒服:我的 下拉刷新組件是通用的,只要不啟動下拉刷新這個功能,它完全就是一個ListView,並且還可以自由的配置沒有數據時候的界面。現在因為一個需求,就加入一個特化的底部操作欄,即使可以控制它的可見性一定程度上還是可以保證它是可用的,但如果我要替換底部操作欄呢?
所以我開始把底部操作欄也可以封裝進來。
一開始想到的解決方法就是利用面向對象的繼承關系,聲明一個基類組件,但實際上,Android的xml文件並不能識別組件的繼承關系,不能讓子類組件用在基類組件聲明的地方。
既然繼承行不通,就利用組合。
聲明一個類,持有自定義布局底部操作欄的引用,就可以對它進行控制。
public class ContactBottomOperation { private BaseBottomOperation bottomOperation; ... }
但要想做到自由替換布局,就必須傳入layout的id,然后由該類對布局的渲染進行控制。
public BaseBottomOperation initBottom(int id) { bottomOperation.setVisibility(View.VISIBLE); bottomOperation.init(id); return bottomOperation; } public class BaseBottomOperation{ ... public void init(int id) { LayoutInflater.from(context).inflate(id, this); } }
問題是解決了,但還是發現一個不舒服的地方:點擊事件呢?
因為我只是持有布局的引用,所以我不能讓該類實現onItemClickListener,無法監聽點擊事件,而自定義的底部操作欄也只有在渲染的時候才知道自己的控件到底是啥。
其實,這個問題根本就不是問題,我完全可以在控制類中獲取到底部操作欄的控件,然后設置相應的事件,但無法使用ButterKnife這樣的工具來簡化代碼。
rlSave = (RelativeLayout)bottom.findViewById(R.id.rl_save); rlSave.setOnItemClickListener(new OnItemClickListener(){ @Override public void onClick(...){ save(); } } }
想到ButterKnife原本就是利用反射的原理,於是我也開始思考反射能否解決我的困擾。
這樣就可以解決問題了,我只要傳入需要監聽的View和調用的方法,就可以完成View和事件的綁定。
bottomOperation.setItemClick(rlFav, this, "favorite"); public void setItemClick(View view, final Object object, final String methodName) { view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Class classObj = object.getClass(); try { Method method = classObj.getMethod(methodName, null); method.invoke(object, null); } catch (NoSuchMethodException e) { LogUtil.e(e.toString()); } catch (InvocationTargetException e) { LogUtil.e(e.toString()); } catch (IllegalAccessException e) { LogUtil.e(e.toString()); } } });
實際編碼中總會遇到各種奇葩的問題,但問題並不是解決了就完事了,往往問題的解決才是真正的開始。