今天来分享下做导航栏的另外一种方法,导航栏可以放在顶部,也可以放在底部,之前分享过一片底部导航栏的实现方式一行代码实现底部导航栏TabLayout,用的是Android自带的控件FragmentTabLayout。今天我们用的一种更为灵活的方式,采用国际惯例Adapter来自定义一个导航栏,可以自己定义每个Tab的布局,可以方便的改变导航栏里面标签的个数。
本文会分享到的内容:
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
传进去数据,然后将Adapter
设置给tabLinearLayout
。
defaultTabAdapter = new DefaultTabAdapter(datas);
tabLinearLayout.setTabAdapter(defaultTabAdapter);
那么增加或者删除Tab
defaultTabAdapter.notifyChanged();
以上,是不是so easy?接下来我们看看背后的逻辑。
2.ListView Adapter背后逻辑
ListView地球人都知道,平时我们在用的时候基本有下面两句话:
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
这两句话背后发生了什么使得ListView可以根据数据做出显示?下面看下源码,为了看了方便做了些调整。对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中找,定义了一个内部类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注册了一个观察者,这个就是在数据变化的时候adapter可以通知ListView。
我们简单说下观察者模式,这个模式可谓是无处不在,主要对象可以简单分为观察者和被观察者。打个比喻,办公室分成三类人,一类是产品经理,一类是前台秘书,一类是程序猿。上班时间程序猿不想撸代码,就委托前台秘书如果产品经理过来了就给大家发个消息,准备好开撕啊。这里程序猿就是观察者,产品经理就是被观察者,当被观察者有动静时,前台秘书就通知观察者。
再说个我们常用的点击事件,Button
就是被观察者,OnClickListener
就是观察者,二者通过setOnClickListener
达成关系,view
在状态变化的时候自动通知(当然这里是Android系统做的工作)观察者OnClickListener
。所以这里Button
就对应上面栗子中的产品经理,OnClickListener
就对应程序猿,setOnClickListener
就对应的前台秘书的作用。
对应到ListView
就是在ListView
内部声明了一个观察者AdapterDataSetObserver
,在上面setAdapte
r中AdapterDataSetObserver
注册到adapter
中。adapter
就起到中介者的作用,在数据(被观察者)变化再通知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中注册观察者,要实现抽象类DataSetObserver
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
public void onInvalidated() {
// Do nothing
}
}
在adapter中含有一个DataSetObservable,类似于中介作用,在数据变化时通知观察者,也就是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
前面源码分析有木有很枯燥?我们接着来点实战提提精神。这里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);
}
为了方便用户使用,我们实现一个默认的TabAdapter
.为了内存优化,这里也是使用了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的观察者模式的逻辑实现。
1.首先声明一个观察者,实现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
方法,我们在里面实现更新。
以上就是adapter的观察者逻辑,和Android源码略微不一样的地方就是我们这里adapter就是一个DataSetObservable,源码中是adapter中包含了一个DataSetObservable,就这样。
从上面可以看出,增加和删除的逻辑主要是在tabOnChanged()方法中。首先从adapter中getView获得布局,然后动态添加到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);
}
同时也将布局文件添加到数组中,这个是为了点击事件的处理,在点击其中一个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的内容了。
5.总结
我们通过分析Android源码的Adapter
实现原理,结合观察者设计模式的讲解,应该是比较清晰的。学习并实践,动手实现了一个Adapter
,用来充当被观察者,自定义一个TabLinearLayout
导航栏作为观察者,从而实现动态添加Tab和删除Tab。
到这里我们的实现导航栏的第二种方式已经分享完毕,大家可以下车喽,希望我有说清楚让大家有点收获。
感谢@右倾倾的支持与理解!
你们的赞是我最大的动力,谢谢!
欢迎关注公众号:JueCode