android 自学日记(五) ——ListView

前言:此篇是学习笔记,知识内容学习自:《第一行代码》、《android群英传》、《疯狂android讲义》。

使用基础ListView

ListView是最常用的控件之一,它以垂直列表的形式显示所有列表项,是比较难用好,也非常重要的。
ListView本身只是一个容器,而Adapter负责把内容添加到这个容器中,通过调用setAdapter()方法来实现。
基本使用的话很简单,第一步:在布局文件中加入ListView控件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.app.test.MainActivity">

    <ListView  android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" />

</FrameLayout>

第二步:在Activity中调用setAdapter()给ListView添加内容:

public class MainActivity extends AppCompatActivity {
    private ListView listView;
    //列表内容data
    private String[] data = new String[20];
    //适配器
    private ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.listView);
        //给data赋值
        for (int i = 0; i < 20; i++) {
            data[i] = "第" + i + "项";
        }
       //创建adapter,其中三个参数依次是:上下文,子布局id,内容
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data);
        listView.setAdapter(adapter);
    }
}

这样就完成了!

自定义ListView界面

ListView的界面可以通过自定义布局来实现自定义的效果,接下来就来创建一个自定义ListView界面。
首先创建一个item的布局文件,我们仿造微信显示的内容。

<?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">

    <ImageView  android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:src="@drawable/a" />

    <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" android:orientation="vertical">

        <TextView  android:id="@+id/title" android:layout_width="match_parent" android:layout_height="30dp" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="item1" />

        <TextView  android:id="@+id/body" android:layout_width="match_parent" android:layout_height="30dp" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="boooooooooody1" />
    </LinearLayout>
</LinearLayout>

效果是这样的:
这里写图片描述

接着我们要新建一个Msg类用于管理item的信息:

public class Msg {
    private int imageId;
    private String title;
    private String body;

    public Msg(int imageId, String title, String body) {
        this.imageId = imageId;
        this.title = title;
        this.body = body;
    }

    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

非常简单,就是3个成员变量:图片的资源id,title,body,还有构造器和各自的get,set方法。
接着是自定义适配器,我们继承自BaseAdapter:

public class MyAdapter extends BaseAdapter {
    private Context mContext;
    private List<Msg> msgLsit;
    private LayoutInflater inflater;
    private ImageView imageView;
    private TextView title;
    private TextView body;

    public MyAdapter(Context context, List<Msg> msgLsit) {
        this.msgLsit = msgLsit;
        mContext = context;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return msgLsit.size();
    }

    @Override
    public Object getItem(int position) {
        return msgLsit.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Msg msg = (Msg) getItem(position);
        View view = inflater.inflate(R.layout.layout_item, null);
        imageView = (ImageView) view.findViewById(R.id.imageView);
        title = (TextView) view.findViewById(R.id.title);
        body = (TextView) view.findViewById(R.id.body);
        imageView.setImageResource(msg.getImageId());
        title.setText(msg.getTitle());
        body.setText(msg.getBody());
        return view;
    }
}

重写了4个方法,重点看getView()这个方法,此方法会在子项被滚动到屏幕是调用,因此在这个方法里我们加载刚刚新建的子布局,并给控件附上内容。
最后就是在Activity中调用:

public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private List<Msg> msgList = new ArrayList<Msg>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        for (int i = 0; i < 20; i++) {
            msgList.add(new Msg(R.drawable.a, "第" + i + "项", "内容:吧啦吧啦~"));
        }
        MyAdapter adapter = new MyAdapter(this, msgList);
        listView = (ListView) findViewById(R.id.listView);
        listView.setAdapter(adapter);
    }
}

看下效果:
android 自学日记(五) ——ListView_第1张图片

性能优化

前面我们已经基本可以自由自在使用ListView了,但是那上述方法其实效率是很低下的。因为每次调用getView()方法就会去执行finViewById()方法,实际上我们只要调用一次就可以了。因此,我们可以使用ViewHolder来提高效率。
只需在我们自定义的adapter中加一个内部类ViewHolder,用来保存子布局的控件:

class ViewHolder {
        private ImageView imageView;
        private TextView title;
        private TextView body;
    }

然后修改getView()方法:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Msg msg = (Msg) getItem(position);
        ViewHolder viewHolder;
        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = inflater.inflate(R.layout.layout_item, null);
            viewHolder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
            viewHolder.title = (TextView) convertView.findViewById(R.id.title);
            viewHolder.body = (TextView) convertView.findViewById(R.id.body);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.imageView.setImageResource(msg.getImageId());
        viewHolder.title.setText(msg.getTitle());
        viewHolder.body.setText(msg.getBody());
        return convertView;
    }

