您的位置:首页 > 产品设计 > UI/UE

自学Android之UI组件:(一)ListView功能详解And实战

2017-03-11 17:59 387 查看
转载的老板请注明出处:http://blog.csdn.net/cc_xz/article/details/61422291 万分感谢!

前言:

从本篇开始讲解关于UI方面的内容,不过数量不会很多,这是因为,比较基础的UI例如Button等,网上的相关教程非常多,而且使用起来也比较简单。关于UI的大部分精力计划将放在RecyclerView等高级UI上,以及自定义View中。

而且从本篇开始,将以代码为主,再给予非常详细的注释来进行说明。话不多说,本篇开始。

通过本篇,你将了解到:

1.初步实现一个ListView。

2.对ListView进行优化。

实现使用ListView显示数据:

首先来看本篇文章实现的效果:



代码思路如下:

/** 代码思路:
* 1.首先初始化所需组件,以及定义MainActivity的布局文件。
* 2.然后新建一个类定义ListView显示的数据格式。
* 3.通过ArrayList添加用于显示数据。
* 4.继承自BaseAdapter并定义Adapter的构造方法。
* 5.定义ListView布局页面。
* 6.定义Adapter的getView方法。
* 7.定义Adapter的其他方法。
* 8.应用Adapter。*/


以下代码在activity_main中写入:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

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

</LinearLayout>


以下代码在MainActivity中写入:

private String TAG = "来自于MainActivity";
private Context mContext;
private SpursAdapter mAdapter;
private ListView mListView;
private ArrayList<GetSpurs> mListData;


以下代码在MainActivity的onCreate()中写入:

/* 代码执行1:
* 1.将MainActivity的上下文环境作为参数传递给Context中,用于Adapter使用。
* 2.初始化ListView组件。值得注意的是,ListView是写在activity_main中的。
* 3.然后创建一个ArrayList对象,用于放置数据。*/
mContext = MainActivity.this;
mListView = (ListView) findViewById(R.id.ListView);
mListData = new ArrayList<>();


以下代码在新创建的GetSpurs类中写入:

/* 代码执行2:
* 1.首先确定ListView都需要显示那些内容,然后根据显示的内容的格式来创建变量。
* 2.1.这里分别创建用于显示名称的String变量,显示说明的String变量以及显示头像的int类型变量。
* 2.2.值得注意的是,图片资源路径在Android是由R文件进行存储,而R文件存储的格式就是int类型。3
* 3.接着对应创建的变量,分别创建构造方法及get方法。
* 4.值得注意的是,很多文章中都创建是set方法,但是本篇中不会用到set方法,所以无需设置。*/
public class GetSpurs {
private String TAG = "来自于GetSpurs";
private String mName;
private String mExplain;
private int mIcon;

public GetSpurs(String name, String explain, int icon) {
mName = name;
mExplain = explain;
mIcon = icon;
}

public String getName() {
return mName;
}

public String getExplain() {
return mExplain;
}

public int getIcon() {
return mIcon;
}
}


以下代码在MainActivity中写入:

/* 代码执行3:
* 1.通过创建好的ArrayList对象来将数据填充进去。
* 2.首先使用已经创建好的数据类型类(在前面创建ArrayList的时候已经指定了泛型)在.add()中新建一个内部匿名对象。
* 3.接着通过GetSpurs中的构造方法的的参数顺序来初始化希望在ListView中显示的数据的内容。
* 4.你可以按照我给出的数据来进行填充,也可以根据你自己的想法进行改造。*/
mListData.add(new GetSpurs("科怀·莱昂纳德","比赛终结者/窒息兄弟/马刺新领袖",R.drawable.im_spurs_1));
mListData.add(new GetSpurs("拉马库斯·阿尔德里奇","原中投小王子/现大里锋/马刺二当家/不是关键·篮板·不抢星人",R.drawable.im_spurs_2));
mListData.add(new GetSpurs("保罗·加索尔","进攻轴/速度巨慢/站撸抓冒",R.drawable.im_spurs_3));
mListData.add(new GetSpurs("托尼·帕克","法国自行车/小陀螺转/马刺掌舵者",R.drawable.im_spurs_4));
mListData.add(new GetSpurs("丹尼·格林","窒息兄弟/皇阿玛/不是关键·三分·不进星人",R.drawable.im_spurs_5));
mListData.add(new GetSpurs("帕蒂·米尔斯","米神/三分神经刀/XJBD小分队成员",R.drawable.im_spurs_6));
mListData.add(new GetSpurs("大卫·李","大腿李/篮下终结者/颜值帝",R.drawable.im_spurs_7));
mListData.add(new GetSpurs("马努·吉诺比利","妖刀/男神没有之一/XJBD小分队队长",R.drawable.im_spurs_8));
mListData.add(new GetSpurs("乔纳森·西蒙斯","身体爆劲/扣篮不进.....",R.drawable.im_spurs_9));
mListData.add(new GetSpurs("德维恩·戴德蒙","身体爆劲/真正蓝领/穷人小乔丹",R.drawable.im_spurs_10));
mListData.add(new GetSpurs("德章泰·穆雷","新秀一枚/潜力股",R.drawable.im_spurs_11));


