文章目錄
繼承關係
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" />