本文為菜鳥窩作者蔣志碧的連載。“從 0 開始開發一款直播 APP ”系列來聊聊時下最火的直播 APP,如何完整的實現一個類"騰訊直播"的商業化項目
視頻地址:http://www.cniao5.com/course/10121
【從 0 開始開發一款直播 APP】3.1 高層封裝之 Adapter — ListView & GridView
【從 0 開始開發一款直播 APP】3.2 高層封裝之 Adapter — RecyclerView 實現單布局展示
【從 0 開始開發一款直播 APP】3.3 高層封裝之 Adapter -- RecyclerView 實現多條目展示
【從 0 開始開發一款直播 APP】3.4 高層封裝之 Adapter -- RecyclerView 優雅的添加 Header、Footer
一、前言
我們在開發中寫得最多的就是對 ListView、GridView 的適配器,我們熟悉得不能再熟悉,Adapter 一般都是繼承 BaseAdapter 并復寫其中的方法,getView 里面使用 ViewHolder 綁定控件。
二、常見示例
public class TraditionAdapter extends BaseAdapter {
private Context mContext;
private List<Item> mItems;
public TraditionAdapter(Context context, List<Item> items) {
mContext = context;
mItems = items;
}
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int position) {
return mItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (holder == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
holder = new ViewHolder();
holder.titleText = (TextView) convertView.findViewById(R.id.tv1);
holder.descText = (TextView) convertView.findViewById(R.id.tv2);
holder.img = (ImageView) convertView.findViewById(R.id.img);
convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
Item item = mItems.get(position);
holder.titleText.setText(item.getTv1());
holder.descText.setText(item.getTv2());
holder.img.setImageResource(item.getRes());
return convertView;
}
class ViewHolder{
TextView titleText;
TextView descText;
ImageView img;
}
}
這種重復的代碼大家應該都寫了很多遍了,TraditionAdapter 繼承 BaseAdapter,getView 使用 ViewHolder 綁定控件。而通常每個 ListView 布局都會有一個 Adapter,Adapter 中也會有一個對應的 ViewHolder。由此看來,要減少代碼量就要將 Adapter、ViewHolder 封裝成通用類。
想要 Adapter
與 ViewHolder
通用,目前需要以下幾步:
2.1、數據是活的,TraditionAdapter 中的 Item 類應作為范型傳入 Adapter
2.2、getCount()、getItem()、getItemId() 這三個方法一直都不變,將其封裝
2.3、抽取 ViewHolder 類,解決控件綁定問題
2.4、Adapter 類封裝,實現與 ViewHolder 神匹配
知道了大概步驟,先來封裝 ViewHolder 吧。
三、ViewHodler 類封裝
ViewHolder 通過 convertView.setTag() 與 convertView 進行綁定,然后當 convertView復用時,直接利用 convertView.getTag() 獲取的ViewHolder 的 convertView 布局中的控件,省去了findViewById() 的時間
實際上每個 convertView 會綁定一個 ViewHolder 對象,這個 ViewHolder 主要用于幫 convertView 存儲布局中的控件。
那么我們只要寫出一個通用的 ViewHolder,對于任意的 convertView,提供一個對象讓其 setTag() 即可
ViewHodler
類封裝,需要做以下幾步:
1、返回 ViewHolder
2、獲取控件
3、設置控件
4、convertView 的復用
每個布局有不同的控件,每個控件有自己的 id 和數據。存儲這些控件需要使用 SparseArray。
SparseArray 簡介
SparseArray
是 android 提供的新的存儲鍵值對的 API,相比 HashMap,SparseArray 性能更好。原因如下:
1、SparseArray 更加優化內存
2、key 的類型是 int 型,免去裝箱操作,時間性能優于 HashMap
3、SparseArray 結構簡單,使用一位數組存儲 key 和 value
開始 ViewHolder 的封裝。
3.1、創建 BaseViewHolder 類,需要的變量大概有 View mConvertView 復用機制,SparseArray<View> mViews 存儲所有控件,int mPosition 記錄 View 位置信息
public class BaseViewHolder{
//復用的View
private final View mConvertView;
//所有控件集合
private SparseArray<View> mViews;
//記錄位置信息
private int mPosition;
/**
* BaseViewHolder 構造函數
* @param context 上下文對象
* @param parent 父類容器
* @param layoutId 布局 Id
* @param position item位置信息
*/
public BaseViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
this.mPosition = position;
this.mViews = new SparseArray<View>();
mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
//設置 tag
mConvertView.setTag(this);
}
/**
* 通過 viewId 獲取控件
* @param viewId 控件id
* @param <T> View 子類
* @return 返回 View
*/
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
//返回 ViewHolder
public static BaseViewHolder getViewHolder(Context context, View convertView, ViewGroup parent,int layoutId, int position) {
//BaseViewHolder 為空,創建新的,否則返回已存在的
if (convertView == null) {
return new BaseViewHolder(context, parent, layoutId, position);
} else {
BaseViewHolder holder = (BaseViewHolder) convertView.getTag();
//更新 item 位置信息
holder.mPosition = position;
return holder;
}
}
//獲取 convertView
public View getConvertView() {
return mConvertView;
}
}
3.2、設置控件以及監聽(采用鏈式編程方法)
/**
* 設置 TextView 的值
* @param viewId
* @param text
* @return
*/
public BaseViewHolder setText(int viewId, String text)
{
TextView tv = getView(viewId);
tv.setText(text);
return this;
}
/**
* 設置TImageView的值
* @param viewId
* @param resId
* @return
*/
public BaseViewHolder setImageResource(int viewId, int resId)
{
ImageView view = getView(viewId);
view.setImageResource(resId);
return this;
}
/**
* 設置是否可見
* @param viewId
* @param visible
* @return
*/
public BaseViewHolder setVisible(int viewId, boolean visible)
{
View view = getView(viewId);
view.setVisibility(visible ? View.VISIBLE : View.GONE);
return this;
}
/**
* 設置tag
* @param viewId
* @param tag
* @return
*/
public BaseViewHolder setTag(int viewId, Object tag)
{
View view = getView(viewId);
view.setTag(tag);
return this;
}
public BaseViewHolder setTag(int viewId, int key, Object tag)
{
View view = getView(viewId);
view.setTag(key, tag);
return this;
}
/**
* 設置 Checkable
* @param viewId
* @param checked
* @return
*/
public BaseViewHolder setChecked(int viewId, boolean checked)
{
Checkable view = (Checkable) getView(viewId);
view.setChecked(checked);
return this;
}
/**
* 點擊事件
*/
public BaseViewHolder setOnClickListener(int viewId,View.OnClickListener listener)
{
View view = getView(viewId);
view.setOnClickListener(listener);
return this;
}
/**
* 觸摸事件
*/
public BaseViewHolder setOnTouchListener(int viewId,View.OnTouchListener listener)
{
View view = getView(viewId);
view.setOnTouchListener(listener);
return this;
}
/**
* 長按事件
*/
public BaseViewHolder setOnLongClickListener(int viewId,View.OnLongClickListener listener)
{
View view = getView(viewId);
view.setOnLongClickListener(listener);
return this;
}z
四、Adapter 類封裝
Adapter
封裝需要以下幾步:
1、上例一直存在的 Item 類將作為范型傳入Adapter
2、封裝 getCount()、getItem()、getItemId() 三個方法
3、封裝 getView()
4、綁定 ViewHolder
創建 BaseAdapter 繼承 android.widget.BaseAdapter 類,重寫方法及構造函數,根據需求,需要的參數有 List<T> mDatas 數據源,Context mContext 上下文對象,int mLayoutId 布局。
public abstract class BaseAdapter<T> extends android.widget.BaseAdapter {
protected List<T> mDatas;
protected Context mContext;
protected int mLayoutId;
public BaseAdapter(List<T> datas, Context context, int layoutId) {
mDatas = datas;
mContext = context;
this.mLayoutId = layoutId;
}
@Override
public int getCount() {
return mDatas == null ? 0 : mDatas.size();
}
@Override
public T getItem(int position) {
return mDatas == null ? null : mDatas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BaseViewHolder holder = BaseViewHolder.getViewHolder(mContext, convertView, parent, mLayoutId, position);
T t = mDatas.get(position);
//抽象出 ViewHolder 讓用戶去實現填充數據
bindData(holder, t);
return holder.getConvertView();
}
public abstract void bindData(BaseViewHolder holder, T t);
}
五、Adapter 和 ViewHolder 的使用
**5.1、 創建 SimpleAdapter.java 繼承 BaseAdapter 類,傳入 Item 數據源,根據布局找到需要的控件并填充數據,添加監聽等。 **
public class SimpleAdapter extends BaseAdapter<Item> {
public SimpleAdapter(List datas, Context context) {
super(datas, context, R.layout.list_item);
}
@Override
public void bindData(BaseViewHolder holder, final Item item) {
holder.setText(R.id.tv1,item.getTv1())
.setText(R.id.tv2,item.getTv2())
.setImageResource(R.id.img,item.getRes())
.setOnClickListener(R.id.tv2, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,item.getTv2(),Toast.LENGTH_SHORT).show();
}
});
}
}
5.2、由于加了點擊事件,運行起來點擊事件無效果,并不是因為代碼有問題,而是焦點搶占
原因,因此需要在布局文件 activity_adapter.xml 中設置是否可點擊
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:clickable="false"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:padding="5dp"
android:layout_marginRight="40dp"
android:id="@+id/img"
android:layout_width="100dp"
android:layout_height="100dp"/>
<LinearLayout
android:clickable="false"
android:padding="10dp"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:paddingTop="10dp"
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="40dp"/>
<TextView
android:id="@+id/tv2"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="40dp"/>
</LinearLayout>
</LinearLayout>
5.3、 AdapterActivity.java
public class AdapterActivity extends BaseActivity {
private SimpleAdapter mAdapter;
private ListView mListView;
private ArrayList<Item> Datas;
@Override
protected void setToolbar() {
}
@Override
protected void setListener() {
}
//添加數據
@Override
protected void initData() {
Datas = new ArrayList<>();
for (int i = 1; i <= 30; i++) {
Item item = new Item(R.drawable.tab_publish_normal, " get 新技能" + i, "揀到漂亮妹子 "+i+" 枚,在大街上");
Datas.add(item);
}
mAdapter = new SimpleAdapter(Datas,this);
mListView.setAdapter(mAdapter);
}
@Override
protected void initView() {
mListView = obtainView(R.id.list);
}
@Override
protected int getLayoutId() {
return R.layout.activity_adapter;
}
}
六、運行效果展示
6.1、ListView
6.2、GridView
七、總結
對 Adapter
傳統寫法熟練,對 ConverView
復用了解
對 ViewHolder
的使用熟悉,尤其是控件綁定
了解 SparseArray
的優缺點,對其基本使用熟悉
筆者只是傳達了封裝的思想,能力有限,封裝不到位的地方還望大家留言指正
更多內容,請關注菜鳥窩(微信公眾號ID: cniao5),程序猿的在線學習平臺。轉載請注明出處,本文出自菜鳥窩,原文鏈接http://www.cniao5.com/forum/thread/2ac69d820f0611e790dc00163e0230fa