1. Xml -> View 对象

Xml 转换为 View 对象的三种方式

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view = null;
    // 方式一,底层调用 LayoutInflater.from
    // 参数一:上下文,参数二:布局资源 id
    // 参数三:ViewGroup(比如如 LinearLayout,RelativeLayout),如果传入,创建出的 View 就是 ViewGroup 的子 View,如果不需要放入 ViewGroup,传入 null
    view = View.inflate(mContext, R.layout.item, null);
    // 方式二
    view = LayoutInflater.from(mContext).inflate(R.layout.item, null);
    // 方式三,谷歌源码中使用这种方法
    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    view = inflater.inflate(R.layout.item, null);
}

2. BaseAdapter

Adapter 在 MVC 模式中起到控制器的作用,将数据填充到视图中

private class MyAdapter extends BaseAdapter {
    @Override
    public int getCount() {
        // 这个方法的返回值决定了当前 listview 究竟要展示多少条数据
        return 5;
    }

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

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // 有多少数据就会调用多少次 getView, getView 返回每一条数据的视图
        // position: 此视图的位置
        // convertView: 如果可能,要重用的旧视图
        // parent: 此视图最终要附加到的父级
        convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal,parent,false);
        TextView txt_aName = convertView.findViewById(R.id.txt_aName);
        txt_aName.setText("aaa");
        return convertView;
    }
}

3. BaseAdapter 优化

上面代码,每次我们都将 xml 转换为 View,如果我们滑动界面过快,导致系统来不及回收旧资源,并过多创建新的资源,会导致内存爆掉,所以我们需要尽可能的使用旧的视图。

convertView 即为系统给我们的可以重用的视图,如果为空,我们再去创建,不为空,直接使用即可。

findViewById 也会被调用多次,我们也需要优化,尽可能重用。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    // 为空则创建
    if (convertView == null) {
        convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal, parent, false);
        holder = new ViewHolder();
        holder.txt_aName = convertView.findViewById(R.id.txt_aName);
        convertView.setTag(holder);   //将Holder存储到convertView中
    } else {
        // 不为空则复用
        holder = (ViewHolder) convertView.getTag();
    }
    holder.txt_aName.setText("aaa");
    return convertView;
}

static class ViewHolder {
    TextView txt_aName;
}

4. ListView 入门

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

Listview 宽度和高度不要使用 wrap_content,会导致 getView 多次被调用(浪费性能),因为要计算界面到底要放几条数据

5. ArrayAdapter

每个条目中需要修改的数据十分简单 只有一个 textview 的内容需要修改 这个时候可以使用 arrayadapter

<?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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"/>
    <TextView 
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ListView lv_list =  findViewById(R.id.lv_list);
    String[] names = {"张三","李四","王五","赵六","冯奇"};
    // 第一个参数:上下文
    // 第二个参数:包含且只有一个 textview 的布局文件 id
    // 第三个参数 要展示的数据
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.item, names);
    lv_list.setAdapter(adapter);
}

6. SimpleAdapter

功能比较强大,用起来比较复杂

Activity_main.xml

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

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

</RelativeLayout>

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"
    android:padding="5dp" >
    <ImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"/>
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <TextView 
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:layout_marginLeft="5dp"
            android:text="旧情复燃...."/>
        <TextView 
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:layout_marginLeft="5dp"
            android:textColor="#88000000"
            android:text="旧情复燃...."/>
    </LinearLayout>

</LinearLayout>

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ListView lv_list = findViewById(R.id.lv_list);
    List<Map<String, String>> data = new ArrayList<Map<String,String>>();
    String[] from = {"title","content"};
    Map<String, String> item1 = new HashMap<String, String>();
    item1.put("title", "中国足球再次冲击世界杯");
    item1.put("content", "9月6日晚在沈阳与伊朗0:0");
    data.add(item1);
    Map<String, String> item2 = new HashMap<String, String>();
    item2.put("title", "黑马android95期高薪就业");
    item2.put("content", "黑马android95期高薪就业平均薪水20k");
    data.add(item2);
    int[] to = new int[]{R.id.tv_title,R.id.tv_content};
    // 第一个参数 上下文
    // 第二个参数 要显示的数据,每一个条目的数据放到一个 map 中,再放到 list 里
    // 第三个参数 要加载条目的布局文件的 id
    // 第四个参数 string 数组,每一个元素,都是保存要展示数据的 map 中的 key,要显示的数据是 key 对应的 value
    // 第五个参数 int 数组,存放的是要展示数据的控件 id,每一个元素跟 String 数组的元素要对应起来  
    // 总结: 展示数据是用 string 数组的元素作为 Key 到 map 中取值,取出的值展示到 int 数组中对应元素的控件 id 对应的控件上
    SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.item, from, to);
    lv_list.setAdapter(adapter);
}

7. 可复用的 BaseAdapter

参考文献:菜鸟教程

activity_main.xml

<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:orientation="vertical"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/list_book"
        android:layout_width="wrap_content"
        android:layout_weight="1"
        android:layout_height="0dp"/>

    <ListView
        android:id="@+id/list_app"
        android:layout_width="wrap_content"
        android:layout_weight="1"
        android:layout_height="0dp"/>

</LinearLayout>

item_one.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"
    android:padding="5dp">

    <ImageView
        android:id="@+id/img_icon"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:src="@mipmap/iv_icon_baidu" />

    <TextView
        android:id="@+id/txt_aname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:text="百度"
        android:textSize="20sp" />


</LinearLayout>

item_two.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"
    android:padding="10dp">

    <TextView
        android:id="@+id/txt_bname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="《第一行代码Android》"
        android:textColor="#F3684A"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/txt_bauthor"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:text="郭霖"
        android:textColor="#44BDED"
        android:textSize="18sp" />

</LinearLayout>

App.java

package com.mi.study;

public class App {
    private int aIcon;
    private String aName;

    public App() {
    }

    public App(int aIcon, String aName) {
        this.aIcon = aIcon;
        this.aName = aName;
    }

    public int getaIcon() {
        return aIcon;
    }

    public String getaName() {
        return aName;
    }

    public void setaIcon(int aIcon) {
        this.aIcon = aIcon;
    }

    public void setaName(String aName) {
        this.aName = aName;
    }
}

Book.java

package com.mi.study;

public class Book {
    private String bName;
    private String bAuthor;

    public Book() {
    }

    public Book(String bName, String bAuthor) {
        this.bName = bName;
        this.bAuthor = bAuthor;
    }

    public String getbName() {
        return bName;
    }

    public String getbAuthor() {
        return bAuthor;
    }

    public void setbName(String bName) {
        this.bName = bName;
    }

    public void setbAuthor(String bAuthor) {
        this.bAuthor = bAuthor;
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Context mContext;
    private ListView list_book;
    private ListView list_app;

    private MyAdapter<App> myAdapter1 = null;
    private MyAdapter<Book> myAdapter2 = null;
    private List<App> mData1 = null;
    private List<Book> mData2 = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        init();
    }

    private void init() {

        list_book = (ListView) findViewById(R.id.list_book);
        list_app = (ListView) findViewById(R.id.list_app);

        //数据初始化
        mData1 = new ArrayList<App>();
        mData1.add(new App(R.mipmap.iv_icon_baidu, "百度"));
        mData1.add(new App(R.mipmap.iv_icon_douban, "豆瓣"));
        mData1.add(new App(R.mipmap.iv_icon_zhifubao, "支付宝"));

        mData2 = new ArrayList<Book>();
        mData2.add(new Book("《第一行代码Android》", "郭霖"));
        mData2.add(new Book("《Android群英传》", "徐宜生"));
        mData2.add(new Book("《Android开发艺术探索》", "任玉刚"));

        //Adapter初始化
        myAdapter1 = new MyAdapter<App>((ArrayList) mData1, R.layout.item_one) {
            @Override
            public void bindView(ViewHolder holder, App obj) {
                holder.setImageResource(R.id.img_icon, obj.getaIcon());
                holder.setText(R.id.txt_aname, obj.getaName());
            }
        };
        myAdapter2 = new MyAdapter<Book>((ArrayList) mData2, R.layout.item_two) {
            @Override
            public void bindView(ViewHolder holder, Book obj) {
                holder.setText(R.id.txt_bname, obj.getbName());
                holder.setText(R.id.txt_bauthor, obj.getbAuthor());
            }
        };

        //ListView设置下Adapter:
        list_book.setAdapter(myAdapter2);
        list_app.setAdapter(myAdapter1);

    }
}

MyAdapter.java

public abstract class MyAdapter<T> extends BaseAdapter {

    private ArrayList<T> mData;
    private int mLayoutRes;           //布局id


    public MyAdapter() {
    }

    public MyAdapter(ArrayList<T> mData, int mLayoutRes) {
        this.mData = mData;
        this.mLayoutRes = mLayoutRes;
    }

    @Override
    public int getCount() {
        return mData != null ? mData.size() : 0;
    }

    @Override
    public T getItem(int position) {
        return mData.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = ViewHolder.bind(parent.getContext(), convertView, parent, mLayoutRes, position);
        bindView(holder, getItem(position));
        return holder.getItemView();
    }

    public abstract void bindView(ViewHolder holder, T obj);

    //添加一个元素
    public void add(T data) {
        if (mData == null) {
            mData = new ArrayList<>();
        }
        mData.add(data);
        notifyDataSetChanged();
    }

    //往特定位置,添加一个元素
    public void add(int position, T data) {
        if (mData == null) {
            mData = new ArrayList<>();
        }
        mData.add(position, data);
        notifyDataSetChanged();
    }

    public void remove(T data) {
        if (mData != null) {
            mData.remove(data);
        }
        notifyDataSetChanged();
    }

    public void remove(int position) {
        if (mData != null) {
            mData.remove(position);
        }
        notifyDataSetChanged();
    }

    public void clear() {
        if (mData != null) {
            mData.clear();
        }
        notifyDataSetChanged();
    }


    public static class ViewHolder {

        private SparseArray<View> mViews;   //存储ListView 的 item中的View
        private View item;                  //存放convertView
        private int position;               //游标
        private Context context;            //Context上下文

        //构造方法,完成相关初始化
        private ViewHolder(Context context, ViewGroup parent, int layoutRes) {
            mViews = new SparseArray<>();
            this.context = context;
            View convertView = LayoutInflater.from(context).inflate(layoutRes, parent, false);
            convertView.setTag(this);
            item = convertView;
        }

        //绑定ViewHolder与item
        public static ViewHolder bind(Context context, View convertView, ViewGroup parent, int layoutRes, int position) {
            ViewHolder holder;
            if (convertView == null) {
                holder = new ViewHolder(context, parent, layoutRes);
            } else {
                holder = (ViewHolder) convertView.getTag();
                holder.item = convertView;
            }
            holder.position = position;
            return holder;
        }

        @SuppressWarnings("unchecked")
        public <T extends View> T getView(int id) {
            T t = (T) mViews.get(id);
            if (t == null) {
                t = (T) item.findViewById(id);
                mViews.put(id, t);
            }
            return t;
        }


        /**
         * 获取当前条目
         */
        public View getItemView() {
            return item;
        }

        /**
         * 获取条目位置
         */
        public int getItemPosition() {
            return position;
        }

        /**
         * 设置文字
         */
        public ViewHolder setText(int id, CharSequence text) {
            View view = getView(id);
            if (view instanceof TextView) {
                ((TextView) view).setText(text);
            }
            return this;
        }

        /**
         * 设置图片
         */
        public ViewHolder setImageResource(int id, int drawableRes) {
            View view = getView(id);
            if (view instanceof ImageView) {
                ((ImageView) view).setImageResource(drawableRes);
            } else {
                view.setBackgroundResource(drawableRes);
            }
            return this;
        }


        /**
         * 设置点击监听
         */
        public ViewHolder setOnClickListener(int id, View.OnClickListener listener) {
            getView(id).setOnClickListener(listener);
            return this;
        }

        /**
         * 设置可见
         */
        public ViewHolder setVisibility(int id, int visible) {
            getView(id).setVisibility(visible);
            return this;
        }

        /**
         * 设置标签
         */
        public ViewHolder setTag(int id, Object obj) {
            getView(id).setTag(obj);
            return this;
        }
    }
}

8. ListView 多布局实现

参考:菜鸟教程

MutiLayoutAdapter.java

public class MutiLayoutAdapter extends BaseAdapter {

    //定义两个类别标志
    private static final int TYPE_BOOK = 0;
    private static final int TYPE_APP = 1;
    private Context mContext;
    private ArrayList<Object> mData = null;