以下代码在新创建的SpursAdapter类中写入:

/* 代码执行4:
* 1.首先创建一个类型,用于继承自BaseAdapter。
* 2.继承完后,根据编译器的提示,重写出getCount()等方法。
* 3.接着定义Adapter中所需的变量。
* 4.1.然后创建构造方法,用于在MainActivity调用Adapter时传输参数。
* 4.2.第一个参数是在MainActivity中定义好的数据源,Adapter就是用于将数据源进行处理,然后显示到ListView中。
* 4.3.由于我们需要定义ListView的布局,所以需要获取布局中的ID,这需要一个Activity的上下文环境,所以将MainActivity的上下文环境传输至此。*/
public class SpursAdapter extends BaseAdapter{
private String TAG = "来自于SpursAdapter";
private ArrayList<GetSpurs> mListData;
private Context mContext;

public SpursAdapter(ArrayList<GetSpurs> listData, Context context){
mListData = listData;
mContext = context;
}
@Override
public int getCount() {
return 0;
}

@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) {
return null;
}
}


以下代码在新创建的list_item Layout中写入:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">

<ImageView
android:id="@+id/ImageView"
android:layout_width="160dp"
android:layout_height="140dp"
android:paddingLeft="10dp" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:id="@+id/TextViewName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingTop="8dp"
android:textColor="#AAAAAA"
android:textSize="24sp" />

<TextView
android:id="@+id/TextViewSays"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingTop="5dp"
android:textColor="#72b6e3"
android:textSize="17sp" />
</LinearLayout>
</LinearLayout>


值得注意的是,当你在上面的布局文件中写出什么样子的布局,ListView就将以什么界面去显示布局。而且可能让你困惑的是,如果此时通过Android Studio的布局查看器查看当前布局,你会发现两个TextView只占了很少的部分,如下图:



这是因为,布局页面的垂直方向设置成了”horizontal”(水平方向),而最上层的LinearLayout中只有“两个”布局文件,分别是显示图片的ImageView和放置TextView的LinearLayout。

而我在放置TextView的LinearLayout中设置了”vertical”(垂直方向),又考虑到不同球员的名称长度、说明长度不同,所以并没有强行设置TextView的宽高,等到数据填充进去的时候,自动会根据数据源的大小而改变两个组件的大小。就像前面所展示的效果图一样。

以下代码在SpursAdapter的getView()中写入:

注意敲黑板,下面的代码是本篇的重中之重!!!

/* 代码执行6:
* 1.1.getView()是Adapter中最重要的接口回调,它用于将需要在视图中显示的数据同显示数据的组件进行绑定。
* 1.2.每增加一个Icon,则需要调用一次getView,用于创建Icon。
* 2.1.getView提供了三个参数,第一个参数为当前显示的Icon的位置,即:第一个Icon对应的值为0,第10个Icon对应的值为9。
* 2.2.converView则是一个用于获取View布局文件的对象,我们通过mContext对象中存储的MainActivity的上下文交给它,从而获得用于ListView显示的布局文件。
* 2.3.第三个参数是ViewGroup,即是否要定义这个ListView的父View。此处暂且不表。*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
/* 代码执行6:
* 1.1.首先通过LayoutInflater的.from()获取到一个Activity的上下文环境,即需要添加的Layout是依附哪个Activity上显示。
* 1.2.接着通过inflate()来获取定义好的Layout,即当前ListView显示的布局文件。
* 1.3.后面的null,就是添加的ListView的父View,可以添加一个ViewGroup类型的父布局,如果不需要也可以填写为null。
* 2.1.然后分别创建ListView布局文件中的三个组件的对象,但是值得注意的是,在获取ID时同在Activity中直接获取不同。
* 2.2.是通过定义好的convertView来调用findViewById()。
* 2.3.这是因为当前类并不是Activity,所以并不具有获取ID的方法,但是通过LayoutInflater可以获取到一个布局,且返回格式为View。
* 2.4.这就表示通过了一个定义好的View视图来获取ID,即获取这个定义好的View中你的组件ID。
* 3.1.之后分别给三个组件进行赋值。
* 3.2.所赋的值全部来自于在MainActivity中定义好的ArrayList,通过get(),这时就使用到了position参数。即:当前要显示哪个Icon,就从对应的ArrayList中取出对应的数据。
* 3.3.由于定义的ArrayList是通过GetSpurs定义的数据格式,当取出数据时,也需要通过GetSpurs的getXXX()来获取数据。(暂时还用不到set方法)。
* 4.1.再次强调一遍,每显示一个Icon,就调用一次getView(),所以每次都把新的View视图(convertView)返回给ListView(这里是Adapter)。*/
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, null);
ImageView imageView = (ImageView) convertView.findViewById(R.id.ImageView);
TextView textViewName = (TextView) convertView.findViewById(R.id.TextViewName);
TextView textViewSays = (TextView) convertView.findViewById(R.id.TextViewSays);

