今天來分享下做導航欄的另外一種方法,導航欄可以放在頂部,也可以放在底部,之前分享過一片底部導航欄的實現(xiàn)方式一行代碼實現(xiàn)底部導航欄TabLayout,用的是Android自帶的控件FragmentTabLayout。今天我們用的一種更為靈活的方式,采用國際慣例Adapter來自定義一個導航欄,可以自己定義每個Tab的布局,可以方便的改變導航欄里面標簽的個數(shù)。
本文會分享到的內(nèi)容:
1.ListView Adapter源碼分析
2.自定義Adapter
3.自定義控件
4.觀察者設計模式
看下Demo:
點擊添加按鈕可以添加標簽:
點擊刪除按鈕可以刪除標簽:
接下來我們正式開車~~~
1.使用
我們先來看下怎么使用,首先看下布局文件,很簡單就是,在底部放置自定義控件TabLinearLayout。
<Button
android:text="點擊添加"
android:id="@+id/add"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:text="點擊刪除"
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中就是構造Adapter
傳進去數(shù)據(jù),然后將Adapter
設置給tabLinearLayout
。
defaultTabAdapter = new DefaultTabAdapter(datas);
tabLinearLayout.setTabAdapter(defaultTabAdapter);
那么增加或者刪除Tab
defaultTabAdapter.notifyChanged();
以上,是不是so easy?接下來我們看看背后的邏輯。
2.ListView Adapter背后邏輯
ListView地球人都知道,平時我們在用的時候基本有下面兩句話:
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
這兩句話背后發(fā)生了什么使得ListView可以根據(jù)數(shù)據(jù)做出顯示?下面看下源碼,為了看了方便做了些調整。對adapter通過registerDataSetObserver注冊了觀察者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);
}
這個觀察者mDataSetObserver在ListView中找不到,我們到他父類AbsListView中找,定義了一個內(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中的源碼還是比較多的,這個不是我們今天的重點。我們今天只要知道setAdapter中做了一件事就是給adapter注冊了一個觀察者,這個就是在數(shù)據(jù)變化的時候adapter可以通知ListView。
我們簡單說下觀察者模式,這個模式可謂是無處不在,主要對象可以簡單分為觀察者和被觀察者。打個比喻,辦公室分成三類人,一類是產(chǎn)品經(jīng)理,一類是前臺秘書,一類是程序猿。上班時間程序猿不想擼代碼,就委托前臺秘書如果產(chǎn)品經(jīng)理過來了就給大家發(fā)個消息,準備好開撕啊。這里程序猿就是觀察者,產(chǎn)品經(jīng)理就是被觀察者,當被觀察者有動靜時,前臺秘書就通知觀察者。
再說個我們常用的點擊事件,Button
就是被觀察者,OnClickListener
就是觀察者,二者通過setOnClickListener
達成關系,view
在狀態(tài)變化的時候自動通知(當然這里是Android系統(tǒng)做的工作)觀察者OnClickListener
。所以這里Button
就對應上面栗子中的產(chǎn)品經(jīng)理,OnClickListener
就對應程序猿,setOnClickListener
就對應的前臺秘書的作用。
對應到ListView
就是在ListView
內(nèi)部聲明了一個觀察者AdapterDataSetObserver
,在上面setAdapte
r中AdapterDataSetObserver
注冊到adapter
中。adapter
就起到中介者的作用,在數(shù)據(jù)(被觀察者)變化再通知ListView。
接著看另外一句話adapter.notifyDataSetChanged()
的源碼,很簡單一句話,就是調用mDataSetObservable
的notifyChanged
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
接著跟到mDataSetObservable
的notifyChanged
中,就是調用觀察者的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();
}
}
}
對于ListView,觀察者就是AdapterDataSetObserver
,這個是在AbdListView中,super就是AdapterView
。
public void onChanged() {
super.onChanged();
}
我們進去看到了很熟悉的一句話requestLayout
,就是進行ListView的重繪。
class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
……
requestLayout();
}
}
到這里就恍然大悟了,就是在ListView中注冊觀察者,要實現(xiàn)抽象類DataSetObserver
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
public void onInvalidated() {
// Do nothing
}
}
在adapter中含有一個DataSetObservable,類似于中介作用,在數(shù)據(jù)變化時通知觀察者,也就是DataSetObserver,然后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();
}
}
}
}
3.自定義Adapter
前面源碼分析有木有很枯燥?我們接著來點實戰(zhàn)提提精神。這里adapter我們就不包含,直接繼承DataSetObservable。然后擴展三個方法,都比較簡單,見名知意就不多說了。
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);
}
為了方便用戶使用,我們實現(xiàn)一個默認的TabAdapter
.為了內(nèi)存優(yōu)化,這里也是使用了ViewHolder進行重用。布局也很簡單就是上面一個ImageView,底下是一個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的觀察者模式的邏輯實現(xiàn)。
1.首先聲明一個觀察者,實現(xiàn)DataSetObserver的兩個方法。
private DataSetObserver mTabDataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
tabOnChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
removeAllViews();
}
};
2.接著,在setTabAdapter中將觀察者mTabDataSetObserver注冊到mTabAdapter中。
public void setTabAdapter(TabAdapter tabAdapter) {
this.mTabAdapter = tabAdapter;
removeAllViews();
mTabAdapter.registerObserver(mTabDataSetObserver);
mTabAdapter.notifyChanged();
}
3.最后,在用戶調用defaultTabAdapter.notifyChanged();
后會調用我們觀察者的onChanged
方法,我們在里面實現(xiàn)更新。
以上就是adapter的觀察者邏輯,和Android源碼略微不一樣的地方就是我們這里adapter就是一個DataSetObservable,源碼中是adapter中包含了一個DataSetObservable,就這樣。
從上面可以看出,增加和刪除的邏輯主要是在tabOnChanged()方法中。首先從adapter中getView獲得布局,然后動態(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ù)組中,這個是為了點擊事件的處理,在點擊其中一個Tab時需要更新剩下的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.總結
我們通過分析Android源碼的Adapter
實現(xiàn)原理,結合觀察者設計模式的講解,應該是比較清晰的。學習并實踐,動手實現(xiàn)了一個Adapter
,用來充當被觀察者,自定義一個TabLinearLayout
導航欄作為觀察者,從而實現(xiàn)動態(tài)添加Tab和刪除Tab。
到這里我們的實現(xiàn)導航欄的第二種方式已經(jīng)分享完畢,大家可以下車嘍,希望我有說清楚讓大家有點收獲。
感謝@右傾傾的支持與理解!
你們的贊是我最大的動力,謝謝!
歡迎關注公眾號:JueCode