Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ListView常用拓展(Android群英傳)

ListView常用拓展(Android群英傳)

編輯:關於Android編程

ListView雖然使用廣泛,但系統原生的ListView顯然是不能滿足用戶在審美、功能上不斷提高的需求。不過也不要緊,Android完全可以定制化,讓我們非常方便地對原生ListView進行拓展、修改。於是,在開發者的創新下,ListView越來越豐富多彩,各種各樣的基於原生ListView的拓展讓人目不暇接。下面來看幾個常用的ListView拓展。

具有彈性的ListView

Android默認的ListView在滾動到頂端或者底端的時候,並沒有很好的提示。在Android5.X中,Google為這樣的行為只添加了一個半月形的陰影效果,如下圖所示。


頂部陰影效果

  而在iOS系統中,列表都是具有彈性效果的,即滾動到底端或者頂端後會繼續往下或者往上滑動一段距離。不得不說,這樣的設計的確更加的友好,雖然不知道Google為什麼不模仿這樣的設計,但我們可以自己修改ListView,讓ListView也可以“彈性十足”。
  網上有很多通過重寫ListView來實現彈性效果的方法,比如增加HeaderView或者使用ScrollView進行嵌套,方法有很多,不過這裡可以使用一種非常簡單的方法來實現這個效果。雖然不如那些方法可定制化高、效果豐富,但主要目的是讓讀者朋友們學會如何從源代碼中找到問題的解決辦法。
  我們在查看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具有彈性了!所以,既然我們不知道為什麼Google不采用這樣的修改,那我們就自己來修改一下吧。重寫這個方法,並將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了。效果如下圖所示。


彈性ListView效果

  當然,為了能夠滿足多分辨率的需求,我們可以在修改maxOverScrollY值的時候,可以通過屏幕的density來計算具體的值,讓不同分辨率的彈性距離基本一致,代碼如下所示。

 

private void initView() {
    DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
    float density = metrics.density;
    mMaxOverDistance = (int) (density * mMaxOverDistance);
}

自動顯示、隱藏布局的ListView

相信通過Google+的朋友應該非常熟悉這樣一個效果:當我們在ListView上滑動的時候,頂部的ActionBar或者Toolbar就會相應的隱藏或者顯示。這樣的效果一出現,各種App競相模仿,不得不說,Google的應用一直都是Android設計的風向標。
  大家可以發現,在滾動前界面上加載了上方的標題欄和右下角的懸浮編輯按鈕,如下圖所示。


滾動前界面

  當用戶向下滾動時,標題欄和懸浮按鈕消失了,讓用戶有更大的空間去閱讀,如下圖所示。

滾動後界面

  下面我們就來仿照這個例子設計一個類似的效果。
  我們知道,讓一個布局顯示或者隱藏並帶有動畫效果,可以通過屬性動畫來很方便地實現,所以這個效果的關鍵就在於如何獲得ListView的各種滑動事件。所以借助View的OnTouchListener接口來監聽ListView的滑動,通過比較與上次坐標的大小,來判斷滑動的方向,並通過滑動的方向來判斷是否需要顯示或隱藏對應的布局。在開始判斷滑動事件之前,我們還要做一些准備工作,首先需要給ListView增加一個HeaderView,避免第一個Item被Toolbar遮擋,代碼如下所示。

 

View header = new View(this);
header.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
        (int) getResources().getDimension(R.dimen.abc_action_bar_default_height_material)));
mListView.addHeaderView(header);

在代碼中,通過使用abc_action_bar_default_height_material屬性獲取系統Actionbar的高度,並設置給HeaderView。另外,定義一個mTouchSlop變量來獲取系統認為的最低滑動距離,即超過這個距離的移動,系統就將其定義為滑動狀態了,對這個值得獲取非常簡單,代碼如下所示。

mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();

有了前面的准備工作,下面我們就可以判斷滑動的事件了,關鍵代碼如下所示。

@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();
            if (mCurrentY - mFirstY > mTouchSlop) {
                // down
                if (mShow) {
                    toolbarAnim(0);
                }
                mShow = !mShow;
            } else if (mCurrentY - mFirstY < mTouchSlop) {
                // up
                if (mShow) {
                    toolbarAnim(1);
                }
                mShow = !mShow;
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return false;

代碼邏輯非常簡單,只是通過滑動點的坐標改變大小,來判斷移動的方向,並根據移動方向來執行不同的動畫效果。
  有了前面的分析,實現這樣一個效果就非常簡單了,最後加上控制布局顯示隱藏的動畫,如下所示。

private void toolbarAnim(int flag) {
    if (mAnimator != null && mAnimator.isRunning()) {
        mAnimator.cancel();
    }
    if (flag == 0) {
        mAnimator = ObjectAnimator.ofFloat(mToolbar,
                "translationY", mToolbar.getTranslationY(), 0);
    } else {
        mAnimator = ObjectAnimator.ofFloat(mToolbar,
                "translationY", mToolbar.getTranslationY(),
                -mToolbar.getHeight());
    }
    mAnimator.start();
}

動畫也是最簡單的位移屬性動畫。不過這裡需要說一點題外話,這裡使用了Toorbar這樣一個新控件,Google已經推薦它來逐漸取代ActionBar了,因為它更加靈活。但是在使用的時候,一定要注意使用的theme一定是要NoActionBar的,不然會引起沖突。同時,不要忘記引入編譯,代碼如下。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.4.0'
}

