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地址了。

三、实现效果

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/37306.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Camx--概述

该部分代码主要位于 vendor/qcom/proprietary/ 目录下&#xff1a; 其中 camx 代表了通用功能性接口的代码实现集合&#xff08;CamX&#xff09;&#xff0c;chi-cdk代表了可定制化需求的代码实现集合&#xff08;CHI&#xff09;&#xff0c;从图中可以看出Camx部分对上作为H…

Linux 性能分析之iostat命令详解

Linux 性能分析之iostat命令详解 iostat命令是IO性能分析的常用工具&#xff0c;其是input/output statistics的缩写。本文将着重于下面几个方面介绍iostat命令&#xff1a; iostat的安装iostat命令行选项说明iostat输出内容分析如何确定磁盘IO的瓶颈iostat实际案例 命令的安…

django boostrap html实现可拖拽的左右布局,鼠标拖动调整左右布局的大小或占比

一、实现的效果 最近需要在Django项目中,实现一个左右布局的html页面,页面框架使用的是boostrap。但这个布局不是简单的左右分栏布局,而是需要实现可以通过鼠标拖拽的方式动态调整左右两侧布局的大小和占比。效果大致如下: 一开始,页面分为左右两块布局: 鼠标放到中间的…

Python脚本之连接MySQL【四】

本文为博主原创&#xff0c;未经授权&#xff0c;严禁转载及使用。 本文链接&#xff1a;https://blog.csdn.net/zyooooxie/article/details/124640412 之前写了篇 Python脚本之连接MySQL【三】&#xff0c;日常使用过程中&#xff0c;代码实际有很多改动&#xff0c;特此更新…

【先进PID控制算法(ADRC,TD,ESO)加入永磁同步电机发电控制仿真模型研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

定量分析计算51单片机复位电路工作原理 怎么计算单片机复位电容和电阻大小

下面画出等效电路图 可以知道单片机内必然有一个电阻RX&#xff0c;为了简化分析&#xff0c;我们假设他是线性电阻&#xff08;不带电容&#xff0c;电感的支路&#xff09; 还有一个基础知识&#xff1a; 电容器的充电放电曲线&#xff1a; 还需要知道电容电压的变化是连续…

微信小程序data-item设置获取不到数据的问题

微信小程序data-item设置获取不到数据的问题 简单说明&#xff1a; 在微信小程序中&#xff0c;通过列表渲染使用wx:for根据数组中的每一项重复渲染组件。同时使用bindtap给每一项绑定点击事件clickItem&#xff0c;再通过data-item绑定数据。 **问题&#xff1a;**通过data-i…

观察者模式实战

场景 假设创建订单后需要发短信、发邮件等其它的操作&#xff0c;放在业务逻辑会使代码非常臃肿&#xff0c;可以使用观察者模式优化代码 代码实现 自定义一个事件 发送邮件 发送短信 最后再创建订单的业务逻辑进行监听&#xff0c;创建订单 假设后面还需要做其它的…

取个对象值导致系统崩溃

取个对象值导致系统崩溃 前言 想必各位小伙经常在项目中遇到一些错误&#xff0c;取对象值的时候&#xff0c;经常报错,又或者某些项目突然就挂经常都是出现在一些对象取值上面&#xff0c;然后就被领导一顿训斥 报错分析 例如&#xff1a; 下面这个报错大家想必不会陌生&am…

最大交换(力扣)枚举 JAVA

给定一个非负整数&#xff0c;你至多可以交换一次数字中的任意两位。返回你能得到的最大值。 示例 1 : 输入: 2736 输出: 7236 解释: 交换数字2和数字7。 示例 2 : 输入: 9973 输出: 9973 解释: 不需要交换。 注意: 给定数字的范围是 [0, 10^8] 解题思路&#xff1a; 1、数最…

【量化课程】08_2.深度学习量化策略基础实战

文章目录 1. 深度学习简介2. 常用深度学习模型架构2.1 LSTM 介绍2.2 LSTM在股票预测中的应用 3. 模块分类3.1 卷积层3.2 池化层3.3 全连接层3.4 Dropout层 4. 深度学习模型构建5. 策略实现 1. 深度学习简介 深度学习是模拟人脑进行分析学习的神经网络。 2. 常用深度学习模型架…

山东布谷科技直播软件源码Nginx服务器横向扩展:搭建更稳定的平台服务

在直播软件源码平台中&#xff0c;服务器扮演着重要的角色&#xff0c;关系着视频传输、数据处理、用户管理等工作的顺利完成。随着互联网的迅猛发展&#xff0c;直播行业也随之崛起&#xff0c;全世界的人们都加入到了直播软件源码平台中&#xff0c;用户流量的增加让服务器的…

视频高效剪辑,轻松平均分割视频,生成高质量M3U8

您是否在处理视频剪辑时常常面临繁琐的切分工作&#xff1f;是否希望能够快速而精准地平均分割视频&#xff0c;并生成适用于在线播放的高质量m3u8文件&#xff1f;现在&#xff0c;我们的智能视频剪辑大师为您提供了一种简便而高效的解决方案&#xff01;无需复杂操作&#xf…

Leaflet入门,Leaflet如何实现vue双向绑定数据添加到图片标记物到地图上,动态根据vue数据更新到地图上以及鼠标经过标记物显示提示框

前言 本章使用Leaflet的vue2-leaflet或者vue-leaflet插件方式实现vue数据绑定地图数据,实现地图标记物与vue数据的双向联动更新,以及鼠标经过标记物显示提示框功能。 实现效果演示 vue如何使用Leaflet vue2如何使用:《Leaflet入门,如何使用vue2-leaflet实现vue2双向绑定…

【量化课程】02_3.投资学基础概念

文章目录 1. 投资和投资学的关系1.1 什么是投资&#xff1f;1.2 什么是投资学&#xff1f; 2. 投资学的主要内容2.1 金融市场与投资环境2.1.1 金融资产2.1.2 债券市场的意义2.1.3 金融市场与经济2.1.4 投资过程2.1.5 竞争性的市场2.1.6 市场参与者2.1.7 主要的市场债券市场外汇…

Stable Diffusion 插件开发经验

Stable Diffusion近来大热,但是插件开发的资料少之又少。 这里提供一些插件开发的经验,可供参考。 1.替换启动页。 如果你想要在运行Stable Diffusion的基础上想要一些独有的操作,不想要进行裁剪,直接替换启动页面,那么就需要在webui中进行替换 这里modules.ui.create_ui…

基于ipad协议的gewe框架进行微信群组管理(二)

友情链接 geweapi.com 点击访问即可。 获取群组详情 小提示&#xff1a; 该接口可以一次查询20个群组查询出来的信息是不带公告的 请求URL&#xff1a; http://域名地址/api/group/detail 请求方式&#xff1a; POST 请求头&#xff1a; Content-Type&#xff1a;applica…

2009年下半年 软件设计师 上午试卷

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

【Microsoft 支持】【数据库-MySql】当您尝试从大于 5000 的 TCP 端口连接时收到错误 WSAENOBUFS (10055)

​ 一、转载原文 When you try to connect from TCP ports greater than 5000 you receive the error ‘WSAENOBUFS (10055)’ Symptoms If you try to set up TCP connections from ports that are greater than 5000, the local computer responds with the following WSAE…

如何使用Spark/Flink等分布式计算引擎做网络入侵检测

如何使用Spark/Flink等分布式计算引擎做网络入侵检测 引言16 Distributed Abnormal Behavior Detection Approach Based on Deep Belief Network and Ensemble SVM Using Spark17 Spark configurations to optimize decision tree classification on UNSW-NB1518 A dynamic spa…