android学习笔记----ListView和各种适配器简介

打气筒(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";@Overrideprotected 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 {@Overridepublic int getCount() {return 7;}@Overridepublic Object getItem(int i) {return null;}@Overridepublic long getItemId(int i) {return 0;}@Overridepublic 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;}}
}

如果inflate(R.layout.item, null);如果item比较少,没有占满屏幕空间,那么第二次及以后打开应用程序在日志中会看到创建新item和使用回收item的交替奇怪现象,这种交替情况也会在ListView设置layout_height="wrap_content"时出现,所以ListView的layout_height要设置为match_parent。 如果inflate(R.layout.item, parent, false);布局按照R.layout.item来,可以手动设置宽高看到效果(仅仅宽度,高度不管是wrap_content还是match_parent或者其他具体值展示出来的效果是一样的,由系统内部指定了),推荐使用。但如果是RecyclerView则不一样,指定的高度就按指定的高度来。

第三个参数为false表示暂时不要附属到父ListView,以方便下一步操作,比如设置控件的属性。

最后return view;ListView会将返回值作为子项添加进来。 如果inflate(R.layout.item, parent, true);或者view = inflater.inflate(R.layout.item, parent);效果相同,但是都会报错 java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView

因为如果第三个参数为true,那么返回的就是根节点parent—>listview。

我们现在分析如果第三个参数为true时为什么出错。

首先看到我们之前有设置数据适配器,lv.setAdapter(new MyAdapter());

