开篇

前一段时间有一个需求,需要做一个左右双向联动的列表。类似于饿了么点餐页面,但是远没有那么复杂,只是用于展示的一个页面。不是很复杂,做完已经很久了,目前为止没有出现什么问题,所以在此将它分享给大家。

番外

此功能依赖于BaseRecyclerViewAdapterHelper的adapter框架编写,如果各位看官没有接触过这个库,请先点击链接,大致了解过后再回来看此文章,事半功倍(强烈推荐此库,太好用了)。

先看一下效果图吧,下图是做好的demo的效果图:

首先是布局,布局文件是两个RecyclerView,和一个TextView组成。

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="horizontal">

android:id="@+id/rec_left"

android:layout_width="0dp"

android:layout_height="match_parent"

android:layout_weight="1"

android:background="@color/white" />

android:layout_width="0.5dp"

android:layout_height="match_parent"

android:background="@color/divider" />

android:layout_width="0dp"

android:layout_height="match_parent"

android:layout_weight="3">

android:id="@+id/rec_right"

android:layout_width="match_parent"

android:layout_height="match_parent" />

android:id="@+id/right_title"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:background="@color/divider"

android:paddingBottom="@dimen/dp10"

android:paddingLeft="@dimen/dp13"

android:paddingRight="@dimen/dp10"

android:paddingTop="@dimen/dp10"

android:singleLine="true"

android:textColor="@color/black"

android:textSize="@dimen/sp16" />

功能

然后再看一下数据格式吧(由于是自己写的demo,所以数据格式只是简单的处理了一下,在正常开发的时候,这个格式需要同后台同事协商好):

//获取数据(若请求服务端数据,请求到的列表需有序排列)

private void initData() {

left = new ArrayList<>();

left.add("第一组");

left.add("第二组略略略略略略略");

left.add("第三组哈哈哈哈哈哈哈哈哈哈hahahahahahaha");

left.add("第四组哈哈哈哈哈嗝~");

left.add("第五组");

left.add("第六组哎呀我去");

left.add("第七组");

right = new ArrayList<>();

right.add(new ScrollBean(true, left.get(0)));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("1111111", left.get(0))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("1111111", left.get(0))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("1111111", left.get(0))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("1111111", left.get(0))));

right.add(new ScrollBean(true, left.get(1)));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("2222222", left.get(1))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("2222222", left.get(1))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("2222222", left.get(1))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("2222222", left.get(1))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("2222222", left.get(1))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("2222222", left.get(1))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("2222222", left.get(1))));

right.add(new ScrollBean(true, left.get(2)));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("3333333", left.get(2))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("3333333", left.get(2))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("3333333", left.get(2))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("3333333", left.get(2))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("3333333", left.get(2))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("3333333", left.get(2))));

right.add(new ScrollBean(true, left.get(3)));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("4444444", left.get(3))));

right.add(new ScrollBean(true, left.get(4)));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("5555555", left.get(4))));

right.add(new ScrollBean(true, left.get(5)));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("6666666", left.get(5))));

right.add(new ScrollBean(true, left.get(6)));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("7777777", left.get(6))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("7777777", left.get(6))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("7777777", left.get(6))));

right.add(new ScrollBean(new ScrollBean.ScrollItemBean("7777777", left.get(6))));

for (int i = 0; i < right.size(); i++) {

if (right.get(i).isHeader) {

//遍历右侧列表,判断如果是header,则将此header在右侧列表中所在的position添加到集合中

tPosition.add(i);

}

}

}

首先是左侧列表的数据集,很简单,只是一个String类型的list集合。而右侧列表有人看了可能会懵,这个是我为什么想让大家看了上面那个adapter库之后再看这篇文章的原因,右侧的数据格式是brvah专门应对这种情况而定义的一种数据格式。简单来说,每一组中间我都空着一行来区分,每一组的第一条数据(也就是那行短的)是右侧组名的数据,其余等长的则是每一组的item数据。组名的先不说,单说item的bean,里面有两个参数,一个是String类型的文本内容,一个是区分分组的String类型的type。代码如下:

public static class ScrollItemBean {

private String text;

private String type;

public ScrollItemBean(String text, String type) {

this.text = text;

this.type = type;

}

public String getText() {

return text;

}

public void setText(String text) {

this.text = text;

}

public String getType() {

return type;

}

public void setType(String type) {

this.type = type;

}

}

然后在最下面有一个循环,遍历右侧数据集合,将右侧组名所在的position拿出来存在一个集合中(此position用来点击左侧列表然后定位右侧列表位置的,当中妙处,不必多言,请往下看)。

