Android_适配器(adapter)之BaseAdapter
BaseAdapter是应用最多的一种适配了。它是一个抽象类,需要重写方法完成自定义适配器的功能,这就比较自由灵活,能实现各种想要的效果。
之前讲到的SimpleAdapter和ArrayAdapter 就是它的子类。
下面介绍如何使用BaseAdapter实现自定义适配器。
基础知识点
使用BaseAdapter,只需继承它并实现下面4个方法即可:
public int getCount() //item的数目,即适配器要显示的数据项个数
public Object getItem(int position) //获取指定位置的数据项
public long getItemId(int position) //获取指定位置的数据项id
public View getView(int position, View convertView, ViewGroup parent) //获取每一项显示内容-view
下面通过简单示例,详细讲解BaseAdapter的使用以及注意事项
示例讲解
这次示例使用GridView作为显示组件。
布局文件,仅一个GridView的组件:base_adapter_act.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <GridView android:id="@+id/base_adapter_lv" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
文本资源,适配器填充的数据。和SimpleAdapter一样的:
<array name="anime_name"> <item>海贼王</item> <item>进击的巨人</item> <item>火影忍者</item> <item>斩赤红之瞳</item> <item>秦时明月</item> <item>西游记</item> <item>葫芦娃</item> </array> <array name="anime_author"> <item>尾田荣一郎</item> <item>谏山创</item> <item>岸本齐史</item> <item>タカヒロ</item> <item>玄机科技</item> <item>央视</item> <item>上海美术电影</item> </array>
每一项的布局文件:base_adapter_grid_item.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/anime_cover_img" android:layout_width="match_parent" android:layout_margin="1dp" android:layout_height="100dp" android:scaleType="fitXY"/> <LinearLayout android:layout_width="match_parent" android:layout_height="35dp" android:orientation="vertical" android:layout_below="@id/anime_cover_img"> <TextView android:id="@+id/anime_name_txt" android:layout_width="match_parent" android:layout_height="0dp" android:gravity="center" android:textStyle="bold" android:textSize="15sp" android:layout_weight="3"/> <TextView android:id="@+id/anime_author_txt" android:layout_width="match_parent" android:layout_height="0dp" android:gravity="center" android:textStyle="italic" android:layout_weight="2" android:textSize="10sp"/> <View android:layout_width="match_parent" android:layout_height="3dp" /> </LinearLayout> </RelativeLayout>
常识1:android:layout_weight分配的大小=(父控件大小-android:layout_width)*权重比例大小
如果android:layout_width的大小已经达到或超过父控件的大小,则android:layout_weight是会失效的。
BaseAdapterActivity.java, 适配器的使用,代码如下
package com.flx.adaptertest.baseadapter; import android.app.Activity; import android.content.res.Resources; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.CheckBox; import android.widget.GridView; import android.widget.ListView; import android.widget.SimpleAdapter; import com.flx.adaptertest.R; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class BaseAdapterActivity extends Activity { private static final String TAG = "BaseAdapterActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate( savedInstanceState ); setContentView( R.layout.base_adapter_act ); GridView gridView = findViewById(R.id.base_adapter_lv); gridView.setNumColumns(2); gridView.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.d( TAG, "onItemClick: position="+ position + ";text=" + parent.getAdapter().getItem(position).toString()); } } ); List<AnimeBean> animeBeans = new ArrayList<>(); Resources resources = this.getResources(); String[] anime_names = resources.getStringArray( R.array.anime_name ); String[] anime_authors = resources.getStringArray( R.array.anime_author ); int[] coverImgs = {R.drawable.hzw1, R.drawable.jjdjr1, R.drawable.hyrz1, R.drawable.zchzt1, R.drawable.qsmy1, R.drawable.xyj1, R.drawable.hlw1}; for (int i = 0; i < anime_names.length; i++) { animeBeans.add(new AnimeBean(anime_names[i], anime_authors[i], coverImgs[i])); } gridView.setAdapter( new AnimeAdapter(this, animeBeans) ); } }
这里创建了两个类,AnimeBean和AnimeAdapter。
AnimeBean是一个java bean类,数据以Bean类组织,其中主要是Getter和Setter的方法。适配器显示的每一项数据在一个AnimeBean对象中。
AnimeAdapter是继承BaseAdapter的自定义的适配器类,实现了上述讲到的4个方法。
AnimeBean.java
package com.flx.adaptertest.baseadapter; import android.support.annotation.NonNull; public class AnimeBean { private String mAnimeName; private String mAnimeAuthor; private int mAnimeCoverImg; public AnimeBean(String animeName, String animeAuthor, int animeCoverImg) { this.mAnimeName = animeName; this.mAnimeAuthor = animeAuthor; this.mAnimeCoverImg = animeCoverImg; } public String getmAnimeName() { return this.mAnimeName; } public void setmAnimeName(String animeName) { this.mAnimeName = animeName; } public String getmAnimeAuthor() { return this.mAnimeAuthor; } public void setmAnimeAuthor(String animeAuthor) { this.mAnimeAuthor = animeAuthor; } public int getmAnimeCoverImg() { return this.mAnimeCoverImg; } public void setmAnimeCoverImg(int animeCoverImg) { this.mAnimeCoverImg = animeCoverImg; } @NonNull @Override public String toString() { return "mAnimeName:"+mAnimeName+";mAnimeAuthor:"+mAnimeAuthor+";mAnimeCoverImg:"+mAnimeCoverImg; } }
最后一个toString用户打印Bean里面数据的内容,在BaseAdapterActivity的适配器点击项监听处有调用。
AnimeAdapter.java
package com.flx.adaptertest.baseadapter; import android.content.Context; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import com.flx.adaptertest.R; import java.util.List; public class AnimeAdapter extends BaseAdapter { private static final String TAG = "AnimeAdapter"; private LayoutInflater mLayoutInflater;//布局加载器对象 private List<AnimeBean> mAnimeBeans;//数据源 //构造方法,包含了数据源和上下文。将数据和适配器关联起来了。 public AnimeAdapter(Context context, List<AnimeBean> animeBeans) { mLayoutInflater = LayoutInflater.from(context); mAnimeBeans = animeBeans; } //item的数目,即适配器要显示的数据项个数 @Override public int getCount() { return mAnimeBeans != null ? mAnimeBeans.size() : 0; } //获取指定位置的数据项 @Override public Object getItem(int position) { return mAnimeBeans != null ? mAnimeBeans.get(position) : null; } //获取指定位置的数据项id @Override public long getItemId(int position) { return position; } //获取每一项显示内容-view /** *方法3: * 这种方法改进了方法1和方法2的弊端,避免了每次都创建新的View对象和通过findViewById查找组件 而造成的耗时 耗资源的问题。 * 创建View对象通过判空避免了:if (convertView == null) * findViewById()通过ViewHolder缓存下来了,通过setTag设置到View了,需要时可以直接获取到。 */ @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; //convertView是显示项的视图。为null时即表示未被实例化过,GridView缓存池中没有缓存 Log.d( TAG, "getView: position=" + position +";convertView="+convertView ); if (convertView == null) { convertView = mLayoutInflater.inflate( R.layout.base_adapter_grid_item, null ); //创建ViewHolder对象,并赋值 viewHolder = new ViewHolder(); viewHolder.animeNameTxt = convertView.findViewById(R.id.anime_name_txt); viewHolder.animeAuthorTxt = convertView.findViewById(R.id.anime_author_txt); viewHolder.animeCoverImg = convertView.findViewById(R.id.anime_cover_img); //通过setTag,设置与convertView关联的标签ViewHolder,将convertView与ViewHolder关联 convertView.setTag(viewHolder); } else { //从缓存中返回convertView设置的标签:ViewHolder viewHolder = (ViewHolder) convertView.getTag(); } AnimeBean animeBean = mAnimeBeans.get(position); viewHolder.animeNameTxt.setText(animeBean.getmAnimeName()); viewHolder.animeAuthorTxt.setText(animeBean.getmAnimeAuthor()); viewHolder.animeCoverImg.setImageResource(animeBean.getmAnimeCoverImg()); return convertView; } //缓存控件 private class ViewHolder { public TextView animeNameTxt; public TextView animeAuthorTxt; public ImageView animeCoverImg; } /** * 方法1: * 这种方法的弊端在于:很明显,每次都会创建新的convertView对象,并通过findViewById查找对应组件。 * 当数据项很大且比较复杂时问题很明显,耗时 耗资源 * 没有使用GridView ListView的缓存机制。 */ /* @Override public View getView(int position, View convertView, ViewGroup parent) { convertView = mLayoutInflater.inflate( R.layout.base_adapter_grid_item, null ); TextView animeNameTxt = convertView.findViewById(R.id.anime_name_txt); TextView animeAuthorTxt = convertView.findViewById(R.id.anime_author_txt); ImageView animeCoverImg = convertView.findViewById(R.id.anime_cover_img); AnimeBean animeBean = mAnimeBeans.get(position); animeNameTxt.setText(animeBean.getmAnimeName()); animeAuthorTxt.setText(animeBean.getmAnimeAuthor()); animeCoverImg.setImageResource(animeBean.getmAnimeCoverImg()); return convertView; }*/ /** * 方法2: * 该方法时方法1的改进,使用了缓存机制。 * 这种方法的弊端在于:每次都会通过findViewById()去遍历视图树,当布局很复杂时就会很耗时 */ /*@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate( R.layout.base_adapter_grid_item, null ); } TextView animeNameTxt = convertView.findViewById(R.id.anime_name_txt); TextView animeAuthorTxt = convertView.findViewById(R.id.anime_author_txt); ImageView animeCoverImg = convertView.findViewById(R.id.anime_cover_img); AnimeBean animeBean = mAnimeBeans.get(position); animeNameTxt.setText(animeBean.getmAnimeName()); animeAuthorTxt.setText(animeBean.getmAnimeAuthor()); animeCoverImg.setImageResource(animeBean.getmAnimeCoverImg()); return convertView; }*/ }
代码中注释做了详细说明,需要注意几点:
1.自定义的Adapter继承了BaseAdapter,需要实现最初讲到的4个方法。getCount()、getItem()、getItemId()都比较简单,注意getView()的实现。
2.GridView、ListView等是由缓存机制的,当需要显示的时候才会显示,不需要显示的时候在缓存池中。当要显示的信息过多,超过屏幕很多,滑出的信息和滑入的信息 都是从缓存池中获取的,缓存池中的信息不会创建所有或马上销毁。(最后部分有将数据调整为1000后 也能看出这一点)
3.上述代码中,getView()列出了3种方法,方法1、方法2有各自明显缺陷,方法3是一个比较好的方法很好的避免了每次都创建新的View对象和通过findViewById查找组件 而造成的耗时 耗资源的问题。具体请看上述代码和注释。
示例效果
下面是效果图
点击其中一项,log如下:
2019-11-27 14:36:33.949 4655-4655/? D/BaseAdapterActivity: onItemClick: position=0;text=mAnimeName:海贼王;mAnimeAuthor:尾田荣一郎;mAnimeCoverImg:2131165272
要看下getView() 方法3的效果,可以将数据调整为1000组,可以在BaseAdapterActivity中做如下修改
// for (int i = 0; i < anime_names.length; i++) { // animeBeans.add(new AnimeBean(anime_names[i], anime_authors[i], coverImgs[i])); // } for (int i = 0; i < 1000; i++) { int ii = i%anime_names.length; animeBeans.add(new AnimeBean(anime_names[ii], anime_authors[ii], coverImgs[ii])); }
后面大部分View是没有创建的,向下滑动 才会逐步创建,如下的log,
滑动逐步显示后面的信息,后面信息都不需要每次创建View而是通过viewHolder = (ViewHolder) convertView.getTag()从缓存中获取来的,然后填充数据即可。