现在看到ListView源码的setAdapter

    public void setAdapter(ListAdapter adapter) {// mAdapter为红色看不了,说明这个变量不在这个类里,那么查找父类AbsListView(抽象类)里面// 有一个ListAdapter mAdapter;if (mAdapter != null && mDataSetObserver != null) {mAdapter.unregisterDataSetObserver(mDataSetObserver);}resetList();mRecycler.clear();if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);} else {mAdapter = adapter;}mOldSelectedPosition = INVALID_POSITION;mOldSelectedRowId = INVALID_ROW_ID;// AbsListView#setAdapter will update choice mode states.super.setAdapter(adapter);......}

首先我们通过打印log可以知道这个parent就是ListView实例,那么再通过源码来找找看,在父类AbsListView(抽象类)声明了ListAdapter mAdapter(ListAdapter接口的引用),这个mAdapter已经用new myAdapter()实例化了,那么到底什么时候调用getView呢?

调用getView会回调实现类的getView,我们来看看mAdapter什么时候调用了getView,现在来看看AbsListView里面的mAdapter.getView(…)会在哪里调用?

找到有一句

final View updatedView = mAdapter.getView(position, transientView, this);

这里的getView声明为View getView(int position, View convertView, ViewGroup parent);

传进去了this,parent实例化为this,这个this指什么?别忘了我们是通过lv对象找上来的,this就是ListView实例啊,解决了上面的一个问题。

至于为什么传true出错,看到public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法

首先声明了View result = root ;//最终返回值为result 中间执行了temp = createViewFromTag(root, name, attrs);

创建了View

然后接着看到

if(root!=null){params = root.generateLayoutParams(attrs);if (!attachToRoot){temp.setLayoutParams(params);}}可以看到,当root不为null,attachToRoot为false时,为temp设置了LayoutParams.继续往下,
if (root != null && attachToRoot){root.addView(temp, params);}当root不为null,attachToRoot为true时,将tmp按照params添加到root中。然后
if (root == null || !attachToRoot) { result = temp; } 

如果root为null,或者attachToRoot为false则,将temp赋值给result。 最后返回result。

从上面的分析已经可以看出:

Inflate(resId , null ) 只创建temp ,返回temp

Inflate(resId , parent, false )创建temp,然后执行temp.setLayoutParams(params);返回temp

Inflate(resId , parent, true ) 创建temp,然后执行root.addView(temp, params);最后返回root

由上面已经能够解释:

Inflate(resId , null )不能正确处理宽和高是因为:layout_width,layout_height是相对了父级设置的,必须与父级的LayoutParams一致。而此temp的getLayoutParams为null

Inflate(resId , parent,false ) 可以正确处理,因为temp.setLayoutParams(params);这个params正是root.generateLayoutParams(attrs);得到的。

Inflate(resId , parent,true )不仅能够正确的处理,而且已经把resId这个view加入到了parent,并且返回的是parent,和以上两者返回值有绝对的区别,还记得文章前面的例子上,MyAdapter里面的getView报的错误:

java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView

这是因为源码中调用了root.addView(temp, params);而此时的root是我们的ListView对象,而ListView里面没有addView方法,我们继续往父类上面找,看到了AdapterView里面有addView方法,ListView为AdapterView的子类: 直接看AdapterView的源码:

 @Overridepublic void addView(View child) {thrw new UnsupportedOperationException("addView(View) is not supported in AdapterView");}

可以看到这个错误为啥产生了。

AdapterView下不应该有任何视图!

获取打气筒LayoutInflater对象推荐第二种或者第三种写法,即

LayoutInflater.from(getApplicationContext())或者(LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);

ArrayAdapter源码是第二种写法,SimpleAdapter的源码是第三种写法。这两种写法都可以。

inflater从指定的xml结点加载布局只推荐inflate(R.layout.item, parent, false);写法,源码都是这么写的

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"><ImageViewandroid:id="@+id/iv_icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/ic_launcher_round" /><TextViewandroid: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" /><TextViewandroid: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"><ListViewandroid:id="@+id/lv"android:layout_width="match_parent"android:layout_height="match_parent"></ListView></LinearLayout>

运行结果:

img

如果需要隐藏列表项的分割线, 可在对应xml 文件中的 ListView XML 元素上设置 两个属性。

我们希望 android**:divider 设置为 “@null” 并将 android:dividerHeight 设置为 “0dp”。**

  <ListView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/list"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"android:divider="@null"android:dividerHeight="0dp"/>

设置前(左)和设置后(右)的示范图:

img

img

ArrayAdapter用法:

简单来讲一下ListView和ArrayAdapter:

我们可以将 ListView 看成由 ArrayAdapter 提供支持,没有该适配器的话 ListView 就是个空的容器。提供支持是指 ArrayAdapter 关系到应该显示在屏幕上的数据集,例如,它可以关系到数组甚至数据列表。所以 ArrayAdapter 知道如何将该数据传输或调整到列表项视图中,并在 ListView 中显示。

我们来详细了解下这一切的原理

当你第一次将适配器与 ListView 相关联时 该 ListView 会询问,你想要显示多少个项?

ArrayAdapter 知道这一信息,所以 ListView 对 ArrayAdapter 调用方法,即用户当前正在查看的列表位置。比如它会传入位置 0

但用户也可能位于列表中的位置 1 处 甚至 100 处,知道这一信息后,ArrayAdapter 会查看数据的内部来源,如果传入数组,那么它会查看数组,若传入列表比如ArrayList,它也可能会查看列表(如果列表是来源数据的话)并获取相关信息。ArrayAdapter具有说明来告诉它自己如何创建列表项视图,并返回给ListView当屏幕被占满后 ListView 将停止向ArrayAdapter 寻求更多的列表项列表项视图仅在需要时才创建,**当视图被滚动离开屏幕后,它们就会被添加到Scrap Pile,**比如前两个列表项不再可见,它们将进入 Scrap Pile,然后当我们请求新的列表项时,我们可以通过将这些视图再返回到 ArrayAdapter 重复使用它们,**ListView 将请求列表中特定位置的视图,同时传入之前用过的视图。比如,ListView 请求的是位置 6 处的项,并向 ArrayAdapter 传入可重复使用的以前视图,ArrayAdapter 可以通过在回收过的视图里放入数据,使用回收的视图,**然后再接着比如通过调用 TextView setText() 方法来更改名字,这样我们可以向 ListView 返回全新的列表项以便添加到新的层级并显示到屏幕上。

我们将讨论下适配器可以如何应用到每个类别

ListView.setAdapter() 方法需要 ListAdapter 作为其输入参数,所以 ListAdapter 是个接口,意味着没有实现任何状态,所有方法都是抽象的,此外 Android 团队创建了 BaseAdapter 类,它是个抽象类,为 ListAdapter 的某些方法提供了实现,同时让其他某些方法保留为抽象方法,这时候就需要 ArrayAdapter 了。ArrayAdapter 是个具体类,方法都实现了,没有方法保留为抽象方法。我们可以创建一个 ArrayAdapter 对象实例并用在我们的应用中,因为我们已经验证了 ArrayAdapter 是个 ListAdapter,我们可以将 ArrayAdapter 对象作为listView.setAdapter() 输入参数传入。

在 Android 中适配器模式是个非常常见的模式。适配器知道数据来源是什么,例如数组或列表。并且知道如何将每项呈现为视图,与此同时 ListView 负责在屏幕上显示这些视图,检测用户的触摸手势,并跟踪用户是否位于整个列表中。 ListView lv = (ListView) findViewById(R.id.lv); ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item1, R.id.tv_name, objects); // 适合自定义布局 lv.setAdapter(adapter);

