使用BaseAdapter来实现更灵活的列表,由于是一个抽象类,需要写一个适配器继承该类。
依然是ListView上部分提到的四个方法。
布局和前面基本没有差别,需要注意的是也需要单独定义一个布局文件listitem.xml(即你的自定义布局,名字随意啦)
listitem.xml
activity_main.xml
同样需要一个字符串数组,int类型数组(图片) ?当然还得准备相应的图片资源,放在drawable文件夹即可
四个方法的参数和之前基本一致,所以自定义的内容也基本类似,需要注意的同样需要一个构造方法,来传递上下文
private Context context;
public MyAdapter(Context context){
this.context = context;
}
同样的
getView()
方法需要获取Textview和ImageView并将定义好的两个数组填充进去,最后返回view
完整代码:
package com.example.listview2;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = findViewById(R.id.listview);
listView.setAdapter(new MyAdapter(this));
}
//自定义适配器
static class MyAdapter extends BaseAdapter{
private String[] titles ={
"title-1",
"title-2",
"title-3",
"title-4",
"title-5",
};
private int[]icons={
R.drawable.a1,
R.drawable.a2,
R.drawable.a3,
R.drawable.a4,
R.drawable.a5,
};
private Context context;
public MyAdapter(Context context){
this.context = context;
}
@Override
public int getCount() {
return titles.length;
}
@Override
public Object getItem(int position) {
return titles[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(context);
View view =inflater.inflate(R.layout.listitem,null);
TextView tv = view.findViewById(R.id.textView);
ImageView iv = view.findViewById(R.id.imageView);
tv.setText(titles[position]);
iv.setImageResource(icons[position]);
return view;
}
}
}
以?的为例
在MainActivity的中的
getView()中
添加一条System.out.println来进行测试
可以看到屏幕上有三个列表项,所以打印了三条语句。
如果滑动屏幕...则会反复打印当前屏幕上的列表项对应的view...
即不断的创建view对象...可以看到0就出现了两次,后面的地址都是不一样的。
综上,这样就造成了很大的浪费,为了解决这个问题。
首先把ListView布局中的宽高改成match_parent
(但是其实再这个项目里好像没啥用...)
?关键
利用convertView进行缓存,当convertView为空的时候,才去实例化view。
(convertView也是view所以也不用再单独定义一个view了)
可以看到 为1的条目地址是一样的,即没有重新创建对象,这样就不会有浪费了。
优化后完整代码:
package com.example.listview2;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = findViewById(R.id.listview);
listView.setAdapter(new MyAdapter(this));
}
//自定义适配器
static class MyAdapter extends BaseAdapter{
private String[] titles ={
"title-1",
"title-2",
"title-3",
"title-4",
"title-5",
};
private int[]icons={
R.drawable.a1,
R.drawable.a2,
R.drawable.a3,
R.drawable.a4,
R.drawable.a5,
};
private Context context;
public MyAdapter(Context context){
this.context = context;
}
@Override
public int getCount() {
return titles.length;
}
@Override
public Object getItem(int position) {
return titles[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
LayoutInflater inflater = LayoutInflater.from(context);
//实例化一个布局文件
convertView =inflater.inflate(R.layout.listitem,null);
}
TextView tv = convertView.findViewById(R.id.textView);
ImageView iv = convertView.findViewById(R.id.imageView);
System.out.println(position+"---- "+convertView);
tv.setText(titles[position]);
iv.setImageResource(icons[position]);
return convertView;
}
}
}
?的方法虽然可以解决重新创建对象的问题,但是依旧有重复调用的现象,即上下滚动界面,还是会出现若干个1的条目。
关键在于将findViewById的组件和view绑定
新建一个内部类ViewHolder
声明你的组件 ,用于保存第一次查找的组件
取代原来的直接定义TextView、ImageView
并且利用
convertView.setTag(vh);
进行存储
convertView.getTag();
进行读取
最终修改版——完整代码:
package com.example.listview2;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = findViewById(R.id.listview);
listView.setAdapter(new MyAdapter(this));
}
//自定义适配器
static class MyAdapter extends BaseAdapter{
private String[] titles ={
"title-1",
"title-2",
"title-3",
"title-4",
"title-5",
};
private int[]icons={
R.drawable.a1,
R.drawable.a2,
R.drawable.a3,
R.drawable.a4,
R.drawable.a5,
};
private Context context;
public MyAdapter(Context context){
this.context = context;
}
@Override
public int getCount() {
return titles.length;
}
@Override
public Object getItem(int position) {
return titles[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vh;
if(convertView == null){
LayoutInflater inflater = LayoutInflater.from(context);
//实例化一个布局文件
convertView =inflater.inflate(R.layout.listitem,null);
vh = new ViewHolder();
vh.tv = convertView.findViewById(R.id.textView);
vh.iv = convertView.findViewById(R.id.imageView);
convertView.setTag(vh);
}else
{
vh = (ViewHolder) convertView.getTag();
}
vh.tv.setText(titles[position]);
vh.iv.setImageResource(icons[position]);
return convertView;
}
//用于保存第一次查找的组件,避免下次重复查找
static class ViewHolder{
ImageView iv;
TextView tv;
}
}
}
当ListView的选项非常多的时候,就需要分页了。下面先准备实验基本的代码,流程的话和?基本一致
需要一个子布局listitem.xml
需要一个News类
package com.example.listview_fenye;
public class News {
String title;
String content;
}
activity_main.xml
MainActivity
package com.example.listview_fenye;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.Vector;
public class MainActivity extends AppCompatActivity {
private ListView listView;
//容器装载对象
private Vector news = new Vector<>();
private MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = findViewById(R.id.listview);
initData();
myAdapter = new MyAdapter();
listView.setAdapter(myAdapter);
}
private int index = 1;
//初始化数据
private void initData(){
//每次初始化10条
for(int i=0;i<10;i++){
News n = new News();
n.title = "title:"+index;
n.content = "content:"+index;
index++;
news.add(n);
}
}
//创建一个Adapater,方便直接用 news 起见没加static 实际上应该加上
class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return news.size();
}
@Override
public Object getItem(int position) {
return news.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vh;
if(convertView==null){
convertView = getLayoutInflater().inflate(R.layout.listitem,null);
vh = new ViewHolder();
vh.tv_content = convertView.findViewById(R.id.tv_content);
vh.tv_title = convertView.findViewById(R.id.tv_title);
convertView.setTag(vh);
}else{
vh = (ViewHolder) convertView.getTag();
}
News n = news.get(position);
vh.tv_title.setText(n.title);
vh.tv_content.setText(n.content);
return convertView;
}
class ViewHolder{
TextView tv_title;
TextView tv_content;
}
}
}
基本效果:
下面先实现加载数据的提示,需要单独新建一个布局
通过listView.addFooterView来加到界面中去(或者...Head...)
所以要先准备一个loading布局(loading.xml,名字无所谓啦)
效果的话..可能有点丑,哎呀算了..将就一下
MainActivity修改后的代码的话 在下面的部分一起放好了...不然要贴两次
然后用线程模拟加载
写一个线程类(继承Thred)
class LoadDataThread extends Thread{
@Override
public void run(){
initData();//再加10条数据
try {
Thread.sleep(2000);//休眠2s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
implements AbsListView.OnScrollListener
重写其两个方法
onScrollStateChanged(AbsListView view, int scrollState) 状态改变...
部分参数参数解析
scrollState:具有如下三个状态:
分别是正在滚动(手已经离开了)、空闲的状态、手还在滚动条上的状态
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) 滚动状态发生变化..
部分参数参数解析:
firstVisibleItem 第一个可显示的选项
visibleItemCount 可显示的选项总量
totalItemCount 选项总量
当前两个参数等于最后一个参数时,即滚动到最后
判断滚动到最底下进行加载。
这里有一个注意事项
即:
不能写成?
所以需要用到
Handler机制
用到其handleMessage(Message msg)方法来处理消息
再在子线程中使用
sendEmptyMessage()来发送
完整代码:
package com.example.listview_fenye;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.Vector;
public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener {
private ListView listView;
//容器装载对象
private Vector news = new Vector<>();
private MyAdapter myAdapter;
//数据更新完成后的标记 0x1即16进制的1
private static final int DAYA_UPDATE = 0x1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = findViewById(R.id.listview);
listView.setOnScrollListener(this);
View footview = getLayoutInflater().inflate(R.layout.loading,null);
listView.addFooterView(footview);
initData();
myAdapter = new MyAdapter();
listView.setAdapter(myAdapter);
}
private int index = 1;
//初始化数据
private void initData(){
//每次初始化10条
for(int i=0;i<10;i++){
News n = new News();
n.title = "title:"+index;
n.content = "content:"+index;
index++;
news.add(n);
}
}
private int visibleLastIndex;//用来可显示的最后一条数据的索引
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(myAdapter.getCount()==visibleLastIndex && scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE){
new LoadDataThread().start(); //启动线程
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
visibleLastIndex = firstVisibleItem+visibleItemCount-1; //减1是因为还有个正在加载中...
}
//线程间通讯机制
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case DAYA_UPDATE:
myAdapter.notifyDataSetChanged();//由主线程自己更新消息
break;
}
}
};
//线程类
class LoadDataThread extends Thread{
@Override
public void run(){
initData();//再加10条数据
try {
Thread.sleep(2000);//休眠2s
} catch (InterruptedException e) {
e.printStackTrace();
}
//子线程不能直接访问UI组件
// myAdapter.notifyDataSetChanged();
//通过handler给主线程发送一个消息标记
handler.sendEmptyMessage(DAYA_UPDATE);
}
}
//创建一个Adapater,方便直接用 news 起见没加static 实际上应该加上
class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return news.size();
}
@Override
public Object getItem(int position) {
return news.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vh;
if(convertView==null){
convertView = getLayoutInflater().inflate(R.layout.listitem,null);
vh = new ViewHolder();
vh.tv_content = convertView.findViewById(R.id.tv_content);
vh.tv_title = convertView.findViewById(R.id.tv_title);
convertView.setTag(vh);
}else{
vh = (ViewHolder) convertView.getTag();
}
News n = news.get(position);
vh.tv_title.setText(n.title);
vh.tv_content.setText(n.content);
return convertView;
}
class ViewHolder{
TextView tv_title;
TextView tv_content;
}
}
}
效果如图:
即可以无限制向下加载新的数据
expandable刚好翻译为可扩展的,在视图中显示垂直滚动两级列表。和ListView还是有一定区别的,允许且仅允许两个层次:组织单独可以扩展为其子项。
(总而言之,就是能实现二级菜单)
1、设置数据源
2、设置适配器,ExpandableAdapter类为ExpandableList的一个子类,需要写一个继承BaseExpandableListAdapter,
方法很多,所以需要重写的方法也很多,如下:
//hasStableIds()在本例中不用
虽然要重写的方法很多,但是根据方法名,基本上也能理解方法的含义,看下面部分的代码直接就可以理解了。
关键在于下面两个方法的重写
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
return null;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
return null;
}
为了完成两个方法,所以需要建立一个布局group_layout.xml和child_layout.xml,两个布局基本上一样就好(但最好还是有点区别,不要完全一样,不然有点奇怪)
MainActivity
package com.example.expandblelistview;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private ExpandableListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = findViewById(R.id.expandablelistview);
listView.setAdapter(new MyExpandableAdapter());
//点击事件
listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
Toast.makeText(MainActivity.this,childs[groupPosition][childPosition],Toast.LENGTH_LONG).show();
return true;
}
});
}
//提供一组数据
private String[]groups={"分组1","分组2"};
private String[][] childs={{"测试1","测试2","测试3"},{"测试9","测试8","测试7"}};
//适配器
class MyExpandableAdapter extends BaseExpandableListAdapter {
@Override
public int getGroupCount() {
return groups.length;
}
@Override
public int getChildrenCount(int groupPosition) {
return childs[groupPosition].length;
}
@Override
public Object getGroup(int groupPosition) {
return groups[groupPosition];
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return childs[groupPosition][childPosition];
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
if(convertView==null){
convertView = getLayoutInflater().inflate(R.layout.group_layout,null);
}
ImageView icon = convertView.findViewById(R.id.icon);
TextView title = convertView.findViewById(R.id.title);
title.setText(groups[groupPosition]);
return convertView;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
if(convertView==null){
convertView = getLayoutInflater().inflate(R.layout.child_layout,null);
}
ImageView icon = convertView.findViewById(R.id.icon);
TextView title = convertView.findViewById(R.id.title);
title.setText(childs[groupPosition][childPosition]);
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
}
group_layout.xml
child_layout.xml
activity_main.xml