可能大家都注意到了,右侧有一个分类的title悬停在列表之上,那下一步就从这里开始。

首先是构建adapter以及给右侧初始化一些数据:

rightManager = new GridLayoutManager(mContext, 3);

if (rightAdapter == null) {

rightAdapter = new ScrollRightAdapter(R.layout.scroll_right, R.layout.layout_right_title, null);

recRight.setLayoutManager(rightManager);

recRight.addItemDecoration(new RecyclerView.ItemDecoration() {

@Override

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

super.getItemOffsets(outRect, view, parent, state);

outRect.set(dpToPx(mContext, getDimens(mContext, R.dimen.dp3))

, 0

, dpToPx(mContext, getDimens(mContext, R.dimen.dp3))

, dpToPx(mContext, getDimens(mContext, R.dimen.dp3)));

}

});

recRight.setAdapter(rightAdapter);

} else {

rightAdapter.notifyDataSetChanged();

}

rightAdapter.setNewData(right);

//设置右侧初始title

if (right.get(first).isHeader) {

rightTitle.setText(right.get(first).header);

}

解释一波,setNewData()是adapter库中自带的一个方法,也就是给adapter添加数据,此处不提。下面一个方法,是给右侧列表上方悬停title设置初始数据的,其中有一个成员变量:first,这个参数是一个int类型的数值,记录右侧列表中可见的第一个item的position。first我抽取了一个成员变量,并且给了一个默认值0。

//记录右侧当前可见的第一个item的position

private int first = 0;

再看看new Adapter里面的两个布局,相信您看完上面的链接,就会知道:前一个布局文件是右侧列表item的布局(红),后面一个布局则是右侧列表分类title的布局(绿)。对应位置见下图:

重点

右侧的初始化工作完成之后,就来到了本文的重点部分,也就是监听右侧列表的滑动。添加一个监听:recRight.addOnScrollListener()。里面重写两个方法:onScrollStateChanged()和onScrolled()。

首先来看onScrollStateChanged(),里面只有一句:

@Override

public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

super.onScrollStateChanged(recyclerView, newState);

//获取右侧title的高度

tHeight = rightTitle.getHeight();

}

然后我们再看onScrolled(),几乎所有的重点代码都在这个方法里面,所以我就拆开一步一步分析(代码中基本每一句都有注释,所以在文章中就不过多的解释了):

1.先是右侧顶部title设置偏移量的方法(右侧当前显示哪一组,顶部title就显示该组名):

//判断如果是组名的item

if (right.get(first).isHeader) {

//获取此组名item的view

View view = rightManager.findViewByPosition(first);

if (view != null) {

//如果此组名item顶部和父容器顶部距离大于等于title的高度,则设置偏移量

if (view.getTop() >= tHeight) {

rightTitle.setY(view.getTop() - tHeight);

} else {

//否则不设置

rightTitle.setY(0);

}

}

}

2.title的偏移量处理完了之后,就是title赋值的问题了:

//因为每次滑动之后,右侧列表中可见的第一个item的position肯定会改变,并且右侧列表中可见的第一个item的position变换了之后,

//才有可能改变右侧title的值,所以这个方法内的逻辑在右侧可见的第一个item的position改变之后一定会执行

int firstPosition = rightManager.findFirstVisibleItemPosition();

if (first != firstPosition && firstPosition >= 0) {

//给first赋值

first = firstPosition;

//不设置Y轴的偏移量

rightTitle.setY(0);

//判断如果右侧可见的第一个item是否是header,设置相应的值

if (right.get(first).isHeader) {

rightTitle.setText(right.get(first).header);

} else {

rightTitle.setText(right.get(first).t.getType());

}

}

3.title的赋值问题之后,是与左侧关联的方法:

//遍历左边列表,列表对应的内容等于右边的title,则设置左侧对应item高亮

for (int i = 0; i < left.size(); i++) {

if (left.get(i).equals(rightTitle.getText().toString())) {

//左侧adapter中的方法

leftAdapter.selectItem(i);

}

}

4.最后就是一些细节上面的处理了:

//如果右边最后一个完全显示的item的position,等于bean中最后一条数据的position(也就是右侧列表拉到底了),

//则设置左侧列表最后一条item高亮

if (rightManager.findLastCompletelyVisibleItemPosition() == right.size() - 1) {

leftAdapter.selectItem(left.size() - 1);

}

右侧Adapter也没有什么可看的,就是brvah中封装的一个adapter,里面只是给右侧组名和item赋值而已,附上代码:

public class ScrollRightAdapter extends BaseSectionQuickAdapter {

public ScrollRightAdapter(int layoutResId, int sectionHeadResId, List data) {

super(layoutResId, sectionHeadResId, data);

}

@Override

protected void convertHead(BaseViewHolder helper, ScrollBean item) {

helper.setText(R.id.right_title, item.header);

}

@Override

protected void convert(BaseViewHolder helper, ScrollBean item) {

ScrollBean.ScrollItemBean t = item.t;

helper.setText(R.id.right_text, t.getText());

}

}

至此为止,右侧列表的处理,也就是本文中最重要的一部分已经都结束了,那我们再来看看左侧列表吧。由于左侧的处理只是点击,所以除了初始化左侧RecyclerView之外,只有一个点击事件,代码如下:

private void initLeft() {

if (leftAdapter == null) {

leftAdapter = new ScrollLeftAdapter(R.layout.scroll_left, null);

recLeft.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));

recLeft.addItemDecoration(new DividerItemDecoration(mContext, DividerItemDecoration.VERTICAL));

recLeft.setAdapter(leftAdapter);

} else {

leftAdapter.notifyDataSetChanged();

}

leftAdapter.setNewData(left);

leftAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {

@Override

public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

switch (view.getId()) {

//点击左侧列表的相应item,右侧列表相应的title置顶显示

//(最后一组内容若不能填充右侧整个可见页面,则显示到右侧列表的最底端)

case R.id.item:

leftAdapter.selectItem(position);

rightManager.scrollToPositionWithOffset(tPosition.get(position), 0);

break;

}

}

});

}

这就是左侧RecyclerView的初始化以及联动的处理方法,最后看一看左侧adapter吧:

public class ScrollLeftAdapter extends BaseQuickAdapter {

private List tv = new ArrayList<>();

public ScrollLeftAdapter(int layoutResId, @Nullable List data) {

super(layoutResId, data);

}

@Override

protected void convert(BaseViewHolder helper, String item) {

helper.setText(R.id.left_text, item)

.addOnClickListener(R.id.item);

//将左侧item中的TextView添加到集合中

tv.add((TextView) helper.getView(R.id.left_text));

//设置进入页面之后,左边列表的初始状态

if (tv != null && getData() != null && tv.size() == getData().size()) {

selectItem(0);

}

helper.getView(R.id.item).setSelected(true);

}

}

这里面有一个selectItem(),这个也就是本文最后一个需要注意的地方了,此方法是右侧列表滑动,左侧跟着联动的一个方法。使用此方法需要传一个参数,这个position是右侧组名所对应的position(知道上面为何要循环取出右侧列表组名所对应的position了吧)。附上代码:

//传入position,设置左侧列表相应item高亮

public void selectItem(int position) {

for (int i = 0; i < getData().size(); i++) {

if (position == i) {

tv.get(i).setBackgroundColor(0xff0068b7);

tv.get(i).setTextColor(ContextCompat.getColor(mContext, R.color.white));

//以下是指定某一个TextView滚动的效果

tv.get(i).setEllipsize(TextUtils.TruncateAt.MARQUEE);

tv.get(i).setFocusable(true);

tv.get(i).setFocusableInTouchMode(true);

tv.get(i).setMarqueeRepeatLimit(-1);

} else {

tv.get(i).setBackgroundColor(0xffffffff);

tv.get(i).setTextColor(ContextCompat.getColor(mContext, R.color.black));

//失去焦点则停止滚动

tv.get(i).setEllipsize(TextUtils.TruncateAt.END);

tv.get(i).setFocusable(false);

tv.get(i).setFocusableInTouchMode(false);

tv.get(i).setMarqueeRepeatLimit(0);

}

}

}

判断方法中,前两行是对应item的背景和字体颜色的改变,下面四行,是一个跑马灯的效果(左侧item中,我设置了只显示一行,对应的item中的内容如果显示不下,则无限滚动显示)。

结尾

好了,demo中比较重要的方法我都罗列出来了,也贴出了相应的代码,最后还是忍不住推荐一下这个adapter库:BaseRecyclerViewAdapterHelper,这个库真的是太好用了,我现在几乎所有的项目包括本篇文章涉及到的内容,都在用这个,强烈推荐!!!

最后附上demo,希望可以帮助到大家。

