美文网首页程序员Android开发Android知识
Adapter源码简析及自定义实战

Adapter源码简析及自定义实战

作者: juexingzhe | 来源:发表于2017-07-08 22:34 被阅读0次

今天来分享下做导航栏的另外一种方法,导航栏可以放在顶部,也可以放在底部,之前分享过一片底部导航栏的实现方式一行代码实现底部导航栏TabLayout,用的是Android自带的控件FragmentTabLayout。今天我们用的一种更为灵活的方式,采用国际惯例Adapter来自定义一个导航栏,可以自己定义每个Tab的布局,可以方便的改变导航栏里面标签的个数。
本文会分享到的内容:

1.ListView Adapter源码分析

2.自定义Adapter

3.自定义控件

4.观察者设计模式

看下Demo:


初始页.png

点击添加按钮可以添加标签:


添加.png

点击删除按钮可以删除标签:

删除.png

接下来我们正式开车~~~

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就对应的前台秘书的作用。

观察者模式.png

对应到ListView就是在ListView内部声明了一个观察者AdapterDataSetObserver,在上面setAdapter中AdapterDataSetObserver注册到adapter中。adapter就起到中介者的作用,在数据(被观察者)变化再通知ListView。

接着看另外一句话adapter.notifyDataSetChanged()的源码,很简单一句话,就是调用mDataSetObservablenotifyChanged

private final DataSetObservable mDataSetObservable = new DataSetObservable();

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

接着跟到mDataSetObservablenotifyChanged中,就是调用观察者的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

相关文章

  • Adapter源码简析及自定义实战

    今天来分享下做导航栏的另外一种方法,导航栏可以放在顶部,也可以放在底部,之前分享过一片底部导航栏的实现方式一行代码...

  • mybatis-spring解析

    1、概述 原生Mybatis源码简析(上)原生Mybatis源码简析(下)在介绍原生Mybatis源码简析文章中,...

  • ArrayMap详解及源码分析

    一、前言 在 《SparseArray详解及源码简析》 中,我们熟悉了 SparseArray 的基本用法、特点以...

  • SparseArray详解及源码简析

    一、前言 SparseArray 是 Android 在 Android SdK 为我们提供的一个基础的数据结构,...

  • LinkedHashMap 详解及源码简析

    一、前言 在 HashMap详解以及源码分析 这篇文章中,对 HashMap 的实现原理进行了比较深入的分析。而在...

  • Android之ListView

    一、ListView之Adapter ArrayAdapter SimpleAdapter 自定义Adapter ...

  • go学习计划 2018-07-13

    go语言入门及实战 go web开发 go源码及项目阅读

  • Lua源码简析及使用技巧

    lua是一款非常小巧的开源的脚本语言,5.1版本的的压缩包仅208KB,代码仅有17000多行。使用标准c编写的它...

  • OkHttp基本使用及源码简析

    使用一个框架要问自己你为什么选择这个框架,它的优势在哪里,以及它的基本原理是怎么样的? OkHttp的优势 支持S...

  • Flink自定义StreamOperator

    在上一篇StreamOperator源码简析从源码角度分析了StreamOperator以及其实现类,此篇幅主要分...

网友评论

    本文标题:Adapter源码简析及自定义实战

    本文链接:https://www.haomeiwen.com/subject/cxujhxtx.html