仿房产销冠APP销控表界面-多RecyclerView同步滚动

2023-10-16 19:59

本文主要是介绍仿房产销冠APP销控表界面-多RecyclerView同步滚动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、简述

最近在做一个地产项目,其实之前做出了一版,但现在要求重做(连上架的机会都没有),很服气啊~~而现在做的项目呢,比上一版功能要求更多,其中,销控表的界面效果要求跟房产销冠APP的销控表界面差不多,先来看下房产销冠APP的销控表效果吧:

房产销冠APP的销控表效果

说说我第一次看到这个界面效果时的感觉,就一个词:amazing~ 是的,公司就我一个人做安卓开发,感觉有点压力山大,但是,不怂,静下心来分析一下就明朗多了。先说说本文核心技术重点:两个RecyclerView同步滚动。好,下面进入正文。

二、分析

1、布局分析

我认为的布局实现:将销控表分为左右两部分:左边是楼层列表,右边是单元(房间)列表。楼层列表就是一个简单的LinearLayout+TextView+RecyclerView,单元(房间)列表则有点小复杂(HorizontalScrollView、LinearLayout)+TextView+RecyclerView。为了各位看客能直观理解,我特意做了张图,请看:

其中黄色区域就是销控表的部分。

布局实现

2、效果分析

  1. 当左边的楼层列表上下滑动时,右边的单元(房间)列表也跟着一起滑动,单元(房间)列表上的单元编号不动。
  2. 当右边的单元(房间)列表上下滑动时,左边的楼层列表也跟着一起滑动,单元(房间)列表上的单元编号不动。
  3. 当右边的单元(房间)列表左右滑动时,单元(房间)列表上的单元编号一起左右滑动,左边的楼层列表不动。

那么,要实现1、2的效果,可以监听这两个列表的滚动,当其中一个列表滚动时,让另一个列表滚动相同的距离即可。要实现3的效果就简单了,因为HorizontalScrollView中嵌套RecyclerView并没有滚动冲突,HorizontalScrollView处理水平滑动事件,RecyclerView处理竖直滚动事件,所以暂时不用理(后面还是要做点简单处理的)。

三、实现

1、布局

上面已经分析出了布局结构,下面直接贴布局代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#f5f5f5"android:orientation="vertical"><android.support.design.widget.AppBarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><android.support.v7.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="销控表"android:textColor="#000"android:textSize="16sp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical|right"android:layout_marginRight="10dp"android:text="统计"android:textColor="#000"android:textSize="12sp"/></android.support.v7.widget.Toolbar></android.support.design.widget.AppBarLayout><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="#fff"android:gravity="center"android:padding="10dp"android:text="CSDN_LQR的私人后宫-项目1期-1栋"android:textColor="#333"android:textSize="10sp"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="10px"android:orientation="horizontal"><!--楼层--><LinearLayoutandroid:layout_width="60dp"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="50dp"android:background="#fff"android:gravity="center"android:padding="10dp"android:text="楼层&#x000A;单元"android:textSize="12sp"/><android.support.v7.widget.RecyclerViewandroid:id="@+id/rv_layer"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="1dp"/></LinearLayout><!--单元(房间)--><HorizontalScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginLeft="4dp"android:fillViewport="true"android:scrollbars="none"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="50dp"android:background="#fff"android:gravity="center"android:padding="10dp"android:text="3"android:textSize="12sp"/><android.support.v7.widget.RecyclerViewandroid:id="@+id/rv_room"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="1dp"/></LinearLayout></HorizontalScrollView></LinearLayout>
</LinearLayout>

再通过列表的数据进行填充(这部分不是重点就不贴出来了),效果就出来了:

初步效果

接下来就是实现同步滚动效果了。

2、多RecyclerView同步滚动实现

一个大体的思路就是分别对其中一个列表设置滚动监听,当这个列表滚动时,让另一个列表也一起滚动。
但细节上要考虑到,这种监听是双向的,A列表滚动时触发其滚动回调接口,导致B列表滚动,而此时B列表也已经设置过滚动监听,它的滚动也会触发它的滚动回调接口,导致A列表滚动,这样就形成了一个死循环。所以适当添加或移除滚动监听是本功能实现的重难点,下面直接贴出代码,请自行结合代码及注释理解。