    public MutiLayoutAdapter(Context mContext, ArrayList<Object> mData) {
        this.mContext = mContext;
        this.mData = mData;
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }

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

    @Override
    public int getItemViewType(int position) {
        if (mData.get(position) instanceof App) {
            return TYPE_APP;
        } else if (mData.get(position) instanceof Book) {
            return TYPE_BOOK;
        }
        return super.getItemViewType(position);
    }

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        int type = getItemViewType(position);
        ViewHolder1 holder1 = null;
        ViewHolder2 holder2 = null;

        if (convertView == null) {
            switch (type) {
                case TYPE_APP:
                    holder1 = new ViewHolder1();
                    convertView = LayoutInflater.from(mContext).inflate(R.layout.item_one, parent, false);
                    holder1.img_icon = convertView.findViewById(R.id.img_icon);
                    holder1.txt_aname = convertView.findViewById(R.id.txt_aname);
                    convertView.setTag(holder1);
                    break;
                case TYPE_BOOK:
                    holder2 = new ViewHolder2();
                    convertView = LayoutInflater.from(mContext).inflate(R.layout.item_two, parent, false);
                    holder2.txt_bname = convertView.findViewById(R.id.txt_bname);
                    holder2.txt_bauthor = convertView.findViewById(R.id.txt_bauthor);
                    convertView.setTag(holder2);
                    break;
            }
        } else {
            switch (type) {
                case TYPE_APP:
                    holder1 = (ViewHolder1) convertView.getTag();
                    break;
                case TYPE_BOOK:
                    holder2 = (ViewHolder2) convertView.getTag();
                    break;
            }

        }

        Object obj = mData.get(position);
        //设置下控件的值
        switch (type) {
            case TYPE_APP:
                App app = (App) obj;
                if (app != null) {
                    holder1.img_icon.setImageResource(app.getaIcon());
                    holder1.txt_aname.setText(app.getaName());
                }
                break;
            case TYPE_BOOK:
                Book book = (Book) obj;
                if (book != null) {
                    holder2.txt_bname.setText(book.getbName());
                    holder2.txt_bauthor.setText(book.getbAuthor());
                }
                break;
        }
        return convertView;
    }

    private static class ViewHolder1 {
        ImageView img_icon;
        TextView txt_aname;
    }

    private static class ViewHolder2 {
        TextView txt_bname;
        TextView txt_bauthor;
    }
}

核心:

  1. 重写 getItemViewType() 方法对应 View 是哪个类别。
  2. 重写 getViewTypeCount() 方法iew返回 总共多少个类别
  3. getView 会自动调用 getItemViewType 获得对应类别,再加载对应的 convertView

9. RecyclerView

替代 ListView 的,比 ListView 更强大,支持横向滚动

9.1 纵向滚动

布局:

fruit_item.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_height="wrap_content"
    android:layout_margin="5dp" >

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp" />

</LinearLayout>
<?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">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

adapter

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private List<Fruit> mFruitList;

    public FruitAdapter(List<Fruit> fruitList) {
        mFruitList = fruitList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }


    @Override
    public int getItemCount() {
        return mFruitList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(View view) {
            super(view);
            fruitImage = view.findViewById(R.id.fruit_image);
            fruitName = view.findViewById(R.id.fruitname);
        }
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<Fruit>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        // 指定 recyclerView 的布局方式
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }
}

9.2 横向滚动

fruit_item.xml

<?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:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />

</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<Fruit>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        // 指定 recyclerView 的布局方式
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }
}

9.3 网格布局

RecyclerView 提供了 GridLayoutManager 实现网格布局。

9.4 瀑布流布局

RecyclerView 提供了 StaggeredGridLayoutManager 实现瀑布流布局。

fruit_item.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_height="wrap_content"
    android:layout_margin="5dp" >

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp" />

</LinearLayout>

瀑布流布局的宽度根据布局的列数自动适配,不是固定值,使用 match_parent 即可

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<Fruit>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        // 垂直方向 3 列
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }
}

9.5 点击事件

RecyclerView 所有的点击事件都由具体的 View 去注册

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
        final ViewHolder holder = new ViewHolder(view);
        holder.fruitView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(), "you clicked view " + fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}