- 引子
要在一个手掌大的手机上展示各种丰富的信息,横向滑动与上下滑动已经成为了手机应用不可缺少的一部分。对于上下滑动,有一个被小看的神器ListView配合Adapter。为什么被小看了呢?对比ScrollView来说,ListView不仅能够实现不同界面的分离,便于开发与维护,而且其中的自动回收机制可以给用户提供更好的流畅性。下面随便截了几个能够通过ListView轻松实现的界面
看完本篇博客后你就可以用最简单的代码与最简单的逻辑实现上面的界面了,甚至如果你已经理解了其中的原理那么市面上95%以上的复杂带滑动的界面你都可以通过ListView来实现。
- adapter的简单介绍
ListView能有这么强大的功能缺少不了Adapter的贡献。对于ListView的封装一般都是通过自定义ViewGroup来实现自动滑动回收布局来实现类似瀑布流的功能。这儿主要介绍对adapter的封装。
首先简单介绍下adaper,首先google官方api(中国地区直接可用)-
https://developer.android.google.cn/reference/android/widget/BaseAdapter.html
简单介绍下registerDataSetObserver、enable与getViewTypeCount,因为后面会利用这两个方法进行设计
getViewTypeCount是放回这个布局有多少种type-注意:这个方法只有第一次会调用,之后notifyDataSetChanged不会再次调用这个方法了。
而registerDataSetObserver其中用到了观察者模式,用处就是在notifyDataSetChanged的时候会去通知其中的观察者。
enable是否可以进入,返回true就可以点击,返回false就没有点击效果
- 对BaseAdapter中控件复用进行封装
我们在使用baseadapter时一般都需要手动在getView中使用getTag,setTag方法来使其中的holder得到重用来减少findviewbyid从而提高性能。那么使用以下SimpleListAdapter后这个工作就不需要你来手动进行了。
/**
* Created by Sober_philer on 2017/3/8 10:31
* adapter的基类,实现了holder的自定义封装和一些基本方法的封装
*/
public abstract class SimpleListAdapter<H,X extends SimpleListAdapter.SimpleHolder> extends BaseAdapter {
private List<H> dates = new ArrayList<>();//数据源
protected Context context;
/**
* 需要更新数据是调用这个方法,如刷新界面
* @param dates 新的数据
*/
public void replaceAll(List<H> dates){
this.dates.clear();
this.dates.addAll(dates);
notifyDataSetChanged();
}
//构造方法
public SimpleListAdapter(Context context) {
this.context = context;
}
private List<H> getDates(){
return dates;
}
@Override
public int getCount() {
return dates.size();
}
@Override
public H getItem(int position) {
return dates.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
X holder;
if(convertView == null){
convertView = createView(convertView, parent);
holder = createHolder(convertView);
convertView.setTag(holder);
}else {
holder = (X) convertView.getTag();
}
setContent(dates.get(position), convertView, holder);
return convertView;
}
/**
* 获取当前adapter绑定的holder
*/
public abstract X createHolder(View parent);
/**
* 当前adapter的item的view
*/
public abstract View createView(View convertView, ViewGroup parent);
/**
* 绑定数据
*/
public abstract void setContent(H date, View convertView, X holder);
public static class SimpleHolder{}
}
使用方法如下
public class TestBaseAdapter extends Activity {
private ListView lv;
private List<String> dates = new ArrayList<>();
private InnerAdapter ad;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_base_adapter);
lv = (ListView) findViewById(R.id.lv);
ad = new InnerAdapter(this);
lv.setAdapter(ad);
for(int i=0;i<100;i++)
dates.add("date : "+i);
ad.replaceAll(dates);
}
private static class InnerAdapter extends SimpleListAdapter<String, InnerAdapter.InnerHolder>{
public InnerAdapter(Context context) {
super(context);
}
@Override
public InnerHolder createHolder(View parent) {
return new InnerHolder(parent);
}
@Override
public View createView(View convertView, ViewGroup parent) {
return View.inflate(context, android.R.layout.simple_list_item_1, null);
}
@Override
public void setContent(String date, View convertView, InnerHolder holder) {
holder.tv1.setText(date);
}
public class InnerHolder extends SimpleListAdapter.SimpleHolder{
private TextView tv1;
InnerHolder(View v){
tv1 = (TextView) v.findViewById(android.R.id.text1);
}
}
}
}
- 上面的adapter只是对holder的简单封装,下面介绍实现上面3张图的效果的一个关键adapter.其主要思想是通过itemtype来区分开不能的类型,当然这些都不需要我们来做。
/**
* Created by Sober_philer on 2017/3/8 09:23
* 可以放置多个adapter来实现不同的布局
*/
public class SectionAdapter extends BaseAdapter {
private boolean isInited;//是否已经初始化
/**
* 是否可以点击,这儿返回false不让点击
*/
@Override
public boolean isEnabled(int position) {
return false;
}
/**
* 各种adapter保存在这儿
*/
private List<BaseAdapter> lists = new ArrayList<>();
/**
* 添加adapter
*/
public void addWrapper(BaseAdapter wrapper) {
if (isInited)//如果已经初始化了则抛出异常,否则界面可能会显示异常
throw new RuntimeException("cannot addWrapper after init");
lists.add(wrapper);
}
//计算所以的adapter中item的总数量
@Override
public int getCount() {
int count = 0;
for (BaseAdapter tempAd : lists) {
count += tempAd.getCount();
}
return count;
}
//计算需要的总type
@Override
public int getViewTypeCount() {
isInited = true;
int typeCount = 0;
for (BaseAdapter tempAd : lists) {
typeCount += tempAd.getViewTypeCount();
}
if(typeCount == 0)
return 1;
return typeCount;
}
//计算当前的type
@Override
public int getItemViewType(int position) {
int nowType = 0;
for (int i = 0; i < lists.size(); i++) {
BaseAdapter tempAd = lists.get(i);
if (position < tempAd.getCount()) {
return nowType + tempAd.getItemViewType(position);
} else {
nowType += tempAd.getViewTypeCount();
position -= tempAd.getCount();
}
}
throw new RuntimeException("error in Wraperadapter getitemView type");
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
//添加观察者,否则notifydatesetchanged时子adapter不会生效
@Override
public void registerDataSetObserver(DataSetObserver observer) {
for(BaseAdapter tempAd : lists){
tempAd.registerDataSetObserver(observer);
}
}
//取消注册观察者,否则内存可能溢出
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
for(BaseAdapter tempAd : lists){
tempAd.unregisterDataSetObserver(observer);
}
}
//具体获取那个adapter的view;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
for (int i = 0; i < lists.size(); i++) {
BaseAdapter tempAd = lists.get(i);
if (position < tempAd.getCount()) {
return tempAd.getView(position, convertView, parent);
} else {
position -= tempAd.getCount();
}
}
return null;
}
}
上面的scetionadapter实现了一个模块一个模块的添加进来,各个布局直接没有耦合关系。比如引子中第三章网易云的那张截图,可以分为4个模块来实现。banner是一个模块、私人FM是一个模块、推荐歌单是一个模块、下面的内容是一个模块.
当然这儿只是介绍了一些理论,当你真正开始使用后你才会发现这样使用的好处。
2017-9-1
对recycleview进行了相应的封装并增加了几个常用adapter,代码是最好的讲解https://github.com/LiuLinXin/HXFrame