学习笔记,这个花了一天的时间整理。
目录
将数据库显示到ListView的小Demo源码地址:https://github.com/liuchenyang0515/ListView_DataBase
打气筒(LayoutInflater对象)介绍:
MainActivity.java
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 找到控件
ListView lv = (ListView) findViewById(R.id.lv);
// 设置数据适配器
lv.setAdapter(new MyAdapter());
}
private class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return 7;
}
@Override
public Object getItem(int i) {
return null;
}
@Override
public long getItemId(int i) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 1.想办法把自己定义的布局转换成一个view对象就可以了
View view;
if (convertView == null) {
// 创建新的view对象,可以通过打气筒把一个布局资源转换成一个view对象
// resource 就是我们定义的布局文件
// 第一种获取打气筒服务
// view = View.inflate(getApplicationContext(), R.layout.item, null);
// 第二种打气筒写法
view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item, parent, false);
// 第三种打气筒写法
/*LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.item, parent, false);*/
Log.d(TAG, "新建的item: " + position);
} else {
view = convertView;
Log.d(TAG, "回收的item" + position);
}
return view;
}
}
}
如果view = inflater.inflate(R.layout.item, null);布局形式按照父布局activity_main的ListView来,如果item比较少,没有占满屏幕空间,那么第二次及以后打开应用程序在日志中会看到创建新item和使用回收item的交替奇怪现象,这种交替情况也会在ListView设置layout_height="wrap_content"时出现,所以ListView的layout_height要设置为match_parent。
如果view = inflater.inflate(R.layout.item, parent, false);布局按照R.layout.item来,可以手动设置宽高看到效果,推荐使用。
如果view = inflater.inflate(R.layout.item, parent, true);或者view = inflater.inflate(R.layout.item, parent);效果相同,但是都会报错
java.lang.UnsupportedOperationException:
addView(View, LayoutParams) is not supported in AdapterView
获取打气筒LayoutInflater对象推荐第二种或者第三种写法,即
LayoutInflater.from(getApplicationContext())或者(LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
ArrayAdapter源码是第二种写法,SimpleAdapter的源码是第三种写法。这两种写法都可以。
inflater从指定的xml结点加载布局只推荐inflate(R.layout.item, parent, false);写法,源码都是这么写的
关于inflater方法的讲解可以参见这三位博主的帖子
inflate第三个参数意思
从源码角度解析的有郭大神的:
Android LayoutInflater原理分析,带你一步步深入了解View(一)
以及另一篇感觉很不错的:
Android LayoutInflate深度解析 给你带来全新的认识
item.xml
<?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">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:layout_toRightOf="@+id/iv_icon"
android:ellipsize="end"
android:lines="1"
android:text="谢霆锋王菲旧情复燃阿迪法拉利删掉了福建省富拉尔基发父路径爱上附件为"
android:textColor="#000000"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv_title"
android:layout_toRightOf="@id/iv_icon"
android:ellipsize="end"
android:lines="1"
android:text="谢霆锋王菲旧情复燃阿迪法拉利删掉了福建省富拉尔基发父路径爱上附件为"
android:textColor="#999999"
android:textSize="15sp" />
</RelativeLayout>
activity_main.xml
<?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">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
运行结果:
ArrayAdapter用法:
MainActivity.java
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;
import android.widget.ListView;
/**
* 不管是什么adapter,作用就是把数据展示到listview
*/
public class MainActivity extends AppCompatActivity {
String objects[] = {"张三", "李四", "王五", "老六", "凑数", "还有谁"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1。找到控件
ListView lv = (ListView) findViewById(R.id.lv);
// 2.创建一个arrayAdapter
//ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, objects);
/*第二个参数resource:包含要在实例化视图时使用的布局文件的资源ID。
第三个参数textViewResourceId:要填充的布局资源中TextView的id*/
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.item1, R.id.tv_name, objects); // 适合自定义布局
// 设置数据适配器
lv.setAdapter(adapter);
}
}
item1.xml
<?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">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="TextView" />
</LinearLayout>
运行结果:
不管调用ArrayAdapter的哪个重载方法,最终到这个方法:
private ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
@IdRes int textViewResourceId, @NonNull List<T> objects, boolean objsFromResources) {
mContext = context;
mInflater = LayoutInflater.from(context);
mResource = mDropDownResource = resource;
mObjects = objects;
mObjectsFromResources = objsFromResources;
mFieldId = textViewResourceId;
}
这里用到的是刚刚介绍的三种拿到打气筒LayoutInflater对象的方式的第二种,源码这么写LayoutInflater.from(context),第三种也可以,我个人比较喜欢这种写法,拿到打气筒LayoutInflater是LayoutInflater.from(context);
而ArrayAdapter继承了BaseAdapter,而BaseAdapter是个抽象类,那么一定有重写getCount方法,getView方法等4个方法
@Override
public int getCount() {
return mObjects.size();
}
这个mObject就是外面传进来的字符串数组asList转换成固定大小的List集合的引用,所以有几个字符串就显示几个item,该方法返回此适配器表示的数据集中有多少项。
再来看getView方法
@Override
public @NonNull View getView(int position, @Nullable View convertView,
@NonNull ViewGroup parent) {
return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
private @NonNull View createViewFromResource(@NonNull LayoutInflater inflater, int position,
@Nullable View convertView, @NonNull ViewGroup parent, int resource) {
final View view;
final TextView text;
if (convertView == null) {
view = inflater.inflate(resource, parent, false);
} else {
view = convertView;
}
try {
if (mFieldId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = (TextView) view;
} else {
// Otherwise, find the TextView field within the layout
text = view.findViewById(mFieldId);
if (text == null) {
throw new RuntimeException("Failed to find view with ID "
+ mContext.getResources().getResourceName(mFieldId)
+ " in item layout");
}
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}
final T item = getItem(position);
if (item instanceof CharSequence) {
text.setText((CharSequence) item);
} else {
text.setText(item.toString());
}
return view;
}
这里看到了inflater从指定的xml结点加载布局写法,推荐。
if (convertView == null) {
view = inflater.inflate(resource, parent, false);
} else {
view = convertView;
}
SimpleAdapter用法:
MainActivity.java
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1.找到控件
ListView lv = (ListView) findViewById(R.id.lv);
// 1.1准备listview要显示的数据
List<Map<String, String>> data = new ArrayList<Map<String, String>>();
Map<String, String> map1 = new HashMap<String, String>();
map1.put("name", "张飞");
map1.put("phone", "1388888");
Map<String, String> map2 = new HashMap<String, String>();
map2.put("name", "赵云");
map2.put("phone", "110");
Map<String, String> map3 = new HashMap<String, String>();
map3.put("name", "貂蝉");
map3.put("phone", "1386666");
Map<String, String> map4 = new HashMap<String, String>();
map4.put("name", "关羽");
map4.put("phone", "119");
// 1.2把Map加到list
data.add(map1);
data.add(map2);
data.add(map3);
data.add(map4);
// 2.定义数据适配器,我们定义的布局文件
SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.item,
new String[]{"name", "phone"}, new int[]{R.id.tv_name, R.id.tv_phone});
// 3,设置数据是配资
lv.setAdapter(adapter);
}
}
item.xml
<?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="horizontal">
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="测试1"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_phone"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="测试2"
android:textColor="#ff0000"
android:textSize="20sp" />
</LinearLayout>
activity_main.xml
<?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="match_parent">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</RelativeLayout>
运行结果:
分析一下SimpleAdapter源码:
构造方法就这一种,没有其他重载方法。
public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
@LayoutRes int resource, String[] from, @IdRes int[] to) {
mData = data;
mResource = mDropDownResource = resource;
mFrom = from;
mTo = to;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
参数:
context:与此SimpleAdapter关联的视图正在运行的上下文
data:地图清单。列表中的每个条目对应于列表中的一行。映射包含每一行的数据,并应包括“from“中指定的所有条目。
resource:定义此列表项视图的视图布局的资源标识符。布局文件至少应包括“to”中定义的命名视图
from:将添加到与每个项关联的Map中的列名列表。
to:应该在“from”参数中显示列的视图。这些都应该是TextView。此列表中的第一个N个视图给出from参数中第一个N列的值。
意思就是从一个list集合中(装的map集合)获取数据,from要输入键和值,to就是给出到底哪个textview显示键,哪个textview显示值,给出textview的id就可以了。
这里获取inflater是刚刚说的第三种方式(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
SimpleAdapter也是继承的BaseAdapter,实现了抽象方法,其中有getCount()和getView()
public int getCount() {
return mData.size();
}
这个就是返回集合list大小,list里面有几个map,就有几条信息,就会显示几个item,该方法返回此适配器表示的数据集中有多少项。
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
ViewGroup parent, int resource) {
View v;
if (convertView == null) {
v = inflater.inflate(resource, parent, false);
} else {
v = convertView;
}
bindView(position, v);
return v;
}
仍然是推荐inflater.inflate(resource, parent, false);
将数据库的数据显示到ListView
这里给出主要代码,详细代码见Demo源码,地址在文章开头。
主要实现数据库的增删改查和把数据显示到ListView,以及getView优化和listView点击事件。
MainActivity.java
import android.database.Cursor;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.listview_database.dao.ContactInfoDao;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
private EditText et_name;
private EditText et_phone;
private ContactInfoDao dao;
private List<Person> lists;
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.id_lv);
et_name = (EditText) findViewById(R.id.id_name);
et_phone = (EditText) findViewById(R.id.id_number);
dao = new ContactInfoDao(this, "mydb", "contactinfo", null, 1);
lists = new ArrayList<Person>();
// lv.setAdapter(new MyAdapter());// 放在这里添加数据再查询会挂掉Exception dispatching input event.
}
public void add(View view) {
String name = et_name.getText().toString().trim();
String phone = et_phone.getText().toString().trim();
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(phone)) {
Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
return;
} else {
dao.add(name, phone);
Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show();
}
}
public void delete(View view) {
String name = et_name.getText().toString().trim();
if (TextUtils.isEmpty(name)) {
Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
return;
} else {
dao.delete(name);
Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
}
}
public void modify(View view) {
String name = et_name.getText().toString().trim();
String phone = et_phone.getText().toString().trim();
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(phone)) {
Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
return;
} else {
dao.update(name, phone);
Toast.makeText(this, "修改成功", Toast.LENGTH_SHORT).show();
}
}
public void find(View view) {
String name = et_name.getText().toString().trim();
if (TextUtils.isEmpty(name)) {
Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
return;
} else {
Cursor cursor = dao.query(name);
String phone = null;
if (cursor.moveToFirst()) { // 将光标移动到第一行,如果游标为空,此方法将返回false。
String str1 = null;
do {
phone = cursor.getString(cursor.getColumnIndex("phone"));
str1 = "name:" + name + "phone:" + phone;
// 把javabean对象装到集合
lists.add(new Person(name, phone));
Log.d(TAG, str1);
} while (cursor.moveToNext()); // 将光标移动到下一行,如果游标已经超过结果集中的最后一个条目,此方法将返回false。
lv.setAdapter(new MyAdapter());
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Person person = lists.get(position);
Toast.makeText(MainActivity.this, person.getName() + ":" + person.getPhone(),
Toast.LENGTH_SHORT).show();
}
});
}
cursor.close();
if (phone == null) {
Toast.makeText(this, "无此联系人信息", Toast.LENGTH_SHORT).show();
}
}
}
// 定义listview的数据适配器
private class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return lists.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item, parent, false);
viewHolder = new ViewHolder();
viewHolder.tv_name = (TextView) view.findViewById(R.id.tv_name);
viewHolder.tv_phone = (TextView) view.findViewById(R.id.tv_phone);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
Person person = lists.get(position);
viewHolder.tv_name.setText(person.getName());
viewHolder.tv_phone.setText(person.getPhone());
return view;
}
class ViewHolder {
TextView tv_name, tv_phone;
}
}
}
这里给出优化版的getView,避免快速滑动的时候每次加载布局,这就会成为性能的瓶颈。
getView方法有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便于之后可以重用。
接着我们新增一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为null的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag方法,将ViewHolder对象存储在View中,当convertView不为null时,调用View的getTag方法,把ViewHolder对象重新取出。这样所有控件的实例都换存在了ViewHolder里,就没必要每次都通过findViewById()方法来获取控件实例了。
ContactInfoDao.java
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.listview_database.MyDatabaseHelper;
public class ContactInfoDao {
private MyDatabaseHelper helper;
public ContactInfoDao(Context context, String name, String tableName, SQLiteDatabase.CursorFactory factory, int version) {
helper = new MyDatabaseHelper(context, name, tableName, factory, version);
}
public long add(String name, String phone) {
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("phone", phone);
long rowId = db.insert(helper.getTableName(), null, values);
db.close();
return rowId;
}
public int delete(String name) {
SQLiteDatabase db = helper.getWritableDatabase();
int rowId = db.delete(helper.getTableName(), "name = ?", new String[]{name});
db.close();
return rowId;
}
public int update(String name, String phone) {
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("phone", phone);
int rowId = db.update(helper.getTableName(), values, "name = ?", new String[]{name});
db.close();
return rowId;
}
public Cursor query(String name) {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query(helper.getTableName(), null, "name = ?", new String[]{name}, null, null, null);
return cursor;
}
}
MyDatabaseHelper.java
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.widget.Toast;
public class MyDatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
private String tableName;
private String TAG = "MyDatabaseHelper";
public final String CREATE_TABLE;
public MyDatabaseHelper(Context context, String name, String tableName, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
this.tableName = tableName;
CREATE_TABLE = "create table " + tableName + "(" +
"id integer primary key autoincrement," +
"name char(20), " +
"phone varchar(20));";
}
public String getTableName() {
return tableName;
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.d(TAG, "onCreate: ");
db.execSQL(CREATE_TABLE);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG, "onUpgrade: ");
db.execSQL("drop table if exits " + tableName);
onCreate(db);
}
}
item.xml
<?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="horizontal">
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="左边"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_phone"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="右边"
android:textColor="#ff0000"
android:textSize="20sp" />
</LinearLayout>
==================================Talk is cheap, show me the code================================