《Android开发艺术探索》之Android性能优化ListView和RecyclerView(十七)

Posted 公孙勤修

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Android开发艺术探索》之Android性能优化ListView和RecyclerView(十七)相关的知识,希望对你有一定的参考价值。


                                                    第十五章  Android性能优化、ListView和RecyclerView

       过多使用CPU资源(处理耗时任务)可能会导致应用无法响应(ANR);过多使用内存可能会导致程序内存溢出(OOM)。本章介绍性能优化方案,包括:布局优化、绘制优化、内存泄漏优化、响应速度优化、ListView优化、Bitmap优化、线程优化以及一些优化建议。

       内存泄漏不会影响程序功能异常,但会导致android程序内存占用过大,本章介绍了内存泄漏工具MAT。

       程序设计除了功能开发、提高性能之外,还有一个是代码的可维护性和可扩展性。通过合理的设计原则去完成,包括良好的代码风格、清晰地代码层级、代码的可扩展性以及合理的设计模式。

(1)Android的性能优化方法

       主要介绍了布局优化,绘制优化,内存泄漏优化,响应速度优化,ListView优化,Bitmap优化,线程优化以及一些性能建议。

1.1.布局优化

      主要思想是减少布局文件的层级。

      首先删除布局中无用的控件和层级,其次有选择地使用性能较低的ViewGroup,譬如RelativeLayout,Framelayout优于LinearLayout,LinearLayout优于RelativeLayout,但Framelayout、LinearLayout嵌套时建议使用RelativeLayout,因为嵌套会增加层级、降低性能。

      布局优化另一种手段是采用<include>、<merge>标签和ViewStub。

1.1.1.<include>标签

       该标签可以讲一个指定的布局文件加载到当前的布局文件中。@layout/titlebar指定了另外一个布局,通过这种方式不用把该布局的内容重复写,仅支持android:layout开头的属性,比如高宽,不支持background等属性。若include标签指定了android:layout_*属性,则必须指定高和宽。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="vertical">

<include layout="@layout/titlebar"/>
.....
</LinearLayout>

1.1.2.<merge>标签

       < merge >标签一般和< include >一起使用从而减少布局的层级,eg:上面的示例是两个竖直的线性布局,那么显然被包含的线性布局是多余的,通过 < merge >标签就可以去掉多余的一层LinearLayout。

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="vertical">
<Button
android:layout_
android:layout_
android:text="@string/one" />
</merge>

1.1.3.ViewStub

       继承了View,非常轻量级且宽高都是0,本身不参与任何的布局和绘制过程,ViewStub的意义是按需加载所需的布局文件。使用时候再行加载。

1.2.绘制优化

        onDraw方法要避免执行大量的操作。体现在两方面:

       1.onDraw不要创建新的局部对象,onDraw可能会被频繁调用,一瞬间可能会产生大量临时对象,占用大量内存、系统频繁进行垃圾回收,降低效率;

       2.onDraw中不要做耗时操作,也不能执行重量级操作(譬如成千上万次循环),会抢占CPU时间片,View绘制不流畅。View绘制帧率保证60fps最佳,绘制时间不超过16ms。

1.3.内存泄漏优化

       一方面分析内存泄露的原因,另一方面使用MAT来找出潜在的内存泄漏并进行解决。

      场景一:静态变量导致的内存泄漏

public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//内存泄漏,Activity无法正常销毁,因为静态变量context引用了它
private static Context mContext;
//内存泄漏,mView是一个静态变量,他的内部持有了当前的Activity,所以Activity仍然无法释放
private static View mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
mView = new View(this);
}
}

       场景二:单例模式导致的内存泄漏

       提供一个单例模式的TestManager,可以接受外部的注册并将外部的监听器进行存储。单例模式的特点是其生命周期和Application保持一致,因此Activity对象无法及时被释放。

       场景三:属性动画导致的内存泄漏

       属性动画中有一类无限循环的动画(无限动画),如果在 Activity中播放此类动画且没有在 onDestroy中去停止动画,那么动画会一直播放下去,尽管已经无法在界面上看到动画效果了,并且这个时候 Activity的View会被动画持有,而View又持有了Activity,最终Activity无法释放。解决方法:在onDestroy中调用animator.cancel()来停止动画。

public class MainActivity extends AppCompatActivity  {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mButton = (Button) findViewById(R.id.mButton);
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton,"rotation",0,360).setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
//animator.cancel();
}
}

1.4.响应速度优化和ANR日志分析

        响应速度优化的核心思想是避免在主线程中做耗时操作,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件就会出现ANR。BroadcastReceiver如果10s内未执行完操作也会ANR。Service在20s之内未处理完也会ANR。

       解决方案:1.在子线程进行I/O操作;2.降低子线程优先级;3.使用Handler处理子线程结果;4.在onCreate和onResume回调方法中尽量减少耗时操作;5.BroadCastReceiver的onReceiver中也要尽量减少耗时操作;建议使用IntentService(异步的、会自动停止的Service)进行处理。eg://在onCreate中休眠30s   SystemClock.sleep(30 * 1000);

1.5.ListView和Bitmap优化

       ListView优化:1.复用convertView、使用ViewHolder并避免在getView中执行耗时操作;2.根据列表滑动状态来控制任务执行频率,当列表快速滑动时显然不合适加载图片;3.尝试开启硬件加速;4.item中有图片时采用异步加载、并对图片进行适当压缩;5.数据应当采用分页加载。

       Bitmap优化:通过BitmapFactory.Options对图片进行采样,主要用到了inSampleSize参数。

1.6.线程优化

       采用线性池避免程序中存在大量的Thread;线程池可避免线程创建、销毁所带来的开销,并且可以重用内部的线程;

1.7.一些性能建议:

       1.避免创建过多的对象;2.不要过多使用枚举,枚举占用的空间要比整型大;3.常量请使用static final来;修饰4.使用一些Android特有的数据结构,比如SparseArray和Pair等;5.适当使用软引用和弱引用;6.采用内存缓存和磁盘缓存;7.尽量采用静态内部类,这样避免潜在的由于内部类导致的内存泄漏。

(2)内存泄漏之MAT工具

        MAT是一款强大的内存泄漏分析工具。

(3)提高程序的可维护性

       代码风格、代码层次性、单一职责原则、面向扩展编程以及设计模式。

       代码风格:命名规范,代码排版,注释。

       代码层次:分层,将业务逻辑划分子逻辑,分解任务达到简化逻辑的目的;单一职责:每一层只关注少量的逻辑。

       面向扩展编程会使得程序有较好的扩展性。

       设计模式可以提高代码的可维护性和可扩展性。常见的设计模式包括单例模式、工厂模式以及观察者模式。灵活运用。        

(4)ListView

      ListView使用很常见。

4.1.ListView简单用法

      步骤一:布局中加入ListView控件,设置XML。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
>
<ListView
android:layout_
android:layout_
android:id="@+id/list_view"
/>
</LinearLayout>

步骤二:借助适配器来将数据传递给ListView,在这里选择ArrayAdapter,通过泛型来指定适配数据类型,在构造函数中将要适配的数据传入(当前上下文、ListView子项布局的id以及待适配的数据),最后调用setAdapter方法将构建好的适配器对象传递进去,这样ListView和数据之间的关联就完成了。

public class MainActivity extends AppCompatActivity {
private String[] data = {"Apple","Banana","Orange","Pear","Apple","Banana","Orange","Pear",
"Apple","Banana","Orange","Pear","Apple","Banana","Orange","Pear","Apple","Banana","Orange","Pear"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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);
}
}

4.2.定制ListView的界面

     步骤一:创建定义实体类,用于ListView适配器的适配类型。

/**
* 创建定义实体类,用于ListView适配器的适配类型。
*/
public class Fruit {
private String name;//水果名字
private int imageid;//水果对应的资源id
public Fruit(String name,int imageid){
this.imageid = imageid;
this.name = name;
}
public int getImageid() {
return imageid;
}
public String getName() {
return name;
}
}

步骤二:自定义布局文件,用于显示图片和名称,并让TextView在竖直方向上居中。

<!--自定义布局文件,fruit_item.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_>
<ImageView
android:id="@+id/fruit_image"
android:layout_
android:layout_
/>
<TextView
android:layout_
android:layout_
android:layout_gravity="center_vertical"
android:id = "@+id/fruit_name"
android:layout_marginLeft="10dp"/>
</LinearLayout>

步骤三:自定义适配器,继承自ArrayAdapter,并将泛型指定为Fruit。

/**
* 新建适配器继承自ArrayAdapter,将泛型指定为Fruit类。
*/
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(@NonNull Context context, @LayoutRes int resource,List<Fruit> objects) {
super(context, resource,objects);
this.resourceId = resource;
}
//在每个子项被滚动到屏幕内的时候被调用
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);//获取当前项的Fruit实例
//使用LayoutInflater来为这个子项加载我们的布局,第三个为false意思是只让我们在父布局中声明的layout属性生效,但不为View添加父布局。
View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
//获取两个控件的实例
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitname = (TextView) view.findViewById(R.id.fruit_name);
//设置显示的图片和文字
fruitImage.setImageResource(fruit.getImageid());
fruitname.setText(fruit.getName());
return view;
}
}

步骤四:MainActivity中初始化所有水果数据,调用构建适配器传递数据。

public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();//初始化水果数据
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
ListView listview = (ListView) findViewById(R.id.list_view);
listview.setAdapter(adapter);
}
private void initFruits() {
for (int i = 0; i < 5; i++) {
Fruit apple = new Fruit("Apple", R.mipmap.ic_launcher);
fruitList.add(apple);
Fruit banana = new Fruit("banana", R.mipmap.ic_launcher_round);
fruitList.add(banana);
Fruit orange = new Fruit("orange", R.mipmap.ic_launcher);
fruitList.add(orange);
Fruit grape = new Fruit("grpae", R.mipmap.ic_launcher_round);
fruitList.add(grape);
}
}
}

4.3.提升ListView的运行效率

       目前运行效率较低,因为在FruitAdapter的getView方法中,每次都会将布局重新加载一遍,当ListView快速滚动,就可能会遇到性能问题。

       解决方法一:convertView用于将之前加载好的布局进行缓存,以便之后可以进行复用。

//在每个子项被滚动到屏幕内的时候被调用
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);//获取当前项的Fruit实例
//使用LayoutInflater来为这个子项加载我们的布局,第三个为false意思是只让我们在父布局中声明的layout属性生效,但不为View添加父布局。
View view;
if(convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
}else{
view = convertView;
}
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
....
}

解决方法二:虽然convertview不会再去重复加载布局,但却在每次getView时候使用findviewbyid获取控件的实例。借助ViewHolder对这部分性能进行优化。ViewHolder可以对控件的实例进行缓存。若convertview为null时,将控件实例存放在ViewHolder中,然后调用View的setTag方法将ViewHolder存储到view当中。若不为null,每次取出即可。

public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(@NonNull Context context, @LayoutRes int resource,List<Fruit> objects) {
super(context, resource,objects);
this.resourceId = resource;
}
//在每个子项被滚动到屏幕内的时候被调用
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);//获取当前项的Fruit实例
//使用LayoutInflater来为这个子项加载我们的布局,第三个为false意思是只让我们在父布局中声明的layout属性生效,但不为View添加父布局。
View view;
ViewHolder viewHolder;
if(convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
viewHolder.fruitname = (TextView) view.findViewById(R.id.fruit_name);
view.setTag(viewHolder);
}else{
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
//设置显示的图片和文字
viewHolder.fruitImage.setImageResource(fruit.getImageid());
viewHolder.fruitname.setText(fruit.getName());
return view;
}

private class ViewHolder {
ImageView fruitImage;
TextView fruitname;
}
}

4.4.ListView的点击事件

       使用setOnItemClickListener来为ListView注册一个监听器,当点击ListView的一个子项时,就回回调onItemClick方法,这个方法可以通过position参数来判断出用户点击的是哪一个子项,获取并显示出来。

listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = fruitList.get(position);
Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
}
});

(5)更强大的滚动组件-RecyclerView

         ListView存在一定的缺陷性,譬如扩展性不够好,譬如无法实现横向的滚动。步骤如下:

5.1.RecyclerView的基本用法

       步骤一:dependicies闭包内添加如下内容,Sync now之后修改activity_main.xml布局文件:

dependencies {
.....
compile com.android.support:appcompat-v7:25.3.1
compile com.android.support.constraint:constraint-layout:1.0.2
compile com.android.support:recyclerview-v7:25.+
testCompile junit:junit:4.12
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_>

<android.support.v7.widget.RecyclerView
android:layout_
android:layout_
android:id="@+id/recycler_view" />
</LinearLayout>

步骤二:将实体类Fruit和自定义布局文件fruit_item.xml复制过来,接着为RecyclerView准备一个适配器,新建适配器FruitAdapter类继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,其中ViewHolder为内部类。

/**
* 新建适配器FruitAdapter类继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,其中ViewHolder为内部类。
*/
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
//由于继承自RecyclerView.Adapter,会重写三个方法。
private List<Fruit> mFruitList;
//定义内部类ViewHolder,继承自RecyclerView.ViewHolder,传入的参数是RecyclerView子项的最外层布局
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitimage;
TextView fruitname;

public ViewHolder(View itemView) {
super(itemView);
//获取布局中TextView和ImageView的实例
fruitname = (TextView) itemView.findViewById(R.id.fruit_name);
fruitimage = (ImageView) itemView.findViewById(R.id.fruit_image);
}
}
//将要展示的数据传递给全局变量mFruitList,后序操作都在此的基础之上。
public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
//创建ViewHolder实例,并将加载出来的布局fruit_item传递到构造方法中获取实例,最后返回的是ViewHolder实例。
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
//用于对RecyclerView子项的数据进行赋值,通过get获取position得到当前项的Fruit实例,然后设置到holder的imageview和textview中。
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitimage.setImageResource(fruit.getImageid());
holder.fruitname.setText(fruit.getName());
}
//告诉recylerview有多少子项,直接返回数据源的长度。
@Override
public int getItemCount() {
return mFruitList.size();
}
}

步骤三:MainActivity中初始化所有水果数据,获取RecyclerView实例,在其中设置了Linearlayout线性布局,将水果数据传入FruitAdapter构造函数中,并在Recylerview中完成适配器的设置。(一定要细心、细心、细心啊)

public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化所有水果数据
initFruits();
//获取RecyclerView实例
RecyclerView recyclerview = (RecyclerView) findViewById(R.id.recycler_view);
//创建一个LinearLayoutManager对象
LinearLayoutManager layoutmanager = new LinearLayoutManager(this);
//将线性布局设置进去
recyclerview.setLayoutManager(layoutmanager);
//将水果数据传入FruitAdapter构造函数中
FruitAdapter adapter = new FruitAdapter(fruitList);
//完成适配器设置
recyclerview.setAdapter(adapter);
}
private void initFruits() {
for (int i = 0; i < 5; i++) {
Fruit apple = new Fruit("Apple", R.mipmap.ic_launcher);
fruitList.add(apple);
Fruit banana = new Fruit("banana", R.mipmap.ic_launcher_round);
fruitList.add(banana);
Fruit orange = new Fruit("orange", R.mipmap.ic_launcher);
fruitList.add(orange);
Fruit grape = new Fruit("grpae", R.mipmap.ic_launcher_round);
fruitList.add(grape);
}
}
}

5.2.实现横向滚动和瀑布流布局

       步骤一:修改fruit_item.XML,将Linearlayout改成垂直方向排列,内部元素均水平居中。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_>
<!--布局中均水平居中-->
<!--自定义布局文件-->
<ImageView
android:id="@+id/fruit_image"
android:layout_
android:layout_
android:layout_gravity="center_horizontal"
/>
<TextView
android:layout_
android:layout_
android:layout_gravity="center_horizontal"
android:id = "@+id/fruit_name"
android:layout_marginTop="10dp"/>
</LinearLayout>

步骤二:使用 layoutmanager.setOrientation()修改布局方向即可。

public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
.....
LinearLayoutManager layoutmanager = new LinearLayoutManager(this);
//设置布局的排列方向,默认是纵向排列的,使用LinearLayoutManager.HORIZONTAL让其布局变为横向排列。
//RecyclerView将布局排列交给LayoutManager去管理,而非ListView自身,LayoutManager提供了一系列可扩展的布局排列接口
layoutmanager.setOrientation(LinearLayoutManager.HORIZONTAL);
//将线性布局设置进去
recyclerview.setLayoutManager(layoutmanager);
//将水果数据传入FruitAdapter构造函数中
FruitAdapter adapter = new FruitAdapter(fruitList);
//完成适配器设置
recyclerview.setAdapter(adapter);
}
.....
}

步骤三:除了LinearlayoutManager之外,我们还可以使用GridLayoutManager实现网格布局,使用StaggerGridLayoutManager实现瀑布流布局,在这使用StaggerGridLayoutManager展示下效果。修改fruit_item.xml调整布局,修改MainActivity,加入StaggerGridLayoutManager。

//xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_margin="5dp"
android:layout_>
<!--布局中均水平居中-->
<!--自定义布局文件-->
<ImageView
android:id="@+id/fruit_image"
android:layout_
android:layout_
android:layout_gravity="center_horizontal"
/>
<TextView
android:layout_
android:layout_
android:layout_gravity="left"
android:id = "@+id/fruit_name"
android:layout_marginTop="10dp"/>
</LinearLayout>
//MainActivity.java
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化所有水果数据
initFruits();
//获取RecyclerView实例
RecyclerView recyclerview = (RecyclerView) findViewById(R.id.recycler_01);
//创建一个LinearLayoutManager对象
// LinearLayoutManager layoutmanager = new LinearLayoutManager(this);
StaggeredGridLayoutManager layoutmanager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
//将线性布局设置进去
recyclerview.setLayoutManager(layoutmanager);
//将水果数据传入FruitAdapter构造函数中
FruitAdapter adapter = new FruitAdapter(fruitList);
//完成适配器设置
recyclerview.setAdapter(adapter);
}
private void initFruits() {
for (int i = 0; i < 5; i++) {
Fruit apple = new Fruit(getRandomLengthName("Apple"), R.mipmap.ic_launcher);
fruitList.add(apple);
Fruit banana = new Fruit(getRandomLengthName("banana"), R.mipmap.ic_launcher_round);
fruitList.add(banana);
Fruit orange = new Fruit(getRandomLengthName("orange"), R.mipmap.ic_launcher);
fruitList.add(orange);
Fruit grape = new Fruit(getRandomLengthName("grpae"), R.mipmap.ic_launcher_round);
fruitList.add(grape);
}
}
private String getRandomLengthName(String name) {
Random random = new Random();
int length = random.nextInt(20)+1;
StringBuilder builder = new StringBuilder();
for(int i =0;i<length;i++){
builder.append(name);
}
return builder.toString();
}
}

5.3.RecyclerView的点击事件

       ListView只能处理子项的点击事件,但却不能处理子项里面的某个按钮,Recyclerview既可以处理所有的View点击事件。

      修改ViewHolder类,在ViewHolder中添加fruitview来保存子项最外层布局的实例,然后再onCreateViewHolder中注册点击事件,对外部布局和ImageView都注册了点击事件。Recyclerview可以轻松实现任意控件和布局的点击事件。

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
//由于继承自RecyclerView.Adapter,会重写三个方法。
private List<Fruit> mFruitList;
//定义内部类ViewHolder,继承自RecyclerView.ViewHolder,传入的参数是RecyclerView子项的最外层布局
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitimage;
TextView fruitname;
View fruitview;
public ViewHolder(View itemView) {
super(itemView);
fruitview = itemView;
//获取布局中TextView和ImageView的实例
fruitname = (TextView) itemView.findViewById(R.id.fruit_name);
fruitimage = (ImageView) itemView.findViewById(R.id.fruit_image);
}
}
....
//创建ViewHolder实例,并将加载出来的布局fruit_item传递到构造方法中获取实例,最后返回的是ViewHolder实例。
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
final ViewHolder viewHolder = new ViewHolder(view);
viewHolder.fruitview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = viewHolder.getAdapterPosition();
Fruit fruit = mFruitList.get(pos);
Toast.makeText(v.getContext(),"you clicked the view"+fruit.getName(),Toast.LENGTH_LONG).show();
}
});
viewHolder.fruitimage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = viewHolder.getAdapterPosition();
Fruit fruit = mFruitList.get(pos);
Toast.makeText(v.getContext(),"you clicked the image"+fruit.getName(),Toast.LENGTH_LONG).show();
}
});
return viewHolder;
}
.....
}


以上是关于《Android开发艺术探索》之Android性能优化ListView和RecyclerView(十七)的主要内容,如果未能解决你的问题,请参考以下文章

《Android开发艺术探索》之Bitmap的加载和Cache(十四)

[读书笔记]《Android开发艺术探索》第十五章笔记

android开发艺术探索读书笔记之-------view的事件分发机制

android线程与线程池-----线程池《android开发艺术与探索》

android开发艺术探索学习 之 结合Activity的生命周期了解Activity的LaunchMode

《Android开发艺术探索》之View的事件体系和工作原理学习笔记