ListView
基本认知:
ListView绝对可以称得上是Android中最常用的控件之一,几乎所有应用程序都会用到它。由于手机屏幕空间都比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助ListView来实现。
工作原理:
当屏幕需要显示x个item时,那么ListView只会创建x+1个视图,当第一个item离开屏幕时,此item的view就会被拿来重用(用于显示下一个item(即第x+1个)的内容)。
使用方法:
1.创建一个ListViewTest项目,让Android Studio自动创建好活动。然后修改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/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
</ListView
</LinearLayout
引入了ListView后,即使你的ListView没有内容,可视化编辑器preview仍然会这样显示:
preview.png2.为了便于管理,可以在res目录下的values文件夹下新建一个array.xml文件,内容如下:
<?xml version="1.0" encoding="utf-8"?
<resources
<string-array name="apps"
<item支付宝</item
<item微博</item
<item微信</item
<itemQQ</item
<item知乎</item
<item淘宝</item
</string-array
</resources
3.在Activity初始化的时候加载资源,内容如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
/**
* 初始化ListView,内容适配
*/
private void init(){
//加载array.xml内容
Resources resources = this.getResources();
String[] data = resources.getStringArray(R.array.apps);
//适配器,适配内容
ArrayAdapter<String adapter = new ArrayAdapter<String(
MainActivity.this, android.R.layout.simple_list_item_1, data
);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
}
添加item.png
4.适配器Adapter介绍:
既然ListView是用于展示大量数据的,这些数据可以是从网上下载的,也可以是从数据库中读取的,应该视具体的应用场景来决定。数组中的数据是无法直接传递给ListView的,还需要借助适配器来完成。Android中提供了很多的适配器的实现类,其中最简单的就是ArrayAdapter(注意不是SimpleAdapter)。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入即可。ArrayAdapter有多个构造函数的重载,根据需要选择最合适的一种。
①ArrayAdapter:简单、易用的Adapter,用于将数组绑定为列表项的数据源,支持泛型操作
②SimpleAdapter:功能强大的Adapter,用于将XML中控件绑定为列表项的数据源
③SimpleCursorAdapter:与SimpleAdapter类似,用于绑定游标(直接从数据数取出数据)作为列表项的数据源
④BaseAdapter:可自定义ListView,通用用于被扩展。扩展BaseAdapter可以对各个列表项进行最大程度的定制。
这里简单介绍其中的一种:new ArrayAdapter<( Context context , @LayoutRes int resource , String[] objects );
Context:context表示上下文对象,参数:MainActivity.this
@LayoutRes:resource表示ListView子项布局的id,参数:android.R.layout.simple_list_item_1(android.R.layout.simple_list_item_1这是Android内置的布局文件,里面只有一个TextView,可用于简单显示一段文本)
List<String | String[]:objects中指定要适配的数据。
适配器构建好之后,还需要调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
5.定制ListView
首先定义一个实体类,作为ListView适配器的适配类型。新建类Apps,代码如下:
public class Apps {
private String name;
private int icon;
public Apps(String name, int icon) {
this.name = name;
this.icon = icon;
}
public String getName() {
return name;
}
public int getIcon() {
return icon;
}
}
然后需要为ListView的子项指定一个自定义的布局,在layout目录下新建list_view_item_layout.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"
<ImageView
android:id="@+id/iv_apps_icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:scaleType="fitXY" /
<TextView
android:id="@+id/tv_apps_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="30dp" /
<Switch
android:id="@+id/sh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginRight="30dp" /
</LinearLayout
为了简洁,我在ImageView和TextView没有引入内容。其实引入内容并不会对结果造成影响,而且可以在preview中预览,以便调整布局。
接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Apps类。新建类AppsAdapter,代码如下:
public class AppsAdapter extends ArrayAdapter<Apps {
private int resourceID; //resourceID指定ListView的布局方式
//重写AppsAdapter的构造器
public AppsAdapter(@NonNull Context context, int resource, List<Apps objects) {
super(context, resource, objects);
resourceID = resource;
}
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//获取当前Apps实例
Apps apps = getItem(position);
//使用LayoutInflater为子项加载传人布局
View view = LayoutInflater.from(getContext()).inflate(resourceID, null);
ImageView appsIcon = (ImageView) view.findViewById(R.id.iv_apps_icon);
TextView appsName = (TextView) view.findViewById(R.id.tv_apps_name);
//传入Apps对象的属性
appsIcon.setImageResource(apps.getIcon());
appsName.setText(apps.getName());
return view;
}
}
AppsAdapter重写了父类的一组构造函数,用于将上下文、ListView子项布局的id和数据都传递进来。另外又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。
在getView方法中,首先通过getItem()方法得到当前项的Apps实例,然后使用LayoutInflater来为这个子项加载我们传入的布局,接着调用View的findViewById()方法分别获取到ImageView和TextView的实例,并分别调用它们的setImageResource()和setText()方法来设置显示的图片和文字,最后将布局返回,自定义适配器就完成了。下面修改MainActivity中的代码中的init()为init_Entity(),如下所示:
private void init_Entity() {
Apps alipay = new Apps("支付宝", R.drawable.ic_alipay);
Apps weibo = new Apps("微博", R.drawable.ic_weibo);
Apps wechat = new Apps("微信", R.drawable.ic_wechat);
Apps QQ = new Apps("QQ", R.drawable.ic_qq);
Apps taobao = new Apps("淘宝", R.drawable.ic_taobao);
apps.add(alipay);
apps.add(weibo);
apps.add(wechat);
apps.add(QQ);
apps.add(taobao);
AppsAdapter adapter = new AppsAdapter(MainActivity.this, R.layout.list_view_item_layout, apps);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
效果图:
复杂item.png提高效率:
说ListView这个控件很难用,就是因为它有很多的细节可以优化,其中运行效率就是很重要的一点。目前我们的ListView运行效率是很低的,但Item量很少,可能体现不出来。但是当我们给上面加载资源代码来个循环,你就会发现ListView滑动卡顿。因为在AppsAdapter的getView()方法中每次都将布局重新加载了一遍,当ListView快速滚动的时候这就会成为性能的瓶颈。
getView()中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。在getView()方法中进行了判断,如果convertView为空,则使用LayoutInflater去加载布局,如果不为空则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候可以表现出更好的性能。
不过,虽然现在已经不会再去重复加载布局,但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例。
我们可以借助一个ViewHolder内部类来对这部分性能进行优化,修改BrowserAdapter中的setView()方法,如下所示:
//使用ConvertView,ViewHolder提高运行效率
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Apps apps = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceID, null);
viewHolder = new ViewHolder();
viewHolder.appsIcon = (ImageView) view.findViewById(R.id.iv_apps_icon);
viewHolder.appsName = (TextView) view.findViewById(R.id.tv_apps_name);
viewHolder.sh = (Switch) view.findViewById(R.id.sh);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.appsIcon.setImageResource(apps.getIcon());
viewHolder.appsName.setText(apps.getName());
return view;
}
class ViewHolder {
ImageView appsIcon;
TextView appsName;
Switch sh;
}
新增了一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为空的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为空的时候调用View的getTag()方法,把ViewHolder重新取出。这样所有的控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewById()方法来获取控件实例了。经过这两步的优化之后,ListView的运行效率完全可以满足我们的需要了。
优化前后内存对比:
优化前:
优化前内存.png优化后:
优化后内存.png可以看出,无论优化前后,刚启动时所需内存都是67M左右,但是当我们快速滑动时,优化后的ListView内存从53M下降到了37M左右,不再卡顿,运行效率得到了提高。
ListView的点击时间
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<? parent, View view, int position, long id) {
Apps app = apps.get(position);
Toast.makeText(MainActivity.this, app.getName().toString(), Toast.LENGTH_SHORT).show();
}
});
使用setOnItemClickListener()方法来为ListView注册一个监听器,当用户点击了ListView中的任何一个子项时都会回调onItemClick()方法,在这个方法中可以通过position参数判断出用户点击的是哪一个子项,然后执行相应的程序。
RecyclerView
基本认知:
在android5.0,开始出现了一些新特性,其中就包括了非常受欢迎的RecyclerView。RecyclerView是一种新的视图组,其目的是为任何基于适配器的视图提供相似的渲染方式。该控件用于在有限的窗口中展示大量数据集,它被作为ListView和GirdView控件的继承者。RecyclerView高度接耦,异常的灵活,通过设置不同的LayoutManager,ItemDecoration,ItemAnimator来实现令人想象不到的效果。
工作原理:
当屏幕需要显示x个item时,那么ListView只会创建x+1个视图,当第一个item离开屏幕时,此item的view就会被拿来重用(用于显示下一个item(即第x+1个)的内容)。
重要类及优点:
RecyclerView.Adapter :强制使用ViewHolder,性能优化,不需要重写getView(),只需要写好ViewHolder,View 的复用已经封装好。
LayoutManager:管理布局,设置为LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager可以轻易地实现ListView,GridView以及流式布局的列表效果。它还可以管理滚动和循环利用。
ItemAnimator:可实现增删动画
优点:
1.可通过复用ViewHolder实现view的复用,Item复用性高
2.高度解耦,灵活,可定制性、可扩展性高
3.通过布局管理器LayoutManager控制其显示方式
4.通过ItemDecoration控制Item间的间隔
5.通过ItemAnimator控制Item增删动画
使用方法:
1.添加依赖:
在项目的build.gradle中添加相应的依赖库:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//添加依赖
implementation 'com.android.support:recyclerview-v7:28.0.0'
}
2.在布局文件中使用:
<?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.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/
</LinearLayout
3.为RecyclerView准备一个适配器:(最重要就是这个类了)
public class IconAdapter extends RecyclerView.Adapter<IconAdapter.ViewHolder {
private List<Icon mIconlist;
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.icon_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
holder.IconView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
Icon icon = mIconlist.get(position);
Toast.makeText(view.getContext(), "you clicked view is " + icon.getName(), Toast.LENGTH_SHORT).show();
}
});
holder.IconImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
Icon icon = mIconlist.get(position);
Toast.makeText(view.getContext(), "you clicked image is " + icon.getName(), Toast.LENGTH_SHORT).show();
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Icon icon = mIconlist.get(position);
holder.IconImage.setImageResource(icon.getImageId());
holder.IconTxt.setText(icon.getName());
}
@Override
public int getItemCount() {
return mIconlist.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
View IconView;
ImageView IconImage;
TextView IconTxt;
public ViewHolder(View view) {
super(view);
IconView = view;
IconImage = (ImageView) view.findViewById(R.id.icon_image);
IconTxt = (TextView) view.findViewById(R.id.icon_txt);
}
}
public IconAdapter(List<Icon IconList) {
mIconlist = IconList;
}
}
4.在活动中使用RecyclerView,代码如下:(数据绑定,初始化等步骤和ListView的使用一致,不多说。)
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
IconAdapterHorization adapter = new IconAdapterHorization(iconList);
recyclerView.setAdapter(adapter);
最后贴一张三种布局的运行图:
RecyclerView.png上一篇:Android基础(5)—自定义View
下一篇:Android基础(7)—异步消息处理机制 Handler
精彩内容不够看?更多精彩内容,请到微信搜索 “危君子频道” 订阅号,每天更新,欢迎大家关注订阅!
微信公众号
网友评论