android联动动画,Android双向列表左右联动效果相关推荐

  1. android 仿qq好友动态,Android UI仿QQ好友列表分组悬浮效果

    本文实例为大家分享了Android UI仿QQ好友列表分组悬浮效果的具体代码,供大家参考,具体内容如下 楼主是在平板上測试的.图片略微有点大,大家看看效果就好 接下来贴源代码: PinnedHeade ...

  2. android jason动画,Android 动画之Lottie动画使用

    Android 动画之Lottie动画使用 一:简介 Lottie是Airbnb开源的一套跨平台的完整解决方案,设计师只需要使用After Effects(简称AE)设计动画之后,使用Lottic提供 ...

  3. android弹球动画,Android动画之自定义Evaluator实现弹球效果

    前言 今天给大家带来的是自定义Evaluator实现弹球效果,我们先给大家来个效果图. 下面我们介绍具体代码流程 1. 自定义Point类 public class Point { private i ...

  4. android decorview动画,Android窗口机制(二)Window,PhoneWindow,DecorView,setContentView源码理解...

    Android窗口机制系列 前篇文章中出现了PhoneWindow,DecorView这些类,如果是第一次见过的话,肯定会觉得陌生.这篇文章主要跟大家讲解Window,PhoneWindow,Deco ...

  5. android 减速动画,Android View Animation

    概述 可译为视图动画,分为 缩放动画 平移动画 渐变动画 旋转动画 Android系统中定义了一个抽象类Animation来定义这种视图动画,它的具体子类如下表: 动画名称 对应的子类 xml中标签 ...

  6. android 底部动画,Android实现360手机助手底部的动画菜单

    首先来看下我们实现的效果和360效果的对比: 360手机助手效果演示 本库实现的效果(Icon来自360手机助手,侵删) xml布局文件 注:为了美观,讲每个Button的高度以及固定,设置wrap_ ...

  7. android 4 动画,[Android]开发App,你得知道这些4——动画

    0.前言 上一篇文章中,我们讲解了Android的触摸事件 有兴趣的可以去看一看 准备工作 在读本文前,你最好有以下准备: (1)安装Android Stuido(以下简称AS) (2)有一定的Jav ...

  8. android 逐字动画,Android实现文本逐字显示View(类似rpg游戏人物对话,文本逐字显示)...

    前面好多篇文章都是Android Studio.源码编译.ndk等相关教程,今天敲一敲代码,不然都生锈了哈_. 来个古装动画美图,缓解大家疲劳的眼睛...(话说有木有人知道这是谁???) Paste_ ...

  9. android 科技动画,android开发ViewFlipper触摸动画

    [IT168技术]介绍:在教程中,我们可以学习如何解决在Android项目的共同问题,有更多的关于Android的项目信息,我建议你下载ADT Android Pluglins和Eclipse. 背景 ...

  10. android 飞机动画,Android实现纸飞机的简单操作

    在项目中,我们要求做一个纸飞机的功能:就是当打开这个界面时,会有4架纸飞机从屏幕左侧飞入,然后到达自己的位置坐上下浮动,同时云彩也不断地从屏幕右侧飘到屏幕左侧.当你点击其中一个纸飞机时,这个纸飞机先向 ...

最新文章

  1. 线程池之SingleThreadPool学习
  2. 分享5个免费的在线 SQL 数据库环境,简直太方便了!
  3. 数据库中死锁那些事儿
  4. @MarkFan 口语练习录音 20140415 [MDL演讲口语录音]
  5. unity中解析excel表
  6. 使用Bootstrap开发网站首页
  7. JDBC之数据库的连接步骤(六步)
  8. 现代化蔬菜大棚采用什么和计算机自动控制,温室大棚自动控制系统存在的意义以及未来的发展...
  9. 第七届 蓝桥杯 省赛 第八题 四平方和
  10. JTabel 中监听某个单元格的值确实改变了
  11. 大学 计算机 试题,【分享】《大学计算机基础》试题题库及答案 ~~~~~~~~~~~...
  12. 小程序列表倒计时使用wxs实现
  13. 百度地图android兼容,支持离线地图 百度地图Android版上线
  14. 什么原因导致LED发光二极管的光衰?
  15. h5分享微信好友朋友圈
  16. linux文件误删恢复debugfs和extundelete
  17. VS code C/C++扩展包依赖cpptools-win32.vsix离线安装
  18. 什么是函数的副作用——理解js编程中函数的副作用
  19. 2021.07.13【B站】是这样崩的
  20. 一.了解 iOS 操作系统架构

热门文章

  1. 硬盘重装系统:电脑本地硬盘重装系统步骤
  2. [0CTF 2016]piapiapia 1
  3. 完全指南:在 Linux 中如何打印和管理打印机
  4. 公共关系礼仪实务章节测试题——公共关系的类型(六)
  5. paypal支付接口说明
  6. Python enumerate,iter的用法
  7. 注册微信公众平台测试账号
  8. 决策树案例学习(Python实现)
  9. doctrine-orm基础(单用doctrine避坑指南)
  10. 沃邮箱 android,沃邮箱Android客户端产品体验报告