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

    @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;
        }
    }
}

如果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的源码:

 @Override
   public 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">

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

运行结果:

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[] = {"张三", "李四", "王五", "老六", "凑数", "还有谁"};

    @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 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个方法

    @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>

运行结果:

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() {
            @Override
            public 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">

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:id="@+id/text_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingLeft="16dp">

        <TextView
            android: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" />

        <TextView
            android: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() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                // Get the {@link Word} object at the given position the user clicked on
                Word 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;

    @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()方法来获取控件实例了。

这里一定要写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;
    }

    @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>

更多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零基础入门到精通,高手进阶之路

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

15.Golang中的反射机制及应用

目录 概述实践基本应用复杂应用 结束 概述 Golang中的反射用法还是比较简单的 reflect.TypeOf(arg)reflect.ValueOf(arg) 实践 基本应用 package mainimport ("fmt""reflect" )func reflectNum(arg interface{}) {fmt.Println("type ", re…

Vue中嵌入原生HTML页面

Vue中嵌入html页面并相互通信 需求&#xff1a;b2b支付需要从后获取到数据放到form表单提交跳转&#xff0c;如下&#xff1a; 但是vue目前暂时没找到有类似功能相关文档&#xff0c;所以我采用iframe嵌套的方式 1. Vue中嵌入Html <iframe src"/static/gateway.htm…

Unity 设置鼠标

前言 本章主要对鼠标图标样式还有鼠标显隐进行设置 图标样式的设置 代码控制 有时候需要有改变鼠标样式的需求可以使用如下代码 Cursor.SetCursor(this.mouseTexture, Vector2.zero, CursorMode.Auto); 传入的要替换的图标偏移量允许您在支持的平台上使用硬件光标&#xff0…

Blender教程(基础)-内插面、分离、环切、倒角-08

一、内插面 菜单位置如下图位置。 单击需要处理的面&#xff0c;出现一个黄色的圈。 1、菜单选中内插 鼠标悬停在黄色圈内单击左键可以来回实现内插&#xff0c;但是发现并不好操作。 2、快捷键内插 在选中需要操作的面之后&#xff0c;鼠标移动到外面&#xff0c;键盘在英…

【计算机网络】网络的网络

网络的网络 客户 customer 接入ISP提供商 provider 全球承载ISP多个ISP的层级结构 第一层ISP &#xff08;tier-1 ISP &#xff09; 位于顶部 区域ISP &#xff08;reginal ISP&#xff09;Level 3通信 &#xff0c;AT&T&#xff0c;Sprint &#xff0c;NTT存在点&#x…

C#,打印漂亮的贝尔三角形(Bell Triangle)的源程序

以贝尔数为基础&#xff0c;参考杨辉三角形&#xff0c;也可以生成贝尔三角形&#xff08;Bell triangle&#xff09;&#xff0c;也称为艾特肯阵列&#xff08;Aitkens Array&#xff09;&#xff0c;皮埃斯三角形&#xff08;Peirce Triangle&#xff09;。 贝尔三角形的构造…

excel怎么设置密码?轻松保护您的工作表

在数字化时代&#xff0c;数据的安全性显得尤为重要。excel作为我们日常工作中广泛使用的办公软件&#xff0c;其中可能包含了大量的敏感数据。为了确保这些数据不被未授权的人访问&#xff0c;本文将为您详细介绍excel怎么设置密码&#xff0c;从而有效地保护您的数据安全。 方…

尝试搭建域

使用window 7 作为dmz 主机 server_2008 作为 预控 前提两台主机都在同一个LAN 下&#xff08;设置一个LAN 区域&#xff09;&#xff0c;同样防火墙关闭状态 首先搞定server 2008 中的域 1. 在计算机属性中选择更改设置中将主机名修改一下&#xff1a; 2. 在server 2008 中…

PMP报名以及考试

本人刚通过这个考试 撒花 花了2000块报了个培训班&#xff0c;需要培训班去帮你报名&#xff0c;PMP考试费用3900&#xff0c;总计5900&#xff0c;还是有点小贵。 先提前看完视频课&#xff0c;对项目管理各个环节都留点印象&#xff0c;然后开始刷题&#xff0c;考试的最强的…

【服务端性能测试】性能测试策略如何做

一、需求收集 先需要确认本次测试目的是什么&#xff0c;然后再看我们需要用什么参数来判断这个目的是否能够达成。 1.1 业务性能指标参考&#xff1a; TPS、QPS、RT、请求成功率&#xff08;一般请求成功率>99.99%&#xff09; 1.2 硬件性能指标参考&#xff1a; 即服…

useEffect的第二个参数

目录 1、第一个参数&#xff1a; 2、第二个参数&#xff1a; 2.1 不传值&#xff1a;无限循环 2.2 空数组作为依赖&#xff1a;执行一次 2.3 基本类型作为依赖&#xff1a;无限循环 2.4 引用类型 2.4.1 数组作为依赖&#xff1a;无限循环 2.4.2 函数作为依赖&#…

TensorFlow2实战-系列教程11:RNN文本分类3

&#x1f9e1;&#x1f49b;&#x1f49a;TensorFlow2实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 6、构建训练数据 所有的输入样本必须都是相同shape&#xff08;文本长度&#xff0c;…

苍穹外卖项目可以写的简历和如何优化简历

文章目录 重点写中规写添加自己个性的项目面试会问道的问题 我是一名双非大二计算机本科生&#xff0c;希望我的分享对你有帮助&#xff0c;点赞关注不迷路。 简历编写一直是很多人求职人的心病&#xff0c;我自己上学期有一门课程是去校内企业面试&#xff0c;当时我就感受出…

【lesson26】学习MySQL事务前的基础知识

文章目录 CURD不加控制&#xff0c;会有什么问题&#xff1f;CURD满足什么属性&#xff0c;能解决上述问题&#xff1f;什么是事务&#xff1f;为什么会出现事务事务的版本支持 CURD不加控制&#xff0c;会有什么问题&#xff1f; CURD满足什么属性&#xff0c;能解决上述问题&…

使用Python的Turtle模块来绘制爱心图案

import turtle as t# 设置画布大小和颜色 t.setup(800, 600) t.bgcolor(white)# 设置画笔颜色和粗细 t.pensize(2) t.color(red)# 定义爱心函数 def heart():t.begin_fill()t.left(140)t.forward(224)for i in range(200):t.right(1)t.forward(2)t.left(120)for i in range(200…

Springboot+Redis

首先前提我们要在自己的本机电脑或者服务器上安装一个redis的服务器 Redis配置 添加依赖: <!-- SpringBoot Boot Redis --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artif…

sklearn 计算 tfidf 得到每个词分数

from sklearn.feature_extraction.text import TfidfVectorizer# 语料库 可以换为其它同样形式的单词 corpus [list(range(-5, 5)),list(range(-6,4)),list(range(12)),list(range(13))]# corpus [ # [Two, wrongs, don\t, make, a, right, .], # [The, pen, is, might…

【小白学unity记录】使用unity播放声音

1. 示例 unity中播放声音涉及到两个组件。AudioSource和AudioClip。AudioSource可以理解为播放器&#xff0c;AudioClip可以理解为音频片段文件。AudioSource可以通过.clip属性切换音频片段。 using UnityEngine;public class PlayerController : MonoBehaviour {private int…

油分离器的介绍

压缩机的排气中带有冷冻机油&#xff0c;这些冷冻机油如果随制冷剂蒸汽进入冷凝器、蒸发器后将 在传热表面形成油膜&#xff0c;从而影响换热效果。因此通常在压缩机与冷凝器之间装设油分离器&#xff0c;用 来分离制冷剂蒸汽中挟带的冷冻机油。在氟利昂制冷系统中&#xff0c;…

搜索引擎评价指标及指标间的关系

目录 二分类模型的评价指标准确率(Accuracy,ACC)精确率(Precision,P)——预测为正的样本召回率(Recall,R)——正样本注意事项 P和R的关系——成反比F值F1值F值和F1值的关系 ROC&#xff08;Receiver Operating Characteristic&#xff09;——衡量分类器性能的工具AUC&#xff…