1)封装一个可以自行取消监听的滚动回调接口

这样的封装使我们不用在其他地方考虑列表空闲状态时的处理,会省去很多事。

/*** @创建者 CSDN_LQR* @描述 实现一个RecyclerView.OnScrollListener的子类,当RecyclerView空闲时取消自身的滚动监听*/
public class MyOnScrollListener extends RecyclerView.OnScrollListener {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);if (newState == recyclerView.SCROLL_STATE_IDLE) {recyclerView.removeOnScrollListener(this);}}
}

2)为楼层列表控件设置滚动监听

以下两段代码涉及两个列表滚动同步和添加或移除滚动监听的时机,具体代码及注释我已经写得很清楚了,请仔细看:

private final RecyclerView.OnScrollListener mLayerOSL = new MyOnScrollListener() {@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);// 当楼层列表滑动时,单元(房间)列表也滑动mRvRoom.scrollBy(dx, dy);}
};/*** 设置两个列表的同步滚动*/
private void setSyncScrollListener() {mRvLayer.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {private int mLastY;@Overridepublic boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {// 当列表是空闲状态时if (rv.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {onTouchEvent(rv, e);}return false;}@Overridepublic void onTouchEvent(RecyclerView rv, MotionEvent e) {// 若是手指按下的动作,且另一个列表处于空闲状态if (e.getAction() == MotionEvent.ACTION_DOWN && mRvRoom.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {// 记录当前另一个列表的y坐标并对当前列表设置滚动监听mLastY = rv.getScrollY();rv.addOnScrollListener(mLayerOSL);} else {// 若当前列表原地抬起手指时,移除当前列表的滚动监听if (e.getAction() == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) {rv.removeOnScrollListener(mLayerOSL);}}}@Overridepublic void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}});...
}

3)为单元(房间)列表设置滚动监听

对于单元(房间)列表滚动监听的设置,跟前面一样,我就顺便写一下好了。

private final RecyclerView.OnScrollListener mRoomOSL = new MyOnScrollListener() {@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);// 当单元(房间)列表滑动时,楼层列表也滑动mRvLayer.scrollBy(dx, dy);}
};/*** 设置两个列表的同步滚动*/
private void setSyncScrollListener() {...mRvRoom.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {private int mLastY;@Overridepublic boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {if (rv.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {onTouchEvent(rv, e);}return false;}@Overridepublic void onTouchEvent(RecyclerView rv, MotionEvent e) {if (e.getAction() == MotionEvent.ACTION_DOWN && mRvLayer.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {mLastY = rv.getScrollY();rv.addOnScrollListener(mRoomOSL);} else {if (e.getAction() == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) {rv.removeOnScrollListener(mRoomOSL);}}}@Overridepublic void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}});
}

好了,到这里同步滚动效果就实现了,先看看效果。

不完美的效果

3、处理水平滚动列表事件

在上图中,我们可以看到 ,同步滚动效果确实是实现了,但有个问题,只要一水平滚动后,再来滚动左边的楼层列表时程序就会崩溃,若是滚动右边的单元(房间)列表则会滚动不同步,会造成这种情况是因为,当水平滚动是时,事件被HorizontalScrollView处理了,导致右边的单元(房间)列表的滚动监听没有被移除。

代码执行解析

当我们去滚动左边的楼层列表时,会为其设置滚动监听,这时这两个列表都存在滚动监听,所以就造成了监听的递归调用(死循环),于是内存就妥妥的溢出了。下面是错误提示:

内存溢出

所以,解决的方法就是,当HorizontalScrollView处理水平滚动事件时,取消列表的滚动监听,而ScrollView本身不支持滚动监听,所以需要重新HorizontalScrollView,向外提供滚动监听功能。自定义HorizontalScrollView代码如下:

/*** @创建者 CSDN_LQR* @描述 自定义HorizontalScrollView,向外提供滑动监听功能*/
public class ObservableHorizontalScrollView extends HorizontalScrollView {private ScrollViewListener scrollViewListener = null;public ObservableHorizontalScrollView(Context context) {super(context);}public ObservableHorizontalScrollView(Context context, AttributeSet attrs,int defStyle) {super(context, attrs, defStyle);}public ObservableHorizontalScrollView(Context context, AttributeSet attrs) {super(context, attrs);}public void setScrollViewListener(ScrollViewListener scrollViewListener) {this.scrollViewListener = scrollViewListener;}@Overrideprotected void onScrollChanged(int x, int y, int oldx, int oldy) {super.onScrollChanged(x, y, oldx, oldy);if (scrollViewListener != null) {scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);}}public interface ScrollViewListener {void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldx, int oldy);}}  

接着就是替换代码中的HorizontalScrollView控件

...
<!--单元(房间)-->
<com.lqr.topsales.ObservableHorizontalScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginLeft="4dp"android:fillViewport="true"android:scrollbars="none"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="50dp"android:background="#fff"android:gravity="center"android:padding="10dp"android:text="3"android:textSize="12sp"/><android.support.v7.widget.RecyclerViewandroid:id="@+id/rv_room"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="1dp"/></LinearLayout>
</com.lqr.topsales.ObservableHorizontalScrollView>
...

在代码中监听HorizontalScrollView滚动,当其滚动时,移除列表控件的移动监听事件:

mSvRoom.setScrollViewListener(new ObservableHorizontalScrollView.ScrollViewListener() {@Overridepublic void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldx, int oldy) {mRvLayer.removeOnScrollListener(mLayerOSL);mRvRoom.removeOnScrollListener(mRoomOSL);}
});

再来试试效果:

最终效果

四、最后附上DEMO连接

TopsalesSellControlTableDemo

这篇关于仿房产销冠APP销控表界面-多RecyclerView同步滚动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/220452

相关文章

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用

Nacos集群数据同步方式

《Nacos集群数据同步方式》文章主要介绍了Nacos集群中服务注册信息的同步机制,涉及到负责节点和非负责节点之间的数据同步过程,以及DistroProtocol协议在同步中的应用... 目录引言负责节点(发起同步)DistroProtocolDistroSyncChangeTask获取同步数据getDis

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

MySQL主从同步延迟原理及解决方案

概述 MySQL的主从同步是一个很成熟的架构,优点为: ①在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力; ②在从主服务器进行备份,避免备份期间影响主服务器服务; ③当主服务器出现问题时,可以切换到从服务器。 相信大家对于这些好处已经非常了解了,在项目的部署中也采用这种方案。但是MySQL的主从同步一直有从库延迟的问题,那么为什么会有这种问题。这种问题如何解决呢? MyS

一款支持同一个屏幕界面同时播放多个视频的视频播放软件

GridPlayer 是一款基于 VLC 的免费开源跨平台多视频同步播放工具,支持在一块屏幕上同时播放多个视频。其主要功能包括: 多视频播放:用户可以在一个窗口中同时播放任意数量的视频,数量仅受硬件性能限制。支持多种格式和流媒体:GridPlayer 支持所有由 VLC 支持的视频格式以及流媒体 URL(如 m3u8 链接)。自定义网格布局:用户可以配置播放器的网格布局,以适应不同的观看需求。硬

MFC中App,Doc,MainFrame,View各指针的互相获取

纸上得来终觉浅,为了熟悉获取方法,我建了个SDI。 首先说明这四个类的执行顺序是App->Doc->Main->View 另外添加CDialog类获得各个指针的方法。 多文档的获取有点小区别,有时间也总结一下。 //  App void CSDIApp::OnApp() {      //  App      //  Doc     CDocument *pD

使用条件变量实现线程同步:C++实战指南

使用条件变量实现线程同步:C++实战指南 在多线程编程中,线程同步是确保程序正确性和稳定性的关键。条件变量(condition variable)是一种强大的同步原语,用于在线程之间进行协调,避免数据竞争和死锁。本文将详细介绍如何在C++中使用条件变量实现线程同步,并提供完整的代码示例和详细的解释。 什么是条件变量? 条件变量是一种同步机制,允许线程在某个条件满足之前进入等待状态,并在条件满