13、ListView 适配器

13.1ListView常用属性
1.android:divider=”” 列表之间绘制的颜色或者图片。一般开发中用于分隔表项。在实际开发过程中,如果你不想要列表之间的分割线,可以设置属性为 @null
2. android:dividerHeight=”” 前面 divider 的高度。
3. android:stackFromBottom=”” 使它们的内容从底部开始显示。默认是 false 从顶部开始显示,如果设置为 true 则从底部开始显示。
4. android:transcriptMode=”” 设置列表的 transcriptMode 模式,该模式指定列表添加新的选项的时候,是否自动滑动到底部,显示新的选项。
共三个枚举值:
disabled:取消 transcriptMode 模式,默认的 。
normal:当接受到数据集合改变的通知,并且仅仅当最后一个选项已经完全显示在屏幕的时候,自动滑动到底部。
alwaysScroll:无论当前列表显示什么选项,列表将会自动滑动到底部显示最新的选项。
13.2ListView常用方法
setEmptyView(View)设置ListView没有数据时展示的布局
需要注意。该方法需要的参数 View。该 View 必须和 ListView 在同一个布局容器中。
setHeaderView()添加头部布局,可以添加多个(需要动态加载布局)
setFooterView()添加尾部布局,可以添加多个(需要动态加载布局)
13.3适配器
Android 中适配器是连接后端数据和前端显示的接口,是数据和UI之间重要的纽带,主要在 View 上显示【一般是 listview】。可以看作是界面数据绑定的一种理解。它所操纵的数据一般都是一些比较复杂的数据,如数组,链表,数据库,集合等。适配器就像显示器,把复杂的东西按人可以接受的方式来展现。
高级控件:ListView、GridView[网格视图]、Spinner[下拉列表]、Gallery[画廊]、ViewPage 等都需要使用适配器来为其设置数据源。
常用的适配器有:ArrayAdapter,SimpleAdapter,CursorAdapter 这三个都是继承 BaseAdapter,BaseAdapter是一个抽象类,需要子类继承并实现其中的方法才能使用,常用于用户自定义适配器时,显示比较复杂的数据。

ArrayAdapter
例:
public class MainActivity extends AppCompatActivity {

private ListView listView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //动态加载布局...
    View main = getLayoutInflater().inflate(R.layout.activity_main, new FrameLayout(this), false);
    setContentView(main);

// setContentView(R.layout.activity_main);

    ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, getData());
    listView = (ListView) main.findViewById(R.id.listView);

    //动态加载布局....
    //第一步:得到布局加载器

// LayoutInflater inflater= getLayoutInflater();//在Activity中
// LayoutInflater inflater= LayoutInflater.from(this);//在有Context 的地方
// LayoutInflater inflater= (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//在有Context 的地方
LayoutInflater inflater = LayoutInflater.from(this);
//第二步:使用布局加载器 加载布局
View view1 = inflater.inflate(R.layout.view_list_header, listView, false);
View view2 = inflater.inflate(R.layout.view_list_header, listView, false);
View view3 = inflater.inflate(R.layout.view_list_header, listView, false);
//添加头部布局
// listView.addHeaderView(view1);
// listView.addHeaderView(view2);
//添加尾部布局
// listView.addFooterView(view3);

    //添加空的视图(当ListView 没有数据的时候显示)
    listView.setEmptyView(findViewById(R.id.lv_emptyView));//要求该视图 必须在 ListView 所在的容器里

    view1.findViewById(R.id.btn_sss).setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            Toast.makeText(MainActivity.this, "呵呵", Toast.LENGTH_SHORT).show();
        }
    });

    //绑定适配器到ListView
    listView.setAdapter(arrayAdapter);
}

//初始化数据
public List<String> getData() {
    ArrayList<String> data = new ArrayList<>();
    for (int i = 0; i < 50; i++) {
        data.add("我和你吻别,在无人的街道..." + i);
    }
    return data;
}   }

SimpleAdapter
参数说明:
context : 上下文
data:数据源 格式 List

