Android ListView

Android ListView

專門(mén)用于處理那種內(nèi)容元素很多,手機(jī)屏幕無(wú)法展示出所有內(nèi)容的情況剃氧。ListView可以使用列表的形式來(lái)展示內(nèi)容,超出屏幕部分的內(nèi)容只需要通過(guò)手指滑動(dòng)就可以移動(dòng)到屏幕內(nèi)了阻星。

ListView屬性

設(shè)置分割線

  • android:divider朋鞍,設(shè)置分割線風(fēng)格,可以是顏色妥箕,也可以是圖片滥酥。當(dāng)不需要分割線時(shí),賦值@null即可畦幢。
  • android:dividerHeight坎吻,設(shè)置分割線高度。

滾動(dòng)條設(shè)置

android:scrollbars,通過(guò)該屬性可以設(shè)置滾動(dòng)條狀態(tài)宇葱。不需要滾動(dòng)條時(shí)瘦真,賦值none刊头。

取消Item點(diǎn)擊效果

android:listSelector,賦值為#00000000(color ARGB),或者@android:color/transparent诸尽。

設(shè)置ListView Item顯示位置

默認(rèn)顯示第一個(gè)原杂,調(diào)用
ListView.setSelection(pos)從指定item開(kāi)始顯示。

但是此方法是瞬間完成滾動(dòng)操作弦讽,可以使用以下三種方法實(shí)現(xiàn)平滑滾動(dòng):

  • smoothScrollBy(distance, duration),指定滾動(dòng)距離污尉,distance的正,負(fù)決定滾動(dòng)的方向(正值向上往产,負(fù)值向下)被碗。滾動(dòng)速度有duration決定,即滾動(dòng)時(shí)間仿村。
  • smoothScrollByOffset(offset),方法參數(shù)是指現(xiàn)在顯示的第一個(gè)item視圖的偏移量,負(fù)號(hào)代表向上移動(dòng),正號(hào)代表向下移動(dòng).
  • smoothScrollToPosition(pos),平滑移動(dòng)到指定的position item.

ListView基礎(chǔ)用法

動(dòng)態(tài)修改ListView

在某些情況下锐朴,ListView的數(shù)據(jù)更新了,那么就需要?jiǎng)討B(tài)的修改ListView item的顯示蔼囊》僦荆可以調(diào)用Adapter.notifyDataSetChanged()方法。代碼如下:

mList.add("new");
mAdapter.notifyDataSetChanged();

mList是BaseAdapter的數(shù)據(jù)畏鼓。更新它之后調(diào)用notifyDataSetChanged()更新視圖酱酬。注意調(diào)用notifyDataSetChanged()之后是更新了整個(gè)ListView的所有item視圖,也就是重新繪制了ListView云矫,所以效率有點(diǎn)差膳沽,可以試試單獨(dú)更新某個(gè)item。

通過(guò)add添加新數(shù)據(jù).gif

遍歷item

當(dāng)需要遍歷ListView的item時(shí)让禀,可以使用getFirstVisiblePosition(),getChildAt(),getChildCount()等方法挑社。不過(guò)需要注意:

  • getFirstVisiblePosition(),返回的是當(dāng)前屏幕上顯示的第一個(gè)item(包括不完整的)在所有item中的位置index巡揍。
  • getChildCount()痛阻,返回當(dāng)前屏幕顯示的item數(shù)量。
  • getChildAt()腮敌,返回指定位置的item視圖阱当。指定位置的范圍不能超過(guò)當(dāng)前屏幕顯示的數(shù)量,因?yàn)榉祷氐囊晥D是在當(dāng)前顯示的item中的糜工。

通過(guò)以上三種方法斗这,更進(jìn)一步了解了視圖緩存機(jī)制。ListView并沒(méi)有給所有數(shù)據(jù)項(xiàng)創(chuàng)建item視圖啤斗,只給需要顯示的創(chuàng)建表箭。

item.getTop()

當(dāng)通過(guò)item視圖調(diào)用getTop()時(shí),返回的坐標(biāo)是以item左上角點(diǎn)在以ListView左上角為原點(diǎn)構(gòu)建的坐標(biāo)系的Y坐標(biāo)彼水。

空ListView

當(dāng)列表沒(méi)有數(shù)據(jù)時(shí)凤覆,可以設(shè)置一個(gè)提示用戶的View盯桦。通過(guò)ListView.setEmptyView()來(lái)設(shè)置拥峦。

protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_empty_list_view);
        initView();
    }

    private void initView() {
        mListView = (ListView) findViewById(R.id.id_list_view_empty);
        mEmptyImg = (ImageView) findViewById(R.id.img_empty_view);
        mListView.setEmptyView(mEmptyImg);
        mList = new ArrayList<>();
        mAdapter = new ViewHolderAdapter(mList, this);

        mListView.setAdapter(mAdapter);
    }

    public void btnAddItem(View view) {
        mList.add("new");
        mAdapter.notifyDataSetChanged();
        mListView.smoothScrollToPosition(mList.size() - 1);
    }

布局代碼

<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/id_list_view_empty"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>

    <ImageView
        android:id="@+id/img_empty_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:src="@mipmap/ic_launcher"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="btnAddItem"
        android:text="@string/add_item"/>
</LinearLayout>

效果:

空ListView顯示圖像卖子,通過(guò)add添加新數(shù)據(jù).gif

滑動(dòng)監(jiān)聽(tīng)

OnTouchListener

mListView.setOnTouchListener(new View.OnTouchListener() {
            int position = 0;
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //觸摸時(shí)操作
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //移動(dòng)時(shí)操作
                        break;
                    case MotionEvent.ACTION_UP:
                        //手指離開(kāi)時(shí)操作
                        //用getTop()方法獲取到的坐標(biāo)是相對(duì)于父控件坐標(biāo)系的坐標(biāo).
                        position = mListView.getChildAt(0).getTop();
                        mView.setText("顯示的第一個(gè)Item在ListView中的坐標(biāo):" + position);
                        break;
                }
                return false;
            }
        });
TouchListener略号,底部TextView顯示第一個(gè)item Y軸坐標(biāo).gif

OnScrollListener

mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
            int pos = 0;
            int lastVisibleItemPos = 0;
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                switch(scrollState) {
                    case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                        //滑動(dòng)停止時(shí)
                        pos = mListView.getChildAt(0).getTop();
                        mTextView.setText("顯示的第一個(gè)Item在ListView中的坐標(biāo):" + pos);
                        break;
                    case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                        //正在滾動(dòng)時(shí)
                        mTextView.setText("正在滑動(dòng)...");
                        break;
                    case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                        //手指拋動(dòng)時(shí),手指用力滑動(dòng)后,手指離開(kāi)屏幕ListView由于慣性繼續(xù)滑動(dòng)
                        mTextView.setText("漂移中...");
                        break;
                    default:
                        break;
                }
            }
            //滾動(dòng)時(shí)一直調(diào)用
            //firstVisibleItem當(dāng)前顯示的第一個(gè)item在所有item中的index
            //visibleItemCount當(dāng)前顯示的item數(shù)量
            //totalItemCount所有item 數(shù)量
            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                //判斷滑動(dòng)方向
                if(firstVisibleItem > lastVisibleItemPos) {
                    //判斷是否滾動(dòng)到最后一項(xiàng)
                    if(firstVisibleItem + visibleItemCount == totalItemCount
                            && totalItemCount > 0) {
                        Log.d(TAG, "滾動(dòng)到了最后一個(gè)item");
                    }
                    Log.d(TAG, "上滑");
                }else if(firstVisibleItem < lastVisibleItemPos) {
                    Log.d(TAG, "下滑");
                }
                lastVisibleItemPos = firstVisibleItem;
            }
        });
OnSrollListener洋闽,底部TextViw根據(jù)不同滑動(dòng)狀態(tài)顯示不同的內(nèi)容玄柠,停止滑動(dòng)是顯示第一個(gè)item Y軸坐標(biāo).gif

ListView點(diǎn)擊事件

mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//                lastScrollY = mListView.getScrollY();
//                Log.d(TAG, lastScrollY + "");
                lastScrollY = getScrollY();
                Log.d(TAG, lastScrollY + "");
                mTextView.setText(String.valueOf(lastScrollY));
                Intent intent = new Intent(RestoreListView.this, JumpActivity.class);
                startActivity(intent);
                mListView.setSelection(0);
            }
        });

ListView基礎(chǔ)優(yōu)化(ViewHolder)

優(yōu)化的原理是基于ListView的RecycleBin機(jī)制

