1.前言
相信在安卓中,很多人都遇到一个需求,即两个RecyclerView如何相关联,本文进行基于此功能下的仿饿了么点餐的实行。在实行这个需求时,我相信有不少人一脸懵圈。其实官方已经给RecyclerView提供了相关方法——smoothScrollToPosition()提供我们使用,不过在使用这个方法时要考虑多种情况。
2.步骤详情
如图片所示:我们将步骤拆开:
- 获取数据:我们需要获取两个RecyclerView的数据,这个我们用上玩安卓的导航接口:https://www.wanandroid.com/navi/json 获取数据即可。我们成左边数据为父数据,右边的数据为子数据。
- UI设置:给右边的RecyclerView添加item头部,这部分内容涉及了一点自定义View,因为我对自定义View也不算精通,所以随便画了一下,可能很丑。
2.1 获取数据
老套路,用上okhttp获取数据即可,比较特殊的一点就是需要我们在右边的实体类中填上两个字段isFirst、isLast为第二步铺垫。
去掉不必要的字段和注释后,实体类NavigationBean代码如下:
public class NavigationBean implements Serializable {
private List<DataBean> data;
public List<DataBean> getData() {
return data;
}
public static class DataBean implements Serializable {
private String name;
private List<ArticlesBean> articles;
public String getName() {
return name;
}
public List<ArticlesBean> getArticles() {
return articles;
}
public static class ArticlesBean implements Serializable {
// 新添加的字段、重点
private boolean isFirst;
private boolean isLast;
private String chapterName;
private String title;
public String getTitle() {
return title;
}
public String getChapterName() {
return chapterName;
}
public boolean isFirst() {
return isFirst;
}
public boolean isLast() {
return isLast;
}
public void setFirst(boolean first) {
isFirst = first;
}
public void setLast(boolean last) {
isLast = last;
}
}
}
}
在完成bean类的设计后,我们要在页面的xml里填上两个RecyclerView的UI控件。这里用一个View进行分隔,当然如果你觉得没必要也可以省去。
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/navigation_recyclerView_left"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#ff3322"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/navigation_recyclerView_right"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"/>
接下来就是控制器代码,即获取数据。特殊点即在解析返回数据时需要为我们新增的两个字段设置值。
这里老实说应该不能贴上全部代码,但是又怕有新同学看不懂,觉得啰嗦的同学还请见谅。关键代码在parseData()解析数据方法中,我们需要对多个同样父数据的子数据集合始末位置进行标记。
private List<NavigationBean.DataBean> leftBeans = new ArrayList<>();
private List<NavigationBean.DataBean.ArticlesBean> rightBeans = new ArrayList<>();
private NavigationBean navigationBean;
private RecyclerView rightRv;
/**
* 发送请求,获取数据
*/
private void initSent() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.wanandroid.com/navi/json")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String string = response.body().string();
parseData(string);
runOnUiThread(new Runnable() {
@Override
public void run() {
initView();
}
});
}
});
}
/**
* 解析返回数据
* @param string
*/
private void parseData(String string){
Gson gson = new Gson();
navigationBean = gson.fromJson(string, NavigationBean.class);
leftBeans = navigationBean.getData();
for (NavigationBean.DataBean dataBean:navigationBean.getData()){
for (int i = 0 ;i<dataBean.getArticles().size();i++){
// 定好始末content的状态
if (i==0){
dataBean.getArticles().get(i).setFirst(true);
}else if (i==dataBean.getArticles().size()){
dataBean.getArticles().get(i).setLast(true);
}
rightBeans.add(dataBean.getArticles().get(i));
}
}
}
/**
* 设置UI
*/
private void initView() {
// 左RV
RecyclerView leftRv = findViewById(R.id.navigation_recyclerView_left);
leftRv.setLayoutManager(new LinearLayoutManager(this));
leftRv.setAdapter(new LeftAdapter(leftBeans));
// 右RV
rightRv = findViewById(R.id.navigation_recyclerView_right);
rightRv.setLayoutManager(new LinearLayoutManager(this));
rightRv.setAdapter(new RightAdapter(rightBeans));
}
2.2 设置ItemDecoration
在获取完数据后,我们得对右边的列表进行分类,即添加对应的父数据(ItemDecoration)。
需要我们对右rv数据进行分组,根据左rv数据(父数据)进行归类。
这便需要用到ItemDecoration这个抽象类,我们需要自定义类继承该类。
public class DemoItemDecoration extends RecyclerView.ItemDecoration {
private int mSectionHeight = 40;
private Context context;
// 头部列表集合
private List<NavigationBean.DataBean.ArticlesBean> titles ;
// 头部背景画笔
private Paint bPaint;
// 头部文字画笔
private Paint TextPaint;
public DemoItemDecoration(Context context, List<NavigationBean.DataBean.ArticlesBean> titles) {
this.context = context;
this.titles = titles;
// 设置画笔
bPaint = new Paint();
bPaint.setColor(Color.GRAY);
// 文字笔
TextPaint = new Paint();
// 颜色
TextPaint.setColor(Color.WHITE);
// 字体大小
TextPaint.setTextSize(40);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
// 获取列表的位置
int position = parent.getChildAdapterPosition(view);
// 在每个组上面空出一定距离 目前为40
if(titles.get(position).isFirst()){
outRect.top = mSectionHeight;
}
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
// 获取列表长度
int childCount = parent.getChildCount();
for(int i = 0;i<childCount;i++){
// child 为列表的view
View child = parent.getChildAt(i);
//获取列表位置
int position = parent.getChildAdapterPosition(child);
NavigationBean.DataBean.ArticlesBean app = titles.get(position);
// 设置位于头部的文字
if (app.isFirst()){
// 获取四个方向的距离,来布置画布
float sectionLeft = parent.getLeft();
float sectionTop = child.getTop() - mSectionHeight;
float sectionRight = parent.getWidth();
float sectionBottom = child.getTop();
c.drawRect(0,sectionTop,sectionRight,sectionBottom,bPaint);
// 文字笔居中
c.drawText(app.getChapterName(),sectionLeft,child.getTop()-5,TextPaint);
}
}
}
}
在控制器的回调UI线程方法中调用DemoItemDecoration 类,将父数据作为参数传入。
private void initSent() {
...
runOnUiThread(new Runnable() {
@Override
public void run() {
initView();
// 增加头部。参数:context、右列表实体集合
DemoItemDecoration decoration = new DemoItemDecoration(MainActivity.this,rightBeans);
rightRv.addItemDecoration(decoration);
}
});
...
}
3.最后
到此就为左右两个rv设置数据,同时添加给右边的Rv数据进行分组和添加每组头部。之后关于联动的代码逻辑和实现效果就在二为大家展现。
目前效果如下: