Android之Adapter(適配器)

繼承關係

Adapter是一個接口:
在這裏插入圖片描述
BaseAdapter是一個抽象類,BaseAdapter也實現了ListAdapter接口,通常會繼承BaseAdapter類來實現更復雜的需求:
在這裏插入圖片描述

常用的Adapter

適配器對象充當視圖(AdapterView)與該視圖的基礎數據之間的橋樑,ListView ,GridView等均爲AdapterView的子類,所以通常配合適配器使用。適配器提供對數據項的訪問, 適配器還負責爲數據集中的每個項目生成一個View 。

ArrayAdapter

常用的構造函數:ArrayAdapter(Context context, int resource, T[] objects),依次傳入的三個參數爲:①當前上下文;②ListView等組件子項佈局的id;③需要適配的數據。

示例

運行效果:
在這裏插入圖片描述
代碼:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView listView = findViewById(R.id.listView);

        List<String> list = new ArrayList<>();
        for(int i = 0; i < 20; i++){
            list.add(String.valueOf(i));
        }
        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, list);
        listView.setAdapter(arrayAdapter);
    }
}

R.layout.simple_list_item_1

注:上面ArrayAdapter構造函數的第二個參數R.layout.simple_list_item_1是Android提供的列表item,下面列出的是其它自帶的item,也可以使用自己自定義的xml文件。常用的幾個:

  • android.R.layout.simple_list_item_1 //一行text
  • android.R.layout.simple_list_item_2 //一行title,一行text
  • android.R.layout.simple_list_item_single_choice //單選按鈕
  • android.R.layout.simple_list_item_multiple_choice //多選按鈕
  • android.R.layout.simple_list_item_checked //勾選框
    在這裏插入圖片描述

SimpleAdapter

雖然字面上看是“簡單適配器”,但是SimpleAdapter功能強大,可以實現複雜的效果,已能滿足許多需求。

構造函數: SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to),傳入的五個參數分別是:①當前上下文;②數據列表,列表中的每個條目對應於列表中的一行,映射包含每一行的數據,並且應該包含“from”中指定的所有條目;③顯示列表項數據的視圖資源符,此佈局中需包含“to”參數中定義的視圖;④與每個列表項關聯的映射中的列名;⑤在“from”參數中顯示的列表項視圖。

示例

運行效果:
在這裏插入圖片描述
代碼:

  • 自定義每個列表項佈局:list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    
    <ImageView
        android:id="@+id/ivChatHead"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:baselineAlignBottom="true"
        android:paddingLeft="8dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="8dp"
            android:textColor="#000000"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/tvContent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="8px"
            android:textColor="#808080"
            android:textSize="14sp" />

    </LinearLayout>
</LinearLayout>

  • MainActivity.java
public class MainActivity extends AppCompatActivity {
    private int[] chatHead = new int[]{R.mipmap.chathead1, R.mipmap.chathead2, R.mipmap.chathead3};
    private String[] name = new String[]{"張三", "李四", "王五"};
    private String[] content = new String[]{"我是張三,大家好", "今天天氣不錯", "好嗨喲"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView listView = findViewById(R.id.listView);

        List<Map<String, Object>> list = new ArrayList<>();
        for (int i = 0; i < name.length; i++) {
            Map<String, Object> map = new HashMap<>();
            map.put("chatHead", chatHead[i]);
            map.put("name", name[i]);
            map.put("content", content[i]);
            list.add(map);
        }
        
        SimpleAdapter myAdapter = new SimpleAdapter(this, list, R.layout.list_item,
                new String[]{"chatHead", "name", "content"}, new int[]{R.id.ivChatHead, R.id.tvName, R.id.tvContent});
        listView.setAdapter(myAdapter);
    }
}

調整爲類似微信列表佈局

微信的列表佈局與上面的差別在分隔線,上面使用的是默認的分隔線。這裏做了兩個調整:①將組件調整對齊;②更改分隔線爲自定義。

運行效果:
在這裏插入圖片描述
代碼:

  • 自定義分隔線:list_item_driver.xml
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@color/lineColorGray"
    android:insetLeft="70dp">
</inset>

注: "inset"標籤,可以將其它的Drawable內嵌到自己當中(上面引入了顏色資源來設置分隔線的顏色),並可以在四周預留出一定的間距。

  • 列表項佈局:list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:gravity="center">
        <ImageView
            android:id="@+id/ivChatHead"
            android:layout_width="50dp"
            android:layout_height="50dp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="70dp"
        android:orientation="vertical"
        android:gravity="center_vertical">

        <TextView
            android:id="@+id/tvName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/tvContent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#808080"
            android:textSize="15sp" />

    </LinearLayout>
</LinearLayout>

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@drawable/list_item_driver"
        android:dividerHeight="1dp">

    </ListView>

</android.support.constraint.ConstraintLayout>

注:給ListView設置自定義分隔線時,必須重新設置線高(android:dividerHeight),否則分隔線將不顯示。具體可以查看ListView的源碼,如果不設置高度,Android會將高度設置爲-1。

參數from是如何與參數to綁定的

使用SimpleAdapter之後可能會產生疑問

爲什麼to參數中的View會顯示from參數的值爲自己的text,如果form參數不是String/int類型的,而是Bitmap類型的,那麼to參數中的View還會顯示嗎?

答案是:不會。

這種情況就需要自定義Adapter了,SimpleAdapter參數的綁定是有限制的,查看SimpleAdapter的源碼後這一點會更加清晰。

下面是從SimpleAdapter源碼中提取出來的方法,可以回答上面的問題。

/**
 * Called by bindView() to set the image for an ImageView but only if
 * there is no existing ViewBinder or if the existing ViewBinder cannot
 * handle binding to an ImageView.
 * <p>
 * This method is called instead of {@link #setViewImage(ImageView, String)}
 * if the supplied data is an int or Integer.
 *
 * @param v     ImageView to receive an image
 * @param value the value retrieved from the data set
 * @see #setViewImage(ImageView, String)
 */
public void setViewImage(ImageView v, int value) {
    v.setImageResource(value);
}

/**
 * Called by bindView() to set the image for an ImageView but only if
 * there is no existing ViewBinder or if the existing ViewBinder cannot
 * handle binding to an ImageView.
 * <p>
 * By default, the value will be treated as an image resource. If the
 * value cannot be used as an image resource, the value is used as an
 * image Uri.
 * <p>
 * This method is called instead of {@link #setViewImage(ImageView, int)}
 * if the supplied data is not an int or Integer.
 *
 * @param v     ImageView to receive an image
 * @param value the value retrieved from the data set
 * @see #setViewImage(ImageView, int)
 */
public void setViewImage(ImageView v, String value) {
    try {
        v.setImageResource(Integer.parseInt(value));
    } catch (NumberFormatException nfe) {
        v.setImageURI(Uri.parse(value));
    }
}

/**
 * Called by bindView() to set the text for a TextView but only if
 * there is no existing ViewBinder or if the existing ViewBinder cannot
 * handle binding to a TextView.
 *
 * @param v    TextView to receive text
 * @param text the text to be set for the TextView
 */
public void setViewText(TextView v, String text) {
    v.setText(text);
}

如果to參數中的View是TextView的話,則會調用上面的setViewText()方法,將from參數設置爲TextView的text,此時如果from參數不是String類型的而是int類型的,則會調用SimpleAdapter源碼中的bindView(int position, View view)方法將int轉換爲String。

如果to參數中的View是ImageView 的話,則會調用上面的setViewImage()方法來設置ImageView 的背景圖片,不過這裏只接受int類型和uri類型的參數,所以傳進來Bitmap類型的參數的話將不會正常顯示,只好通過自定義Adapter的方式來彌補了。

自定義Adapter

這裏用自定義的Adapter實現上面同樣的效果,與上面不同的是:這裏傳進來的頭像圖片不是項目目錄下的資源,而是某個文件目錄下的圖片。

示例

代碼:

  • 列表項數據類:MyListItemInfo.java
public class MyListItemInfo {
    private Bitmap chatHead;
    private String name;
    private String content;

    public Bitmap getChatHead() {
        return chatHead;
    }

    public void setChatHead(Bitmap chatHead) {
        this.chatHead = chatHead;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
  • 自定義Adapter:MyListAdapter .java
public class MyListAdapter extends BaseAdapter {
    private Context context = null;
    private List<MyListItemInfo> list = null;

    public MyListAdapter(Context context, List<MyListItemInfo> list){
        this.context = context;
        this.list = list;
    }

    @Override
    public int getCount() {
        int count = 0;
        if (list != null) {
            count = list.size();
        }
        return count;
    }

    @Override
    public MyListItemInfo getItem(int position) {
        MyListItemInfo item = null;
        if (list != null) {
            item = list.get(position);
        }
        return item;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    //convertView參數用於將之前加載好的佈局進行緩存,以便舊的View可以重用
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        //如果convertView爲null,則創建一個ViewHolder對象,並將控件的實例放在ViewHolder中
        if (convertView == null) {
            viewHolder = new ViewHolder();
            LayoutInflater mInflater = LayoutInflater.from(context);
            convertView = mInflater.inflate(R.layout.list_item, parent);

            viewHolder.ivChatHead = (ImageView) convertView.findViewById(R.id.ivChatHead);
            viewHolder.tvName = (TextView) convertView.findViewById(R.id.tvName);
            viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tvContent);
            //將Holder存儲到convertView中
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        //爲viewHolder設置項目數據
        MyListItemInfo myListItemInfo = getItem(position);
        if (myListItemInfo != null) {
            viewHolder.ivChatHead.setImageBitmap(myListItemInfo.getChatHead());
            viewHolder.tvName.setText(myListItemInfo.getName());
            viewHolder.tvContent.setText(myListItemInfo.getContent());
        }

        return convertView;
    }

    //使用ViewHolder對控件的實例進行緩存
    private static class ViewHolder{
        private ImageView ivChatHead;
        private TextView tvName;
        private TextView tvContent;
    }
}

  • MainActivity.java
public class MainActivity extends AppCompatActivity {
    private List<Bitmap> chatHeadList = null;
    private String[] name = new String[]{"張三", "李四", "王五"};
    private String[] content = new String[]{"我是張三,大家好", "今天天氣不錯", "好嗨喲"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String filePath= Environment.getExternalStorageDirectory().toString() + "/AAAMyImage";
        //運行時權限申請
        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
        }else {
            try {
                chatHeadList = getBitmapFromFiles(filePath);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        ListView listView = findViewById(R.id.listView);

        List<MyListItemInfo> list = new ArrayList<>();
        int nameLength = name.length;
        for (int i = 0; i < nameLength; i++) {
            MyListItemInfo myListItemInfo = new MyListItemInfo();
            if (chatHeadList != null) {
                myListItemInfo.setChatHead(chatHeadList.get(i));
            }
            myListItemInfo.setName(name[i]);
            myListItemInfo.setContent(content[i]);
            list.add(myListItemInfo);
        }

        MyListAdapter myAdapter = new MyListAdapter(this, list);
        listView.setAdapter(myAdapter);
    }
    
    public static List<Bitmap> getBitmapFromFiles(String path) throws FileNotFoundException {
        File file = new File(path);
        //listFiles是獲取該目錄下所有文件和目錄的絕對路徑
        File[] files = file.listFiles();
        if (files == null){
            Log.e("error","空目錄");
            //return null;
        }

        List<Bitmap> bitmapList = new ArrayList<>();
        int filesLength = files.length;
        for(int i =0; i < filesLength; i++){
            FileInputStream fis = new FileInputStream(files[i]);

            //把流轉化爲Bitmap圖片
            bitmapList.add(BitmapFactory.decodeStream(fis));
        }
        return bitmapList;
    }
}
  • 程序中需要讀取本地文件,需添加權限:AndroidManifest.xml
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章