SimpleAdapter与ViewBinder结合使用(因为SimpleAdapter是系统写好的,可以使用ViewBinder来自己绑定视图)
simpleAdapter.setViewBinder(new ViewBinder() {

        @Override
        public boolean setViewValue(View view, Object data, String textRepresentation) {

            return false;
        }
    });

setViewValue 方法的参数说明:
view 当前需要绑定的视图控件(不是整个布局,而是布局里面具体某一个ImageView 或 TextView 等);
data:当前 view 所对应的 数据
textRepresentation:该参数是由 data 经过处理的,内部做了非空判断,所以该参数永远不为 null.
例:
public class MainActivity extends AppCompatActivity {

private ListView lv_show;
//初始化数据
ArrayList<HashMap<String, Object>> list = new ArrayList<>();

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

    HashMap<String, Object> map1 = new HashMap<>();
    map1.put("icon", R.mipmap.icon0);
    map1.put("title", "订阅号");
    map1.put("msg", "iphone7发布了,激动我买note7");
    map1.put("time", 17567898762L);
    list.add(map1);
    /******************/
    HashMap<String, Object> map2 = new HashMap<>();
    map2.put("icon", R.mipmap.icon1);
    map2.put("title", "QQ邮箱提醒");
    map2.put("msg", "阿斯顿飞开了家");
    map2.put("time", 17567898762L);
    list.add(map2);
    /******************/
    HashMap<String, Object> map3 = new HashMap<>();
    map3.put("icon", R.mipmap.icon2);
    map3.put("title", "微信活动");
    map3.put("msg", "日体育偶朋友");
    map3.put("time", 17567898762L);
    list.add(map3);
    /******************/
    HashMap<String, Object> map4 = new HashMap<>();
    map4.put("icon", R.mipmap.icon3);
    map4.put("title", "美团外卖");
    map4.put("msg", "飞规划局快乐空间和高峰过后");
    map4.put("time", 17567898762L);
    list.add(map4);
    /******************/


    String from[] = {"title", "icon", "msg", "time"};
    int to[] = {R.id.tv_title, R.id.imageView, R.id.tv_lastMsg, R.id.tv_time};

    //创建SimpleAdapter

    /**
     * @params 参数一:Context
     * @params 参数二:数据源,List<? extends Map<String,?>>
     * @params 参数三:布局 Layout ID
     * @params 参数四:由参数二 的 每一个Map 的 key 所组成的 String[]
     * @params 参数五:由参数三 布局 里面的子控件的 View 的 ID 组成的 int[]
     *
     * 注意:参数四与参数五,一个数据对应一个view。数组的数据和控件下标位置要对齐
     *
     */
    SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.view_list_item_wechat, from, to);


    //ViewBind
    adapter.setViewBinder(new SimpleAdapter.ViewBinder() {

        @Override
        public boolean setViewValue(View view, Object data, String textRepresentation) {

            if (view.getId() == R.id.tv_time && data instanceof Long) {
                //如果是时间,我们自己绑定
                TextView t = (TextView) view;
                Long time = Long.parseLong(data + "");
                t.setText(new SimpleDateFormat("HH:mm", Locale.CHINA).format(new Date(time)));
                return true;//返回true 自己绑定数据
            }

            if (view instanceof ImageView) {
                //如果是ImageView 添加一个点击事件
                view.setOnClickListener(new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        Toast.makeText(MainActivity.this, "点击了头像", Toast.LENGTH_SHORT).show();
                    }
                });
            }
            return false;//交给系统绑定
        }
    });

    lv_show.setAdapter(adapter);

    //设置ListView 的 Item的点击事件
    lv_show.setOnItemClickListener(new AdapterView.OnItemClickListener() {

        /**
         *
         * @param parent 设置该点击事件的 AdapterView 本身(ListView)
         * @param view 点击的Item的视图对象
         * @param position 视图在 Adapter 中的位置
         * @param id 视图在ListView 中的行的下标
         */
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Log.d(TAG, "parent=" + parent);
            Log.d(TAG, "view=" + view);
            Log.d(TAG, "position=" + position);
            Log.d(TAG, "id=" + id);
            HashMap<String, Object> map = list.get(position);
            Object title = map.get("title");
            Toast.makeText(MainActivity.this, "" + title, Toast.LENGTH_SHORT).show();
        }
    });
}   }

