BaseAdapter是最基础的Adapter类,也是最实用最常用的一个类,但是相比于ArrayAdapter之类的,对初学者来说却比较难理解。所以在这里介绍一下BaseAdapter。
案例查看: >>Android 案例: 控件GridView的使用-手机管家九宫格<<
Adapter
是什么?
An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to the data items. The Adapter is also responsible for making a View for each item in the data set.
翻译过来,简单理解就是adapter是view和数据的桥梁。在一个ListView或者GridView中,你不可能手动给每一个格子都新建一个view,所以这时候就需要Adapter的帮忙,它会帮你自动绘制view并且填充数据。
BaseAdapter
是什么
从英文就可以知道了,最基础的Adapter,也就是说,它可以做所有的事情。所以为什么说最实用最常用,原因就在于它的全能性,不会像ArrayAdapter等的封装好的类有那么多局限性,但是这样的话,使用起来自然会更加麻烦一点。但是这篇文章就会告诉你怎么熟练使用它。
BaseAdapter
怎么用
在ListView、GridView或者其他的view中,使用setAdapter方法传入我们的baseAdapter就可以用了。
gridView.setAdapter(baseAdapter);
问题就出在这个mBaseAdapter要怎么写了。重点终于来了。
可以新建一个java文件MyBaseAdapter,继承自BaseAdapter,并且实现它的4个基础方法。
public class MyBaseAdapter extends BaseAdapter {
@Override
public int getCount() {
return 0;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
默认实现这4个方法后,接下来我们就要重写这4个方法了。那么具体这4个方法有什么用呢?我们在后面再讲。
MyBaseAdapter这个类写好后,我们就新建一个mBaseAdapter,在setAdapter方法的时候传进去就好了。
MyBaseAdapter baseAdapter= new MyBaseAdapter();
这样的三步法就可以实现BaseAdapter的使用了。
但是,这当然没完。因为现在你还没告诉你的BaseAdapter你要叫它干什么呢。所以接下来我们就要修改MyBaseAdapter
了。
BaseAdapter方法详解
学会BaseAdapter其实只需要掌握四个方法:
getCount
, getItem
, getItemId
, getView
getCount
: 要绑定的条目的数目,比如格子的数量
getItem
: 根据一个索引(位置)获得该位置的对象
getItemId
: 获取条目的id
getView
: 获取该条目要显示的界面
可以简单的理解为,adapter先从getCount
里确定数量,然后循环执行getView
方法将条目一个一个绘制出来,所以必须重写的是getCount
和getView
方法。而getItem
和getItemId
是调用某些函数才会触发的方法,如果不需要使用可以暂时不修改。
接下来用一个简单的demo来展示一下BaseAdapter。
首先在MyBaseAdapter中添加super()方法,用来将数据源传入MyBaseAdapter中。其中this.data = data;的意思是将传入的形参赋值给我们的私有变量以供使用。mContext是在后面要调用到,这里先不解释。
private String[] data;
private Context mContext;
public MyBaseAdapter(Context mContext, String[] data) {//用于数据的桥梁可以传入
super();
this.mContext = mContext;
this.data = data;
}
然后就可以将getCount的返回值修改为我们的数据源的长度,要显示几个条目,我们就传入多长的数据源就可以了。
public int getCount() {//该方法如果返回0的话就不调用下面getView(.....)方法如果大于0掉用该方法
return data.length;
}
接着将getView的方法改为显示出我们数据源的字符串。
/**
* @param position 表示当前索引
* @param convertView 缓存区 就是这个convertView其实就是最关键的部分
* 原理上讲 当ListView滑动的过程中 会有item被滑出屏幕 而不再被使用 这时候Android会
* 回收这个条目的view 这个view也就是这里的convertView
* 也就是为了不浪费内存重新调用第一个地址 就是被划上去的那个View
* 当item1被移除屏幕的时候 我们会重新new一个View给新显示的item_new
* 而如果使用了这个convertView 我们其实可以复用它 这样就省去了new View的大量开销
* @param parent 每个ItemView里面的容器 返回的View直接添加到容器中来
* @return View 就是每个ItemView要显示的内容
*/
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = new TextView(mContext);
textView.setText(data[position]);
return textView;
}
这里为每一个条目新建一个TextView用来显示字符串,新建的时候就需要传入一个Context,就用到了我们之前的mContext。
这样我们就可以使用MyBaseAdapter来显示一个字符串数组了。不过还需要在前面修改一下,在新建mBaseAdapter的时候要传入context和数据源。
String[] strings = {"a","b","c"};
MyBaseAdapter mBaseAdapter = new MyBaseAdapter(getApplicationContext(),strings);
接下来贴一下完整的代码吧。ListView的新建我就不讲了。
- MainActivity部分
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
public class MainActivity extends AppCompatActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
String[] strings = {"a","b","c"};
MyBaseAdapter mBaseAdapter = new MyBaseAdapter(getApplicationContext(),strings);
listView.setAdapter(mBaseAdapter);
}
}
- MyBaseAdapter部分
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class MyBaseAdapter extends BaseAdapter {
private String[] data;
private Context mContext;
public MyBaseAdapter(Context mContext, String[] data) {
super();
this.mContext = mContext;
this.data = data;
}
@Override
public int getCount() {
return data.length;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = new TextView(mContext);
textView.setText(data[position]);
return textView;
}
}
BaseAdapter进阶使用
当然一个TextView显然满足不了我们的需要,这也完全不能显示BaseAdapter的全能性。那接下来,就来展示一下如何用BaseAdapter显示一个自定义布局。
- 首先新建一个layout,我命名为item,这个就是我们每个条目要展示的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@mipmap/ic_launcher"
android:id="@+id/imageView" />
<Button
android:text="Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button" />
<TextView
android:text="TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView" />
</LinearLayout>
- 接下来修改getView方法,让它显示我们这个item布局
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.item,null);
final TextView textView = (TextView) view.findViewById(R.id.textView);
Button button = (Button) view.findViewById(R.id.button);
ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
imageView.setImageResource(R.mipmap.ic_launcher);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.append("!");
}
});
textView.setText(data[position]);
return view;
}
主要讲解一下前两句。LayoutInflater是用来加载布局的,用LayoutInflater的inflate方法就可以将你的item布局绘制出来。其中getView方法中的三个参数,position是指现在是第几个条目;convertView是旧视图,就是绘制好了的视图;parent是父级视图,也就是ListView之类的。
用inflate方法绘制好后的view最后return返回给getView方法就可以了。
BaseAdapter的优化使用
上面的convertView是旧视图是什么意思呢?就是listview如果超出了屏幕,滑动的时候会隐藏掉一部分,这时候就将隐藏掉的部分保存到convertView中。那么如果是我们之前的写法,每次返回的时候就没有使用convertView,重新创建了一个View,这样子浪费了系统资源。那要怎么利用convertView优化呢?
同样我们还是对getView进行进一步修改。
- 首先定义一个类ViewHolder,用来标记我们的控件
static class ViewHolder{
TextView textView;
ImageView imageView;
Button button;
}
- 接下来使用ViewHolder优化
在getView方法中,Adapter先从xml中用inflate方法创建view对象,然后在这个view找到每一个控件。这里的findViewById操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是使用viewHolder,把每一个控件都放在Holder中,当第一次创建convertView对象时,把这些控件找出来。然后用convertView的setTag将viewHolder设置到Tag中,以便系统第二次绘制ListView时从Tag中取出。当第二次重用convertView时,只需从convertView中getTag取出来就可以。
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(mContext);
ViewHolder holder = null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item, null);
holder = new ViewHolder();
holder.button = (Button) convertView.findViewById(R.id.button);
holder.textView = (TextView) convertView.findViewById(R.id.textView);
holder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.imageView.setImageResource(R.mipmap.ic_launcher);
holder.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("click","button");
}
});
holder.textView.setText(data[position]);
return convertView;
}
先判断convertView是否为空,是的话就创建ViewHolder,不是的话就取出ViewHolder,这样就可以实现复用convertView了。
总结
在继承BaseAdapter类时要实现一下方法
- BaseAdapter 默认提供了四个方法:
--- getCount() 必须 告诉列表默认显示多少条数据
--- getView(int position, View convertView, ViewGroup parent) 必须 告诉列表每项怎么显示
--- getItem(int position) 可选
--- getItemId(int position) 可选
- 细节:
getCount() 只有在setAdapter()与notifyDataSetChanged();调用时才调用
getView(int position, View convertView, ViewGroup parent) 只有getCount() 不为0的时候才调用
每个View都可以携带一个对象 这样对象就可以不用作为全局变量了 只要有View就有对象
子项里面有需要绑定的子控件可以创建一个ViewHolder类来封装
MyAdapter adapter = new MyAdapter();//该方法用来绑定的
ListView对象.setAdapter(adapter); //绑定的时候多次调用
重点
- 每一个View 都可以获取Context. —>v.getContext();
- FILL_PARENT(安卓2.3版本以前的写法)==MATCH_PARENT
- 通过布局文件直接转换成View LayoutInflater.from(mContext).inflate(R.layout.xxx,root);