天天看点

《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_width="match_parent"
    android:layout_height="match_parent"
    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_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        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_width="match_parent"
    android:layout_height="match_parent"
    >
    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        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_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        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_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        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_width="100dp"
    android:layout_height="wrap_content">
    <!--布局中均水平居中-->
    <!--自定义布局文件-->
    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        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_width="match_parent"
    android:layout_margin="5dp"
    android:layout_height="wrap_content">
    <!--布局中均水平居中-->
    <!--自定义布局文件-->
    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        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的点击事件

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

继续阅读