不要脸啦!咱们的官方群放在前面:(/ω\)
我建的这个群,一个很积极向上的群。主要是用来交流Android开发技术的,里面还有一个小分群,每天早晨6点,晨读练口语。也有一些小伙伴,自发组织背单词之类。
重点:里面小姐姐超多哇!(o゚▽゚)o 想加入进来的老铁姐妹们,来吧。
==================================================================
学安卓的小伙伴们,今天咱们通过一个列子,来学学Android RecyclerView的用法。RecyclerView毋庸置疑可以说是Android系统中,到目前为止最强大的控件。它可以加载任何布局,而且加载数量没有限制。做安卓开发,这块是你必须要会的。
咱们这个教程面向的读者定位是刚接触Android开发不久的新人,所以写的比较详细。Android开发大佬看这个教程,可能会有点啰嗦,尽量憋喷。
基础好的大佬可以直接去GitHub撸我的源码:
https://github.com/SteveSuper/Android-RecyclerView-Demo
下图就是我们今天要实现的列子,左图就是RecyclerView实现的布局,点击其中的每个条目可以启动对应的详情页面(右图)。 (•̀ω•́ 」∠)
跟着我做完整个列子,你将会增加以下技能:
1> 如何使用RecyclerView,这是最基本的。
2> 如何在RecyclerView中加载多布局,咱们这个例子就加载了两种布局。
3> 如何实现RecyclerView的点击事件,启动新的Acitivity。
4> 如何使用目前最流行的Glide,从网络加载图片。
5> 如何自定义Toolbar(或者叫ActionBar),让App更好看些。
6> 等等吧,还有好多其他小技能,不列举了。
在开始之前,我得先硬塞给你一张图,先给你简单科普下RecyclerView的实现原理,让你心里有点数。我知道你可能还没接触过RecyclerView这东西,读下面这一段可能会犯晕。没事,先尽力尝试理解下就好,也不用刻意记忆。下面这段话,等你长大成人了,慢慢的你就懂了....... ←_←
ღ⊙□⊙╱ 下面这里敲黑板了啊!要划重点了啊!
- RecyclerView在屏幕上显示的各个条目,加载的是同一个,或者几个xml布局文件,只不过是往里面塞了不同的数据而已。
- RecyclerView每个条目中的各个控件(ImageView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
- RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
- 有一个叫LayoutManager的家伙负责RecyclerView条目的显示方式,比如垂直显示,水平显示,还是卡片式显示等。
看完上面那一段话,我估计很多萌萌小白们,已经开始犯晕了,没事,犯晕是对学习RecyclerView最起码的尊重,挺好的...... (●´ϖ`●)
==【第一部分】===================================================
好,接下来咱们需要着手实现咱们的列子了。但在咱们实现上面那个相对比较复杂的列子之前,咱们先通过用最少,最简单的代码,先把最简单的RecyclerView运行起来。然后咱们一步一步的,增添代码,实现咱们最终的列子。好吧?
首先咱们要实现的Demo是这样子的:
交代一下我的开发环境:我写这篇教程的时候,我用的Android Studio版本是3.2,本地的JDK是1.8。
这部分的任务清单(To-Do List)
1> 新建Project
2> 添加RecyclerView库依赖
3> 修改下App主题颜色
4> 在activity_main.xml中添加RecyclerView组件
5> 创建RecyclerView条目的布局文件
6> 实现RecyclerView需要的ViewHolder和Adapter两个辅助类
7> 在MainActivity.java中启动RecyclerView
那咱们就按照上面的任务清单走,清晰明朗。
(1)首先建个新Project,咱们App的名称就叫:Meizi。剩下的,按照你平常的设置,一带一路默认就好,进入Project。
(2)添加RecyclerView库依赖:咱们今天要学的RecyclerView这个组件,存在于Android的一个支持库中:'com.android.support:recyclerview-v7:27.1.0'
,需要添加这个库的依赖。不过现在添加这个库很简单,在activity_main.xml页面视图中,直接点击图中的下载按钮就好。或者直接在build.gradle(Mocule:app)中,添加下面的库依赖是一样的。
implementation 'com.android.support:recyclerview-v7:27.1.1'
(3)修改下App主题颜色:接下来咱们先修改一下rec/values/colors.xml中的颜色值,并添加几个颜色(后面用),对应的代码如下:
rec/values/colors.xml
#FF1744
#CC1236
#FF1744
#000000
#FFFFFF
#DDDDDD
这里我把App标题栏和Android通知栏的颜色都设置成了红色,遵循的Material Design设计风格。
(4)在activity_main.xml中添加RecyclerView组件:接下来把activity_main.xml中的“Hello World”删掉,并添加一个RecyclerView组件。相关设置如图,对于代码如下。
activity_main.xml
(5)创建RecyclerView条目的布局文件:在创建条目布局文件之前,咱们先需要把下面这张妹子图片meizi.jpg素材放到res/drawbale目录中。
然后在res/layout目录下新建一个Layout resource file,起名为meizi_item.xml,其他都默认就好。这个meizi_item.xml就是咱们的RecyclerView中的每个条目的布局文件。上面两步操作完后,显示如下图:
咱们的这个条目布局文件很简单:父容器用ConstraintLayout ,里面就放一张照片。设置属性如下图,代码在下面。
嘱咐一句:父容器ConstraintLayout的layout_height
,要设置成wrap_content
。
至于为什么用ConstraintLayout ,小伙伴就自己搜搜它的优点以及高级用法吧,这货也是Android官方主推的组件。
好,基本的布局啥的,就这么多,接下来要进入重头戏了。
(6)实现RecyclerView需要的ViewHolder和Adapter两个辅助类。
下面的要注意啦啊!这里我需要发布下小高能预警!"(ºДº*) 有皇家晕码血统的小可爱们请先撤离下现场......(ノ°ο°)ノ 因为接下来咱们要实现RecyclerView的核心了。这里我有必要再贴出来RecyclerView的实现原理,铺垫一下:
- RecyclerView在屏幕上显示的各个条目,加载的是同一个,或者几个xml布局文件,只不过是往里面塞了不同的数据而已。
- RecyclerView每个条目中的各个控件(ImagaeView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
- RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
- 有一个叫LayoutManager的家伙负责RecyclerView条目的显示方式,比如垂直显示,水平显示,还是卡片式显示等。
好,按照上面的步骤,第一项的那个xml布局文件已经完成了,接下来需要实现传说中的ViewHolder和Adapter了。
接下来憋说话,先看我操作,伴随头晕目眩属正常生理现象。///•A•///
在java/com.nidebao.meizi包下,新建一个名为MeiziAdapter.java类,这个就是咱们的Adapter,让这个类继承RecyclerView.Adapter,并且需要在RecyleView.Adpter后面加上泛型
紧接着,在class MeiziAdapter类里面创建内部类ViewHolder,让这个ViewHolder继承RecyclerView.ViewHolder。上面这两波操作完后应该显示如下:
这两行红代码我偷偷解释下←_←:咱们的ViewHolder
继承RecyclerView.ViewHolder
,在RecyclerView的实现原理中也提到了,这是必须的,ViewHolder把控着RecyclerView里面控件。这里咱们把ViewHolder
写成了MeiziAdapter
的内部类。咱们的MeiziAdapter
也必须继承RecyclerView.Adapter
,它的功能就是为RecyclerView适配数据。并且需要在RecyclerView.Adapter
的后面加上ViewHolder
泛型。为什么这样写?Andriod组件设计人员就是这样的设计的,你点开一下那个RecyclerView.Adapter源码,你看它的类的定义就是下面这样子。
public abstract static class Adapter {
......
抛开这两行代码的内容,如果连这两句代码的逻辑都看不懂的小伙贼,你课下要偷偷补补Java基础了啊。ღ⊙□⊙╱
好,接下来咱们处理下姨妈红。先在内部类ViewHolder上按Alt+Enter键,选择"Create constructor matching super",建立匹配父类构造器。
然后在MeiziAdapter类上按Alt+Enter建,选择"Implement methods",实现那三个抽象方法。
操作完后,对比下图:
MeiziAdapter.java
package com.cool.meizi;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
public class MeiziAdapter extends RecyclerView.Adapter {
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
}
}
估计这一波小操作,晕倒了不少骚年...... 可以的。ಥ_ಥ
咱的Adapter和ViewHolder两个类已经定义好了,下面修改onCreateViewHolder(......)
方法,在里面增添3行代码如下:
@NonNull @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.meizi_item, parent, false);
return new ViewHolder(itemView);
}
听我解释:首先这个onCreateViewHolder(...)
方法就是用来创建ViewHolder的。咱们的代码,分三步实现这一目的:
- 通过
LayoutInflater.from(......)
方法拿到inflater
实例,如果翻译的话,我想把它翻译为”控件吹鼓器“比较合适,inflate的意思就是吹鼓,吹气球的意思。它的作用就是解析指定的xml,并把它像气球一样“吹起来”,用于显示在屏幕上。 - 接下来通过
inflater.inflat(......)
方法,把咱们之前定义的meizi_item.xml
条目布局文件”吹鼓“为View实例。 - 把上面拿到的
meizi_item.xml
的View
实例交给ViewHolder
的构造方法,创建ViewHolder
对象并返回。
好,然后把下面的getItemCount()
方法中的return 0
,改为return 100
。这个从方法名字上也能看出,这个货肯定是决定RecyclerView中条目数量的,没错。
@Override
public int getItemCount() {
return 100;
}
到此咱的ViewHolder和Adapter就暂时写好了。
(7)最后一步,在MainActivity.java
中启动RecyclerView:切换到MainActivity.java
,在其onCreate
方法中,增加下面5行代码。
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**增加下面五行代码 ****************************************/
MeiziAdapter adapter = new MeiziAdapter();
LayoutManager layoutManager = new LinearLayoutManager(this);
RecyclerView recyclerView = findViewById(R.id.recyclerView);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(layoutManager);
}
}
听我解释:ღ⊙□⊙╱
当MainActivity启动时,首先咱们创建了一个咱们自己的MeiziAdapter实例,等待传给RecyclerView。然后new
了一个LayoutManager
实例,上面也提到了,这个LayoutManager
的作用就是决定RecyclerView中条目的显示方式(垂直,水平,卡片式......) 这里的LineraLayoutManager
,就是垂直显示条目。
如果你想水平显示条目,可以这样写:
LayoutManager layoutManager = new LinearLayoutManager(
this, LinearLayoutManager.HORIZONTAL, false
);
如果你想卡片式显示条目,可以这样写(第一参数表示列数):
LayoutManager layoutManager = new StaggeredGridLayoutManager(
2, StaggeredGridLayoutManager.VERTICAL
);
小伙伴可以自己试试并看看效果,这里暂时不做过多扩展。
接下来通过findViewById(......)
,拿到RecyclerView,之后通过它的set...
方法分别为其设置了Adpter
和LayoutManager
。
对了,有的小伙贼可能用的比较老的Android Studio,用findViewById(...)
时,前面需要加上转型。
好啦,好啦!咱们的一小小部分就完成了!这就是能运行起来RecyclerView最少的代码!赶紧的,打开你的模拟器,运行一下!如果没有什么问题,你是按照我说的一步一步写过来的,你的安卓模拟器上显示的应该是这个样子:
如果你看到了这样的界面,我觉得这个时候咱有必要小兴奋一下,奖励给自己一根烟...... 不抽烟的可以叼根筷子意思一下,仪式感很重要...... 233333(ಡωಡ)
==【第二部分】===================================================
接下来,咱们修改下条目布局文件meizi_item.xml
,让咱们的RecyclerView更高端大佬一些。咱们即将实现的样式如下:
这部分的任务清单(To-Do List)
1> 为Project添加几个Icon图标素材
2> 重新布局meizi_item.xml
这部分很简单,就是添加了几个Icon,修改meizi_item.xml
,把上面列子的图片替换为了一个更复杂点的布局而已,没动其他任何Java代码,来一起实现这两步。
首先咱们需要为咱们的项目添加几个icon,如图中的小人icon,爱心icon,评论icon,看我操作。
在res/drawable目录上,右击,依次选择New > Vector Asset,然后双击666!.................. 额,你若真双击了,麻烦退回去,单击就好,不好意思,节目效果,必须有的...... 说下你可能知道的快捷操作:点击下drawable目录,按Alt+Insert,然后选择Vector Asset,可以达到同样的疗效。
下面就来到下面这个视图:
然后点击上面的小Android图标,进入下面的界面:
这里面所有的Icon都是Google粑粑提供的官方Material Icons,左上角的搜索框可以搜索,选择你喜欢的就好。重复这样的操作,咱们需要3个icon,最后你的icon应该出现在左侧的drawable目录里,如下图:
好啦,准备工作就这么多!下面开始重新编写meizi_item.xml
文件。我这里实在不能一步一步的给你演示ConstraintLayout怎么布局各个小控件了啊,要不然这教程就太臃肿了。如果有不会Android ConstraintLayout(约束性布局),自己费点心,先补补这块儿,很简单的。下面我把设计好后的视图,以及代码留下。视图中,各个控件的关系,以及距离都能看清楚,自己比葫芦画瓢按照我的做就好。
代码清单:meizi_item.xml
好啦,这一块儿就这么多,大家应该能搞定吧!搞定后,赶紧再运行一下,模拟器上的显示视图应该是这个样子:
好,如果你实现到了这一步,赶紧给自己点小奖励,再来跟事后烟?(ಡωಡ) ......
接下来,来点小插曲:
你有没有发现,咱们的例子每个条目之间的空隙有点大?感觉有点丑,能不能在条目之间加上一个分割线?
要实现这个,有很多方式:你可以在条目布局中直接插入一个分割线,不过这种方式以后的扩展性不好,这里使用一种更高级的方式。
RecyclerView有一个ItemDecoration
类,“decoration”是修饰,装饰的意思。这个ItemDecoration
就是专门用来设置RecyclerView条目之间空隙,分割线的类。实现也很简单,看我操作:
首先新建一个MeiziItemDecoration
类,让他继承RecyclerView.ItemDecoration
。所有的代码如下:
package com.cool.meizi;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class MeiziItemDecoration extends RecyclerView.ItemDecoration {
private final int verticalSpaceHeight;
public MeiziItemDecoration(int verticalSpaceHeight) {
this.verticalSpaceHeight = verticalSpaceHeight;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.bottom = verticalSpaceHeight;
}
}
verticalSpaceHeight
是咱们自定义的一个成员变量,咱用它来把控条目之间的空隙大小。上面咱们重写了getItemOffsets(...)
方法,outRect.bottom = verticalSpaceHeight;
就是设置条目底部间距的大小。
如果你想设置左右空隙,和上部空隙,增加outRect.top
, outRect.left
和 outRect.right
这几个属性就好。
如果你不想给最后一个条目加上间距,在getItemOffsets(...)
方法内,加上下面这个判断:
if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) {
outRect.bottom = verticalSpaceHeight;
}
好,然后回到activity_main.xml文件,给RecyclerView加上一个灰色背景色:
android:background="@color/grayLite"
最后在MainActivity.java中,添加一行代码,把咱们的修饰类添加给RecyclerView:
recyclerView.addItemDecoration(new MeiziItemDecoration(2));
我指定的大小为2,你可以根据你的喜好修改。编译运行,看看条目之间是不是有分割线效果了?
==【第三部分】===================================================
好,接下来咱们进入本教程的核心中的核心了!实现用Java代码的方式,让RecyclerView动态加载数据,这块还涉及到用Glide从网络加载图片。下面是重点中的重点,代码量也有点大,晕码的哥们,你想晕就晕吧,窝不管了..... ( •̀⊿•́)ง120,我希望你能顶得住!好,开始啦!
接下来要实现的样式是介样的...... 注意哦,好消息是,咱不需要导入图片素材,咱们来从网络加载图片! (•̀ᴗ•́)و ̑̑
这部分的任务清单(To-Do List)
1> 创建提供数据的Model,为RecyclerView提供数据用。
2> 修改ViewHolder内部类,拿到条目中的控件。
3> 如何使用Glide加载图片。
4> 修改MeiziAdapter类,让其给RecyclerView条目中的控件适配数据。
好,既然动态加载数据,肯定要有一个能提供数据的Model(数据模型),跟着我操作,在目录java/com.nidebao.meizi
包下,先建一个Meizi.java类,代码如下,:
package com.cool.meizi;
public class Meizi {
private String imageUrl;
private String title;
private String name;
private int favorites;
private int comments;
public Meizi(String imageUrl, String title, String name, int favorites, int comments) {
this.imageUrl = imageUrl;
this.title = title;
this.name = name;
this.favorites = favorites;
this.comments = comments;
}
public String getImageUrl() { return imageUrl; }
public String getTitle() { return title; }
public String getName() { return name; }
public int getFavorites() { return favorites; }
public int getComments() { return comments; }
}
咱的Meizi就5个变量:imageUrl
,title
,name
,favorites
,comments
,不至晕吧?然后创建了构造方法,和Getters。
好,妹子模型有了,下面咱再建一个“妹子工厂”,MeiziFactory.java,专门来生产大量的Meizi。(´இ皿இ`) 代码如下:
package com.cool.meizi;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MeiziFactory {
private static String[] imageUrls = {
"https://upload-images.jianshu.io/upload_images/11181600-db48df38b76ed467.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-f9cc67a96b58d49e.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-4860ecb6acb8fce5.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-a5db9d1018924867.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-d2131192b602b425.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-e3002c837b5335f9.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-2ddfaaa766a399e5.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-d58b814b598ea3d3.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-8e2e151505cf6899.jpg",
"https://upload-images.jianshu.io/upload_images/11181600-a535283f3402accb.jpg"
};
private static String[] titles = {
"I'm the most beautiful girl in the world! Do you agree?",
"I'm Isabella. Long time no see, do you miss me?",
"Sophia here! I need more boyfriends! More money!",
"Did you watch my new movie? You can't miss it.",
"Hi there! I miss you so much, call me PLEASE!",
"Here's Hannah from Japan. Do you wanna be my guy?",
"Hi! My name is Emma, I have a lot of boyfriends!",
"Hey man! I just wanna tell you, I love you! :)",
"Please be my boyfriend! I need you sooooo much!",
"I'm Sofia, I'm just a very shy girl. Do you like me?"
};
private static String[] names = {
"Olivia", "Isabella", "Sophia", "Elizabeth",
"Charlotte", "Hannah", "Emma", "Emily", "Harper", "Sofia"
};
public static List createMeizis(int num) {
Random rand = new Random();
List meizis = new ArrayList<>();
int arySize = imageUrls.length;
for (int i = 0; i < num; i++) {
int a = i % arySize;
String url = imageUrls[a];
String title = titles[a];
String name = names[a];
int favorites = rand.nextInt(10000);
int comments = rand.nextInt(10000);
meizis.add(new Meizi(url, title, name, favorites, comments));
}
return meizis;
}
}
代码有一点小小多,但不难理解。里面我放了3个Array数组:
imageUrls
里存放的妹子图片网络地址,等会咱们加载用。这些地址都是我自己把图片上传后得到的,你打开每个地址就是一张图片。所有的图片就在这里
https://www.jianshu.com/p/9ae7d796213a
titles
是我随便写的,与妹子的数量对应。
names
是10个妹子的名字。
public static List createMeizis(int num) {
...
}
这个静态方法就是用来生产Meizi的,基础差的小伙伴好好撸撸,不难的,我不多解释了。
OK,Meizi数据模型建好了,那么怎么动态的向RecyclerView中的条目中添加Meizi数据呢?想向条目中添加数据,你首先得能获取到条目中的各个控件吧(TextView, ImageView, Button等)?怎么获取?在哪里获取?难道还是像以前一样,在Activity中,通过findViewById(......)的方式获取?...... 小伙贼,你的想法很好,但不是这样的...... (<_<)
如果你记性好,咱们上面提到了,RecyclerView中的条目,是通过ViewHolder控制的,由他来提供访问权限。
好,小乖乖们下面看我在哪里写,以及怎么写。
修改ViewHolder
内部类,让其持有RecyclerView条目中的各个控件: 回到咱们的MeiziAdapter.java文件,在咱们的内部类ViewHolder
中,添加如下代码。
ViewHolder.class
class ViewHolder extends RecyclerView.ViewHolder {
ConstraintLayout meiziItem; //这里也拿到了条目的Layout,后面添加onClick()方法用;
ImageView image;
TextView title;
TextView name;
TextView favorites;
TextView comments;
public ViewHolder(View itemView) {
super(itemView);
meiziItem = itemView.findViewById(R.id.meizi_item);
image = itemView.findViewById(R.id.meizi_item_image);
title = itemView.findViewById(R.id.meizi_item_title);
name = itemView.findViewById(R.id.meizi_item_name);
favorites = itemView.findViewById(R.id.meizi_item_favorites);
comments = itemView.findViewById(R.id.meizi_item_comments);
}
}
听我解释:这些代码应该不难理解,就是在ViewHolder类中,添加了咱们需要访问的ImageView,TextView控件的成员变量。然后紧接着在ViewHolder的构造器中,通过findViewById(......)
的方式,初始化,或者说获取到那些控件的对象。只不过这里的findViewById(......)
和Activity中的不一样,前面需要有view.findViewById(......)
,其实Activity中的那个findViewById(......)
只是这个的语法糖而已,性质上是一样的。
既然拿到了一个个控件的对象,咱们接来下就可以通过viewHolder.xxx的语法操控这些控件了。
接下来就要用到大名鼎鼎的Glide了,需要让其给Adaper加载网络图片,用这货其实很简单,几步操作搞定:
首先给咱们的App添加网络访问权限,在AndroidManifest.xml
中,添加下面的权限:
别把权限放错地方了亲爱的,所以给你截了个图。
接着在build.gradle(Mocule:app)中,添加Glide库依赖。
implementation 'com.github.bumptech.glide:glide:4.7.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
咱们这个教程没有涉及Glide的高级用法,可以不添加annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
这行依赖,只添加implementation 'com.github.bumptech.glide:glide:4.7.1'
这一行依赖也行,编译更快。具体的请参考Glide官方文档,有中文。
https://github.com/bumptech/glide
好啦,下面回到MeiziAdapter.java,接下来修改MeiziAdapter类,让其给RecyclerView条目中的控件适配数据: 编写MeiziAdapter中的onBindViewHolder(...)
方法,这个方法从名字上就能看出,它是绑定数据用的。你看这个方法需要的参数:(ViewHolder holder, int positon)
,里面的holder肯定是上面的public ViewHolder onCreateViewHolder(...)
方法返回的holder
,position
肯定是RecyclerView条目的位置。
给其添加两个成员变量:Context context;
, List
和对应的构造方法,代码如下:
Context context;
List meizis;
public MeiziAdapter(Context context, List meizis) {
this.context = context;
this.meizis = meizis;
}
然后在onBindViewHolder( ... ) { ... }
中添加如下代码,并把getItemCount(...)
中的return 100;
改为return meizi.size();
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Meizi meizi = meizis.get(position);
Glide.with(context)
.load(meizi.getImageUrl())
.into(holder.image);
holder.title.setText(meizi.getTitle());
holder.name.setText(meizi.getName());
holder.favorites.setText(String.valueOf(meizi.getFavorites()));
holder.comments.setText(String .valueOf(meizi.getComments()));
}
@Override
public int getItemCount() {
return meizis.size();
}
其中
Glide.with(context)
.load(meizi.getImageUrl())
.into(holder.image);
就是Glide的用法,如果你的项目不需要特殊的图片加载优化,基本上这一行代码都能搞定图片加载。
其他的简要解释下:这一块代码,先根据条目的位置拿到对应的Meizi,然后通过Meizi这个实例获取到各个数据(image, title, name, favorites, comments),然后把这些数据通过ViewHolder(holder)适配给RecyclerView的条目中。
下面的getItemCount()
,返回List
的大小,有多少妹子,就创建多少个条目。
不知道小白白们晕了没有?......坚持住,马上就可以事后烟了!→_→
最后一步!回到MainAcitvity.java中,修改如下代码:
咱们创建了一个List
好,不罗嗦,现在赶紧准备好下一根事后烟,从新编译运行下,看看是不是像我下面的样子......
到这里,如果你硬着头皮挺了过来,那么你已经学会了如何动态的向RecyclerView中添加数据了!而且是从网络加载的图片,厉害啊!(๑´ㅂ`๑́)و✧
===============================================================
好,在这个时间点,咱们再来回顾下RecyclerView的实现原理,效果最佳。你再反复阅读下你刚才写的代码,再看看下面这4条,参照下图片,看看是不是有点道理了?
- RecyclerView在屏幕上显示的各个条目,加载的是同一个,或者几个xml布局文件,只不过是往里面塞了不同的数据而已。
- RecyclerView每个条目中的各个控件(ImagaeView,TextView,Button......),是由一个叫ViewHolder的家伙把控的。这个ViewHolder需要咱们自己写,并且它需要继承自RecyclerView.ViewHolder。
- RecyclerView每个条目中的数据,是由一个叫Adapter的家伙适配的,并且它需要上面的ViewHolder的配合。这个Adapter也是需要咱们自己实现的类,并且它需要继承自RecyclerView.Adapter。
- LayoutManager负责RecyclerView条目的显示方式,比如垂直显示,水平显示,还是卡片式显示等。
.
.
==【第四部分】===================================================
好,接下来咱们中场小憩一下,来学点轻松点的:如何自定义Toolbar(也叫ActionBar),这部分涉及到的Java代码不多,先对轻松些,来一块完成。
首先在res/values/styles.xml中增加自定义主题。如下图:
咱们自定义的AppTheme.NoActionBar主题功能就是把MainActivity中系统工具栏去掉。下面两行主题等会咱们自定义的Toolbar要用,先别问我咋回事,你跟着我做完就知道什么是干什么的了。
打开AndroidManifest.xml,把咱们自定义的主题应用到MainActivity上。
如图,加入这行代码就好。
android:theme="@style/AppTheme.NoActionBar"
回到activity_main.xml视图,选中视图预览主题为AppTheme.NoActionBar。这个只是方便咱们设计预览用。
接着在res/layout目录下新建一个toolbar.xml资源布局,代码如下:
然后在activity_main.xml中,用
注意,我这里给android:id="@+id/toolbar"
,下面MainActivity.java中要用到这个id。当然,这个id你可以直接加在toolbar上面(你明白的我的意思骂?)。还有,更改下RecyclerView中的app:layout_constraintTop_toBottomOf="@+id/toolbar"
属性。
这里需要感谢下Redleeo,我之前把
回到MainActivity.java中,加入下面两行代码:
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
这个就是设置咱们刚才自定义的Toolbar用的,注意,Toolbar类要引入import android.support.v7.widget.Toolbar;
。
重新编译运行下,看看咱们刚才自定义的Toolbar能不能显示出来?
......
1年以后......
......
哎,你会发现和之前没变化啊?
是,看上去没样式和之前没变化,但现在显示的Toolbar和系统自带的ActionBar完全是两回事。咱们现在的Toolbar支持Material Design,而且功能更强大。总之让你这样做,都是为你好。还是等你长大了,你就懂了。←←←_←
下面咱们向咱们的新Toolbar中添加几个图标(icon),让例子更好看些。
首先再加入两个Vector图标,一个是menu图标,一个search图标,教过你了,应该会了吧?
Search图标一样,别忘记把颜色设置为白色。
好,现在在MainActivity中的onCreate方法中,加入下面几行代码:
toolbar.setNavigationIcon(R.drawable.ic_menu);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Yahoooo!", Toast.LENGTH_SHORT).show();
}
});
编译运行下,看看有什么惊喜?......
好,接着来,给toolbar添加一个搜索小图标。这里需要以“菜单menu”的方式添加。点击res目录,新建Android Resource File
,弹出如下窗口:
File name:起名为menu_toolbar
Resource type:选择Menu
双击OK。
编辑刚建的这个menu_toolbar.xml代码如下:
其中app:showAsAction="always"
表示总是显示图标到Toolbar,app:showAsAction="never"
表示隐藏图标不显示。
还没完,最后一步回到MainActivity.java,重写下面两个方法:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_toobar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
Toast.makeText(this, "Settings", Toast.LENGTH_SHORT).show();
return true;
} else if (id == R.id.action_search) {
Toast.makeText(this, "Search", Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
对的,这就是创建Toolbar上图标的方法以及点击事件处理。
getMenuInflater().inflate(R.menu.menu_toobar, menu);
就是专门实例化menu菜单的。public boolean onOptionsItemSelected(MenuItem item) { ... }
就是处理菜单按钮点击事件的。
重新编译运行下,看看是不是下面的样子。
如果你真的走到了这里,我觉得我如果再很虚伪,很肤浅的夸你:“你真棒哦!你好腻害!” ............................................. 也是可以的。 (๑´ڡ`๑)
好,到这里离咱们的最终的目标越来越近了!加油哇!
到目前,咱还有两块没完成:
1> 如何让RecyclerView加载多布局?
2> 如何给RecyclerView添加点击事件,启动新的Activity?
好,咱们一个一个来,先把如何让RecyclerView加载多布局这部分完成了,进入咱们的第五部分。
==【第五部分】===================================================
实现让RecyclerView加载多布局,下图就是咱们将要实现的效果。
如图,咱们的RecyclerView中有两种布局,一种大图布局,一种咱们之前创建的小图布局。
既然让RecyclerView加载两种布局,就得有对应的布局文件。来,在res/layout目录下,先创建另一个需要加载的布局文件meizi_item_big.xml。布局样式和代码如下:
注意:里面的关键控件的Id要与咱们上面的那个小布局有所区分,这里我给每个控件加了“big”以示区分,如:meizi_item_big;meizi_item_big_image等...
布局文件有了,那就加载吧。接下来要大改MeiziAdapter.java。小伙贼别走神,看我操作。皇家晕码的,请发挥你的贵族血统,开始你的表演。←_←
首先在MeiziAdapter.java里面,在新增一个ViewHolder内部类,起名为ViewHolderBig,如下:
class ViewHolderBig extends RecyclerView.ViewHolder {
ConstraintLayout meiziItem;
ImageView image;
TextView title;
TextView name;
TextView favorites;
TextView comments;
public ViewHolderBig(View itemView) {
super(itemView);
meiziItem = itemView.findViewById(R.id.meizi_item_big);
image = itemView.findViewById(R.id.meizi_item_big_image);
title = itemView.findViewById(R.id.meizi_item_big_title);
name = itemView.findViewById(R.id.meizi_item_big_name);
favorites = itemView.findViewById(R.id.meizi_item_big_favorites);
comments = itemView.findViewById(R.id.meizi_item_big_comments);
}
}
注意与之前创建的那个ViewHolder的区分,别搞混了。
接着把MeiziAdapter的RecyclerView.Adapter
的泛型改为
RecyclerView.Adapter
,会出现以下姨妈红。
然后把public void onBindViewHolder(@NonNull ViewHolder holder, int position) { ... }
改为如下:
下面还有错误提示对吧?没关系,你如果觉的碍眼,可以把显示错误的代码注释掉,反正等会都要大改。
接下来,加载多布局,需要重写一个叫public int getItemViewType(int position){ ... }
的方法。咱们需要给特定的position,指定特定的ViewType。我下面的逻辑是,position如果是3个倍数,就指定R.layout.meizi_item_big这种ViewType,其他的指定R.layout.meizi_item。代码如下:
@Override
public int getItemViewType(int position) {
if (position % 3 == 0) {
return R.layout.meizi_item_big;
} else {
return R.layout.meizi_item;
}
}
然后,要整改onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {... }
这个方法。首先把返回的类型改为ViewHolder和ViewHolderBig的父类RecyclerView.ViewHolder。至于要创建那种ViewHolder,需要根据ViewType来定,看我下面写的代码。我估计你应该有点小晕了。挑战下,就差最后一步了!(ノ≧∇≦)ノ
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == R.layout.meizi_item_big) {
return new ViewHolderBig(
inflater.inflate(R.layout.meizi_item_big, parent, false)
);
}
else {
return new ViewHolder(
inflater.inflate(R.layout.meizi_item, parent, false)
);
}
}
最后一步:要把咱们的onBindViewHolder( ... ) { ... }
大换血!改为如下代码,在下面我说下代码逻辑。
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
Meizi meizi = meizis.get(position);
if (position % 3 == 0) {
ViewHolderBig holderBig = (ViewHolderBig) holder;
Glide.with(context)
.load(meizi.getImageUrl())
.into(holderBig.image);
holderBig.title.setText(meizi.getTitle());
holderBig.name.setText(meizi.getName());
holderBig.favorites.setText(String.valueOf(meizi.getFavorites()));
holderBig.comments.setText(String.valueOf(meizi.getComments()));
} else {
ViewHolder holderSmall = (ViewHolder) holder;
Glide.with(context)
.load(meizi.getImageUrl())
.into(holderSmall.image);
holderSmall.title.setText(meizi.getTitle());
holderSmall.name.setText(meizi.getName());
holderSmall.favorites.setText(String.valueOf(meizi.getFavorites()));
holderSmall.comments.setText(String .valueOf(meizi.getComments()));
}
}
position % 3 == 0
这个条件是和getItemViewType()中那个条件一致的,要不然会出错。当position是3的倍数时,咱们上面指定的R.layout.meizi_item_big这种ViewType类型,当然要用ViewHolderBig为其绑定数据。要拿到ViewHolderBig,需要从RecyclerView.ViewHolder向下转型过来。ViewHolderBig holderBig = (ViewHolderBig) holder;
转型得到ViewHolderBig后,之后就做一系列的数据绑定了,和之前的一样。
下面的ViewHolder holderSmall = (ViewHolder) holder;
道理一样,不啰嗦了。
好,就这么多!赶紧编译运行下,看看是不是像下图这样?
如果你一步一步的走到了这一步,真的可以说你很牛逼了!我估计大部分人都看不到这句夸你牛逼的话。遇到点挫折就放弃是大多数人的本性,能迎难而上的,都是王者!(鸡汤)
好!咱们就差最后一步了!我本篇教程的任务也马上就完成了!来给RecyclerView添加点击事件,让他可以启动新的Activity。这一步,如果你有点Android基础,会实现点击事件,会启动Activity,那么就很简单了!
来,跟我一步一步做。
==【最后部分】===================================================
首先绑定条目的点击事件,这个有很多种实现方式,这里我用一个最简单的方式:给每个条目的父布局ConstraintLayout,Set OnClickListener。
(注:或者你也可以把OnClickListener绑定到ViewHolder的itemView上面,也很简单,一样的。)
不知道你还有没有印象,咱们在ViewHolder中已经拿到了每个条目的父布局实例,当时没用到,就是下面这两个。
咱们直接把点击事件绑定他们身上。在onBindViewHolder( ... )方法里添加,如下图:
另一个也需要添加。
运行下,点击各个条目,看看有没有反应?你戳妹子应该有反应的......(•̀ω•́ 」∠)(污)
好,下面咱们来实现点击item,启动传说中的Activity。
首先要建一个新的MeiziDetialActivity.java以及对应的xml文件。这个你应该会吧?
布局文件activity_meizi_detial.xml
下面在MeiziAdapter.java中,创建一个startMeiziActivity(Meizi meizi){ ... }
方法,并把它传给上面的两个onClick方法,启动Activity这基本是固定的模式,不解释了吧。
回到MeiziDetialActivity.java,编写代码如下:
package com.cool.meizi;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
public class MeiziDetialActivity extends AppCompatActivity {
ActionBar actionBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meizi_detial);
actionBar = getSupportActionBar();
getMeiziIntent();
}
private void getMeiziIntent() {
String imageUrl = getIntent().getStringExtra("image_url");
String title = getIntent().getStringExtra("title");
String name = getIntent().getStringExtra("name");
int favorites = getIntent().getIntExtra("favorites", 0);
int comments = getIntent().getIntExtra("comments", 0);
setMeizi(imageUrl, title, name, favorites, comments);
}
private void setMeizi(String imageUrl,
String title, String name, int favorites, int comments) {
ImageView iv_image = findViewById(R.id.meizi_detial_image);
TextView tv_title = findViewById(R.id.meizi_detial_title);
TextView tv_name = findViewById(R.id.meizi_detial_name);
TextView tv_favorites = findViewById(R.id.meizi_detial_favorites);
TextView tv_comments = findViewById(R.id.meizi_detial_comments);
Glide.with(this)
.load(imageUrl)
.into(iv_image);
tv_title.setText(title);
tv_name.setText(name);
tv_favorites.setText(String.valueOf(favorites));
tv_comments.setText(String.valueOf(comments));
actionBar.setTitle(name);
}
}
简单说几点,我在MeiziDetialActivity里增加了一个ActionBar actionBar成员变量,在onCreate方法中,通过actionBar = getSupportActionBar();
拿到actionBar的实例,在最后为其设置标题为妹子的名字。actionBar.setTitle(name);
其他的就两个方法,一个getMeiziIntent()
,一个setMeizi( ... )
。拿到从MeiziAdapter发送过来的Meizi“意图”Intent,并把相关数据Set给activity_meizi_detial.xml控件中。
好,最后一小步!给新的MeiziDetialActivity加上返回箭头,很简单。在AndroidManifest中,给MeiziDetialActivity加上android:parentActivityName=".MainActivity"
属性就完啦!
赶紧重新编译运行下!!!看看是不是实现了下面的效果!!!(´°̥̥̥̥̥̥̥̥ω°̥̥̥̥̥̥̥̥`)
欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧耶欧夜
完结撒花!完结撒花!完结撒花!完结撒花!完结撒花!完结撒花!完结
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿ヽ(゚▽゚)ノ✿✿✿
不知道你有没有挺过来!挺过来的,我敬你是条汉子稳如老狗!!!ლ(இдஇ;ლ )
===============================================================
学完不点赞,撅屁股走人的,诅咒你小鸡鸡自动脱落~(/TДT)/