Adapter源碼簡(jiǎn)析及自定義實(shí)戰(zhàn)

今天來分享下做導(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:


初始頁.png

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


添加.png

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

刪除.png

接下來我們正式開車~~~

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)秘書的作用。

觀察者模式.png

對(duì)應(yīng)到ListView就是在ListView內(nèi)部聲明了一個(gè)觀察者AdapterDataSetObserver,在上面setAdapter中AdapterDataSetObserver注冊(cè)到adapter中。adapter就起到中介者的作用,在數(shù)據(jù)(被觀察者)變化再通知ListView。

接著看另外一句話adapter.notifyDataSetChanged()的源碼,很簡(jiǎn)單一句話,就是調(diào)用mDataSetObservablenotifyChanged

private final DataSetObservable mDataSetObservable = new DataSetObservable();

public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }
public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
}

接著跟到mDataSetObservablenotifyChanged中,就是調(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

最后編輯于
?著作權(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,276評(píng)論 25 708
  • 這是我,見過第二次神奇的數(shù)字。一個(gè)小時(shí)過的很慢,一分鐘過的很快,日常生活里,我們錯(cuò)過太多的歲月。 想要提高做某...
    沐星之星星閱讀 358評(píng)論 0 0
  • 我與朱自清初識(shí),是在中學(xué)課本,就是借助初中時(shí)的《春》、高中時(shí)的《荷塘月色》和《綠》三篇經(jīng)典課文,以及后來讀到的《匆...
    阿YAO閱讀 4,397評(píng)論 10 14
  • socket服務(wù)器可通過python來搭建 源程序分享在百度云,下載鏈接如下:鏈接: http://pan.bai...
    dibadalu閱讀 6,234評(píng)論 12 12

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