这里的convertView是getView传进来的参数,用于将之前加载好的布局进行缓存,以便之后使用。第一次传进来的时候肯定是null,我们就用LanyoutInflater加载布局,然后调用setTag()保存viewHoler,第二次传进来就不是null了,因此我们可以直接使用。

分割线、滚动条、点击效果

布局文件中还可以设置一些其他属性,例如:

 <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        //设置分割线,可以是颜色,也可以是图片资源
        android:divider="@android:color/holo_blue_dark"
        //设置好分割线高度
        android:dividerHeight="1dp"
        //设置隐藏滚动条
        android:scrollbars="none"
        //设置点击效果(无)
        android:listSelector="#00000000"
        />

item定位

有些app的列表向上滑动时会有一个按钮,点击后可以回到顶部,其实用的就是ListView的一个方法,调用此方法可以将选定的item列为视图顶部。例如在上述第一个Activity中添加:

public class MainActivity extends AppCompatActivity {
        ...//省略
        listView.setAdapter(adapter);
        listView.setSelection(10);
    }
}

再次运行后会发现是从第10项开始显示。

此方法是瞬间定位的,还有另外几个方法可以平滑地定位到指定位置。
还是上述Activity,添加一个Button和点击事件,调用listView的smoothScrollToPosition()方法,就可以实现平滑地定位到顶部:

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listView.smoothScrollToPosition(0);
            }
        });

下列两个方法同样可以实现平滑定位:
smoothScrollByOffset(int offset);
smoothScrollBy(int distance,int duration);
可以自己尝试下,看看效果。

动态修改ListView内容

ListView中已经显示的内容,在某些情况下可能需要发生变化,如果通过重新设置adapter来更新,这样可以实现,但是效率不会太高。因此,还有一种更简便的方法来实现动态修改:

adapter.notifyDataSetChanged();

修改上述Activity的button点击事件:

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                data[n] = "修改的第" + n + "项";
                adapter.notifyDataSetChanged();
                listView.setSelection(n);
                n++;
            }
        });

运行一下就可以看到,每次点击按钮实现修改item,并定位到修改的item。

遍历item

最常用的方法就是:

for(int i=0;i<listView.getChildCount();i++){ View view = listView.getChildAt(i); }

处理空内容的ListView

当ListView的内容为空时,看不会显示任何内容,其实如果显示一些文字告诉用户“没有任何信息”显得会获得更好地用户体验。而我们也有方法——setEmptyView()可以实现这一功能:
修改xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.app.test.MainActivity">

    <ListView  android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" />

    <TextView  android:id="@+id/t" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="抱歉!没有任何内容可以显示!" android:textSize="40dp" />
</FrameLayout>

其次修改Activity的onCreate方法:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //注掉,相当于msgList是空
//        for (int i = 0; i < 20; i++) {
//            msgList.add(new Msg(R.drawable.a, "第" + i + "项", "内容:吧啦吧啦~"));
//        }
        MyAdapter adapter = new MyAdapter(this, msgList);
        listView = (ListView) findViewById(R.id.listView);
        listView.setEmptyView(findViewById(R.id.t));
        listView.setAdapter(adapter);
    }

当ListView传入内容为空时,则显示TextView,有内容时不显示:
android 自学日记(五) ——ListView_第2张图片

ListView滑动监听

ListView的滑动监听可以使用onTouchListener方法:

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;
            }
        });

通过手指的动作来绑定相应的事件,此方法是很多View共同的。

另一种是onScrollListener,通过set方法设置:

listView.setOnScrollListener()

并可以在匿名内部类OnScrollListener中重写OnScrollStateChanged()和OnScroll()方法:

listView.setOnScrollListener(new AbsListView.OnScrollListener() {
        //当滑动状态改变时调用
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                switch (scrollState) {
                    case SCROLL_STATE_IDLE:
                        Log.d("测试", "停止滑动");
                        break;
                    case SCROLL_STATE_TOUCH_SCROLL:
                        Log.d("测试", "正在滑动");
                        break;
                    case SCROLL_STATE_FLING:
                        Log.d("测试", "手指抛动后的惯性滑动");
                        break;
                }
            }
        //滑动时不断调用
            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {
                    Log.d("测试", "滑动到底部");
                } else if (firstVisibleItem == 0) {
                    Log.d("测试", "滑动到顶部");
                }

                int lastVisibleItem = 0;
                if (firstVisibleItem < lastVisibleItem) {
                    Log.d("测试", "下滑");
                } else if (firstVisibleItem > lastVisibleItem) {
                    Log.d("测试", "上滑");
                }
                lastVisibleItem = firstVisibleItem;
            }
        });

可以复制上面的代码,运行感受下滑动的几种状态。

你可能感兴趣的:(android,ListView)