这三行代码总结了ListView 和 ArrayAdapter 之间的这些互动关系。

暂时你可以想象成用户界面与数据模型之间是分开的,分开的并不完全清晰,因为 ArrayAdapter 处理的是视图,但是主要是 ListView 负责处理用户界面的细节内容,而适配器负责数据,因为界面和数据二者是分开的,你完全可以将某部分替换为其他内容。例如,你可以将同一 ArrayAdapter 与 GridView 相关联,ArrayAdapter 逻辑完全保持不变,GridView 请求的是网格项视图而不是列表项行,适配器依然负责提供这里的每个视图。

你看过 Android 中的下拉菜单吗?在 Android 中,这些叫做 Spinner,你猜怎么着?要填充下拉菜单中的每项,我们为其关联了一个适配器,菜单中的每项都来自数据来源中的某项,可以看出,当你构建 Android 应用时,就会遇到适配器模式。

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[] = {"张三", "李四", "王五", "老六", "凑数", "还有谁"};@Overrideprotected 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"><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="30sp"android:text="TextView" />
</LinearLayout>

批注:

ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item1, R.id.tv_name, objects);

这是4个参数的方法public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)

第二个参数是布局,第三个是textview的id

如果换成3个参数的方法也可以,ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item1, objects);

那么方法声明如下public ArrayAdapter(Context context, int resource, T[] objects)

第二个参数就是布局R.layout.item1。

但是得注意,这么写一定要将根结点变成TextView结点,并且加上xmlns:android=“http://schemas.android.com/apk/res/android”

如果不加,则会报异常java.lang.IllegalStateException: ArrayAdapter requires the resource ID to be a TextView

解决方案如下:

item1.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_height="wrap_content"android:layout_width="match_parent" />

运行结果:

img

如果是自定义适配器呢?比如public class WordAdapter extends ArrayAdapter {…}这里必须要创建构造器去匹配父类,因为父类没有默认的空构造器。

但是如果我们在外面调用只需要传入2个参数WordAdapter itemsAdapter = new WordAdapter(this, list);

那么WordAdapter构造器怎么创建呢?它的父类ArrayAdapter并没有2个参数的构造器,最少也是声明为

public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,@NonNull List<T> objects) {...}

第二个参数为布局id,我们传入0即可

public class WordAdapter extends ArrayAdapter<Word> {public WordAdapter(Context context, ArrayList<Word> list) {super(context, 0, list);}
}

解释如下:

在这里,我们为上下文和列表初始化ArrayAdapter的内部存储。当ArrayAdapter填充单个TextView时,使用第二个参数。因为这是我们自定义的适配器,所以适配器将不使用第二个参数,因此它可以是任何值。在这里,我们使用0。

如果不传入布局id,我们该怎么才能按照这个布局来显示呢?

