今天來分享下做導(dǎo)航欄的另外一種方法,導(dǎo)航欄可以放在頂部,也可以放在底部,之前分享過一片底部導(dǎo)航欄的實(shí)現(xiàn)方式一行代碼實(shí)現(xiàn)底部導(dǎo)航欄TabLayout,用的是Android自帶的控件FragmentTabLayout。今天我們用的一種更為靈活的方式,采用國際慣例Adapter來自定義一個(gè)導(dǎo)航欄,可以自己定義每個(gè)Tab的布局,可以方便的改變導(dǎo)航欄里面標(biāo)簽的個(gè)數(shù)。
本文會(huì)分享到的內(nèi)容:
1.ListView Adapter源碼分析
2.自定義Adapter
3.自定義控件
4.觀察者設(shè)計(jì)模式
看下Demo:

點(diǎn)擊添加按鈕可以添加標(biāo)簽:

點(diǎn)擊刪除按鈕可以刪除標(biāo)簽:

接下來我們正式開車~~~
1.使用
我們先來看下怎么使用,首先看下布局文件,很簡(jiǎn)單就是,在底部放置自定義控件TabLinearLayout。
<Button
android:text="點(diǎn)擊添加"
android:id="@+id/add"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:text="點(diǎn)擊刪除"
android:id="@+id/delete"
android:layout_below="@id/add"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.example.juexingzhe.adapterbottomtab.TabLinearLayout
android:background="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_centerVertical="true"
android:id="@+id/bottom_tab"
android:layout_alignParentBottom="true"
android:layout_height="70dp"/>
然后在Activity中就是構(gòu)造Adapter傳進(jìn)去數(shù)據(jù),然后將Adapter設(shè)置給tabLinearLayout。
defaultTabAdapter = new DefaultTabAdapter(datas);
tabLinearLayout.setTabAdapter(defaultTabAdapter);
那么增加或者刪除Tab
defaultTabAdapter.notifyChanged();
以上,是不是so easy?接下來我們看看背后的邏輯。
2.ListView Adapter背后邏輯
ListView地球人都知道,平時(shí)我們?cè)谟玫臅r(shí)候基本有下面兩句話:
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
這兩句話背后發(fā)生了什么使得ListView可以根據(jù)數(shù)據(jù)做出顯示?下面看下源碼,為了看了方便做了些調(diào)整。對(duì)adapter通過registerDataSetObserver注冊(cè)了觀察者mDataSetObserver。
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
}
這個(gè)觀察者mDataSetObserver在ListView中找不到,我們到他父類AbsListView中找,定義了一個(gè)內(nèi)部類AdapterDataSetObserver。
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
}
setAdapter中的邏輯就先到這,ListView中的源碼還是比較多的,這個(gè)不是我們今天的重點(diǎn)。我們今天只要知道setAdapter中做了一件事就是給adapter注冊(cè)了一個(gè)觀察者,這個(gè)就是在數(shù)據(jù)變化的時(shí)候adapter可以通知ListView。
我們簡(jiǎn)單說下觀察者模式,這個(gè)模式可謂是無處不在,主要對(duì)象可以簡(jiǎn)單分為觀察者和被觀察者。打個(gè)比喻,辦公室分成三類人,一類是產(chǎn)品經(jīng)理,一類是前臺(tái)秘書,一類是程序猿。上班時(shí)間程序猿不想擼代碼,就委托前臺(tái)秘書如果產(chǎn)品經(jīng)理過來了就給大家發(fā)個(gè)消息,準(zhǔn)備好開撕啊。這里程序猿就是觀察者,產(chǎn)品經(jīng)理就是被觀察者,當(dāng)被觀察者有動(dòng)靜時(shí),前臺(tái)秘書就通知觀察者。
再說個(gè)我們常用的點(diǎn)擊事件,Button就是被觀察者,OnClickListener就是觀察者,二者通過setOnClickListener達(dá)成關(guān)系,view在狀態(tài)變化的時(shí)候自動(dòng)通知(當(dāng)然這里是Android系統(tǒng)做的工作)觀察者OnClickListener。所以這里Button就對(duì)應(yīng)上面栗子中的產(chǎn)品經(jīng)理,OnClickListener就對(duì)應(yīng)程序猿,setOnClickListener就對(duì)應(yīng)的前臺(tái)秘書的作用。

