Android_适配器(adapter)之BaseAdapter

BaseAdapter是应用最多的一种适配了。它是一个抽象类,需要重写方法完成自定义适配器的功能,这就比较自由灵活,能实现各种想要的效果。

之前讲到的SimpleAdapterArrayAdapter 就是它的子类。

下面介绍如何使用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()从缓存中获取来的,然后填充数据即可。

 

 

 

 

 

posted @ 2019-11-27 21:15  流浪_归家  阅读(1613)  评论(0编辑  收藏  举报