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()從緩存中獲取來的,然后填充數據即可。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM