閱讀徐宜生《Android群英傳》的筆記——第4章 ListView 使用技巧——剩下部分
4.1.8 處理空 ListView
ListView 用於展示列表資料,但當列表中無資料時,ListView 不會顯示任何資料或提示,按照完善使用者體驗的需求,這裡應該給以無資料的提示。幸好,ListView 提供了一個方法 —— setEmptyView(),通過這個方法我們可以給 ListView 設定一個在空資料下顯示的預設提示。包含 ListView 的佈局設定如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
<ImageView
android:id ="@+id/img_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher" />
</FrameLayout>
在程式碼中,我們通過以下方式給 ListView 設定空資料時要顯示的佈局,程式碼如下所示:
listView = (ListView) findViewById(R.id.list_view);
imgEmpty = (ImageView) findViewById(R.id .img_empty);
listView.setEmptyView(imgEmpty);
通過以上程式碼,就給 ListView 在空資料時顯示一張預設的圖片,用來提示使用者;而在有資料時,則不會顯示。MainActivity 的程式碼如下所示:
package com.example.test;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private ListView listView;
private List<String> mData = new ArrayList<>();
private ViewHolderAdapter adapter;
private ImageView imgEmpty;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
imgEmpty = (ImageView) findViewById(R.id.img_empty);
//下面程式碼註釋掉後表示沒有資料
// for (int i = 0; i < 30; i++) {
// mData.add(i + "");
// }
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
listView.setEmptyView(imgEmpty);
}
}
4.1.9 ListView 滑動監聽
ListView 的滑動監聽,是 ListView 中最重要的技巧,很多重寫的 ListView,基本上都是在滑動事件的處理上下功夫,通過判斷滑動事件進行不同的邏輯處理。而為了更加精確地監聽滑動事件,開發者通常還需要使用 GestureDetector 手勢識別、VelocityTracker 滑動速度檢測等輔助類來完成更好的監聽。這裡介紹兩種監聽 ListView 滑動事件的方法,一個是通過 OnTouchListener 來實現監聽,另一個是使用 OnScrollListener 來實現監聽。
(1)OnTouchListener
OnTouchListener 是 View 中的監聽事件,通過監聽 ACTION_DOWN、ACTION_MOVE、ACTION_UP 這三個事件發生時的座標,就可以根據座標判斷使用者滑動的方向,並在不同的事件中進行相應的邏輯處理,這種方式的使用程式碼如下所示:
listView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//觸控時操作
break;
case MotionEvent.ACTION_MOVE:
//移動時操作
break;
case MotionEvent.ACTION_UP:
//離開時操作
break;
}
return false;
}
});
(2)OnScrollListener
OnScrollListener 是 AbsListView 中的監聽事件,它封裝了很多與 ListView 相關的資訊,使用起來也更加靈活。首先來看一下 OnScrollListener 的一般使用方法,程式碼如下所示:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_IDLE:
//滑動停止時
Log.i(TAG, "SCROLL_STATE_IDLE");
break;
case SCROLL_STATE_TOUCH_SCROLL:
//正在滾動
Log.i(TAG, "SCROLL_STATE_TOUCH_SCROLL");
break;
case SCROLL_STATE_FLING:
//手指拋動時,即手指用力滑動,在離開後 ListView 由於慣性繼續滑動
Log.i(TAG, "SCROLL_STATE_FLING");
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//滾動時一直呼叫
Log.i(TAG, "onScroll");
}
});
OnScrollListener 中有兩個回撥方法 —— OnScrollStateChanged() 和 onScroll()。
先來看第一個方法 OnScrollStateChanged(),這個方法根據它的引數 scrollState 來決定其回撥的次數,scrollState 有以下三種模式:
- SCROLL_STATE_IDEL:滾動停止時。
- SCROLL_STATE_TOUCH_SCROLL:正在滾動時。
SCROLL_STATE_FLING:手指拋動時,即手指用力滑動,在離開後 ListView 由於慣性繼續滑動的狀態。
當用戶沒有做手指拋動的狀態時,這個方法只會回撥 2 次,否則會回撥 3 次,差別就是手指拋動的這個狀態。通常情況下,我們會在這個方法中通過不同的狀態來設定一些標誌 Flag,來區分不同的滑動狀態,供其他方法處理。
下面再來看看 OnScroll() 這個回撥方法,它在 ListView 滾動時會一直回撥,而方法中的後三個 int 型別的引數,則非常精確地顯示了當前 ListView 滾動的狀態,這三個引數如下所示:- firstVisibleItem:當前能看見的第一個 Item 的 ID(從0開始)。
- visibleItemCount:當前能看見的 Item 總數。
totalItemCount:整個 ListView 的 Item 總數。
這裡需要注意的是,當前能看見的 Item 數,包括沒有顯示完整的 Item,即顯示一小半的 Item 也包括在內了。通過這幾個引數,可以很方便地進行一些判斷,比如判斷是否滾動到最後一行,就可以使用如下程式碼進行判斷,當前可視的第一個 Item 的 ID 加上當前可視 Item 的和等於 Item 總數的時候,即滾動到了最後一行。
if (firstVisibleItem + visivleItemCount == totalItemCount & totalItemCount >0){
//滾動到最後一行
}
再比如,可以通過如下程式碼來判斷滾動的方向,程式碼如下所示:
if (firstVisibleItem > lastVisibleItemPosition){
//上滑
}else if (firstVisibleItem < lastVisibleItemPosition){
//下滑
}
lastVisibleItemPosition = firstVisibleItem;
通過一個成員變數 lastVisibleItemPosition 來記錄上次第一個可視的 Item 的 ID 並與當前的可視 Item 的 ID 進行比較,即可知道當前滾動的方向。
要理解整個 OnScrollListener,最好的方法還是在程式碼中新增 Log,並打印出狀態資訊來進行分析學習。在以上程式碼中,已經添加了相應的 Log,對照 Log 進行分析,會很快掌握 OnScrollListener 的用法。
當然,ListView 也給我們提供了一些封裝的方法來獲得當前可視的 Item 的位置等資訊:
//獲取可視區域內最後一個 Item 的 ID
listView.getLastVisiblePosition();
//獲取可視區域內第一個 Item 的 ID
listView.getFirstVisiblePosition();
4.2 ListView 常用拓展
ListView 雖然使用廣泛,但系統原生的 ListView 顯然是不能滿足使用者在審美、功能上不斷提高需求的。不過也不要緊,Android 完全可以定製化,讓我們非常方便地對原生 ListView 進行拓展、修改。於是,在開發者的創新下,ListView 越來越豐富多彩,各種各樣的基於原生 ListView 的拓展讓人目不暇接。下面來看幾個常用的 ListView 拓展。
4.2.1 具有彈性的 ListView
Android 預設的 ListView 在滾動到頂端或者底端的時候,並沒有很好的提示,在 Android 5.X 中,Google 為這樣的行為只添加了一個半月形的陰影效果。而在 IOS 系統中,列表都是具有彈性的,即滾動到底端或者頂端後會繼續往下或者往上滑動一段距離。不得不說,這樣的設計的確更加的友好,雖然不知道 Google 為什麼不模仿這樣的設計,但我們可以自己修改 ListView,讓ListView 也可以“彈性十足”。
這裡可以使用一種非常簡單的方法來實現這個效果,我們在檢視 ListView 原始碼的時候可以發現,ListView 中有一個控制滑動到邊緣的處理方法,如下所示:
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
可以看見這樣一個引數:maxOverScrollY,註釋中這樣寫道 —— Number of pixels to overscroll by in either direction along the Y axis。由此可以發現,雖然它的預設值是0,但其實只要修改這個引數的值,就可以讓 ListView 具有彈性了!重寫這個方法,並將 maxOverScrollY 改為設定的值 —— mMaxOverDistance,程式碼如下所示:
@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 了。當然,為了能夠滿足多解析度的需求,我們可以在修改 maxOverScrollY 值的時候,可以通過螢幕的 density 來計算具體的值,讓不同解析度的彈性距離基本一致,程式碼如下所示:
private void initView() {
DisplayMetrics dm = this.getResources().getDisplayMetrics();
mMaxOverDistance = (int) (dm.density * mMaxOverDistance);
}
最後整理一下所有的程式碼就是下面這樣:
自定義的 ListView:
package com.example.test;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.ListView;
/**
* 自定義 ListView
* Created by HourGlassRemember on 2016/9/28.
*/
public class CustomListView extends ListView {
private int mMaxOverDistance = 60;
public CustomListView(Context context) {
this(context, null);
}
public CustomListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
/**
* 初始化
*/
private void initView() {
DisplayMetrics dm = this.getResources().getDisplayMetrics();
mMaxOverDistance = (int) (dm.density * mMaxOverDistance);
}
@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);
}
}
佈局檔案:
<?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">
<com.example.test.CustomListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
Adapter:
package com.example.test;
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;
/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {
private List<String> mData;
private LayoutInflater mInflater;
public ViewHolderAdapter(Context context, List<String> mData) {
this.mData = mData;
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
//判斷是否快取
if (convertView == null) {
holder = new ViewHolder();
//通過 LayoutInflater 例項化佈局
convertView = mInflater.inflate(R.layout.viewholder_item, null);
holder.imgIcon = (ImageView) convertView.findViewById(R.id.img_icon);
holder.txtTitle = (TextView) convertView.findViewById(R.id.txt_title);
convertView.setTag(holder);
} else {
//通過 tag 找到快取的佈局
holder = (ViewHolder) convertView.getTag();
}
//設定佈局中控制元件要顯示的檢視
holder.imgIcon.setBackgroundResource(R.mipmap.ic_launcher);
holder.txtTitle.setText(mData.get(position));
return convertView;
}
public final class ViewHolder {
public ImageView imgIcon;
public TextView txtTitle;
}
}
MainActivity 類:
package com.example.test;
import android.app.Activity;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private CustomListView listView;
private List<String> mData = new ArrayList<>();
private ViewHolderAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (CustomListView) findViewById(R.id.list_view);
for (int i = 0; i < 30; i++) {
mData.add(i + "");
}
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
}
}
4.2.2 自動顯示、隱藏佈局的 ListView
相信用過 Google+ 的朋友應該非常熟悉這樣一個效果:當我們在 ListView 上滑動的時候,頂部的 ActionBar 或者 Toolbar 就會相應的隱藏或顯示。大家可以發現,在滾動前介面上載入了上方的標題欄和右下角的懸浮編輯按鈕,當用戶向下滾動時,標題欄和懸浮按鈕消失,讓使用者有更大的空間去閱讀。下面我們就來仿照這個例子設計一個類似的效果。程式碼如下所示:
佈局檔案:
<?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.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0000" />
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
MainActivity 類:
package com.example.test;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView listView;
private List<String> mData = new ArrayList<>();
private ViewHolderAdapter adapter;
//系統認為的最低滑動距離
private float mTouchSlop;
private float mFirstY, mCurrentY;
//描述手指的滑動方向,direction為1表示向上,direction為0表示向下
private int direction;
private boolean mShow = true;
private Toolbar mToolbar;
private ObjectAnimator mAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
listView = (ListView) findViewById(R.id.list_view);
//給 ListView 新增 HeaderView,避免第一個 Item 被 Toolbar 遮擋
View headerView = new View(this);
headerView.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
(int) getResources().getDimension(R.dimen.abc_action_bar_default_height_material)));
listView.addHeaderView(headerView);
for (int i = 0; i < 30; i++) {
mData.add(i + "");
}
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
//獲得系統認為的最低滑動距離
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
listView.setOnTouchListener(mTouchListener);
}
View.OnTouchListener mTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mFirstY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
mCurrentY = event.getY();
//計算手指是向上滑動還是向下滑動
direction = mCurrentY - mFirstY > mTouchSlop ? 0 : 1;
if (direction == 1 && mShow) {//向上滑動
toolbarAnim(1);//隱藏HeaderView
} else if (direction == 0 && !mShow) {//向下滑動
toolbarAnim(0);//顯示HeaderView
}
mShow = !mShow;
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
};
/**
* 控制佈局顯示隱藏的動畫
*
* @param flag
*/
private void toolbarAnim(int flag) {
//開始新動畫之前要先取消掉之前的動畫
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY",
mToolbar.getTranslationY(), flag == 0 ? 0 : -mToolbar.getHeight());
mAnimator.start();
}
}
4.2.3 聊天 ListView
通常我們使用的 ListView 的每一項都具有相同的佈局,所以展現出來的時候,除了資料不同,只要你不隱藏佈局,其他的佈局應該都是類似的。而我們熟知的 QQ、微信等聊天 App,在聊天介面,會展示至少兩種佈局,即收到的訊息和自己傳送的訊息,其實這樣的效果也是通過 ListView 來實現的,下面我們就來模仿一個聊天軟體的聊天列表介面,其效果如下圖所示:
這樣的一個 ListView 與我們平時所使用的 ListView 最大的不同,就是它擁有兩個不同的佈局 —— 收到的佈局和傳送的佈局。要實現這樣的效果,就需要拿 ListView 的Adapter“開刀”。具體程式碼如下所示:
傳送方 Item 的佈局(chat_item_send.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="wrap_content">
<ImageView
android:id="@+id/img_send"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:src="@drawable/img2" />
<TextView
android:id="@+id/txt_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:layout_toLeftOf="@+id/img_send"
android:background="@drawable/chat_send"
android:gravity="center"
android:text="傳送方" />
</RelativeLayout>
接收方 Item 的佈局(chat_item_receive.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="wrap_content">
<ImageView
android:id="@+id/img_receive"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:src="@drawable/img1" />
<TextView
android:id="@+id/txt_receive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/img_receive"
android:background="@drawable/chat_receive"
android:gravity="center"
android:text="接收方" />
</RelativeLayout>
ListView 的介面卡(ViewHolderAdapter):
package com.example.test;
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;
/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {
private List<ChatItemBean> mData;
private LayoutInflater mInflater;
public ViewHolderAdapter(Context context, List<ChatItemBean> mData) {
this.mData = mData;
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 獲得第 position 個 Item 是何種型別
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
return mData.get(position).getType();
}
/**
* 返回不同型別的佈局總數
*
* @return
*/
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
//判斷是否快取
if (convertView == null) {
//根據 getItemViewType 的型別來判斷是哪個佈局
if (getItemViewType(position) == 0) {//接收方——“對方”
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.chat_item_receive, parent, false);
holder.imgHead = (ImageView) convertView.findViewById(R.id.img_receive);
holder.txtContent = (TextView) convertView.findViewById(R.id.txt_receive);
} else {//傳送方——“我”
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.chat_item_send, parent, false);
holder.imgHead = (ImageView) convertView.findViewById(R.id.img_send);
holder.txtContent = (TextView) convertView.findViewById(R.id.txt_send);
}
convertView.setTag(holder);
} else {
//通過 tag 找到快取的佈局
holder = (ViewHolder) convertView.getTag();
}
if (mData != null && mData.size() > 0) {
holder.imgHead.setImageBitmap(mData.get(position).getHead());
holder.txtContent.setText(mData.get(position).getContent());
}
return convertView;
}
public final class ViewHolder {
public ImageView imgHead;
public TextView txtContent;
}
}
MainActivity 的佈局檔案:
<?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">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
MainActivity 類:
package com.example.test;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
List<ChatItemBean> data = new ArrayList<>();
data.add(addChatContent(1, "Hi,你是?!"));
data.add(addChatContent(0, "Hello!"));
data.add(addChatContent(1, "你好"));
data.add(addChatContent(0, "在嗎?"));
data.add(addChatContent(1, "你是哪裡的呢?"));
data.add(addChatContent(0, "你猜"));
listView.setAdapter(new ViewHolderAdapter(this, data));
listView.setDivider(null);
}
/**
* 新增聊天內容
*
* @param type
* @param content
*/
private ChatItemBean addChatContent(int type, String content) {
ChatItemBean chatItemBean = new ChatItemBean();
chatItemBean.setType(type);
chatItemBean.setHead(BitmapFactory.decodeResource(getResources(),
type == 0 ? R.drawable.img1 : R.drawable.img2));
chatItemBean.setContent(content);
return chatItemBean;
}
}
4.2.4 動態改變 ListView 佈局
通常情況下,如果要動態地改變點選 Item 的佈局來打到一個 Focus 的效果,一般有兩種方法,一種是將兩種佈局寫在一起,通過控制佈局的顯示、隱藏,來達到切換佈局的效果;另一種則是在 getView() 的時候,通過判斷來選擇載入不同的佈局。兩種方法各有利弊,關鍵還是看使用的場合。下面就以第二種方法,來演示一下這樣的效果,程式執行後初始效果如下所示,第一個 Item 預設 Focus 狀態。當點選其他 Item 的時候,點選的 Item 變為 Focus 狀態,其他 Item 還原。
(程式初始狀態)
(Focus 改變)
具體實現的程式碼如下所示:
佈局檔案:
<?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">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
ListView 的介面卡(ViewHolderAdapter):
package com.example.test;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.List;
/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {
private List<String> mData;
private Context mContext;
//當前 Item 的位置
private int mCurrentItem;
public ViewHolderAdapter(Context mContext, List<String> mData) {
this.mContext = mContext;
this.mData = mData;
}
public void setCurrentItem(int mCurrentItem) {
this.mCurrentItem = mCurrentItem;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 新增選中後 Item 的佈局
*
* @param i
* @return
*/
private View addFocusView(int i) {
LinearLayout layout = new LinearLayout(mContext);
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(R.drawable.img1);
layout.addView(imageView, new LinearLayout.LayoutParams(200, 200));
layout.setGravity(Gravity.CENTER);
return layout;
}
/**
* 新增正常 Item 的佈局
*
* @param i
* @return
*/
private View addNormalView(int i) {
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.HORIZONTAL);
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(R.drawable.img3);
layout.addView(imageView, new LinearLayout.LayoutParams(200, 200));
TextView textView = new TextView(mContext);
textView.setText(mData.get(i));
layout.addView(textView, new LinearLayout.LayoutParams(200, 200));
return layout;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
//通過判斷當前 CurrentItem 是否是那個點選的 position,就可以動態控制顯示的佈局了
layout.addView(mCurrentItem == position ? addFocusView(position) : addNormalView(position));
return layout;
}
}
MainActivity 類:
package com.example.test;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView listView;
private ViewHolderAdapter adapter;
private List<String> mData = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
for (int i = 0; i < 20; i++) {
mData.add(i + "");
}
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
//重寫 ListView 中 Item 點選的監聽事件,記錄當前點選的 Item 的位置,
//並讓 ListView 重新整理一次以便更新介面的內容
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
adapter.setCurrentItem(position);
adapter.notifyDataSetChanged();
}
});
}
}