Android学习笔记二十四之ListView列表视图二

本文是关于Android ListView的优化方法及使用中遇到的问题及其解决方案的总结,包括View复用、ViewHolder优化、分页加载,以及ListView的焦点问题和数据更新操作的详细说明。
摘要由CSDN通过智能技术生成

Android学习笔记二十四之ListView列表视图二

  前面一篇我们介绍了常用的几种适配器的简单实现和ListView的简单使用,这一篇中,我们介绍一下ListView的优化和一些其它的问题。

ListView优化方法一

  在ListView中,我们最常用的就是自定义Adapter,在我们自定义Adapter中,需要实现两个比较重要的方法getCount()和getView(),前者是负责计算ListView的总Item数,后者是生成Item,有多少个Item就会调用getView()方法多少次。getView()方法每次调用的时候都会重新inflate一个View出来返回去,但是对于ListView,只需要保留能够显示的最大的View的数目即可,而新的View可以复用消失的View。ListView给我们提供了可复用的View对象,在getView()方法里面,有一个参数View,这个就是可以复用的View对象。当参数View为null的时候,我们需要inflate一个View,当它不为null的时候,我们可以直接将他返回。例如:

 @Override
public View getView(int i, View view, ViewGroup viewGroup) {
    ViewHolder viewHolder;
    //view为空的时候,inflate一个新的view
    if (view == null) {
        view = LayoutInflater.from(context).inflate(R.layout.item_base_adapter, null);
        viewHolder = new ViewHolder();
        viewHolder.tv_base_adapter = (TextView) view.findViewById(R.id.tv_base_adapter);
        view.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) view.getTag();
    }
    viewHolder.tv_base_adapter.setText(datas[i]);
    //不为空,复用view
    return view;
}
ListView优化方法二

  上面介绍的是对View复用的优化,这样我们可以不必每一item都inflate一个新的view,可以通过复用,减小内存开销。下面我们介绍一个每一个AndroidAPP中都必不可少的操作,获取控件句柄,简单的说就是拿到id。在ListView中,我们inflate一个View,里面也有需要获取到组件id的,我们可以用ViewHolder来实现优化:

  具体的思路就是,我们在ViewHolder中存放我们需要的控件,在View为null的时候,需要inflate一个新的view,同时我们还new一个ViewHolder类的对象,并将findviewById的结果赋值给ViewHolder中对应的成员变量,我们可以调用View中setTag()方法,将ViewHolder和View绑定起来。当view不为null的时候,通过getTag()方法取出ViewHolder对象,这样就可以获得ViewHolder中的成员变量,也不再需要调用findViewById方法了。例如:

@Override
public View getView(int i, View view, ViewGroup viewGroup) {
    ViewHolder viewHolder;
    if (view == null) {
        view = LayoutInflater.from(context).inflate(R.layout.item_base_adapter, null);
        //view为空,new一个ViewHolder对象
        viewHolder = new ViewHolder();
        //获取到ViewHolder对象中成员变量的id
        viewHolder.tv_base_adapter = (TextView) view.findViewById(R.id.tv_base_adapter);
        //调用setTag方法,将ViewHolder绑定到中
        view.setTag(viewHolder);
    } else {
        //view不为空,调用getTag方法,取出保存的ViewHolder对象
        viewHolder = (ViewHolder) view.getTag();
    }
    viewHolder.tv_base_adapter.setText(datas[i]);
    return view;
}

static class ViewHolder {
    TextView tv_base_adapter;
}
ListView优化方法三

  上面介绍了两种ListView的优化方法,第二种优化效率根据google官方文档的解析,可以优化5%左右的效率。下面介绍一下第三种优化方法。

  在我们实际开发中,ListView显示的数据都是在网络中加载,假如网络比较好,能一次将所有的数据加载出来,这样用户体验还好,如果网络不好,那么加载数据需要时间比较久,用户体验就不好。另外,我们知道,虚拟机为每一个进程分配的内存是有限的,如果一下加载太多的数据就会出现内存溢出的情况。为了解决这两个问题,我们可以用分批加载的方法,但是分批加载还是不能完全解决问题。假如我有10万条数据需要加载,分批加载任然可能会出现OOM问题,这时,我们需要将数据分页加载,先分页加载,然后在分批加载,这样用户体验会好一些。