imageView.setBackgroundResource(mListData.get(position).getIcon());
textViewName.setText(mListData.get(position).getName());
textViewSays.setText(mListData.get(position).getExplain());

return convertView;
}


以下代码在SpursAdapter中写入:

/* 代码执行7:
* 1.1.首先定义getCount(),该方法是在ListView开始创建前,获取ListView所显示的长度的。
* 1.2.所以返回数据源的长度,告知ListView本次需要显示多少条数据。
* 2.1.getItem()是用于定义ListView中显示的组件类型,暂时设置null即可,下篇文章设置此处。
* 3.1.getItemId即返回与getItem()对应的ListView显示位置。*/
@Override
public int getCount() {
return mListData.size();
}

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

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


以下代码在MainActivity的onCreate()中写入:

/* 代码执行8:
* 1.首先创建SpursAdapter为对象,并且根据构造方法,将当前的数据源和上下文环境作为参数。(回想一下这两个参数的作用?)
* 2.接着,将定义好的Adapter应用到ListView中,此时ListView开始工作,显示内容。*/
mAdapter = new SpursAdapter(mListData, mContext);
mListView.setAdapter(mAdapter);


以上,你就可以试着运行程序了,这样实现的效果同前面展示的效果相同,再来看一遍,如下图所示:



分析现有的ListView:

好了,现在你的程序已经可以使用了,但是现在存在一个问题,那就是每当创建一个Icon,都会调用一次getView(),现在我们展示的内容还很少,只有10来条,而ListView中的组件也不多,只有3个(组件越多创建的对象越多)。但是现在毕竟只是测试环境,真正的APP上要显示的内容和组件数量要比现在多的多,虽然不会内存溢出,但是如果以现在的状态去运行,那么程序可能会被卡死。

那么这时候如果使用ViewHolder会提供程序运行效率,在不用ViewHolder的时候,每次调用getView方法,都会通过findViewById查找布局里的控件,这个是比较耗费时间的,使用ViewHolder可以把控件存储到ViewHolder里面,在使用时候直接从ViewHolder取出来就可以,这样能有效的提高程序效率。

现在来尝试优化一下。

优化ListView:

废话不多说,直接上代码:

以下代码在SpursAdapter中写入:

/* 代码执行9:
* 1.首先创建一个ViewHolder子类,在这个类中创建我们所需要的组件静态变量。
* 2.要知道,ViewHolder并不是Java或Android提供给我们的一种API,而是一种编程方式。
* 3.这是因为,原来每次新增一个Icon,都需要调用一次getView(),而每调用一次getView(),里面的所有对象、变量都会被重写创建。
* 4.于是采用这种方式,用来保存对象,配合if判断,就可以实现只有在第一次调用getView()创建对象,后面的使用原有对象即可。*/
public class ViewHolder {
ImageView imageView;
TextView textViewName;
TextView textViewSays;
}


以下代码在SpursAdapter的getView()中重写入(原有代码注释掉):

/* 代码执行9:
* 1.首先创建并初始化ViewHolder对象,用于获取其中与组件所对应的对象。
* 2.1.接着判断当前getView()接口传递来的convertView若为不空,则表示已经使用过ListView,无需创建对象,直接调用viewHolder即可。
* 2.2.反之,如果convertView如果为空,则表示是第一次使用ListView,则需要创建各个对象。
* 3.如果已经使用过ListView,则只需通过getTag()将打入的TAG(标记)取出,便可以获得已经完成的初始化操作(对象中包含的组件对象的赋值)。
* 4.1.如果没有使用过ListView,除了获取布局和通过ViewHolder获得组件ID外,还需要打标签。
* 4.2.打标签是指,将ViewHolder现有的信息,即对象初始化数据,通过标签的形式打入converView中,再通过第三步取出,则无需再次初始化组件。*/
ViewHolder viewHolder = new ViewHolder();
if (convertView != null) {
viewHolder = (ViewHolder) convertView.getTag();
} else {
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, null);
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.ImageView);
viewHolder.textViewName = (TextView) convertView.findViewById(R.id.TextViewName);
viewHolder.textViewSays = (TextView) convertView.findViewById(R.id.TextViewSays);
convertView.setTag(viewHolder);
}

/* 代码执行9:
* 1.设置数据的方式同原来的方式大致形同,但是通过ViewHolder获取组件对象来获取ID。*/
viewHolder.imageView.setBackgroundResource(mListData.get(position).getIcon());
viewHolder.textViewName.setText(mListData.get(position).getName());
viewHolder.textViewSays.setText(mListData.get(position).getExplain());


后记:

本篇通过一个比较简单的ListView应用讲解了ListView的使用方式,主要是每个方法的功能、每个步骤的作用。后续的文章也会使用这种方式来书写。

另外,刚刚看了一下效果,发现代码中的注释看着效果还是比较晕,所以推荐把源码下载下载,直接使用Android Studio来观看,个人感觉效果还是比较好的。代码中的注释也比较全。

源码传送门:http://download.csdn.net/detail/cc_xz/9778113
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android ui listview Adapter