運行程序後初始狀態如下圖所示,Toorbar顯示在最上方。


Toolbar顯示

  當向上滑動時,Toolbar隱藏,如下圖所示。

Toolbar隱藏

  再向下滑動時,Toolbar顯示。

 

聊天ListView

通常我們使用的ListView的每一項都具有相同的布局,所以展現出來的時候,除了數據不同,只要你不隱藏布局,其他的布局應該都是類似的。而我們熟知的QQ、微信等聊天App,在聊天界面,會展示至少兩種布局,即收到的消息和自己發送的消息,其實這樣的效果也是通過ListView來實現的,下面我們就來模仿一個聊天軟件的聊天列表界面,其效果如下圖所示。


聊天界面ListView

  這樣一個ListView與我們平時所使用的ListView最大的不同,就是它擁有兩個不同的布局——收到的布局和發送的布局。要實現這樣的效果,就需要拿ListView的Adapter“開刀”。
  在定義BaseAdapter的時候,需要去重寫它的getView()方法,這個方法就是用來獲取布局的,那麼只需要在獲取布局的時候,判斷一下該獲取哪一種布局就可以了。而且,ListView在設計的時候就已經考慮到了這種情況,所以它提供了兩個方法,代碼如下所示。

 

@Override
public int getItemViewType(int position) {
    return super.getItemViewType(position);
}
@Override
public int getViewTypeCount() {
    return super.getViewTypeCount();
}

getItemViewType()方法用來返回第position個Item是何種類型,而getViewTypeCount()方法用來返回不同布局的總數。通過這兩個方法,再結合getView()方法,就可以很輕松地設計出上面的聊天布局了。
  首先來實現兩個布局——chat_item_itemin和chat_item_itemout。布局大同小異,只是方向上有區別。需要注意的是,顯示聊天信息內容的TextView使用了9patch的圖片,這種圖片格式是Android中用來拉伸圖片的,你可以把它想象成在某些方向上拉伸卻不會失真、形變的圖片就可以了,布局代碼如下所示。由於in和out界面內容只是方向上的區別,這裡只貼出一個布局的代碼。




    

    

同時,為了封裝下聊天內容,便於在Adapter中獲取數據信息,我們封裝了一個Bean來保存聊天信息,代碼如下所示。

import android.graphics.Bitmap;

/*********************************************
* author: Blankj on 2016/7/25 14:01
 * blog:   http://blankj.com
* e-mail: [email protected]
*********************************************/
public class ChatListViewBean {

    private int type;
    private String text;
    private Bitmap icon;

    public ChatListViewBean() {
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public Bitmap getIcon() {
        return icon;
    }

    public void setIcon(Bitmap icon) {
        this.icon = icon;
    }
}

非常簡單,我們只是聲明了需要的信息並提供了get和set方法。
  接下來,需要來完成最重要的BaseAdapter了,同樣使用ViewHolder模式來提高ListView的效率,並在getView()方法中進行布局類型的判斷,從而確定使用哪種布局,代碼如下所示。

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;

/*********************************************
 * author:  Blankj on 2016/7/25 14:01
 * blog:    http://blankj.com
 * e-mail:  [email protected]
*********************************************/
public class ChatListViewAdapter extends BaseAdapter {

    private List mData;
    private LayoutInflater mInflater;

    public ChatListViewAdapter(Context context, List data) {
        this.mData = data;
        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 int getItemViewType(int position) {
        ChatListViewBean bean = mData.get(position);
        return bean.getType();
    }

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        if (convertView == null) {
            viewHolder = new ViewHolder();
            if (getItemViewType(position) == 0) {
                convertView = mInflater.inflate(R.layout.chat_item_itemin, null);
                viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon_in);
                viewHolder.text = (TextView) convertView.findViewById(R.id.text_in);
            } else {
                convertView = mInflater.inflate(R.layout.chat_item_itemout, null);
                viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon_out);
                viewHolder.text = (TextView) convertView.findViewById(R.id.text_out);
            }
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.icon.setImageBitmap(mData.get(position).getIcon());
        viewHolder.text.setText(mData.get(position).getText());
        return convertView;
    }

    public final class ViewHolder {
        public ImageView icon;
        public TextView text;
    }
}