ListView出现的一些问题和解决

ListView焦点问题

  在一些情况中,我们需要在ListView的Item中添加Button、EditText、CheckBox等控件,这就涉及到了焦点获取的问题。我们在ListView的Item中添加了Button按钮,点击发现,我们不能触发onItemClick和onItemLongClick方法,这就是ListView的焦点被拦截了。解决办法也很简单:

  给拦截ListView焦点的控件设置Android:focusable=”false”属性或者代码调用setFocusable(false) 防范即可。还有一种方法就是在Item布局的根节点设置android:descendantFocusability=”blocksDescendants” 属性,这个属性有三个可选值

  • beforeDescendants:viewgroup会优先其子类控件而获取到焦点
  • afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
  • blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

这是ListView焦点问题的解决方法。

ListView数据更新问题

首先实现一个简单的ListView

布局代码:

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

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="10dp"
    android:layout_marginTop="10dp"
    android:orientation="horizontal">

    <Button
        android:id="@+id/btn_add_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="插入一条数据" />

    <Button
        android:id="@+id/btn_add_on_position"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="在特定位置插入一条数据" />

</LinearLayout>

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="10dp"
    android:layout_marginLeft="10dp"
    android:orientation="horizontal">

    <Button
        android:id="@+id/btn_delete_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="根据对象删除" />

    <Button
        android:id="@+id/btn_delete_on_position"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="根据位置删除" />

    <Button
        android:id="@+id/btn_delete_all"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="删除所有数据" />

</LinearLayout>

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



</LinearLayout>

Item布局代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">

<ImageView
    android:id="@+id/iv_icon"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:src="@drawable/icon" />

<TextView
    android:id="@+id/tv_data_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_marginLeft="5dp"
    android:layout_toRightOf="@id/iv_icon"
    android:text="加载出来的数据"
    android:textSize="18sp" />

</RelativeLayout>

这里的Item比较简单,就直接是一张图片和一个文字

Activity代码:

package com.example.listviewdemo.activity;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;

import com.example.listviewdemo.MYData;
import com.example.listviewdemo.R;
import com.example.listviewdemo.adapter.DataAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Devin on 2016/7/11.
 */
public class DataUpActivity extends AppCompatActivity {
private Button btn_add_one;
private Button btn_add_on_position;
private Button btn_delete_one;
private Button btn_delete_on_position;
private Button btn_delete_all;
private ListView lv_data;
private DataAdapter adapter;
private List<MYData> datas;
private int flag = 1;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_data);
    btn_add_one = (Button) findViewById(R.id.btn_add_one);
    btn_add_on_position = (Button) findViewById(R.id.btn_add_on_position);
    btn_delete_one = (Button) findViewById(R.id.btn_delete_one);
    btn_delete_on_position = (Button) findViewById(R.id.btn_delete_on_position);
    btn_delete_all = (Button) findViewById(R.id.btn_delete_all);
    lv_data = (ListView) findViewById(R.id.lv_data);
    datas = new ArrayList<>();
    adapter = new DataAdapter(this, datas);
    lv_data.setAdapter(adapter);
  }

}

自定义适配器代码:

package com.example.listviewdemo.adapter;

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 com.example.listviewdemo.MYData;
import com.example.listviewdemo.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Devin on 2016/7/11.
 */
public class DataAdapter extends BaseAdapter {
private List<MYData> datas;
private Context mContext;

public DataAdapter(Context mContext, List<MYData> datas) {
    this.mContext = mContext;
    this.datas = datas;
}

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

@Override
public Object getItem(int i) {
    return i;
}

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

@Override
public View getView(int i, View view, ViewGroup viewGroup) {
    ViewHolder viewHolder;
    if (view == null) {
        view = LayoutInflater.from(mContext).inflate(R.layout.item_data, null);
        viewHolder = new ViewHolder();
        viewHolder.tv_data_text = (TextView) view.findViewById(R.id.tv_data_text);
        viewHolder.iv_icon = (ImageView) view.findViewById(R.id.iv_icon);
        view.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) view.getTag();
    }
    viewHolder.iv_icon.setImageResource(datas.get(i).getIconId());
    viewHolder.tv_data_text.setText(datas.get(i).getContent());
    return view;
}
private static class ViewHolder {
    TextView tv_data_text;
    ImageView iv_icon;
}
}

