zoukankan      html  css  js  c++  java
  • Android群英传笔记——第四章:ListView使用技巧

    Android群英传笔记——第四章:ListView使用技巧


    近期也是比較迷茫。可是有一点点还是要坚持的,就是学习了。近期离职了,今天也是继续温习第四章ListView,也拖了事实上也挺久的了,listview可谓是老牌大将了,非常多的应用场景都要使用它,他也是我们用得最多的控件之中的一个了,尽管如今出来了一个RecyclerView,可是ListView的地位一时半会儿还是撼动不了的。这就促使我们更加应该去把他掌握了

    一.Listview经常使用优化技巧

    我们一步步来把ListView学习好

    1.使用ViewHolder模式提高效率

    ViewHolder模式是提高ListView效率的一个非常重要的方法,他充分利用了ListView的视图缓存机制,避免每次调用getView()的时候去通过findViewById()实例化控件,有人測试结果效率要高百分之五十。我们仅仅须要在自己定义的Adapter里面定义一个内部类ViewHolder即可,代码例如以下:

        public final class ViewHolder{
            public ImageView img;
            public TextView tv;
        }

    然后我们在getView的时候复用即可了,我们来看一下完整的Adapter

    package com.lgl.listviewdemo;
    
    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;
    
    /**
     * 自己定义Adapter
     * Created by LGL on 2016/3/10.
     */
    public class MyAdapter extends BaseAdapter {
    
        private List<String> mData;
        private LayoutInflater mInflater;
    
        //构造方法
        public MyAdapter(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 viewHolder = null;
            //推断是否有缓存
            if (convertView == null) {
                viewHolder = new ViewHolder();
                //通过LayoutInflater去实例化布局
                convertView = mInflater.inflate(R.layout.item, null);
                viewHolder.img = (ImageView) convertView.findViewById(R.id.img);
                viewHolder.tv = (TextView) convertView.findViewById(R.id.tv);
                convertView.setTag(viewHolder);
            } else {
                //通过TAG找到缓存的布局
                viewHolder = (ViewHolder) convertView.getTag();
            }
            //设置布局中要显示的东西‘
            viewHolder.tv.setText(mData.get(position));
            return convertView;
        }
    
        public final class ViewHolder {
            public ImageView img;
            public TextView tv;
        }
    }
    

    假设我们要使用的话。我们就能够直接使用adapter了

    package com.lgl.listviewdemo;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.widget.ListView;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MainActivity extends AppCompatActivity {
    
        private ListView listview;
        private MyAdapter myAdapter;
        private List<String> list = new ArrayList<String>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
    
            listview = (ListView) findViewById(R.id.listview);
    
            for (int i = 0; i < 20; i++) {
                list.add("第" + i + "张");
            }
            myAdapter = new MyAdapter(this, list);
            listview.setAdapter(myAdapter);
        }
    }
    

    我们执行一下

    这里写图片描写叙述

    OK我们继续

    2.设置项目间切割栏、

    ListView在各个项目之中,能够通过切割线进行区分的,系统也提供了两个属性来设置item间的颜色和高度

    android:dividerHeight="10dp"
    android:divider="@android:color/holo_blue_bright"

    我们来执行一下

    这里写图片描写叙述

    可是假设你是须要去掉这个切割线的话,事实上也好办

     android:divider="@null"

    看图

    这里写图片描写叙述

    3.隐藏listview的滚动栏

    这个看需求吧,尽管说实话,我是非常不喜欢这个滚动栏的,可是不可否认他还是须要用来显示运行进度的。去掉的方法也非常easy,加一条属性就能够了

     android:scrollbars="none"

    4.取消ListView的item点击效果

    这里写图片描写叙述

    先来看一下点击的效果。是有一点点灰色的点击效果,我们把他给去掉,加一个点击反馈颜色透明的属性就能够了

     android:listSelector="@android:color/transparent"

    这里;你也能够直接填#00000000

    5.设置ListView须要显示在第几项

    ListView默认是从上到下的,我们能够觉得的去设置他要显示的item

    listview.setSelection(15);

    这个就有点相似于平滑的效果了

    listview.smoothScrollBy(1,15);
    listview.smoothScrollByOffset(15);
    listview.smoothScrollToPosition(15);

    6.动态改动ListView

    这个就非经常常使用了,我们在某一个场景的时候须要对listview添加数据,我们应该怎么样做尼?我们在xml里新添加一个button

    <?

    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/listview" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <Button android:id="@+id/add_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="加入数据" /> </LinearLayout>

    然后我们的点击事件就能够这样写了

     @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.add_data:
                    list.add("我是新添加的数据");
                    //通知刷新
                    myAdapter.notifyDataSetChanged();
                    break;
            }
        }

    来看看效果吧

    这里写图片描写叙述

    7.遍历全部的item

    ListView作为一个ViewGroup,他提供了非常多操纵子View的方法。最经常使用的就是getChilaAt()来获取View了

    for (int j = 0; j < listview.getChildCount();j++){
                View v = listview.getChildAt(12);
            }
    

    8.处理空ListView

    有时候我们获取一条数据的时候,他须要显示列表,有的可能是空,这种话,我们就得处理一下了。我们在布局中新加一个textview

     <TextView
            android:id="@+id/tv_null"
            android:text="没有数据"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    仅仅要在代码中设置假设是空数据的话就载入这个布局就能够

     listview.setEmptyView(findViewById(R.id.tv_null));

    这种话,假设ListView为空数据的时候就会设置这句话了

    9.ListView滑动监听

    ListView的滑动监听可是ListView的非常重要的一个技巧,非常多应用场景须要重写ListView的事实上都要在滑动监听上下非常大的功夫,通过推断滑动事件来处理不同的逻辑这是非常有必要的,开发人员通常还须要GestureDetector手势识别。VelocityTracker滑动速度检測来辅助监听,这里介绍两种监听方法,一种是OnTouchListener,还有一中是OnScrollListener

    -1. OnTouchListener

    OnTouchListener是View的监听事件。包含ACTION_DOWN,UP,MOVE等,通过坐标的改变老发生不同的逻辑

    listview.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent) {
                    switch (motionEvent.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            //触摸时操作
                            break;
                        case MotionEvent.ACTION_MOVE:
                            //移动时操作
                            break;
                        case MotionEvent.ACTION_UP:
                            //离开时操作
                            break;
                    }
                    return true;
                }
            });

    -2 OnScrollListener

    OnScrollListener是AbsListView的监听事件,他封装了非常多ListView的相关信息,所以用起来非常灵活

     listview.setOnScrollListener(new AbsListView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(AbsListView absListView, int i) {
                                   switch (i) {
                        case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                            //滚动停止
                            break;
                        case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                            //正在滚动
                            break;
                        case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                            //手指抛动时,即手指用力滑动的时候
                            break;
                    }
                }
    
                @Override
                public void onScroll(AbsListView absListView, int i, int i1, int i2) {
                        //滚动的时候一直在调用
                }
            });

    OnScrollListener中有两个回调方法onScrollStateChanged()和onScroll。我们先来看看onScrollStateChanged吧,这种方法的參数i来决定其回调的次数,有三种模式

    • OnScrollListener.SCROLL_STATE_IDLE://滚动停止
    • OnScrollListener.SCROLL_STATE_TOUCH_SCROLL://正在滚动
    • OnScrollListener.SCROLL_STATE_FLING://手指抛动时,即手指用力滑动的时候

    当用户没有做手指抛动的动作时,这种方法仅仅会调用2次。否则就调用三次,区别就在于手指抛动的这个状态。通常情况下,我们会在这种方法中通过不同的状态来标注一些FLAG,我们来看下onScrill()这种方法

                    /**
                     * firstVisibleItem:表示在现时屏幕第一个ListItem(部分显示的ListItem也算)在整个ListView的位置(下标从0開始)
                     * visibleItemCount:表示在现时屏幕能够见到的ListItem(部分显示的ListItem也算)总数
                     * totalItemCount:表示ListView的ListItem总数
                     * listView.getFirstVisiblePosition()表示在现时屏幕第一个ListItem(第一个ListItem部分显示也算)在整个ListView的位置(下标从0開始)
                     * listView.getLastVisiblePosition()表示在现时屏幕最后一个ListItem(最后ListItem要全然显示出来才算)在整个ListView的位置(下标从0開始)
                     */
    

    这里有一点要注意的是。当前能看到的item数也包含没有显示全然的。这样我们就又能Get到一些技巧了

     if(i+i1 == i2 &&i2>0){
         //滚动到最后一行
         }

    再比方监听滑动放心

     int i3 = i;
     if(i>i3){
         //上滑
      }else if(i<i3){
         //下滑
      }

    通过一个成员变量来记住上一个可视item就知道滑动的方法了,当然。ListView还提供了非常多获取位置信息的方法

     //获取可视区域内最后一个item的id
    listview.getLastVisiblePosition();
     //获取可视区域内第一个item的id
     listview.getFirstVisiblePosition();

    二.ListView经常使用扩展

    尽管ListView应用非常广泛,可是毕竟是一个显示的东西。扩展性肯定要的,我们接下来说几种常见的扩展

    1.具有弹性的ListView

    Android默认滑动到顶部或者底部仅仅会有一个阴影,而在5.X之后改变成了半圆的阴影

    这里写图片描写叙述

    而在IOS上,列表是具有弹性的,即滚动到顶部或者底部,会再滚动一段距离,这种设计感觉还是挺友好的,我们也来模仿一下

    我们在查看ListView的源代码的时候会发现一个控制滑动到边缘的处理方法

    @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, maxOverScrollY, isTouchEvent);
        }

    我們能够看到这样一个參数maxOverScrollY。就是他负责控制滑动的个数的,默认是0,我们重写ListView

    package com.lgl.listviewdemo;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.DisplayMetrics;
    import android.widget.ListView;
    
    /**
     * 弹性ListView
     * Created by lgl on 16/3/20.
     */
    public class ListViewScroll extends ListView {
    
        private int mMaxOverdistance ;
    
        public ListViewScroll(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            //通过分辨率来调节滑动尺度
            DisplayMetrics metrics = context.getResources().getDisplayMetrics();
            float density = metrics.density;
            mMaxOverdistance = (int)(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, mMaxOverdistance, maxOverScrollY, isTouchEvent);
        }
    }
    

    2.自己主动显示,隐藏布局的ListView

    相信看过Google最新的应用,或者使用了MD风格的应用都知道,列表滑动的时候actionbar能够依据状态显示或者隐藏,

    这一段作者写的非常乱

    这要是讲一下大概,须要使用ToolsBar,然后一个listview

    <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary" />

    不具体记了。在我讲MD风格的时候会具体介绍的

    3.聊天ListView

    相信大家都看过聊天的ListView吧,这个主要是adapter做手脚,事实上设计者早就想到了这种方法,所以在继承BaseAdapter的时候还须要重写两个方法

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

    我们这里大致的了解一个思路。我们能够设置一个tag来识别不同的方向。先写两个item,各自是左边的和右边的布局,然后,我们再来写个实体类Bean

    Bean

    package com.lgl.listviewdemo;
    
    import android.graphics.Bitmap;
    
    /**
     * 实体类
     * Created by lgl on 16/3/20.
     */
    public class Bean {
    
        private int type;
        private String text;
        private Bitmap icon;
    
        public Bean() {
        }
    
        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;
        }
    }
    

    如今就能够编写我们的Adapter了

    SpeakAdapter

    package com.lgl.listviewdemo;
    
    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 lgl on 16/3/20.
     */
    public class SpeakAdapter extends BaseAdapter{
    
        private List<Bean>mData;
        private LayoutInflater mInflater;
    
        //构造方法
        public SpeakAdapter(Context context,List<Bean>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 viewHolder;
            if(convertView == null){
                //区分左右
                if(getItemViewType(position) == 0){
                    viewHolder = new ViewHolder();
                    convertView = mInflater.inflate(R.layout.left_item,null);
                    viewHolder.icon = (ImageView) convertView.findViewById(R.id.left_icon);
                    viewHolder.text = (TextView) convertView.findViewById(R.id.tv_left);
                }else{
                    viewHolder = new ViewHolder();
                    convertView = mInflater.inflate(R.layout.right_item,null);
                    viewHolder.icon = (ImageView) convertView.findViewById(R.id.right_icon);
                    viewHolder.text = (TextView) convertView.findViewById(R.id.tv_right);
                }
                convertView.setTag(viewHolder);
            }else{
                viewHolder = (ViewHolder) convertView.getTag();
            }
    
            viewHolder.icon.setImageBitmap(mData.get(position).getIcon());
            viewHolder.text.setText(mData.get(position).getText());
    
            return convertView;
        }
    
        @Override
        public int getItemViewType(int position) {
            Bean bean = mData.get(position);
            return bean.getType();
        }
    
        @Override
        public int getViewTypeCount() {
            return 2;
        }
    
        public final class  ViewHolder{
            public ImageView icon;
            public TextView text;
        }
    }
    

    这里大致的思路也就是在getview中区分。如今我们的SpeakListViewActivity中就能够这样写

    package com.lgl.listviewdemo;
    
    import android.graphics.BitmapFactory;
    import android.os.Bundle;
    import android.os.PersistableBundle;
    import android.support.v7.app.AppCompatActivity;
    import android.widget.ListView;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 聊天ListView
     * Created by lgl on 16/3/20.
     */
    public class SpeakListViewActivity extends AppCompatActivity {
    
        private ListView mListview;
    
        @Override
        public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
            super.onCreate(savedInstanceState, persistentState);
            setContentView(R.layout.activity_speak);
    
            mListview = (ListView) findViewById(R.id.listview);
            Bean bean1 = new Bean();
            bean1.setType(0);
            bean1.setIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
            bean1.setText("我是左边");
    
            Bean bean2 = new Bean();
            bean2.setType(1);
            bean2.setIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
            bean2.setText("我是右边");
    
            List<Bean>data = new ArrayList<Bean>();
            data.add(bean1);
            data.add(bean2);
    
            SpeakAdapter adapter = new SpeakAdapter(this,data);
            mListview.setAdapter(adapter);
        }
    }
    

    把数据装载好了即可,这里仅仅是演示

    4.动态改变ListView的布局

    都是什么鬼,写的不是非常具体呀

    通常情况下,假设要动态的改变点击item的布局来达到Focus的效果,一般有两种方法。一是将两个布局写在一起。通过布局的显示隐藏来达到切换布局的效果,第二种则是在getView的时候,通过推断来选择不同的载入不同的布局。两种方法都有利弊,关键还是要看看应用场景,所以我们还是得在Adapter下手脚

     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.mipmap.ic_launcher);
            layout.addView(iv,new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT));
            TextView tv = new TextView(mContext);
            tv.setText(list.get(i));
            layout.addView(tv, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
            layout.setGravity(Gravity.CENTER);
            return layout;
        }

    在这两个方法中就能够依据item的不同位置显示不同的信息了。以下我们回到adapter中

    @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 convertView;
        } 

    这样就能够自由选择了
    那我们如今就来监听他的点击逻辑吧

     listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    myAdapter.setCurrentItem(position);
                    myAdapter.notifyDataSetChanged();
                }
            });

    OK。搞定。实在的说吧,这一章不止这点内容,只是本书非常多都一笔带过了。当然,的确进阶书不须要写非常具体,可是…额….我们接着看下一章吧!

    笔记下载链接:http://pan.baidu.com/s/1c0U7k2W password:9v0g

    Demo下载地址:http://download.csdn.net/detail/qq_26787115/9467353

  • 相关阅读:
    Python(2.7.6) 特殊方法
    Python(2.7.6) 列表推导式
    代码神注释鉴赏,喜欢拿去用
    为什么说重启能解决90%的问题
    为什么说重启能解决90%的问题
    编程语言简史
    编程语言简史
    Shell脚本中循环select命令用法笔记
    程序员职业规划
    程序员职业规划
  • 原文地址:https://www.cnblogs.com/wzzkaifa/p/7351293.html
Copyright © 2011-2022 走看看