BaseAdapter
BaseAdapter中的四个方法的说明
class MyAdapter extends BaseAdapter {

    @Override
    public int getCount() {
        return 0;//返回适配器的长度
    }

    @Override
    public Object getItem(int position) {
        //position 当前视图的下标
        return null;//系统不会主动调用该方法,由开发者自己实现
    }

    @Override
    public long getItemId(int position) {
        //position 当前视图的下标
        return 0;//ListView Item 点击事件中第四个参数的返回值
    }

    /**
     * position 当前视图的下标
     * convertView 布局的复用(当有布局滑出 ListView 显示的范围,convertView 就指向 刚滑出的布局)
     * parent ListView 本身
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return null;//最主要的方法,返回当前 Item 展示的布局
    }    }

ListView的ConvertView复用
1.重用了convertView,很大程度上的减少了内存的消耗。通过判断convertView是否为null,是的话就需要产生一个View出来,然后给这个视图数据,最后将这个视图返回给底层,呈献给用户。
2.ViewHolder 为static,也就是静态的,静态类只会在第一次加载时 会耗费比较长时间,但是后面就可以很好帮助加载,同时保证了内存中只有一个ViewHolder,节省了内存的开销
3.给contentView设置tag(setTag()),传入一个viewHolder对象,用于缓存要显示的数据
4.图片采用异步加载方式
5.如果listview需要显示的item很多,就要考虑分页加载。比如一共要显示100条或者更多的时候,我们可以考虑先加载20条,等用户拉到列表底部的时候再去 加载接下来的20条
6.尽量避免在ListView适配器中使用线程,因为线程是产生内存泄露的主要原因在于线程生命周期的不可控制
7.ListView布局的layout_height尽量使用march_parent防止用户误操作屏幕导致重复调用getview方法

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View layout = null;
if (convertView == null) {
layout = getLayoutInflater().inflate(R.layout.activity_main, parent, false);
//在这里去 findViewById 以及更新UI
}else{//如果 convertView 不等于 null 了。则不需要再去加载一次布局
layout = convertView;
//在这里去 findViewById 以及更新UI
}
return layout;
}
例:
MainActivity
public class MainActivity extends AppCompatActivity {

private ListView lv_show;
private ArrayList<Student> data;

private int images[] = {R.mipmap.icon0, R.mipmap.icon1,
        R.mipmap.icon2, R.mipmap.icon3, R.mipmap.icon4,
        R.mipmap.icon5, R.mipmap.icon6, R.mipmap.icon7,
        R.mipmap.icon8, R.mipmap.icon9, R.mipmap.icon10, R.mipmap.icon11};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    lv_show = (ListView) findViewById(R.id.lv_show);
    initData();
    MyAdapter myAdapter = new MyAdapter(this, data);
    lv_show.setAdapter(myAdapter);
    lv_show.setOnItemClickListener(new AdapterView.OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
      Toast.makeText(MainActivity.this,"id="+id,Toast.LENGTH_SHORT).show();
        }
    });
}

//初始化数据
private void initData() {
    data = new ArrayList<>();
    for (int i = 0; i < images.length; i++) {
        long l = System.currentTimeMillis();
        data.add(new Student(images[i], "标题" + i, "最后一条消息" + i, l - i * 10000));
    }
}   }

MyAdapter
public class MyAdapter extends BaseAdapter {

private Context mContext;

private List<Student> mData;


public MyAdapter(Context context, List<Student> data) {
    this.mData = data;
    this.mContext = context;
}

//适配器的Item的长度
@Override
public int getCount() {
    return mData == null ? 0 : mData.size();
}

//每个Item将要显示的视图(View)
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    Student student = getItem(position);

    String title = student.getTitle();
    if ("标题5".equals(title)) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.view_list_item_news, parent, false);
        return view;
    }

    View view = LayoutInflater.from(mContext).inflate(R.layout.view_list_item_wechat, parent, false);
    ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
    TextView tv_title = (TextView) view.findViewById(R.id.tv_title);
    TextView tv_lastMsg = (TextView) view.findViewById(R.id.tv_lastMsg);
    TextView tv_time = (TextView) view.findViewById(R.id.tv_time);


    imageView.setImageResource(student.getImageResId());
    tv_title.setText(title);
    tv_lastMsg.setText(student.getLastMsg());
    //格式化时间
    String dataStr = new SimpleDateFormat("HH:mm", Locale.CHINA).format(new Date(student.getTime()));
    tv_time.setText(dataStr);
    return view;
}

//开发者自己实现,一般用来得到当前 position位置的 数据
@Override
public Student getItem(int position) {
    return mData.get(position);
}

//当用户设置了 ListView的Item的点击事件的时候,讲此值 作为第四个参数传递
@Override
public long getItemId(int position) {
    return 0;
}   }

Student
public class Student {

private int imageResId;
private String title;
private String lastMsg;
private long time;

@Override
public String toString() {
    return "Student{" +
            "imageResId=" + imageResId +
            ", title='" + title + '\'' +
            ", lastMsg='" + lastMsg + '\'' +
            ", time=" + time +
            '}';
}

public Student() {
}

public Student(int imageResId, String title, String lastMsg, long time) {
    this.imageResId = imageResId;
    this.title = title;
    this.lastMsg = lastMsg;
    this.time = time;
}

public int getImageResId() {
    return imageResId;
}

public void setImageResId(int imageResId) {
    this.imageResId = imageResId;
}

public String getTitle() {
    return title;
}

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

public String getLastMsg() {
    return lastMsg;
}

public void setLastMsg(String lastMsg) {
    this.lastMsg = lastMsg;
}

public long getTime() {
    return time;
}

public void setTime(long time) {
    this.time = time;
}  }

上面写法没有布局复用

ConvertView复用写法(使用RecycleBin机制 复用Item布局,防止OOM)
public class MainActivity extends AppCompatActivity {

private Integer[] imageResId = {R.mipmap.a, R.mipmap.b, R.mipmap.c, R.mipmap.e
        , R.mipmap.f, R.mipmap.g, R.mipmap.h, R.mipmap.i};
private ListView lv_show;

private List<Integer> list = new ArrayList<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    lv_show = (ListView) findViewById(R.id.lv_show);
    for (int i = 0; i < 60; i++) {
        list.addAll(Arrays.asList(imageResId));
    }
    lv_show.setAdapter(new MyAdapter());
}

class MyAdapter extends BaseAdapter {

    class ViewHolder {
        TextView tv_title;
        ImageView iv_icon;
    }


    @Override
    public int getCount() {
        return list == null ? 0 : list.size();
    }

    @Override
    public Integer getItem(int position) {
        return list.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Integer data = getItem(position);//当前行的数据
        View layout;
        ViewHolder viewHolder;
        if (convertView == null) {
            layout = getLayoutInflater().inflate(R.layout.view_list_item_wechat, parent, false);
            viewHolder = new ViewHolder();// 当前Item 的布局的子控件的引用的集合
            viewHolder.tv_title = (TextView) layout.findViewById(R.id.tv_title);
            viewHolder.iv_icon = (ImageView) layout.findViewById(R.id.imageView);
            layout.setTag(viewHolder);//把该布局的 子控件的引用 绑定到该布局上
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
            layout = convertView;
        }
        viewHolder.tv_title.setText("Item " + position);
        viewHolder.iv_icon.setImageResource(data);
        return layout;
    }
}    }

Listview卡顿的解决思路
1、使用Adapter提供的convertView 进行复用ItemView
2、使用ViewHolder 减少 findviewbyid 调用次数
3、 Listview 被多层嵌套,多次的onMessure导致卡顿,需要减少嵌套的层数
4、如果多层嵌套无法避免,建议把Listview的高和宽设置为 match_parent
5、使用分页,减少每次ListView加载的数据
6、如果显示图片,可以对图片进行缓存,减少加载的
7、减少不必要的视图更新

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值