Android入門之實現(xiàn)自定義Adapter
介紹
在上一篇“SimpleAdapter“章節(jié)中,我們看到了把:ListView和Listview內(nèi)部詳細頁面進行分離的Adapter的設計手法。
可是,這個SimpleAdapter的構造函數(shù)不夠錄活、苦澀難懂。很難滿足我們實際大多生產(chǎn)場景的開發(fā)。
因此,今天我們就要來看一個更人性化的“自定義BaseAdapter“。實際生產(chǎn)應用場景開發(fā)中充斥著自定義BaseAdapter,因此必須要提及它并且圍繞著這個extends BaseAdapter我們要持續(xù)說不少高級特性。
先來看一下課程最終要實現(xiàn)的目標
有喵、有汪、有金錢。還多了表頭和表尾。
我們這次就要使用真正的面向業(yè)務邏輯、面向對象的手法來實現(xiàn)這個界面。
設計
上述界面其實和上一篇例子相仿,使用到了:1個ImageView、兩個TextView。
只不過這次我們用的是標準MVC模式的自定義Adapter。
項目結構
是不是很詳盡?保姆式教程,不詳盡不稱為“保姆”。
先來看UI端代碼
UI端代碼
這一塊和上一篇幾乎相似,沒有什么太多變化
activity_main.xml文件
很簡單,沒有任何神密可言,就一個“光桿”Listview的存在。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
customized_layout.xml文件
內(nèi)容也是very easy,屬于“常規(guī)損人和”,和上一篇無異。
<?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"> <!-- 定義一個用于顯示頭像的ImageView --> <ImageView android:id="@+id/touxiang" android:layout_width="64dp" android:layout_height="64dp" android:baselineAlignBottom="true" android:paddingLeft="8dp" /> <!-- 定義一個豎直方向的LinearLayout,把QQ呢稱與說說的文本框設置出來 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="8dp" android:textColor="#1D1D1C" android:textSize="20sp" /> <TextView android:id="@+id/description" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="8px" android:textColor="#B4B4B9" android:textSize="14sp" /> </LinearLayout> </LinearLayout>
后端代碼
PetBean.java
package org.mk.android.demo.democustomizedadapter; import java.io.Serializable; public class PetBean implements Serializable { private String name = ""; private int imgId; private String description = ""; public int getImgId() { return imgId; } public void setImgId(int imgId) { this.imgId = imgId; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getName() { return name; } public void setName(String name) { this.name = name; } public PetBean(int touxiang, String name, String description) { this.imgId = touxiang; this.name = name; this.description = description; } }
這個Java Bean里分別就對應著一個ImageView,兩個TextView。
始終記得,把Image傳遞給到Adapter用的是一個int值,它來源于:R.drawable.圖片名稱(不帶.即postfix)。
PetAdapter.java
package org.mk.android.demo.democustomizedadapter; import android.content.Context; 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 java.util.List; public class PetAdapter extends BaseAdapter { private List<PetBean> data; private Context ctx; public PetAdapter(List<PetBean> data, Context ctx) { this.data = data; this.ctx = ctx; } @Override public int getCount() { if (data != null) { return data.size(); } return 0; } @Override public Object getItem(int i) { return null; } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View view, ViewGroup viewGroup) { MyViewHolder viewHolder=null; if (view == null) { view = LayoutInflater.from(ctx).inflate(R.layout.customized_layout, viewGroup, false); viewHolder=new MyViewHolder(); viewHolder.touxiang = (ImageView) view.findViewById(R.id.touxiang); viewHolder.name = (TextView) view.findViewById(R.id.name); viewHolder.description = (TextView) view.findViewById(R.id.description); view.setTag(viewHolder); }else{ viewHolder=(MyViewHolder)view.getTag(); } if (data != null) { viewHolder.touxiang.setBackgroundResource(data.get(i).getImgId()); viewHolder.name.setText(data.get(i).getName()); viewHolder.description.setText(data.get(i).getDescription()); return view; } return null; } static class MyViewHolder { public ImageView touxiang; public TextView name; public TextView description; } }
代碼導讀
整個自定義的Adapter是extends自BaseAdapter,這個BaseAdapter在extends后有幾個方法需要進行覆蓋:
1.構造函數(shù),構造函數(shù)里需要兩個參數(shù):
- 第一個參數(shù),構造函數(shù)里把自定義的數(shù)據(jù)源在上一例里我們用的是List<Map<String,Object>>(不夠面向對象),而這邊就是List<我們的ViewBean>傳進去;
- Context,如果在MainActivity.java里,我們就可以用這樣的形式來傳這個參數(shù):Context ctx = MainActivity.this;
2.public int getCount() ,它返回的就是你的ListView里有多少行的這個size即我們在構造方法里傳入的這個List<ViewBean>的size;
3.public Object getItem(int i),這個方法我們在后一步,高級定制化Adapter里會進一步用到,目前在此我們直接return null就完事了,不用作糾結;
4.public long getItemId(int i),這邊的int i其實是position,我們可以這么干:直接return i即可,它其實是一種“一行行從List<ViewBean>取出數(shù)據(jù)做渲染”用的;
5.public View getView(int i, View view, ViewGroup viewGroup) ,這個函數(shù)是核心,它的故事長了,來看一步步導讀:
這個方法的作用就是一條條把List<ViewBean>數(shù)據(jù)取出來作渲染用的,它依賴于這一句話:LayoutInflater.from(ctx).inflate(R.layout.customized_layout,
viewGroup, false);這個語句被調(diào)用的次數(shù)=List.size(),每調(diào)用一次這條語句,Android界面會渲染一次(一次開銷);
每一個ListView內(nèi)的行顯示的內(nèi)容根據(jù)List<ViewBean>里每一行不同的內(nèi)容會有不同的顯示,在這邊的一行指的就是:一個ImageView+兩個TextView的渲染。因此你要做的就是一個個“控制件名.set屬性(List里取出相應的該行的這個數(shù)組的屬性件)”,因此才有了如此的寫法:name.setText(data.get(i).getName());如:description.setText(data.get(i).getDescription());如:touxiang.setBackgroundResource(data.get(i).getImgId());這樣的東西。隨便說一句:此處的i帶的正是getView里的(int i...)里的這個i,這個i對應著你的List<ViewBean>里當前的“游標”;
全部一個個set完了后,把這個view return出去;
接著我們來說,這塊代碼看似沒邏輯那為什么會有:View Holder?這是一個什么鬼?前面我們提到了一句:
LayoutInflater.from(ctx).inflate(R.layout.customized_layout, viewGroup, false);這個語句被調(diào)用的次數(shù)=List.size(),每調(diào)用一次這條語句,Android界面會渲染一次,這個動作其實是很開銷資源的。比如說我的List<ViewBean>里有100條數(shù)據(jù),Android會界面渲染100次。其實這個渲染只是一個“一次性”的事,在這邊只要渲染一次就夠了,其余99次是多余重復的。渲染太多會造成這個Android極其吃手機的“運存”。所以我們使用了一個小技巧:只在這個View為空時做一次渲染。渲染過后就不要再渲染了,直接填充界面控件內(nèi)的屬性值就行了。因此才有了第一個if (view == null) {的判斷。
那么ViewHolder呢?還是沒有解釋ViewHolder的作用。我們前面解決了這個LayoutInflater.from(ctx).inflate的重復調(diào)用問題,但是讀者們知道嗎?你在getView方法里的findViewById(R.id.description)這樣的東西也是會每次被重復調(diào)用一次的,舉例來說:你有3個控件,在List<ViewBean>里有3行數(shù)據(jù),你以為你只調(diào)用了3次findViewById?其實是調(diào)用了總計3*3=9次,即調(diào)用第二個控件的findViewById時它依舊會重復調(diào)用第一個控件的findViewById。這個動作也是開銷Android的運存和cpu的。那么我們同樣為了減少findViewById的重復調(diào)用,因此我們使用一個MyViewHolder,讓其和我們的ViewBean(此處就是PetBean)一樣的結構,它專門是用于保存已經(jīng)被調(diào)用過findViewById的狀態(tài)(TAG)。然后使用view.setTag和view.getTag來做狀態(tài)保留。如果這個Tag存在那么不用再findViewById一次了。如果不存在再findViewById一次;
接著我們就來看交程序交互后端代碼MainActivity.java
MainActivity.java
package org.mk.android.demo.democustomizedadapter; import androidx.appcompat.app.AppCompatActivity; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.widget.ListView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private List<PetBean> data = null; private Context ctx; private PetAdapter adapter = null; private ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ctx = MainActivity.this; listView = (ListView) findViewById(R.id.listView); data = new ArrayList<PetBean>(); data.add(new PetBean(R.drawable.cat,"貓","這是一只貓")); data.add(new PetBean(R.drawable.dog,"狗","這是一只狗")); data.add(new PetBean(R.drawable.jingqianbao,"金錢豹","這是金錢豹")); adapter = new PetAdapter((List<PetBean>) data, ctx); final LayoutInflater inflater = LayoutInflater.from(this); View headView = inflater.inflate(R.layout.view_header, null, false); View footView = inflater.inflate(R.layout.view_footer, null, false); listView.addHeaderView(headView); listView.addFooterView(footView); listView.setAdapter(adapter); } }
我們這次為我們的ListView增加了一個表頭,一個表尾。表頭表尾分別對應著兩個layout xml文件,它們位于我們項目的res\layout目錄下。
注:
記得在調(diào)用addHeaderView和addFootView的動作必須位于setAdapter(adapter)語句前;
表頭樣式-view_header.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="vertical" android:gravity="center"> <TextView android:layout_width="match_parent" android:layout_height="48dp" android:textSize="18sp" android:text="表頭" android:gravity="center" android:background="#43BBEB" android:textColor="#FFFFFF"/> </LinearLayout>
表尾樣式-view_footer.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="vertical" android:gravity="center"> <TextView android:layout_width="match_parent" android:layout_height="48dp" android:textSize="18sp" android:text="表尾" android:gravity="center" android:background="#ECE9E6" android:textColor="#0C0C0C"/> </LinearLayout>
運行效果
自己動一下手試試就能找到自定義Adapter的感覺。自定義Adapter的作用很大、使用場景也很多。我們后面會繼續(xù)強化自定義Adapter的業(yè)務場景的使用。
到此這篇關于Android入門之實現(xiàn)自定義Adapter的文章就介紹到這了,更多相關Android自定義Adapter內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android布局——Preference自定義layout的方法
PreferenceActivity是一個方便設置管理的界面,但是對于界面顯示來說比較單調(diào),所以自定義布局就很有必要了,下面與大家分享下Preference中自定義layout的方法2013-06-06Android中利用SurfaceView制作抽獎轉盤的全流程攻略
這篇文章主要介紹了Android中利用SurfaceView制作抽獎轉盤的全流程,從圖案的繪制到轉盤的控制再到布局,真的非常全面,需要的朋友可以參考下2016-04-04Android ProgressBar 模擬進度條效果的實現(xiàn)
這篇文章主要介紹了Android ProgressBar 模擬進度條效果的實現(xiàn),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04解決Fedora14下eclipse進行android開發(fā),ibus提示沒有輸入窗口的方法詳解
本篇文章是對Fedora14下eclipse進行android開發(fā),ibus提示沒有輸入窗口的解決方法進行了詳細的分析介紹,需要的朋友參考下2013-05-05Android實現(xiàn)3種側滑效果(仿qq側滑、抽屜側滑、普通側滑)
這篇文章主要為大家詳細介紹了Android實現(xiàn)多種側滑效果,包括仿qq側滑,抽屜側滑,普通側滑三種效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04