可以參考Android ListView工作原理完全解析,帶你從源碼的角度徹底理解

ViewHolder的優(yōu)化就是利用了ListView 的視圖緩沖機(jī)制诫舅,一般情況下代碼都差不多羽利,關(guān)鍵在于BaseAdapter。所以可以參照以下模版來(lái)寫(xiě)刊懈。

BaseAdapter代碼

public class ViewHolderAdapter extends BaseAdapter {
    private List<String> mData;
    private LayoutInflater mInflater;

    public ViewHolderAdapter(List<String> data, Context context) {
        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 View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        //判斷是否緩存
        if (convertView == null) {
            holder = new ViewHolder();
            //通過(guò)LayoutInflater實(shí)例化布局
            convertView = mInflater.inflate(R.layout.viewholder_item, null);
            holder.img = (ImageView) convertView.findViewById(R.id.img_view_holder);
            holder.title = (TextView) convertView.findViewById(R.id.tv_view_holder);
            convertView.setTag(holder);
        } else {
            //通過(guò)Tag找到緩存布局
            holder = (ViewHolder) convertView.getTag();
        }
        //設(shè)置布局中空間要顯示的視圖
        holder.img.setBackgroundResource(R.mipmap.ic_launcher);
        holder.title.setText(mData.get(position));
        return convertView;
    }

    public class ViewHolder {
        public ImageView img;
        public TextView title;
    }
}

代碼分析,在上面的模版中看到覆寫(xiě)了幾個(gè)方法:

  • getCount(),返回item數(shù)量
  • getItem(int position),返回指定位置的item
  • getItemId(int position),返回指定位置item的行號(hào)
  • getView()这弧,返回顯示的item view

整個(gè)優(yōu)化的關(guān)鍵在于ViewHolder模式充分利用了ListView的視圖緩存機(jī)制,避免每一次調(diào)用getView()時(shí)都去實(shí)例化item布局俏讹,并且調(diào)用findViewById()實(shí)例化控件。

ListView進(jìn)階用法

ListView位置恢復(fù)

有兩種方法畜吊,第一種是在監(jiān)聽(tīng)ListView時(shí),通過(guò)ListView.getScrollY()方法獲取最終滾動(dòng)的Y坐標(biāo)殉疼,然后調(diào)用smoothScrollBy()方法恢復(fù)礼预;第二種是通過(guò)記錄當(dāng)前ListView顯示的第一個(gè)Item的index柒巫,調(diào)用smoothToPosition()來(lái)恢復(fù)城豁。第一中方法完全不可行漩绵,因?yàn)榈玫降挠肋h(yuǎn)都是0(通過(guò)ListView調(diào)用getScrollY()方法,得到的坐標(biāo)是ListView左上角在以ListView父視圖左上角為原點(diǎn)構(gòu)建的坐標(biāo)系中的Y軸坐標(biāo)瘩燥,所以一直是0)。第二種方法不精確。

下面采用以下方法:

public int getScrollY() {
        View child = mListView.getChildAt(0);
        if(child == null) {
            return 0;
        }
        int top = -child.getTop();
        int firstVisibleItemPos = mListView.getFirstVisiblePosition();
        return top + firstVisibleItemPos * child.getHeight();
    }

當(dāng)然這是一種理想狀態(tài)企软,默認(rèn)為所有item的高度都相等。

恢復(fù)ListView

mListView.post(new Runnable() {
            @Override
            public void run() {
                mListView.smoothScrollBy(scrolledY, 0);
            }
        });
ListView位置恢復(fù).gif

動(dòng)態(tài)改變ListView布局

有兩種方案:一種是兩種布局都寫(xiě)苇倡,通過(guò)控制布局的顯示隱藏來(lái)達(dá)到切換布局的效果胜嗓;另一種是在getView()時(shí)通過(guò)判斷來(lái)選擇加載不同的布局寥粹。以下主要以第二種方案來(lái)實(shí)現(xiàn)媚狰。

關(guān)鍵思想,要獲取不同的布局肯定要覆寫(xiě)getView()方法。而ListView提供了兩種方法,封裝好了布局的判斷:

  • getItemViewType()赋兵,獲取指定位置item的布局類型拯田。
  • getViewTypeCount()帕膜,獲取不同布局的總數(shù)张弛。

關(guān)鍵代碼

