Android侧滑栏(一)可缩放可一起移动的侧滑栏
在实际的各类App开发中,经常会需要做一个左侧的侧滑栏,类似于QQ这种。
今天这篇文章总结下自己在开发中遇到的这类可以跟随移动且可以缩放的侧滑栏。
一、实现原理
使用 HorizontalScrollView 实现一个水平方向的可滑动的View,左布局为侧滑栏,右布局为自己的主页内容。
来看下android的官方解释,我用谷歌翻译了:
用户可以滚动的视图层次结构的布局容器,允许其大于物理显示。 HorizontalScrollView 是一种 FrameLayout,这意味着您应该在其中放置一个包含要滚动的全部内容的子级;这个子级本身可能是一个具有复杂对象层次结构的布局管理器。经常使用的子级是水平方向的 LinearLayout,它呈现用户可以滚动的顶级项目的水平数组。
TextView 类还负责自己的滚动,因此不需要 HorizontalScrollView,但将两者结合使用可以在更大的容器中实现文本视图的效果。
HorizontalScrollView 仅支持水平滚动。对于垂直滚动,请使用 ScrollView 或 ListView。
属性
关键点:
1、用户可以滚动的视图层次结构的布局容器,允许其大于物理显示。证明就像我们平时的用到的垂直方向的scrollView嵌套几个列表一样。
2、HorizontalScrollView 是一种 FrameLayout,这意味着您应该在其中放置一个包含要滚动的全部内容的子级;这个代表你需要在HorizontalScrollView先放一个总布局,再在这个布局里放左右布局内容。
二、实现过程
第一步:xml布局
<?xml version="1.0" encoding="utf-8"?><wanwan.and.lx.lxslideviewdemo.SlideMenuLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/sliding"android:background="@mipmap/img_bg"tools:context=".MainActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"><LinearLayoutandroid:layout_width="200dp"android:layout_height="match_parent"android:layout_gravity="start"android:orientation="vertical"><RelativeLayoutandroid:id="@+id/sidebarLayout"android:layout_width="match_parent"android:layout_height="match_parent"><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/sidebar_image_app_icon"android:layout_width="80dp"android:layout_height="80dp"android:layout_centerHorizontal="true"android:layout_marginTop="106dp"android:scaleType="fitXY"android:src="@mipmap/ic_launcher" /><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/slide_item_privacy"android:layout_width="200dp"android:layout_height="57dp"android:layout_below="@id/sidebar_image_app_icon"android:layout_marginTop="30dp"><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/set_privacy_lock_img"android:layout_width="16dp"android:layout_height="16dp"android:layout_marginStart="32dp"android:src="@mipmap/privacy"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><androidx.appcompat.widget.AppCompatTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="13.7dp"android:text="Privacy Policy"android:textColor="@color/black"android:textSize="17sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toEndOf="@id/set_privacy_lock_img"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/slide_item_share"android:layout_width="200dp"android:layout_height="57dp"android:layout_below="@id/slide_item_privacy"android:layout_marginTop="10dp"><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/set_share_lock_img"android:layout_width="16dp"android:layout_height="16dp"android:layout_marginStart="32dp"android:src="@mipmap/share"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><androidx.appcompat.widget.AppCompatTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="13.7dp"android:text="Share"android:textColor="@color/black"android:textSize="17sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toEndOf="@id/set_share_lock_img"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/slide_item_update"android:layout_width="200dp"android:layout_height="57dp"android:layout_below="@id/slide_item_share"android:layout_marginTop="10dp"><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/set_update_lock_img"android:layout_width="16dp"android:layout_height="16dp"android:layout_marginStart="32dp"android:src="@mipmap/update"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><androidx.appcompat.widget.AppCompatTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="13.7dp"android:text="Update"android:textColor="@color/black"android:textSize="17sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toEndOf="@id/set_update_lock_img"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout></RelativeLayout></LinearLayout><RelativeLayoutandroid:layout_width="match_parent"android:background="@color/white"android:layout_height="match_parent"><androidx.appcompat.widget.AppCompatTextViewandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_centerInParent="true"android:textStyle="bold"android:textSize="18sp"android:textColor="@color/black"android:layout_height="wrap_content"android:text="这是主页"/><androidx.appcompat.widget.AppCompatImageViewandroid:layout_marginTop="20dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/set"android:layout_centerHorizontal="true"android:layout_below="@id/text"android:id="@+id/set"/></RelativeLayout></LinearLayout></wanwan.and.lx.lxslideviewdemo.SlideMenuLayout>
1.SlideMenuLayout其实就是HorizontalScrollView,这是个自定义控件,待会儿代码附上。
2.可以看到SlideMenuLayout只有一个子View,为LinearLayout,LinearLayout它是全屏且水平布局,且有两个子布局,分为左右。
第二步:自定义控件HorizontalScrollView
class SlideMenuLayout : HorizontalScrollView {/*** 当菜单页显示时,右侧内容页显示宽度*/private var menuRightWidth = 0private lateinit var menuView: Viewprivate lateinit var contentView: View/*** 用于处理飞速滑动*/private var gestureDetector: GestureDetectorvar isMenuOpen: Boolean = falseprivate var btn: AppCompatImageView? = null/*** 是否进行事件拦截*/private var isIntercept = falseconstructor(context: Context) : this(context, null)constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {gestureDetector = GestureDetector(getContext(), GestureDetectorListener())}//用于处理飞速滑动inner class GestureDetectorListener : SimpleOnGestureListener() {override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {//屏蔽向右滑动if (e2.x - e1.x > 30) {Log.e("TAG", "right, right, go go go --->")return true}if (abs(velocityY) > abs(velocityX)) {return false}if (isMenuOpen) {if (velocityX < 0) {closeMenu()return true}} else {if (velocityX > 0) {openMenu()return true}}return super.onFling(e1, e2, velocityX, velocityY)}}/*** 此方法在布局加载完毕时调用*/override fun onFinishInflate() {super.onFinishInflate()val linearLayout: LinearLayout = getChildAt(0) as LinearLayoutval childCount = linearLayout.childCountif (childCount != 2) {throw IllegalArgumentException("LinearLayout child size must be 2!")}menuView = linearLayout.getChildAt(0)val menuLayoutParams = menuView.layoutParamsmenuLayoutParams.width = getScreenWidth() - menuRightWidth - SizeUtils.dp2px(100f)menuView.layoutParams = menuLayoutParams//设置右侧内容页宽度为屏幕宽度contentView = linearLayout.getChildAt(1)linearLayout.removeView(contentView)val contentRelativeLayout = RelativeLayout(context)contentRelativeLayout.addView(contentView)val contentLayoutParams = contentView.layoutParamscontentLayoutParams.width = getScreenWidth()contentRelativeLayout.layoutParams = contentLayoutParamslinearLayout.addView(contentRelativeLayout)btn = contentView.findViewById(R.id.set)btn?.setOnClickListener {openMenu()}}override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {super.onLayout(changed, l, t, r, b)//默认情况下,应该全部展示内容页,关闭左侧菜单页scrollTo(menuView.measuredWidth, 0)}/*** 获取当前屏幕的宽度*/private fun getScreenWidth(): Int {return resources.displayMetrics.widthPixels}/*** 重写该方法,用于处理缩放和透明度效果*/override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {super.onScrollChanged(l, t, oldl, oldt)//滚动的时候,不停的回调 l 从屏幕宽度变化到 0val scale = 1 - l * 1f / menuView.measuredWidth //scale 从 0 到1//处理菜单页缩放和透明度menuView.pivotX = menuView.measuredWidth * 1fmenuView.pivotY = menuView.measuredHeight / 2fmenuView.scaleX = 0.5f + scale * 0.5fmenuView.scaleY = 0.5f + scale * 0.5fmenuView.alpha = 0.25f + 0.75f * scale//处理内容页缩放 缩放到0.7fcontentView.pivotX = 0fcontentView.pivotY = contentView.measuredHeight / 2fcontentView.scaleX = 0.7f + (1 - scale) * 0.3fcontentView.scaleY = 0.7f + (1 - scale) * 0.3f}override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {if (ev.action == MotionEvent.ACTION_MOVE) {return false}if (isMenuOpen) {//如果点击事件落在内容页,则进行拦截并关闭菜单页if (ev.x > menuView.measuredWidth) {//进行事件拦截,不触发button点击事件isIntercept = truereturn true} else {isIntercept = false}}return super.onInterceptTouchEvent(ev)}override fun onTouchEvent(ev: MotionEvent): Boolean {//当执行快速滑动时,后续不再执行if (ev.action == MotionEvent.ACTION_MOVE) {return false}if (gestureDetector.onTouchEvent(ev)) {return gestureDetector.onTouchEvent(ev)}when (ev.action) {MotionEvent.ACTION_UP -> {if (isIntercept) {closeMenu()return true}//当手指抬起时,判断左侧菜单栏应该展示开始关闭//判断逻辑:当滚动x > 屏幕一半是,菜单栏隐藏,否则展开
// if (mScrollX > getScreenWidth() / 2) {
// closeMenu()
// } else {
// openMenu()
// }
// return false}}return super.onTouchEvent(ev)}/*** 打开菜单*/fun openMenu() {smoothScrollTo(0, 0)isMenuOpen = true}override fun dispatchTouchEvent(me: MotionEvent?): Boolean {if (me != null) {this.gestureDetector.onTouchEvent(me)}return super.dispatchTouchEvent(me)}/*** 关闭菜单*/fun closeMenu() {smoothScrollTo(menuView.measuredWidth, 0)isMenuOpen = false}}
关键点:
1、所谓的控制左右滑动,用到的是 smoothScrollTo()方法
2、使用SimpleOnGestureListener来进行手势监听,并且把onTouchEvent和dispatchTouchEvent的部分事件交由其处理。使用重写的onFling方法,进行各类手势处理,由于需求原因,上面的代码我禁止掉了右滑,可根据自己实际需求进行开发。
3、使用onInterceptTouchEvent拦截部分事件。
4、重写onScrollChanged监听滑动时,对左右布局进行缩放,这样会显得更流畅些。
5、重写onFinishInflate方法,拿到左右子布局,对其设置layoutparam属性,点击事件,赋值等等。
tips:menuLayoutParams.width = getScreenWidth() - menuRightWidth - SizeUtils.dp2px(100f)
这个就是设置左侧侧滑栏宽度,因为一般都不会全屏,所以会拿屏幕宽度去减去自己想要的值来展示右侧主页的内容,这儿可以给menuRightWidth设定一个值,不过我需求固定了,就直接在这儿减去了SizeUtils.dp2px(100f)。
第三步:代码调用:
class MainActivity : AppCompatActivity() {private var setImg: AppCompatImageView? = nullprivate var sliding: SlideMenuLayout? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)setImg = findViewById(R.id.set)sliding = findViewById(R.id.sliding)setImg?.setOnClickListener {sliding?.openMenu()}}
}
代码全在这儿了,我就不贴github地址了。
三、实现效果

