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.SlideMenuLayout
        xmlns: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">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <LinearLayout
                android:layout_width="200dp"
                android:layout_height="match_parent"
                android:layout_gravity="start"
                android:orientation="vertical"
                >

                <RelativeLayout
                    android:id="@+id/sidebarLayout"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                    <androidx.appcompat.widget.AppCompatImageView
                        android: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.ConstraintLayout
                        android: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.AppCompatImageView
                            android: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.AppCompatTextView
                            android: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.ConstraintLayout
                        android: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.AppCompatImageView
                            android: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.AppCompatTextView
                            android: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.ConstraintLayout
                        android: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.AppCompatImageView
                            android: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.AppCompatTextView
                            android: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>
            <RelativeLayout
                android:layout_width="match_parent"
                android:background="@color/white"
                android:layout_height="match_parent">
                <androidx.appcompat.widget.AppCompatTextView
                    android: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.AppCompatImageView
                    android: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 = 0
    
    private lateinit var menuView: View
    
    private lateinit var contentView: View
    
    /**
     * 用于处理飞速滑动
     */
    private var gestureDetector: GestureDetector
  
    var isMenuOpen: Boolean = false
    
    private var btn: AppCompatImageView? = null
    /**
     * 是否进行事件拦截
     */
    private var isIntercept = false

    constructor(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 LinearLayout
        val childCount = linearLayout.childCount
        if (childCount != 2) {
            throw IllegalArgumentException("LinearLayout child size must be 2!")
        }
        menuView = linearLayout.getChildAt(0)
        val menuLayoutParams = menuView.layoutParams
        menuLayoutParams.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.layoutParams
        contentLayoutParams.width = getScreenWidth()
        contentRelativeLayout.layoutParams = contentLayoutParams
        linearLayout.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 从屏幕宽度变化到 0
        val scale = 1 - l * 1f / menuView.measuredWidth //scale 从 0 到1
        //处理菜单页缩放和透明度
        menuView.pivotX = menuView.measuredWidth * 1f
        menuView.pivotY = menuView.measuredHeight / 2f
        menuView.scaleX = 0.5f + scale * 0.5f
        menuView.scaleY = 0.5f + scale * 0.5f

        menuView.alpha = 0.25f + 0.75f * scale

        //处理内容页缩放 缩放到0.7f
        contentView.pivotX = 0f
        contentView.pivotY = contentView.measuredHeight / 2f
        contentView.scaleX = 0.7f + (1 - scale) * 0.3f
        contentView.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 = true
                return 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? = null
    private var sliding: SlideMenuLayout? = null
    override 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地址了。

三、实现效果

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

纷享销客稳居2022 H2 SFA SaaS 本土CRM厂商市场份额 TOP 1

近期&#xff0c;国际知名研究机构IDC公布了2022年下半年《中国客户关系管理(CRM)SaaS市场跟踪研究报告》&#xff0c;报告全面解析了中国CRM SaaS以及细分市场SFA SaaS的市场现状&#xff0c;并对全球各大厂商在中国SFA市场的份额占比进行了排名。连接型CRM开创者纷享销客在SF…

MyBatis-Plugin源码全面分析

三、MyBatis-Plugin 1. 基本开发方式 需求&#xff1a;在MyBatis执行之前打印一行醒目的日志&#xff0c;携带参数 实现Interceptor接口&#xff1a; Intercepts(Signature(type Executor.class,method "query",args {MappedStatement.class,Object.class, RowB…

600 V单管IGBT,可在电源应用中实现出色效率

基础半导体器件领域的高产能生产专家Nexperia (安世半导体)今日宣布&#xff0c;将凭借600 V器件系列进军绝缘栅双极晶体管(IGBT)市场&#xff0c;而30A NGW30T60M3DF将打响进军市场的第一炮。Nexperia在其庞大的产品组合中增加了IGBT&#xff0c;满足了市场对于高效高压开关器…

多个项目使用的node版本不一致?vscode dev container + docker 真香

一、接手的项目多了&#xff0c;什么node版本都有~ 遇到这种情况&#xff0c;多数情况会使用 nvm 进行 node 版本管理&#xff0c;具体使用方法可戳nvm的安装与使用。但若要并行开发两个不同环境下的项目&#xff0c;不停切换node版本&#xff0c;也难免有些繁琐。此时&#xf…

HCIP学习--BGP3

目录 前置内容 BGP下一跳的修改问题 BGP的属性 配置 PrefVal权重属性 负载分担 LocPrf 负载分担 NextHop AS-PATH Ogn 配置 MED 配置 BGP选路规则 BGP的社团属性 配置及解释 前置内容 HCIP学习--BGP1_板栗妖怪的博客-CSDN博客 HCIP学习--BGP2_板栗妖怪的博客…

【LVS-NAT配置】

配置 node1&#xff1a;128&#xff08;客户端&#xff09; node2&#xff1a;135&#xff08;调度器&#xff09; RS&#xff1a; node3&#xff1a;130 node4&#xff1a;132 node2添加网络适配器&#xff08;仅主机模式&#xff09; [rootnode2 ~]# nmtui[rootnode2 ~]#…

利用 Splashtop Enterprise 改善公司的网络安全

在我们日益数字化的世界中&#xff0c;对强有力的网络安全措施的需求从未像现在这样迫切。随着组织扩大其数字足迹并采用远程办公解决方案&#xff0c;他们面临着一系列不断变化的挑战。 威胁行为者不断寻找利用漏洞的新方法&#xff0c;这使得企业保持领先地位至关重要。俗话…

Spring Boot实现第一次启动时自动初始化数据库流程详解

随着互联网的发展项目中的业务功能越来越复杂&#xff0c;有一些基础服务我们不可避免的会去调用一些第三方的接口或者公司内其他项目中提供的服务&#xff0c;但是远程服务的健壮性和网络稳定性都是不可控因素。 在测试阶段可能没有什么异常情况&#xff0c;但上线后可能会出…

HCIP BGP小综合

BGP小综合 AS配置AS1AS2 中的小自治系统64512AS2 中的小自治系统64513AS3 测试 首先该实验分成三个AS&#xff0c;AS2里面有联邦&#xff0c;所以配置顺序 要先将IBGP通&#xff0c;然后配置AS1,AS3和联邦 AS配置 AS1 R1 # bgp 1router-id 1.1.1.1peer 12.1.1.2 as-number …

计算机设计大赛国赛一等奖项目分享——基于多端融合的化工安全生产监管可视化系统

文章目录 一、计算机设计大赛国赛一等奖二、项目背景三、项目简介四、系统架构五、系统功能结构六、项目特色&#xff08;1&#xff09;多端融合&#xff08;2&#xff09;数据可视化&#xff08;3&#xff09;计算机视觉&#xff08;目标检测&#xff09; 七、系统界面设计&am…

MySQL8安装和删除教程 保姆级(Windows)

下载 官网: mysql官网点击Downloads->MySQL Community(GPL) Downloads->MySQL Community Server(或者点击MySQL installer for Windows) Windows下有两种安装方式 在线安装 一般带有 web字样 这个需要联网离线安装 一般没有web字样 安装 下载好之后,版本号可以不一样&…

超好用的接口自动化框架,lemon-easytest内测版发布,赶紧用起来~

easytest easytest 是一个接口自动化框架。 功能特点&#xff1a; 支持 http 接口测试 支持 json&#xff0c;html,xml 格式的响应断言 支持数据库断言 支持用例标记筛选 支持用例失败重运行 支持多线程 安装 pip install lemon_easytest 快速使用 不需要写任何代码…

k8s常用资源管理 控制

目录 Pod&#xff08;容器组&#xff09;&#xff1a;Pod是Kubernetes中最小的部署单元&#xff0c;可以包含一个或多个容器。Pod提供了一种逻辑上的封装&#xff0c;使得容器可以一起共享网络和存储资源 1、创建一个pod 2、pod管理 pod操作 目录 创建Pod会很慢 Pod&…

腾讯云轻量应用服务器搭建WordPress网站教程

腾讯云百科分享使用腾讯云轻量应用服务器搭建WordPress网站教程流程&#xff0c;WordPress 是全球最流行的开源的博客和内容管理网站的建站平台&#xff0c;具备使用简单、功能强大、灵活可扩展的特点&#xff0c;提供丰富的主题插件。腾讯云轻量应用服务器提供 WordPress 应用…

打破传统直播,最新数字化升级3DVR全景直播

导语&#xff1a; 近年来&#xff0c;随着科技的不断创新和发展&#xff0c;传媒领域也正经历着一场前所未有的变革。在这个数字化时代&#xff0c;直播已经不再仅仅是在屏幕上看到一些人的视频&#xff0c;而是将观众带入一个真实世界的全新体验。其中&#xff0c;3DVR全景直…

ARMday2

.text .global _start _start:mov r0,#0x1mov r1,#0x0sum:cmp r0,#0x64bhi stopaddls r1,r1,r0addls r0,r0,#0x1bls sumstop:b stop .end

Opencv将数据保存到xml、yaml / 从xml、yaml读取数据

Opencv将数据保存到xml、yaml / 从xml、yaml读取数据 Opencv提供了读写xml、yaml的类实现&#xff1a; 本文重点参考&#xff1a;https://blog.csdn.net/cd_yourheart/article/details/122705776?spm1001.2014.3001.5506&#xff0c;并将给出文件读写的具体使用实例。 1. 官…

STM32基于CubeIDE和HAL库 基础入门学习笔记:物联网项目开发流程和思路

文章目录&#xff1a; 第一部分&#xff1a;项目开始前的计划与准备 1.项目策划和开发规范 1.1 项目要求文档 1.2 技术实现文档 1.3 开发规范 2.创建项目工程与日志 第二部分&#xff1a;调通硬件电路与驱动程序 第三部分&#xff1a;编写最基础的应用程序 第四部分&…

我们常说这个pycharm里有陷阱,第三方库导入失败,看这里!

最近有小伙伴遇到了明明安装了 python 第三方库&#xff0c;但是在 pycharm 当中却导入不成功的问题。 ​ 一直以来&#xff0c;也有不少初学 python 的小伙伴&#xff0c;一不小心就跳进了虚拟环境和系统环境的【陷阱】中。 本文就基于此问题&#xff0c;来说说在 pycharm 当…

前端:Vue.js学习

前端:Vue.js学习 1. 第一个Vue程序2. Vue指令2.1 v-if、v-else-if、v-else2.2 v-for2.3 事件绑定 v-on:2.4 v-model 数据双向绑定2.5 v-bind 绑定属性 3. Vue组件4. Vue axios异步通信5. 计算属性6. 插槽 slots7. 自定义事件内容分发 1. 第一个Vue程序 首先把vue.js拷贝到本地…