Android列表用法之二:實戰ListView高階用法
在我們的專案中,並不是所有列表都是簡單的使用。類似於新聞列表、QQ聊天列表等,具有圖文並排的列表,每個item都有它不同的佈局型別,都有其不同的實現方式。這類複雜的列表表現形式,在各類知名應用當中,不可或缺的存在著。今天我們就來看看,使用ListView能不能實現此類複雜佈局。
今天的例項為眾所周知的QQ聊天介面:
一、縱觀全域性
1、一個ListView。
2、圓形頭像和訊息框。
3、時間分隔行。
二、技術選型
1、列表當然選擇ListView,我們今天使用的就是這個。
2、圓形頭像,目前三方開源的比較多,有CircleImageView、AvatarImageView等可以選擇,今天我就選擇AvatarImageView。為什麼要選擇這個,它是我常用的控制元件之一,只是最近使用它的頻率比較高,就選擇它了。後續我會有專門貼子說明圓形頭像控制元件的選擇。
3、文字框+layout組合成訊息框。文字框顯示訊息內容,layout顯示訊息背景泡泡。
4、item的實現有幾種方法:
1)寫一個佈局xml檔案。裡面include三個xml佈局,一是自己傳送訊息,訊息顯示在右邊的佈局。二是對方發訊息,訊息顯示在介面左邊的佈局。三是時間佈局,顯示在中間,並起到分隔作用。然後根據不同的訊息型別,控制佈局的顯示與隱藏。
2)根據資料時間變化,以及不同訊息的型別。直接構造不同的佈局檔案。這樣就比較輕量級。不會在同一個佈局當中,存在許多無用的佈局。
3)在此我選擇第一種方式,因為ListView有所限制,使用第二種方式不是特別方便。第二種方式我會使用後續的RecyclerView來講解。
三、碼程式碼
1、在上一篇listview簡單用法當中,我們使用的viewholder,是整合在adapter當中進行優化的。另外,在這裡我引入另外一個解耦合比較高的ViewHolder的寫法。
另外新建一個專門的ViewHolder類,在裡面實現靜態的getViewHolder方法,比較簡單,只是將內容移了個地方而已,直接看程式碼:
package oliver.zhantao.oliverproject.listviewhigh;
import android.view.View;
import android.view.ViewStub;
import android.widget.TextView;
import cn.carbs.android.avatarimageview.library.AvatarImageView;
import oliver.zhantao.oliverproject.R;
/**
* ViewHolder的抽離,單獨形成一個類,可以讓程式碼更加明瞭
* 另外,也可以使adapter更加輕量級,降低耦合性。
* <p>
* 將ViewHolder的實現方法完全封裝在其本身的類中,功能更強大
* 其方法與Adapter的getView()方法隔離,條理更加清晰,降低耦合性
* 當getView()中需要實現多種樣式時,不需要寫重複程式碼
* <p>
* Created by ZhanTao on 2017/4/19.
*/
public class HighViewHolder {
public View mineView;
public View otherView;
public View timeView;
public AvatarImageView mineView_head;
public AvatarImageView otherView_head;
public TextView mineView_message;
public TextView otherView_message;
public TextView timeView_time;
// 建構函式中就初始化View
public HighViewHolder(View convertView) {
mineView = convertView.findViewById(R.id.view_mine);
otherView = convertView.findViewById(R.id.view_other);
timeView = convertView.findViewById(R.id.view_time);
mineView_head = (AvatarImageView) mineView.findViewById(R.id.aiv_head);
otherView_head = (AvatarImageView) otherView.findViewById(R.id.aiv_head);
mineView_message = (TextView) mineView.findViewById(R.id.tv_message);
otherView_message = (TextView) otherView.findViewById(R.id.tv_message);
timeView_time = (TextView) timeView.findViewById(R.id.tv_time);
}
// 得到一個ViewHolder
public static HighViewHolder getViewHolder(View convertView) {
HighViewHolder viewHolder = (HighViewHolder) convertView.getTag();
if (viewHolder == null) {
viewHolder = new HighViewHolder(convertView);
convertView.setTag(viewHolder);
}
return viewHolder;
}
}
2、再來看我們的adapter,在這個adapter當中,直接通過靜態方法就可以獲取ViewHolder例項,是不是簡單明瞭多了。注意看getView方法。
package oliver.zhantao.oliverproject.listviewhigh;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.util.List;
import oliver.zhantao.oliverproject.R;
/**
* 聊天列表介面卡,在這裡將資料與控制元件進行繫結
* <p>
* 專案中都是自定義介面卡,很少用到系統提供的簡易介面卡。 所以果斷拋棄系統的簡易介面卡,自定義想咋樣就咋樣,一個字,爽^_^
* <p>
* Created by ZhanTao on 2017/4/17.
*/
public class ListViewHighAdapter extends BaseAdapter {
private Context mContext;
// 餐廳列表
private List<MessageBean> messageList;
private LayoutInflater mInflater;
public ListViewHighAdapter(Context context, List<MessageBean> messageList) {
this.mContext = context;
this.messageList = messageList;
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return messageList.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getViewTypeCount() {
// 已經知道有三種佈局了,直接寫死。當然也可以做成一個可擴充套件的,專案中靈活性更強。
return 3;
}
@Override
public int getItemViewType(int position) {
return messageList.get(position).getLayoutType();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
HighViewHolder holder;
// 1、優化框架搭起
if (convertView == null) {
convertView = mInflater.inflate(R.layout.activity_listview_high_item, null);
}
//這樣使用Holder,簡單明瞭,還不佔用adapter
holder = HighViewHolder.getViewHolder(convertView);
// 2、繫結資料到控制元件
bindViewData(position, convertView, holder);
// 3、控制VIEW的顯示與隱藏
controlViewVisible(position, holder);
// 4、繫結監聽事件
bindViewClickListener(position, convertView, holder);
return convertView;
}
/**
* 繫結資料到控制元件,在這裡做統一處理,方便資料抽離。
*
* @param position 列表索引
* @param convertView 每個position對應的itemView
* @param holder holder物件
*/
private void bindViewData(int position, View convertView, HighViewHolder holder) {
switch (getItemViewType(position)) {
case MessageBean.LAYOUT_TYPE_MINE:
holder.mineView_head.setImageResource(R.drawable.qq_head_mine);
holder.mineView_message.setText(messageList.get(position).getMsg());
break;
case MessageBean.LAYOUT_TYPE_OTHER:
holder.otherView_head.setImageResource(R.drawable.qq_head_other);
holder.otherView_message.setText(messageList.get(position).getMsg());
break;
case MessageBean.LAYOUT_TYPE_TIME:
holder.timeView_time.setText(messageList.get(position).getMsg());
break;
}
}
/**
* 控制每個頁面的顯示與隱藏,根據item型別顯示需要的佈局。
*
* @param position 列表索引
* @param holder holder物件
*/
private void controlViewVisible(int position, HighViewHolder holder) {
switch (getItemViewType(position)) {
case MessageBean.LAYOUT_TYPE_MINE:
holder.mineView.setVisibility(View.VISIBLE);
holder.otherView.setVisibility(View.GONE);
holder.timeView.setVisibility(View.GONE);
break;
case MessageBean.LAYOUT_TYPE_OTHER:
holder.mineView.setVisibility(View.GONE);
holder.otherView.setVisibility(View.VISIBLE);
holder.timeView.setVisibility(View.GONE);
break;
case MessageBean.LAYOUT_TYPE_TIME:
holder.mineView.setVisibility(View.GONE);
holder.otherView.setVisibility(View.GONE);
holder.timeView.setVisibility(View.VISIBLE);
break;
}
}
/**
* 繫結控制元件的各種View的事件,在這裡做統一處理,比如點選事件,長安事件。
*
* @param position 列表索引
* @param convertView 每個position對應的itemView
* @param holder holder物件
*/
private void bindViewClickListener(int position, View convertView, HighViewHolder holder) {
}
}
3、上面只是為了展示另外一種ViewHolder的寫法和用法,當然也貼也許多本例當中複雜列表的實現程式碼,這也是為了簡單。以下我們再來分析一下這種複雜佈局的實現。
首先,我們要在Adapter當中重寫如下兩個方法,這兩個方法用於標識我們的列表當中,有幾種不同的佈局。在這裡我們有三種佈局。
其次,這個型別,我們需要在訊息當中儲存起來,當我們在getView當中,繪製佈局的時候,就需要明確的知道,我們使用的是哪種佈局,那麼就需要通過資料當中的layoutType來判斷。
@Override
public int getViewTypeCount() {
// 已經知道有三種佈局了,直接寫死。當然也可以做成一個可擴充套件的,專案中靈活性更強。
return 3;
}
@Override
public int getItemViewType(int position) {
return messageList.get(position).getLayoutType();
}
4、資料實體類
package oliver.zhantao.oliverproject.listviewhigh;
import java.io.Serializable;
/**
* 訊息資料物件
*
* Created by ZhanTao on 2017/4/28.
*/
public class MessageBean implements Serializable {
//常量,用於標識佈局。
public static final int LAYOUT_TYPE_MINE = 0;
public static final int LAYOUT_TYPE_OTHER = 1;
public static final int LAYOUT_TYPE_TIME = 2;
//訊息內容
private String msg;
//頭像URL
private String headUrl;
//訊息傳送時間
private long time;
//標識佈局型別
private int layoutType;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getHeadUrl() {
return headUrl;
}
public void setHeadUrl(String headUrl) {
this.headUrl = headUrl;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public int getLayoutType() {
return layoutType;
}
public void setLayoutType(int layoutType) {
this.layoutType = layoutType;
}
}
5、下面我們來檢視佈局activity_listview_high_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="match_parent"
android:paddingBottom="@dimen/dp_10"
android:paddingTop="@dimen/dp_10">
<include
android:id="@+id/view_mine"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/activity_listview_high_item_mine" />
<include
android:id="@+id/view_other"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/activity_listview_high_item_other" />
<include
android:id="@+id/view_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/activity_listview_high_item_time" />
</RelativeLayout>
6、自己傳送訊息的佈局。
<?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">
<cn.carbs.android.avatarimageview.library.AvatarImageView
android:id="@+id/aiv_head"
android:layout_width="@dimen/dp_40"
android:layout_height="@dimen/dp_40"
android:layout_marginRight="@dimen/dp_16"
android:layout_alignParentRight="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_80"
android:layout_toLeftOf="@id/aiv_head"
android:background="@drawable/bg_mine"
android:paddingTop="@dimen/dp_13"
android:paddingBottom="@dimen/dp_13"
android:paddingLeft="@dimen/dp_13"
android:paddingRight="@dimen/dp_13"
android:orientation="vertical">
<TextView
android:id="@+id/tv_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/color_000000"
android:gravity="right"
android:text=""
android:textSize="@dimen/dp_14" />
</LinearLayout>
</RelativeLayout>
7、對方傳送訊息的佈局。
<?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">
<cn.carbs.android.avatarimageview.library.AvatarImageView
android:id="@+id/aiv_head"
android:layout_width="@dimen/dp_40"
android:layout_height="@dimen/dp_40"
android:layout_marginLeft="@dimen/dp_16" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/dp_80"
android:layout_toRightOf="@id/aiv_head"
android:background="@drawable/bg_other"
android:paddingTop="@dimen/dp_13"
android:paddingBottom="@dimen/dp_13"
android:paddingLeft="@dimen/dp_13"
android:paddingRight="@dimen/dp_13"
android:orientation="vertical">
<TextView
android:id="@+id/tv_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/color_000000"
android:text=""
android:textSize="@dimen/dp_14" />
</LinearLayout>
</RelativeLayout>
8、時間分隔佈局。
<?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"
android:paddingBottom="@dimen/dp_10"
android:paddingTop="@dimen/dp_10">
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text=""
android:textColor="@color/transparent_45"
android:textSize="@dimen/dp_12" />
</RelativeLayout>
9、以上佈局,具體的實現方式,直接看程式碼, 我就不贅述了,下面貼Activity的程式碼。
package oliver.zhantao.oliverproject.listviewhigh;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
import oliver.zhantao.oliverproject.R;
import oliver.zhantao.oliverproject.constants.Constants;
import oliver.zhantao.oliverproject.listview.RestaurantBean;
/**
* 實戰專案中簡單的列表實現,自定義介面卡。
* 仿寫百度地圖當中,收藏夾裡面,收藏點的列表實現。
* 一切脫離實戰的例項,都是耍流氓。
* <p>
* Created by ZhanTao on 2017/4/14.
*/
public class ListViewHighActivity extends AppCompatActivity {
private ListView listViewHigh;
private ListViewHighAdapter listViewHighAdapter;
private List<MessageBean> datas = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listview_high);
datas = new ArrayList<>();
//1、找到佈局當中的列表控制元件
listViewHigh = (ListView) findViewById(R.id.lv_resturant);
//2、建立介面卡物件, 上下文必須傳(非常有用)、資料必須傳
listViewHighAdapter = new ListViewHighAdapter(ListViewHighActivity.this, datas);
//3、將介面卡與listview進行繫結
listViewHigh.setAdapter(listViewHighAdapter);
//4、構造資料、重新整理列表
getDatasFromNetwork();
}
/**
* 資料封裝,模擬從網路上獲取到的資料
*
* @return 返回資料列表
*/
private List<MessageBean> getDatasFromNetwork() {
MessageBean messageBean = new MessageBean();
messageBean.setMsg("哪個喊她穿高根鞋去追公交啊");
//這裡可以放網路圖片,在此我就不放了,我直接放了兩張頭像圖片在工程當中
//adapter裡我就直接使用了。
messageBean.setHeadUrl("");
messageBean.setTime(System.currentTimeMillis());
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_MINE);
datas.add(messageBean);
messageBean = new MessageBean();
messageBean.setMsg("死神到了,逃不脫");
messageBean.setTime(System.currentTimeMillis());
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_MINE);
datas.add(messageBean);
messageBean = new MessageBean();
messageBean.setMsg("看來你是不看新聞的");
messageBean.setTime(System.currentTimeMillis());
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_OTHER);
datas.add(messageBean);
messageBean = new MessageBean();
//這裡為什麼要加360000,怎麼來的?6分鐘*60秒*1000毫秒=360000
//我們在這裡模擬超過5分鐘的訊息,要顯示一個時間。要注意有一個佈局是時間。
messageBean.setTime(System.currentTimeMillis() + 360000);
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_TIME);
//在這裡處理時間,如果這條訊息時間跟上一條時間相隔5分鐘以上,就顯示一個時間View。
//我這裡是做得比較簡單,原版時間演算法,顯示規則更加複雜些。
if(messageBean.getTime() - datas.get(datas.size() -1 ).getTime() > 300000){
//處理時間
messageBean.setMsg("下午2:41");
}
datas.add(messageBean);
messageBean = new MessageBean();
messageBean.setMsg("博越油耗相當高啊。12L");
messageBean.setTime(System.currentTimeMillis());
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_MINE);
datas.add(messageBean);
messageBean = new MessageBean();
messageBean.setMsg("百公里");
messageBean.setTime(System.currentTimeMillis());
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_MINE);
datas.add(messageBean);
messageBean = new MessageBean();
messageBean.setMsg("你才知道啊");
messageBean.setTime(System.currentTimeMillis());
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_OTHER);
datas.add(messageBean);
messageBean = new MessageBean();
messageBean.setMsg("手動的大概9L");
messageBean.setTime(System.currentTimeMillis());
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_OTHER);
datas.add(messageBean);
messageBean = new MessageBean();
messageBean.setMsg("自動的最小10L");
messageBean.setTime(System.currentTimeMillis());
messageBean.setLayoutType(MessageBean.LAYOUT_TYPE_OTHER);
datas.add(messageBean);
//重新整理列表,如果是真實的網路資料,則放到請求回撥函式當中使用。
//注意如果是在非同步執行緒中,應該怎麼使用?它必須在主執行緒中執行!!
listViewHighAdapter.notifyDataSetChanged();
return datas;
}
}
10、activity_listview_high.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="oliver.zhantao.oliverproject.listview.ListViewSimpleActivity">
<ListView
android:id="@+id/lv_resturant"
android:layout_width="0dp"
android:layout_height="0dp"
android:listSelector="@drawable/listviewsimple_bg_selector"
android:divider="@null"
android:background="@color/color_ECEDF1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
下面我們來看看實際的執行效果,背景泡泡是我從網上扒的,太醜了。在專案中,有UI專門製作的。
以上,完了。程式碼貼完就完了。如有問題或好的建議希望大家留言,感激。
作者:Amir
部落格:http://blog.csdn.NET/amir_zt/
以上原創,轉載請註明出處,謝謝。
http://blog.csdn.net/amir_zt/article/details/72547935