相关文章:
Android侧滑栏(一)可缩放可一起移动的侧滑栏
在实际的各类App开发中,经常会需要做一个左侧的侧滑栏,类似于QQ这种。 今天这篇文章总结下自己在开发中遇到的这类可以跟随移动且可以缩放的侧滑栏。 一、实现原理 使用 HorizontalScrollView 实现一个水平方向的可滑动的View,左布局为侧滑…...
简单程度与自负是否相关?探索STM32的学习价值
事实上,无论STM32是否简单并不重要,更重要的是我们能通过学习STM32获得什么。通过STM32,我们可以学习到许多知识:如果我们制作一个键盘或鼠标,我们可以学习USB协议。如果我们制作一个联网设备,我们需要学习…...
第二章:CSS基础进阶-part3:弹性例子布局
文章目录 Flex盒模型二、常见属性2.1 flex属性2.2 justify-content2.3 flex-wrap2.4 flex-flow2.5 align-items2.6 父容器-align-content Flex盒模型 1、普通盒模型 2、弹性盒布局 使用弹性盒布局能让容器的宽度跟随浏览器窗口的变化而变换 二、常见属性 2.1 flex属性 2.2 …...
函数与方法有区别?
有区别,当然是有区别。 不管是java、rust还是go,他们都是不一样的。 先看定义: 函数(Function) 是一段独立的代码块,用于执行特定的任务。函数可以被多次调用,并且可以接受参数和返回结果。在G…...
VMware vCenter忘记密码操作,和Linus原理一致
mount -o remount,rw / passwd root ## 修改 root 密码要选择对应账户## 输入新密码,再输入一次新密码 umount / ## 卸载根文件系统 reboot -f ## 重新引导 vCenter...
Bert详细学习及代码实现详解
BERT概述 BERT的全称是Bidirectional Encoder Representation from Transformers,即双向Transformer的Encoder,因为decoder是不能获要预测的信息的。在大型语料库(Wikipedia BookCorpus)上训练一个大型模型(12 层到 …...
Vue [Day7] 综合案例
核心概念回顾 state:提供数据 getters:提供与state相关的计算属性 mutations:提供方法,用于修改state actions:存放异步操作 modules:存模块 功能分析 https://www.npmjs.com/package/json-server#ge…...
基于R做宏基因组的进化树ClusterTree分析
写在前面 同上一篇的PCoA分析,这个也是基于公司结果基础上的再次分析,重新挑选样本,在公司结果提供的csv结果表上进行删减,本地重新分析作图 步骤 表格预处理 在公司给的ClusterTree的原始表格数据里选取要保留的样本…...
栈和队列修炼指南(基本操作+OJ练习)
栈和队列修炼指南 1. 栈 1. 1 概念及结构 栈:是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除操作的一端称为栈顶,另一端为栈底。 栈中的数据元素遵守后进先出原则(LIFO)原则 压栈:栈的…...
伪类和伪元素有何区别?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 伪类(Pseudo-class)⭐ 伪元素(Pseudo-element)⭐ 区别总结⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前…...
自动测试框架airtest应用一:将XX读书书籍保存为PDF
一、Airtest的简介 Airtest是网易出品的一款基于图像识别和poco控件识别的一款UI自动化测试工具。Airtest的框架是网易团队自己开发的一个图像识别框架,这个框架的祖宗就是一种新颖的图形脚本语言Sikuli。Sikuli这个框架的原理是这样的,计算机用户不需要…...
ValueError:The following settings are not supported :{‘username‘: ‘neo4j“}
py2neo版本不同所导致的问题,下面我通过一段代码说明该问题。 import py2neoif py2neo.__version__ 4.3.0:graph Graph(http://localhost:7474, username config.neo4j_username, password config.neo4j_password) elif py2neo.__version__ 2021.2.3:graph G…...
360安全卫士右下角广告弹窗太多怎么彻底关闭?
360安全卫士右下角广告弹窗太多怎么彻底关闭? 1、卸载360安全卫士,选择继续卸载,并点击下一步; 2、选择广告弹窗太多,并点击下一步; 3、然后被告知升级极速版永久去广告,可以点击一键去广告。 …...
链表有无环以及确定入环口详解
142.环形链表 II 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测…...
chrome插件开发实例08- 使用Vue.js开发chrome插件
目录 背景 演示 功能介绍 插件下载 注意写法: 背景 将 下面的两个插件 改写成vue.js , elementui 实现chrome插件开发实例0...
PCL 计算外接圆的半径
目录 一、算法原理1、计算公式2、主要函数3、源码解析二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。爬虫自重。 一、算法原理 1、计算公式...
Matlab实现神经网络SOM算法(附上完整仿真源码)
神经网络SOM算法是一种基于自组织的无监督学习算法,其全称为Self-Organizing Map,可以用来对数据进行聚类和可视化。本文将介绍如何使用Matlab实现神经网络SOM算法。 文章目录 一、准备工作二、数据准备三、SOM算法实现四、聚类结果分析五、总结六、完整…...
【遍历】非递归法 二叉树的前中后序遍历
文章目录 非递归法前序遍历后序遍历中序遍历 递归法DFS 非递归法 通过栈Stack来模拟递归。 前序遍历 LeetCode 144 前序遍历:1 2 3 定义:存放答案的List、栈Stack 将root入栈出栈:node,为null则舍弃将node放入list将node.r…...
Python将tiff转换成png
文章目录 问题描述解决方案压缩并转换参考文献 问题描述 base64 的 image/tiff 无法在页面直接展示,将其转换为 image/png 解决方案 from io import BytesIOfrom PIL import Imagewith Image.open(a.tiff) as image:bytesIO BytesIO()image.save(bytesIO, format…...
【大数据】-- 部署 Flink kubernetes operator
目录 1.说明 1.1 版本 1.2 kubernetes 环境 1.3 参考 2.安装步骤 2.1 安装本地 kubernetes 环境...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
Springboot多数据源配置实践
Springboot多数据源配置实践 基本配置文件数据库配置Mapper包Model包Service包中业务代码Mapper XML文件在某些复杂的业务场景中,我们可能需要使用多个数据库来存储和管理不同类型的数据,而不是仅仅依赖于单一数据库。本技术文档将详细介绍如何在 Spring Boot 项目中进行多数…...
Xcode 16.2 版本 pod init 报错
Xcode 版本升级到 16.2 后,项目执行 pod init 报错; ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchron…...
React 样式方案与状态方案初探
React 本身只提供了基础 UI 层开发范式,其他特性的支持需要借助相关社区方案实现。本文将介绍 React 应用体系中样式方案与状态方案的主流选择,帮助开发者根据项目需求做出合适的选择。 1. React 样式方案 1.1. 内联样式 (Inline Styles) 通过 style …...
Tableau for mac 驱动
Tableau 驱动程序安装指南 对于希望在 Mac OS 上使用 Tableau 进行数据分析的用户来说,确保正确安装相应的驱动程序至关重要。Tableau 支持多种数据库连接方式,并提供官方文档指导如何设置这些连接。 安装适用于 Mac 的 JDBC 或 ODBC 驱动程序 为了使…...