我们使用 LayoutInflater将 XML 布局文件变成实际的视图对象,我们手动inflate视图,不需要在构造函数中super向父类传入布局资源 ID,直接LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);即可

具体的示例请见下面链接 ↓↓↓↓↓↓↓↓

关于具有自定义 ArrayAdapter 的示例应用见这里

https://github.com/udacity/ud839_CustomAdapter_Example

不管调用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个方法

    @Overridepublic int getCount() {return mObjects.size();}

这个mObject就是外面传进来的字符串数组asList转换成固定大小的List集合的引用,所以有几个字符串就显示几个item,该方法返回此适配器表示的数据集中有多少项。

再来看getView方法

    @Overridepublic @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 TextViewtext = (TextView) view;} else {//  Otherwise, find the TextView field within the layouttext = 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 {@Overrideprotected 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加到listdata.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"><TextViewandroid:id="@+id/tv_name"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="测试1"android:textSize="20sp" /><TextViewandroid: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"><ListViewandroid:id="@+id/lv"android:layout_width="match_parent"android:layout_height="match_parent"></ListView>
</RelativeLayout>

运行结果:

img

分析一下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的点击事件onItemClick4个参数的意义

举个例子,我们用打印log的形式来了解形如下面的形式,我们实现的方法onItemClick的4个参数是什么意思?

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {Word word = words.get(position);Log.d(TAG, "========= id:" + l + "\nAdapterView: " + adapterView + "\nview: " + view);}});

img

word_list.xml

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/list"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.android.miwok.NumbersActivity"/>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"android:background="@color/tan_background"android:minHeight="@dimen/list_item_height"android:orientation="horizontal"><ImageViewandroid:id="@+id/image"android:layout_width="wrap_content"android:layout_height="wrap_content" /><LinearLayoutandroid:id="@+id/text_container"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:paddingLeft="16dp"><TextViewandroid:id="@+id/miwok_text_view"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:gravity="bottom"android:textAppearance="?android:textAppearanceMedium"android:textColor="@android:color/white"android:textStyle="bold"tools:text="lutti" /><TextViewandroid:id="@+id/default_text_view"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:gravity="top"android:textAppearance="?android:textAppearanceMedium"android:textColor="@android:color/white"tools:text="one" /></LinearLayout>
</LinearLayout>