还有就是一个普通的bean

package com.example.listviewdemo;

/**
 * Created by Devin on 2016/7/11.
 */
public class MYData {
private int iconId;
private String content;

public MYData() {
}

public MYData(int iconId, String content) {
    this.iconId = iconId;
    this.content = content;
}

public int getIconId() {
    return iconId;
}

public void setIconId(int iconId) {
    this.iconId = iconId;
}

public String getContent() {
    return content;
}

public void setContent(String content) {
    this.content = content;
}

@Override
public String toString() {
    return "Data{" +
            "iconId=" + iconId +
            ", content='" + content + '\'' +
            '}';
}
}

这样,我们运行的效果是:

没有显示有Item,因为我们在适配器中添加的是一个空的list,接下来我们实现按钮的事件和具体更新ListView

插入一条数据

在适配器中添加这个方法:

/**
 * 添加数据
 *
 * @param myData
 */
public void addData(MYData myData) {
    if (datas == null) {
        datas = new ArrayList<>();
    }
    datas.add(myData);
    notifyDataSetChanged();
}

在activity中实现点击事件:

btn_add_one.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            MYData data = new MYData(R.drawable.icon, "这是添加的数据~~~~~~X" + flag);
            adapter.addData(data);
            flag++;
        }
    });

就可以实现在ListView中添加一条数据,具体的效果图会在后面统一附上

在指定位置插入一条数据

在适配器中添加如下方法:

/**
 * 在指定位置添加数据
 *
 * @param position
 * @param myData
 */
public void addData(int position, MYData myData) {
    if (datas == null) {
        datas = new ArrayList<>();
    }
    datas.add(position, myData);
    notifyDataSetChanged();
}

实现按钮的点击事件:

btn_add_on_position.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            MYData data = new MYData(R.drawable.icon, "这是添加的数据~~~~~~X" + flag);
            adapter.addData(3, data);
        }
    });

就可以完成在指定位置添加一条数据,这里只是简单的实现,如果list中没有那么多数据,会出现数组下标越界的错误

根据对象删除数据

在适配器中添加如下方法:

/**
 * 根据对象删除数据
 *
 * @param myData
 */
public void removeData(MYData myData) {
    if (datas != null) {
        datas.remove(myData);
    }
    notifyDataSetChanged();
}

实现按钮的点击事件:

btn_delete_one.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            MYData data = datas.get(2);
            adapter.removeData(data);
        }
    });

这里只是简单的删除下标为3的数据,如果listview的长度小于3,会出现数组下标越界的错误

根据位置删除数据

在适配器中添加如下方法:

/**
 * 根据位置删除数据
 *
 * @param position
 */
public void removeData(int position) {
    if (datas != null && position <= datas.size()) {
        datas.remove(position);
    }
    notifyDataSetChanged();
}

实现按钮的点击事件:

btn_delete_on_position.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            adapter.removeData(3);
        }
    });

删除指定位置的数据,需要判定是否存在不然会出现错误

删除所有的数据

在适配器中添加如下方法:

/**
 * 清除所有的数据
 */
public void removeAll() {
    if (datas != null) {
        datas.clear();
    }
    notifyDataSetChanged();
}

实现按钮的点击事件:

btn_delete_all.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            adapter.removeAll();
        }
    });

最后附上效果图:

这里实现ListView的数据更新问题,但是任然存在一些小问题,比如,没有数据的时候显示一片空白,用户体验不是很好。可以重写,根据服务器返回的数据更新界面。这里就不做实现了。下一节我们介绍一下ListView多布局的实现,类似QQ等的聊天界面。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值