編輯:關於Android編程
雖然隨著RecyclerView的不斷普及,相應的資源也越來越多,許多的項目都在使用RecyclerView,但作為他的前輩ListView,加深對ListView的使用有助於我們更好的適應到RecyclerView的使用中。
首先看一下我們實現的效果一些簡單效果

這只是前面的一些簡單效果,後面會有一些進階的效果,希望能耐心的看下去。
ListView的優化主要包括兩個方面,分別是對自身的優化以及其適配器(Adapter)的優化。
ListView自身的優化主要包括一條。對於ListView的layout_height和layout_width設置為match_parent,如果設置為match_parent,一般ListView的寬高會測量三次以上。具體的源碼沒有深入研究。但為什麼會要測量多次,如果對於自定義View稍微有點基礎的會知道,對於View的測量大小有三個類型:
- UNSPECIFIED:未指定的,父類不對子類施加任何限制。
- EXACTLY:確定的,父類確定其子類控件的大小。
- AT_MOST:最大值,需要子類去測量自身大小確定。
如果我們設置寬高為wrap_content,即AT_MOST,表示其寬高有控件本身去測量確定,而如果是match_parent,則EXACTLY,表示確定的大小。
Adapter優化。對於Adapter的優化,主要包括以下幾個步驟:
復用convertView,減少子布局的生成。
定義ViewHolder,減少findViewById()的次數。
下面看一下代碼
/**
* 最基礎的adapter
* Created by Alex_MaHao on 2016/5/17.
*/
public class SimpleBaseAdapter extends BaseAdapter {
private List datas;
public SimpleBaseAdapter(List datas) {
this.datas = datas;
}
@Override
public int getCount() {
return datas==null?0:datas.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vHolder;
if(convertView==null){
//初始化item布局
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_listview_sample,parent,false);
//創建記事本類
vHolder = new ViewHolder();
//查找控件,保存控件的引用
vHolder.tv = ((TextView) convertView.findViewById(R.id.listview_sample_tv));
//將當前viewHolder與converView綁定
convertView.setTag(vHolder);
}else{
//如果不為空,獲取
vHolder = (ViewHolder) convertView.getTag();
}
vHolder.tv.setText(datas.get(position));
return convertView;
}
/**
* 筆記本類,保存對象的引用
*/
class ViewHolder{
TextView tv;
}
}
根據代碼分析思路:
- ListView中的item滑出屏幕時,滑出的item會在getView()方法中返回,及converView參數。所以在這裡判斷,是否為null,如果不為null,表示我們可以對該布局重新設置數據並返回到列表中顯示。
- ViewHolder,保存item中子控件的引用。因為我們復用了converView,那麼對於同一個converView布局,其子控件的引用應該是不變的。所以我們可以獲取到其上的所有子控件的引用並通過ViewHolder進行保存。
- setTag(),getTag():該方法實現了ViewHolder和converView的綁定,就類似於通過setTag()方法,將ViewHolder打包成一個包裹,放在了converView上,再通過getTag()方法,將這個包裹取出。
在很多優化中,會將
ViewHolder定義為static。但在這裡我並沒加上。因為經過測試,加或不加,ViewHolder的創建次數不變。網上查了很多資料,也沒有找到一個讓我信服的理由。唯一有點理的就是基於java的特性。靜態內部類的對象不依賴於其所在的外部類對象。
上一節說了Adapter的優化,但如果我們每次寫都要寫這麼多的優化代碼,這不符合程序員懶惰的天性。那我們只能把他封裝,提取出一個公共的基類,在基類中,我們把布局加載,優化等都默認實現,只需讓子類構造布局文件,以及綁定數據。
那麼,從我們上一節的代碼看,有以下模塊都可以提取為基類:
數據集合datas:對於數據集合,我們通常都是一個List集合,在這裡定義泛型來表述其所包含的內容。 數據優化:布局的復用以及ViewHolder與converView的綁定。 ViewHolder:定義一個ViewHolder,通過map保存控件與id;
子類所需實現的:
數據的初始化 確定item的布局文件 將數據與視圖綁定。
那麼直接看一下我們繼承好的代碼:
public abstract class BaseAppAdapter extends BaseAdapter {
/**
* 泛型,保存數據
*/
protected List datas;
/**
* 構造方法,子類必須實現其構造方法,並初始化數據
* @param datas
*/
public BaseAppAdapter(List datas) {
this.datas = datas;
}
@Override
public int getCount() {
return datas==null?0:datas.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BaseViewHolder vHolder;
if(convertView==null){
convertView = LayoutInflater.from(parent.getContext()).inflate(getItemLayoutId(),parent,false);
vHolder = new BaseViewHolder(convertView);
convertView.setTag(vHolder);
}else{
vHolder = (BaseViewHolder) convertView.getTag();
}
/**
* 數據綁定的回調
*/
bindData(vHolder,datas.get(position));
return convertView;
}
/**
* 子類實現,獲取item布局文件的id
* @return
*/
protected abstract int getItemLayoutId();
/**
* 子類實現,綁定數據
* @param vHolder 對應position的ViewHolder
* @param data 對應的數據綁定
*/
protected abstract void bindData(BaseViewHolder vHolder,T data);
/**
* ViewHolder類
*/
class BaseViewHolder{
/**
* 保存view,以id-view的形式
*/
Map mapView;
View rootView;
public BaseViewHolder(View rootView){
this.rootView = rootView;
mapView = new HashMap();
}
/**
* 通過id查找控件
* @param id
* @return
*/
public View getView(Integer id){
View view = mapView.get(id);
if(view==null){
view = rootView.findViewById(id);
mapView.put(id,view);
}
return view;
}
}
}
在以上代碼中有幾個關鍵點:
有參的構造方法:子類實現時,必須調用父類的構造方法,用以保存數據。getItemLayoutId():item的布局文件id。抽象方法,子類必須實現。對於每一個BaseAppAdapter的子類,都需要通過該方法返回他們所特有的item的id。 bindData(BaseViewHolder vHolder,T data):數據綁定方法,子類必須實現。子類在此方法中將數據設置到試圖上 BaseViewHolder中的getView(Integer id):該方法設計比較巧妙,因為,對於基類我們不知道有哪些id需要查詢,如果直接通過findViewById()方法,則並沒有減少查詢次數。在這裡,通過保存一個map對象,通過id先從map中取,如果不存在,說明是第一次獲取,則使用findViewById查找控件,並將控件以鍵值對的形式保存到map中,則下次查找,會從map中取,減少了findViewById的次數。可能有人會認為,添加了一個map對象,內存占用不是增大了嗎,但是,第一,我們map中保存的都是引用。第二,findViewById()是按照深度優先遍歷查詢的,如果不懂,遍歷肯定能明白吧。
簡單的兩個屬性
android:divider:設置顏色,背景 android:dividerHeight:設置高度
如果設置無分割線,可以設置android:divider="@null"。
該兩個屬性必須同時使用,如果只設置
divider,則沒有效果,同時默認的分割線也會消失。
當然,我們也可以在item布局文件中添加分割線(在底部添加一個線),麻煩點而已。
android:scrollbars="none"
對於ListView,在android5.0一下是一個變色,在android5.0以上是一個波紋動畫。我們可以通過一些設置取消掉他的反饋效果。使用如下代碼
android:listSelector="#0000":點擊顏色設置為透明
ListView作為列表顯示的控件,那麼肯定會存在數據為空的情況,如果我們就直接顯示一面白無疑是不友好的,ListView可以通過設置一個視圖,當列表為null,顯示該視圖。
關鍵方法為setEmpty(View)。具體使用方法如下:
xml文件中,在ListView下設置一個圖片,如下
在java代碼中設置如下
//設置空視圖,調用此方法會默認隱藏Empty_view
mLv.setEmptyView(findViewById(R.id.listview_empty_view));
注意:我們無需設置
ImageView的visible屬性,因為通過setEmpty()方法,已將其顯示與隱藏於ListView所綁定,由ListView來管理。
ListView的滾動對於顯示數據,我們可能會有一些特殊的要求,比如初始顯示到第幾列,或者要調到第幾條顯示。對於此種要求,ListView已經實現了相應的方法讓我調用。
public void setSelection(int position):position為需要顯示在第一條的數據。該方法為瞬間滾動,無滑動效果,類似圖片上瞬間滑動按鈕實現的效果。 public void smoothScrollToPosition(int position):與上面方法作用相同,不過其有過度效果,類似平滑滾動實現的效果。
在ListView中,有如下方法,他控制了回彈設置的值:
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
maxOverScrollY參數便是設置我們可以下拉的空白區域的高度。具體實現如下
public class MyListView extends ListView {
/**
* 下拉回彈效果,下拉的最大距離
*/
private int mMaxOverDistance;
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
//初始化最大距離
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
float density = metrics.density;
mMaxOverDistance = (int) (100*density);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
//注意第九個參數,設置為了我們自定義的值
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxOverDistance, isTouchEvent);
}
}
該方法是保護類型的,所以我們需要通過繼承ListView來修改此方法的實現。
在看到此方法時,第一反應是ScrollView有沒有該方法,查了以後,發現ScrollView也有該方法。那麼,同理,ScrollView也能通過重寫overScrollBy實現下拉回彈的效果。
首先看一下效果圖:

分析其邏輯:
當我們向上滑動列表時,toolbar慢慢消失到頂部。 當我們向下滑動列表時,toolbar慢慢的從頂部顯現。
實現邏輯:
- setOnTouchListener,設置觸摸監聽。
- 對當前手指移動事件進行判斷。
- 如果是向下移動,且ToolBar是隱藏的,則給toolbar設置一個向下顯現的動畫。
- 如果是向上移動,且toolbar是顯現的,則給toolbar設置一個向上隱藏的動畫。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPr+00rvPwsq1z9a0+sLro7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
/**
* Created by Alex_MaHao on 2016/5/17.
*/
public class ToorBarListViewActivity extends AppCompatActivity implements View.OnTouchListener {
private Toolbar mToolBar;
private ListView mLv;
private SimpleBaseAdapter mAdapter;
private List
代碼注釋非常詳細,需要注意的有以下三點:
因為我們的父布局是RelativeLayout,如果不給ListView設置一個headView,則在最頂部時,ListView頂部數據會被隱藏。當然,可能會想到為什麼不用垂直布局,把ListView放在toolbar的下面呢,這樣會導致另一個問題,即當toobar消失時,會導致頂部有一個空白效果。如下圖所示:

監聽兩個條件,滑動方向和toolbar當前顯示的狀態,如果不監聽當前toobar狀態,會導致動畫重復調用。
會存在一種情況,及toolbar處在某一個動畫的時候,突然該表方向,我們需要先停止當前動畫,並以當前偏移量上為基礎,創建新的動畫。這也是創建動畫時使用mToolBar.getTranslationY()作為參數的原因。
在顯示數據中,可能會有這種需求,顯示的列表的布局不同,例如聊天界面,根據不同的聊天人,顯示的位置不同,及加載不同的itemView那麼我們如何實現呢。先看一下實現效果:

請忽略他的丑陋,目的是為了實現效果,下面直接上源碼,看一下實現過程,最後在總結實現的關鍵點:
聊天的實體類,在聊天實體類中,我們必須通過類型區分該聊天內容是自己還是朋友。
/**
* 聊天的實體類
* Created by Alex_MaHao on 2016/5/18.
*/
public class ChatBean {
/**
* 聊天的兩種類型,自己和朋友
*/
public static final int CHAT_MYSELF = 0;
public static final int CHAT_FIRENDS = 1;
/**
* 聊天的頭像
*/
private Drawable userIcon;
/**
* 發送的消息
*/
private String message;
/**
* 確定是否為當前聊天者
*/
private int type;
public ChatBean() {
}
public ChatBean(String message, int type, Drawable userIcon) {
this.message = message;
this.type = type;
this.userIcon = userIcon;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public Drawable getUserIcon() {
return userIcon;
}
public void setUserIcon(Drawable userIcon) {
this.userIcon = userIcon;
}
}
ListView所在的布局文件activity_listview_chat.xml
因為聊天條目沒有點擊反饋效果,所以通過android:listSelector設置無反饋效果。使用android:divider="#0000"和android:dividerHeight="10dp"設置每一個聊天條目的間隔。
我們需要加載不同的布局,所以我們有兩個item布局文件,用來顯示自己的消息和朋友的消息:
item_listview_chat_left,顯示在左邊,朋友消息顯示布局
item_listview_chat_right,顯示在右邊,自己消息的顯示布局
關鍵點來了,構造ChatAdapter,用以顯示數據。
/**
* 聊天的Adapter,實現加載不同的布局
* Created by Alex_MaHao on 2016/5/18.
*/
public class ChatAdapter extends BaseAdapter {
List chatBeens ;
public ChatAdapter(List chatBeens) {
this.chatBeens = chatBeens;
}
@Override
public int getCount() {
return chatBeens.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public int getItemViewType(int position) {
//返回值需要從0開始,對應數據所要加載的對應布局標識
return chatBeens.get(position).getType();
}
@Override
public int getViewTypeCount() {
//不同布局的種類數量
return 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//獲取當前數據的類型
int type = getItemViewType(position);
ViewHolder vHolder = null;
if(convertView==null){
switch (type){
case ChatBean.CHAT_MYSELF:
//加載自己消息顯示的布局
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_listview_chat_right,parent,false);
vHolder = new ViewHolder();
vHolder.icon = ((ImageView) convertView.findViewById(R.id.chat_right_icon));
vHolder.msg = ((TextView) convertView.findViewById(R.id.chat_right_msg));
break;
case ChatBean.CHAT_FIRENDS:
//加載朋友消息顯示的布局
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_listview_chat_left,parent,false);
vHolder = new ViewHolder();
vHolder.icon = ((ImageView) convertView.findViewById(R.id.chat_left_icon));
vHolder.msg = ((TextView) convertView.findViewById(R.id.chat_left_msg));
break;
}
//控件應用與控件綁定
convertView.setTag(vHolder);
}else{
//獲取控件引用
vHolder = (ViewHolder) convertView.getTag();
}
//設置數值
vHolder.icon.setImageDrawable(chatBeens.get(position).getUserIcon());
vHolder.msg.setText(chatBeens.get(position).getMessage());
return convertView;
}
class ViewHolder{
ImageView icon;
TextView msg;
}
}
在adapter中有兩個關鍵的方法:
getViewTypeCount():返回布局的種類數,比如當前我們分為自己的消息和朋友的消息兩個布局,那麼就返回2.
-getItemViewType():返回對應條目下,item布局的類型。ListView通過該方法,實現在getView()中返回相同類型的converView復用。注意,該返回類型要從0開始,不然會數組越界。
因為在這裡,比較巧合,我們的item布局控件都一樣,只不過顯示不一樣,所以只定義了一個ViewHolder類。
關鍵的實現了,那麼看一下activity中的代碼吧
public class ChatListViewActivity extends AppCompatActivity {
private ListView mChatLv;
private List chatBeanList = new ArrayList<>();
private ChatAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listview_chat);
mChatLv = ((ListView) findViewById(R.id.listview_chat_lv));
initChatBeanList();
adapter = new ChatAdapter(chatBeanList);
mChatLv.setAdapter(adapter);
mChatLv.postDelayed(new Runnable() {
@Override
public void run() {
//設置我們ListView顯示在底部
mChatLv.setSelection(adapter.getCount());
}
},200);
}
@Override
protected void onStart() {
super.onStart();
}
/**
* 初始化聊天內容
*/
private void initChatBeanList() {
for (int i = 0;i<10;i++){
ChatBean chat = new ChatBean();
if(i%2==0){
chat.setType(ChatBean.CHAT_MYSELF);
chat.setUserIcon(getApplicationContext().getResources().getDrawable(R.mipmap.qq));
chat.setMessage("微信,微信,我是QQ");
}else{
chat.setType(ChatBean.CHAT_FIRENDS);
chat.setUserIcon(getApplicationContext().getResources().getDrawable(R.mipmap.weixin));
chat.setMessage("QQ,QQ,我是微信");
}
chatBeanList.add(chat);
}
}
}
實現該布局的兩個關鍵方法(adapter中的方法)
-getViewTypeCount():返回布局的種類數,比如當前我們分為自己的消息和朋友的消息兩個布局,那麼就返回2.
-getItemViewType():返回對應條目下,item布局的類型。ListView通過該方法,實現在getView()中返回相同類型的converView復用。注意,該返回類型要從0開始,不然會數組越界。
決定使用多種方式實現下拉刷新和上拉加載,會在下一篇博客中實現。絕對滿滿的干貨。
該項目源碼已經共享到我的github,在模塊
systemwidgetdemo中。
Android事件處理方法總結-基於回調的事件處理
一、Android中的事件處理方法事件處理:響應用戶UI動作,提高應用程序交互性1、基於監聽的事件處理機制2、基於回調的事件處理機制3、Handler消息處理前面我們已經
Android-2電話應用,短信應用
Activity的生命周期 Android的核心組件 1.Viiew :界面 ,組織UI控件 2.Intent :意圖,支持組件之間的通信 3.Activity:
Android實現將一個Activity設置成窗口樣式的方法
本文實例講述了Android實現將一個Activity設置成窗口樣式的方法。分享給大家供大家參考,具體如下:1.在res/value文件夾下的style.xml文件中加入
ANDROID-當網絡發生變化時使用BroadcastReceiver和service通知
Android 中的 Service按運行地點分類:1、本地服務(Local) 該服務依附在主進程上, 服務依附在主進程上而不是獨立的進程,這樣在一定程度上節約了資源,另