在某一个activity中,比如NumbersActivity.java

        .....ListView listView = (ListView) findViewById(R.id.list);listView.setAdapter(adapter);listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {// Get the {@link Word} object at the given position the user clicked onWord word = words.get(position);Log.d(TAG, "========= id:" + l + "\nAdapterView: " + adapterView + "\nview: " + view);}});...

log显示如下:

随便点击一个

img

从0开始从上往下数,第三个下标为2,所以id=2

因为发生单击的AdapterView(抽象类)是ListView(实现类),所以打印出来是android.widget.ListView

因为AdapterView中被单击的视图是LinearLayout(这将是适配器提供的视图),所以日志打印出来是android.widget.LinearLayout

那么我们现在修改一下

将实现类改为GridView,并且将xml文件中的ListView改为GridView

GridView listView = (GridView) findViewById(R.id.list);
<GridView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/list"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.android.miwok.NumbersActivity"/>

打印出来就是android.widget.GridView,如下图:

img

继续修改,将list_item.xml的外层布局LinearLayout改为RelativeLayout,效果如下:

img

打印出来就是android.widget.RelativeLayout

在抽象类AdapterView中有一个内部接口是OnItemClickListener,里面只有唯一的抽象方法onItemClick,我们来看看

public abstract void onItemClick (AdapterView<?> parent, View view, int position, long id)

单击此AdapterView中的项时要调用的回调方法。 如果需要访问与所选项关联的数据,实施者可以调用getItemAtPosition(position)。

参数
parentAdapterView:发生单击的AdapterView。
viewView:AdapterView中被单击的视图(这将是适配器提供的视图)
positionint:适配器中视图的位置。
idlong:已单击的项的行ID。

将数据库的数据显示到ListView

img

img

这里给出主要代码,详细代码见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;@Overrideprotected 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() {@Overridepublic 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 {@Overridepublic int getCount() {return lists.size();}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic 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()方法来获取控件实例了。

这里一定要写view.findViewById,因为findViewById是有上下文的,默认是在Activity的主布局中,我们获取的子布局是view

如果不写view.findViewById而直接写findViewById会有异常java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.widget.TextView.setText(java.lang.CharSequence)’ on a null object reference

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;}@Overridepublic void onCreate(SQLiteDatabase db) {Log.d(TAG, "onCreate: ");db.execSQL(CREATE_TABLE);Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();}@Overridepublic 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"><TextViewandroid:id="@+id/tv_name"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="左边"android:textSize="20sp" /><TextViewandroid: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>

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2023最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/657426.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Springboot 实现基于用户和物品的协同过滤算法

目录 简介 协同过滤算法(简称CF) 算法详解 算法使用 基于用户 基于物品 总结 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的漩涡&#xff0c;忘记了停…

对作用域链的理解(详细解析)

文章目录 一、作用域全局作用域函数作用域块级作用域 二、词法作用域三、作用域链 一、作用域 作用域&#xff0c;即变量&#xff08;变量作用域又称上下文&#xff09;和函数生效&#xff08;能被访问&#xff09;的区域或集合 换句话说&#xff0c;作用域决定了代码区块中变…

腾讯云部署vue+node项目

文章目录 一、安装宝塔二、vue项目部署三、node项目部署 前言: 关于项目部署,一开始也是找了很多资料,费了点时间,所以记录一下。希望能对各位有所帮助。 一、安装宝塔 1.首先在控制台,进入云服务器的终端界面 2.输入命令和密码获取权限,并且安装宝塔界面 yum install -y w…

腾讯云0基础10秒搭建幻兽帕鲁游戏联机服务器

幻兽帕鲁&#xff08;Palworld&#xff09;是一款多人在线游戏&#xff0c;为了获得更好的游戏体验&#xff0c;需要搭建一个稳定、高效的游戏联机服务器。腾讯云提供了一种简单、快速的方法&#xff0c;让新手小白也能0基础10秒搭建幻兽帕鲁游戏联机服务器&#xff01; 本文将…

计算机网络_1.2因特网概述

1.2因特网概述 一、网络、互联网与因特网的区别与联系1、网络2、互联网3、因特网4、 互联网与因特网辨析 二、因特网介绍1、因特网发展的三个阶段2、因特网简介&#xff08;1&#xff09;因特网服务提供者&#xff08;ISP&#xff09;&#xff08;2&#xff09;因特网已经发展成…

基于Springboot的视频网站系统的设计与实现(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的视频网站系统的设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…

ESP8266 传感器搭配 Node-RED实时显示数据,邮件告警 实验

前言 esp8266 12f,wif模块,接倾斜传感器,火焰传感器,烟雾传感器,水浸传感器,蜂鸣器。通过mqtt发布数据,并使用node-red实时获取数据,显示到页面上。并且通过邮件和页面两种方式报警。 需求如下: ①倾斜传感器:监测是否保持平衡。UI界面显示平衡度。如果不平衡,UI界…

Observability:在 Elastic Stack 8.12 中使用 Elastic Agent 性能预设

作者&#xff1a;来自 Elastic Nima Rezainia, Bill Easton 8.12 中 Elastic Agent 性能有了重大改进 最新版本 8.12 标志着 Elastic Agent 和 Beats 调整方面的重大转变。 在此更新中&#xff0c;Elastic 引入了 Performance Presets&#xff0c;旨在简化用户的调整过程并增强…

上位机图像处理和嵌入式模块部署(视频处理vs图像处理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 从目前发展的情况来看&#xff0c;视频处理会慢慢变成一种主流趋势。这里面的原因很多&#xff0c;比如说现在嵌入式soc的算力越来越强、获取图像的…

如何从视频中提取高清图片?可以这样截取

如何从视频中提取高清图片&#xff1f;从视频中提取高清图片可以方便我们制作各种用途所需的素材&#xff0c;如海报、社交媒体配图等。此外&#xff0c;高清图片的细节和色彩也更丰富&#xff0c;可以更好地满足我们的视觉需求。从视频中提取高清图片是一项需要技巧的任务&…

Gateway API 实践之(六)FSM Gateway 的健康检查功能

FSM Gateway 流量管理策略系列&#xff1a; 故障注入黑白名单访问控制限速重试会话保持健康检查负载均衡算法TLS 上游双向 TLS 网关的健康检查功能是一种自动化监控机制&#xff0c;用于定期检查和验证后端服务的健康状况&#xff0c;确保流量只被转发到那些健康且能正常处理请…

Java流程控制for 标签的使用

目录 for语法结构举例breakcontinuereturn 标签 for for循环语句是支持迭代的一种通用结构&#xff0c;是最有效、最灵活的循环结构。for循环在第一次反复之前要进行初始化&#xff0c;即执行初始表达式&#xff1b;随后&#xff0c;对布尔表达式进行判定&#xff0c;若判定结果…

学习鸿蒙基础(2)

arkts是声名式UI DevEcoStudio的右侧预览器可以预览。有个TT的图标可以看布局的大小。和html的布局浏览很像。 上图布局对应的代码&#xff1a; Entry //入口 Component struct Index {State message: string Hello Harmonyos //State 数据改变了也刷新的标签build() {Row()…

C++ 数论相关题目,博弈论,SG函数,集合-Nim游戏

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S 。 现在有两位玩家轮流操作&#xff0c;每次操作可以从任意一堆石子中拿取石子&#xff0c;每次拿取的石子数量必须包含于集合 S &#xff0c;最后无法进行操作的人视为失败。 问如果两人都采用最优策略&#xff0c;…

PEI是聚醚酰亚胺(Polyetherimide)主要应用于哪些行业领域?

聚醚酰亚胺&#xff08;Polyetherimide&#xff0c;PEI&#xff09;由于其优异的性能&#xff0c;被广泛应用于多个工业领域。以下是PEI主要应用的一些行业领域&#xff1a; 1.航空航天工业&#xff1a; PEI的高温稳定性和机械性能使其在航空航天领域中成为一种理想的材料。它用…

系统架构设计师-21年-下午题目

系统架构设计师-21年-下午题目 更多软考知识请访问 https://ruankao.blog.csdn.net/ 试题一必答&#xff0c;二、三、四、五题中任选两题作答 试题一 (25分) 说明 某公司拟开发一套机器学习应用开发平台&#xff0c;支持用户使用浏览器在线进行基于机器学习的智能应用开发…

抵御.360勒索病毒威胁:解密文件的有效方法与预防措施

导言&#xff1a; 近来&#xff0c;网络犯罪的一种新型形式——.360勒索病毒&#xff0c;备受关注。这种病毒通过加密用户文件&#xff0c;要求支付赎金以获取解密密钥。本文91数据恢复将深入介绍.360勒索病毒的特点&#xff0c;同时提供一些有效的恢复方法&#xff0c;并分享…

OpenAI最近推出了ChatGPT的一个新功能,@GPT

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

人工视觉仍然需要图像采集卡

最初&#xff0c;图像采集卡被用作模拟视频数字转换器和图像缓冲器&#xff0c;但如今它们能够执行复杂的任务&#xff0c;例如图像处理。图像采集卡的设计不断发展&#xff0c;旨在提高系统性能并减少计算机处理需求。 除了图像采集之外&#xff0c;图像采集卡还执行机器视觉…

酒店|酒店管理小程序|基于微信小程序的酒店管理系统设计与实现(源码+数据库+文档)

酒店管理小程序目录 目录 基于微信小程序的酒店管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员模块的实现 (1) 用户信息管理 (2) 酒店管理员管理 (3) 房间信息管理 2、小程序序会员模块的实现 &#xff08;1&#xff09;系统首页 &#xff0…