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 环境...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...

图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...

JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...