在以上代碼中,通過在getView()中判斷getItemType(position)的值來決定具體實例化哪一個布局,從而實現在一個ListView中多個布局內容的添加。最後,在測試的Activity裡面添加了一些測試代碼,來測試這個布局。

import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

/*********************************************
* author: Blankj on 2016/7/25 13:30
 * blog:   http://blankj.com
* e-mail: [email protected]
*********************************************/
public class ChatListViewTest extends Activity {

    private ListView mListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
        mListView = (ListView) findViewById(R.id.lv_chat);

        ChatListViewBean bean1 = new ChatListViewBean();
        bean1.setType(0);
        bean1.setIcon(BitmapFactory.decodeResource(getResources(),
                R.drawable.in_icon));
        bean1.setText("Hello how are you?");

        ChatListViewBean bean2 = new ChatListViewBean();
        bean2.setType(1);
        bean2.setIcon(BitmapFactory.decodeResource(getResources(),
                R.drawable.out_icon));
        bean2.setText("Fine thank you, and you?");

        ChatListViewBean bean3 = new ChatListViewBean();
        bean3.setType(0);
        bean3.setIcon(BitmapFactory.decodeResource(getResources(),
                R.drawable.in_icon));
        bean3.setText("I am fine, too");

        ChatListViewBean bean4 = new ChatListViewBean();
        bean4.setType(1);
        bean4.setIcon(BitmapFactory.decodeResource(getResources(),
                R.drawable.out_icon));
        bean4.setText("Bye bye");

        ChatListViewBean bean5 = new ChatListViewBean();
        bean5.setType(0);
        bean5.setIcon(BitmapFactory.decodeResource(getResources(),
                R.drawable.in_icon));
        bean5.setText("See you");

        List data = new ArrayList();
        data.add(bean1);
        data.add(bean2);
        data.add(bean3);
        data.add(bean4);
        data.add(bean5);
        mListView.setAdapter(new ChatListViewAdapter(this, data));
    }
}

在測試代碼中,簡單地添加了一些模擬的聊天內容,並將信息封裝到設置的Bean對象中,最後運行程序,即可得到之前所示的聊天效果界面。

動態改變ListView布局

通常情況下,如果要動態地改變點擊Item的布局來達到一個Focus的效果,一般有兩種方法。一種是將兩種布局寫在一起,通過控制布局的顯示、隱藏,來達到切換布局的效果;另一種則是在getView()的時候,通過判斷來選擇加載不同的布局。兩種方法各有利弊,關鍵還是看使用的場合。下面就以第二種方式,來演示一下這樣的效果,程序運行後初始效果下圖所示,第一個Item為默認Focus狀態。


程序初始狀態

  當點擊其他Item的時候,點擊的Item變為Focus狀態,其他Item還原,效果如下圖所示。

Focus改變

  該效果實現的關鍵還是在於BaseAdapter。在這個實例中,通過如下所示的兩個方法來給Item設置兩種不同的布局——Focus和NZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcm1hbKGjDQo8cD4mbmJzcDs8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> private View addFocusView(int i) { ImageView iv = new ImageView(mContext); iv.setImageResource(R.mipmap.ic_launcher); return iv; } private View addNormalView(int i) { LinearLayout layout = new LinearLayout(mContext); layout.setOrientation(LinearLayout.HORIZONTAL); ImageView iv = new ImageView(mContext); iv.setImageResource(R.drawable.in_icon); layout.addView(iv, new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); TextView tv = new TextView(mContext); tv.setText(mData.get(i)); layout.addView(tv, new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); layout.setGravity(Gravity.CENTER); return layout; }

在這兩個方法中,可以根據Item位置的不同來設置不同的顯示圖片等信息,但這裡為了方便,就統一只顯示一張圖片。
  下面回到BaseAdapter,在getView()方法中,通過判斷點擊的位置來改變相應的視圖,代碼如下所示。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    LinearLayout layout = new LinearLayout(mContext);
    layout.setOrientation(LinearLayout.VERTICAL);
    if (mCurrentItem == position) {
        layout.addView(addFocusView(position));
    } else {
        layout.addView(addNormalView(position));
    }
    return layout;
}

在以上代碼中,通過判斷當前CurrentItem是否是點擊的那個position,就可以動態控制顯示的布局了。當然,僅僅這樣是不夠的,因為getView()是在初始化的時候調用,後面再點擊Item的時候,並沒有再次調用getView()。所以,必須要讓ListView在點擊後,再刷新一次。於是我們請出了notifyDataSetChanged()方法來幫助實現刷新布局的功能,代碼如下所示。

mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view,
                                    int position, long id) {
                mAdapter.setCurrentItem(position);
                mAdapter.notifyDataSetChanged();
            }
        });

項目地址→ListViewExpandation


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved