1. 程式人生 > >閱讀徐宜生《Android群英傳》的筆記——第4章 ListView 使用技巧——剩下部分

閱讀徐宜生《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 改變
(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();
            }
        });
    }

}