對(duì)應(yīng)到ListView就是在ListView內(nèi)部聲明了一個(gè)觀察者AdapterDataSetObserver,在上面setAdapter中AdapterDataSetObserver注冊(cè)到adapter中。adapter就起到中介者的作用,在數(shù)據(jù)(被觀察者)變化再通知ListView。
接著看另外一句話adapter.notifyDataSetChanged()的源碼,很簡(jiǎn)單一句話,就是調(diào)用mDataSetObservable的notifyChanged
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
接著跟到mDataSetObservable的notifyChanged中,就是調(diào)用觀察者的onChanged方法。
public class DataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
對(duì)于ListView,觀察者就是AdapterDataSetObserver,這個(gè)是在AbdListView中,super就是AdapterView。
public void onChanged() {
super.onChanged();
}
我們進(jìn)去看到了很熟悉的一句話requestLayout,就是進(jìn)行ListView的重繪。
class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
……
requestLayout();
}
}
到這里就恍然大悟了,就是在ListView中注冊(cè)觀察者,要實(shí)現(xiàn)抽象類DataSetObserver
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
public void onInvalidated() {
// Do nothing
}
}
在adapter中含有一個(gè)DataSetObservable,類似于中介作用,在數(shù)據(jù)變化時(shí)通知觀察者,也就是DataSetObserver,然后onChanged就會(huì)被回調(diào)了。
public class DataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
}
3.自定義Adapter
前面源碼分析有木有很枯燥?我們接著來點(diǎn)實(shí)戰(zhàn)提提精神。這里adapter我們就不包含,直接繼承DataSetObservable。然后擴(kuò)展三個(gè)方法,都比較簡(jiǎn)單,見名知意就不多說了。
public abstract class TabAdapter<T> extends DataSetObservable {
public abstract int getCount();
public abstract View getView(ViewGroup parent, View convertView, int position);
public abstract T getItem(int position);
}
為了方便用戶使用,我們實(shí)現(xiàn)一個(gè)默認(rèn)的TabAdapter.為了內(nèi)存優(yōu)化,這里也是使用了ViewHolder進(jìn)行重用。布局也很簡(jiǎn)單就是上面一個(gè)ImageView,底下是一個(gè)TextView。
public class DefaultTabAdapter extends TabAdapter {
private List<TabBean> mData = new ArrayList<>();
private ViewHolder viewHolder;
DefaultTabAdapter(ArrayList<TabBean> data) {
mData = data;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public View getView(ViewGroup parent, View convertView, final int position) {
if (convertView == null){
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
convertView = inflater.inflate(R.layout.tab_item, parent, false);
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.tab_img);
viewHolder.textView = (TextView) convertView.findViewById(R.id.tab_txt);
convertView.setTag(viewHolder);
}else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setBackgroundResource(mData.get(position).tabImgSourceUnSelect);
viewHolder.textView.setText(mData.get(position).tabTxt);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, 1);
convertView.setLayoutParams(layoutParams);
return convertView;
}
@Override
public TabBean getItem(int position) {
return mData.get(position);
}
private static class ViewHolder{
ImageView imageView;
TextView textView;
}
}
4.自定義TabLinearLayout
我們先看下adapter的觀察者模式的邏輯實(shí)現(xiàn)。
1.首先聲明一個(gè)觀察者,實(shí)現(xiàn)DataSetObserver的兩個(gè)方法。
private DataSetObserver mTabDataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
tabOnChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
removeAllViews();
}
};
2.接著,在setTabAdapter中將觀察者mTabDataSetObserver注冊(cè)到mTabAdapter中。
public void setTabAdapter(TabAdapter tabAdapter) {
this.mTabAdapter = tabAdapter;
removeAllViews();
mTabAdapter.registerObserver(mTabDataSetObserver);
mTabAdapter.notifyChanged();
}
3.最后,在用戶調(diào)用defaultTabAdapter.notifyChanged();后會(huì)調(diào)用我們觀察者的onChanged方法,我們?cè)诶锩鎸?shí)現(xiàn)更新。
以上就是adapter的觀察者邏輯,和Android源碼略微不一樣的地方就是我們這里adapter就是一個(gè)DataSetObservable,源碼中是adapter中包含了一個(gè)DataSetObservable,就這樣。
從上面可以看出,增加和刪除的邏輯主要是在tabOnChanged()方法中。首先從adapter中g(shù)etView獲得布局,然后動(dòng)態(tài)添加到LinearLayour中。
private void tabOnChanged() {
removeAllViews();
mContainer.clear();
int count = mTabAdapter.getCount();
for (int i = 0; i < count; i++) {
LinearLayout layout = (LinearLayout) mTabAdapter.getView(this, null, i);
final int finalI = i;
layout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
notifyClickEvent(finalI);
}
});
addView(layout);
mContainer.add(layout);
}
mContainer.get(0).getChildAt(0).setBackgroundResource(mTabAdapter.getItem(0).tabImgSourceSelected);
}
同時(shí)也將布局文件添加到數(shù)組中,這個(gè)是為了點(diǎn)擊事件的處理,在點(diǎn)擊其中一個(gè)Tab時(shí)需要更新剩下的Tab。
private void notifyClickEvent(int finalI) {
for (int i = 0; i < mContainer.size(); i++) {
if (i == finalI) {
mContainer.get(i).getChildAt(0).setBackgroundResource(mTabAdapter.getItem(i).tabImgSourceSelected);
continue;
}
mContainer.get(i).getChildAt(0).setBackgroundResource(mTabAdapter.getItem(i).tabImgSourceUnSelect);
}
}
以上就是自定義TabLinearLayout的內(nèi)容了。
5.總結(jié)
我們通過分析Android源碼的Adapter實(shí)現(xiàn)原理,結(jié)合觀察者設(shè)計(jì)模式的講解,應(yīng)該是比較清晰的。學(xué)習(xí)并實(shí)踐,動(dòng)手實(shí)現(xiàn)了一個(gè)Adapter,用來充當(dāng)被觀察者,自定義一個(gè)TabLinearLayout導(dǎo)航欄作為觀察者,從而實(shí)現(xiàn)動(dòng)態(tài)添加Tab和刪除Tab。
到這里我們的實(shí)現(xiàn)導(dǎo)航欄的第二種方式已經(jīng)分享完畢,大家可以下車嘍,希望我有說清楚讓大家有點(diǎn)收獲。
感謝@右傾傾的支持與理解!
你們的贊是我最大的動(dòng)力,謝謝!
歡迎關(guān)注公眾號(hào):JueCode