@Override
    public int getItemViewType(int position) {
        ChatItemListViewBean bean = mData.get(position);

        return bean.getType();
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if(convertView == null) {
            holder = new ViewHolder();
            if(getItemViewType(position) == 0) {
                convertView = mInflater.inflate(R.layout.chat_item_in, null);
                holder.mIcon = (ImageView) convertView.findViewById(R.id.id_img_chat_item_in);
                holder.mTv = (TextView) convertView.findViewById(R.id.id_tv_chat_item_in);
            }else {
                convertView = mInflater.inflate(R.layout.chat_item_out, null);
                holder.mIcon = (ImageView) convertView.findViewById(R.id.id_img_chat_item_out);
                holder.mTv = (TextView) convertView.findViewById(R.id.id_tv_chat_item_out);
            }
            convertView.setTag(holder);
        }else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.mIcon.setImageBitmap(mData.get(position).getIcon());
        holder.mTv.setText(mData.get(position).getText());
        return convertView;
    }

    public class ViewHolder{
        public ImageView mIcon;
        public TextView mTv;
    }

效果:

聊天.png

雜技

獲取ActionBar高度

getResources().getDimensionPixelOffset(
                    android.support.v7.appcompat.R.dimen.abc_action_bar_default_height_material
            )

android.support.v7.appcompat.R.dimen.abc_action_bar_default_height_material是V7包內(nèi)的actionBar height 的資源id。

獲取最小滑動(dòng)距離

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

問(wèn)題

  • 如何實(shí)現(xiàn)單獨(dú)更新某個(gè)指定的item數(shù)據(jù)
  • View.post()
  • 如何實(shí)現(xiàn)彈性ListView
  • 如何自動(dòng)隱藏和顯示Toolbar
  • View.getTranslationY()獲取到的是什么坐標(biāo)
  • ListView的適配器御吞,BaseAdapter揍诽,ArrayAdapter...

參考

判斷ListView的第一個(gè)item是否完全顯示

記錄和恢復(fù)ListView的滑動(dòng)位置

對(duì)于getScrollX() 的理解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饵筑,一起剝皮案震驚了整個(gè)濱河市同窘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鹰椒,老刑警劉巖夺饲,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異听哭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)塘雳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)欢唾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人粉捻,你說(shuō)我怎么就攤上這事礁遣∮” “怎么了寞埠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵麦乞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)哨查,這世上最難降的妖魔是什么腐宋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任翅阵,我火速辦了婚禮,結(jié)果婚禮上厕氨,老公的妹妹穿的比我還像新娘互捌。我一直安慰自己,他們只是感情好撕贞,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布域蜗。 她就那樣靜靜地躺著湘换,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哥谷。 梳的紋絲不亂的頭發(fā)上岸夯,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音们妥,去河邊找鬼猜扮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛监婶,可吹牛的內(nèi)容都是我干的破镰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼压储,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鲜漩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起集惋,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤孕似,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后刮刑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體喉祭,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年雷绢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泛烙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翘紊,死狀恐怖蔽氨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤鹉究,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布宇立,位于F島的核電站,受9級(jí)特大地震影響自赔,放射性物質(zhì)發(fā)生泄漏妈嘹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一绍妨、第九天 我趴在偏房一處隱蔽的房頂上張望润脸。 院中可真熱鬧,春花似錦他去、人聲如沸毙驯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)尔苦。三九已至涩馆,卻和暖如春行施,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魂那。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工蛾号, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涯雅。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓鲜结,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親活逆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子精刷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,756評(píng)論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,727評(píng)論 22 665
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 6,365評(píng)論 0 17
  • 聽(tīng)說(shuō)蔗候,你離開(kāi)了怒允。 聽(tīng)說(shuō),你回到了家鄉(xiāng)锈遥,找到了很好的工作纫事。 那么我是不是終于該把你徹底忘記了呢? 離開(kāi)了這座城市所灸,你...
    七月暮閱讀 297評(píng)論 0 5
  • 17年的4月13日我注冊(cè)了簡(jiǎn)書(shū)爬立,想記錄自己的生活也想分享生活中的感動(dòng)給大家钾唬。其實(shí)寫(xiě)點(diǎn)東西實(shí)在老公的鼓勵(lì)下開(kāi)始的,...
    愛(ài)莫能柱閱讀 520評(píng)論 0 0