优雅实现垂直SeekBar:不继承Seekbar、不自定义View

news2024/11/24 17:35:11

目录

0 前言

关于自定义View

1 实现竖直SeekBar

1.1 XML布局解析

1.1.1 套一层FrameLayout

1.1.2 SeekBar去除左右间距

1.1.3 SeekBar高度无法设置

1.1.4 SeekBar背景设置

1.1.5 底部View尺寸和距底部距离不硬编码

1.2 自定义样式属性与主题

1.2.1 自定义样式属性

1.2.2 自定义主题指定属性

1.3 编码实现

1.3.1 对外提供客制化

1.3.2 支持代码中动态添加使用

1.3.3 布局依赖selfWidth/selfHeight

2 使用竖直SeekBar

3 小结


0 前言

        我们知道Android原生不支持垂直的SeekBar,为什么?我想给他们找个的理由,终究是没找到,或许懒惰是程序员的美德吧!好了,下面切入正题:

        实现下面这个UI,你会怎么做?

        下面我们首先来分解下需求:

  1. 音量或亮度调节UI,需要支持可拖动;
  2. 背景、前景、Icon位置、圆角以及UI尺寸可客制化。

        那么,Android的SeekBar支持可拖动,但是水平方向的,如果使用原生则需要将其变为竖直;否则,需要自定义View去绘制这样一个控件。

        另外,UI元素的可客制化要求不能硬编码,这也有好些细节,后面我们会展开讲讲。

关于自定义View

        其实我并不反对自定义View,尽管我们可以自己去画出来这样一个垂直的SeekBar,可是咱能确保自己对View的measure、layout以及draw的处理好过Android原生吗?不会存在性能问题吗?愚以为不见得。且自定义View,可能还费时费力不讨好,UI还原度达不到设计稿上的效果。

        所以,我的思想是尽可能复用Android原生View,或将View进行组合为最佳,其次为切图,是在没招才去自己创建画布绘制View,但一定要兼顾到功能和性能的平衡。比如:我之前通过自定义View实现的VerticalProgress,对功能和性能的平衡就不一定是最佳。

        【自定义View之VerticalProgress_Swuagg的博客】

1 实现竖直SeekBar

        实现竖直SeekBar,目前大抵有如下3种方案:

方案一:通过继承View自定义实现,如:继承View自定义实现VerticalSeekBar

方案二:通过继承SeekBar重载方法内部旋转实现,如:继承SeekBar重载方法内部旋转实现

方案三:对于API 11和更高版本,通过rotation属性在XML中指定旋转270°实现,如:使用seekbar的XML属性(android:rotation="270")获得垂直效果

        看标题我们就知道,本文采用的方案三,不继承SeekBar,不自定义View,下面我们通过代码详细看看。

1.1 XML布局解析

        下面先看看实现效果:

        接下来我们再分析下xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <SeekBar
            android:id="@+id/seekbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:paddingStart="0dp"
            android:paddingEnd="0dp"
            android:progressDrawable="?attr/UISeekBarProgressDrawable"
            android:rotation="270"
            android:thumb="@null" />
    </FrameLayout>

    <View
        android:id="@+id/icon"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintWidth_percent="0.5" />

</androidx.constraintlayout.widget.ConstraintLayout>

        首先,我们可以看到未硬编码任何数据,我知道你想拿270反驳我,但这不就是需求设计如此嘛。

1.1.1 套一层FrameLayout

        1)SeekBar旋转270°后会存在尺寸问题,需要套一层FrameLayout,且FrameLayout的宽、高与SeekBar相反,为SeekBar的高、宽,SeekBar也需要指定layout_gravity为center;

       2) 我们发现XML中两者的宽、高都是使用match_parent,是为了让用户客制化尺寸,我们在代码中根据外部使用处的宽、高会进行动态修改。

1.1.2 SeekBar去除左右间距

        SeekBar左右存在默认Padding,设置paddingHorizontal为0未生效,需要设置paddingStart和paddingEnd为0.

1.1.3 SeekBar高度无法设置

        需要同时设置minHeight、minWidth、maxHeight、maxWidth,值为宽与高的最小值,因为未硬编码,所以需在代码中实现。

1.1.4 SeekBar背景设置

        通过自定义属性UISeekBarProgressDrawable,根据主题指定相应drawable,可做到随主题切换。背景文件ui_seekbar_vertical_bg.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <corners android:radius="32dp" />
            <stroke
                android:width="2dp"
                android:color="#3A4266" />
            <solid android:color="#323A60" />
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <clip>
            <shape android:shape="rectangle">
                <corners android:radius="32dp" />
                <solid android:color="#6E779C" />
            </shape>
        </clip>
    </item>
</layer-list>

1.1.5 底部View尺寸和距底部距离不硬编码

        1)宽度占比一半实现:通过指定宽高为0,设置layout_constraintDimensionRatio="1:1",layout_constraintWidth_percent="0.5",以及左右对齐;

        2)距底部距离,因为未硬编码,所以需在代码中实现。

1.2 自定义样式属性与主题

1.2.1 自定义样式属性

    <declare-styleable name="SeekBarVertical">
        <attr name="UISeekBarProgress" format="integer" />
        <attr name="UISeekBarMax" format="integer" />
        <attr name="UISeekBarIcon" format="reference" />
        <attr name="UISeekBarProgressDrawable" format="reference" />
    </declare-styleable>

1.2.2 自定义主题指定属性

    <!--应用Theme主题-->
    <style name="DefaultAppTheme" parent="android:Theme.Black.NoTitleBar.Fullscreen">
        <item name="UISeekBarProgressDrawable">@drawable/ui_seekbar_vertical_bg</item>
    </style>

1.3 编码实现

package com.agg.ui

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.SeekBar
import androidx.constraintlayout.widget.ConstraintLayout

/**
 * Description:
 * CreateDate:     2023/7/11 14:58
 * Author:         agg
 */
class SeekBarVertical : ConstraintLayout {

    lateinit var parentView: ConstraintLayout
    lateinit var seekBar: SeekBar
    lateinit var icon: View

    private var selfWidth: Int = -1
    private var selfHeight: Int = -1
    private var seekBarMinMaxValue: Int = -1
    private var selfProgress: Int = -1
    private var selfMax: Int = -1
    private var iconDrawable: Drawable? = null

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyle: Int = 0) : super(
        context, attrs, defStyle
    ) {
        attrs?.let { initAttrs(context, attrs) }
        initView()
    }

    /**
     * 代码中动态添加时使用。如:
     *
     * binding.root.addView(SeekBarVertical(this,192,432))
     */
    constructor(context: Context, width: Int, height: Int) : super(context) {
        selfWidth = width
        selfHeight = height
        seekBarMinMaxValue = if (selfHeight < selfWidth) selfHeight else selfWidth
        initView()
    }

    @SuppressLint("ResourceType")
    private fun initAttrs(context: Context, attrs: AttributeSet) {
        // 获取Android原生宽、高属性
        context.obtainStyledAttributes(
            attrs, intArrayOf(android.R.attr.layout_width, android.R.attr.layout_height)
        ).apply {
            selfWidth = getDimensionPixelSize(0, -1)
            selfHeight = getDimensionPixelSize(1, -1)
            seekBarMinMaxValue = if (selfHeight < selfWidth) selfHeight else selfWidth
            recycle()
        }

        // 获取SeekBarVertical自定义属性
        context.obtainStyledAttributes(
            attrs, R.styleable.SeekBarVertical
        ).apply {
            selfProgress = getInt(R.styleable.SeekBarVertical_UISeekBarProgress, -1)
            selfMax = getInt(R.styleable.SeekBarVertical_UISeekBarMax, -1)
            iconDrawable = getDrawable(R.styleable.SeekBarVertical_UISeekBarIcon)
            recycle()
        }
    }

    @SuppressLint("NewApi")
    private fun initView() {
        // 未设置宽高则直接返回
        if (selfWidth < 0 || selfHeight < 0) return

        parentView = View.inflate(context, R.layout.ui_seekbar_vertical, this) as ConstraintLayout

        // 设置控件整体宽、高
        (parentView.findViewById<ConstraintLayout>(R.id.parent).layoutParams as LayoutParams).apply {
            width = selfWidth
            height = selfHeight
        }

        // 设置SeekBar宽、高,以及progress和max值
        seekBar = parentView.findViewById<SeekBar>(R.id.seekbar).apply {
            (layoutParams as FrameLayout.LayoutParams).apply {
                width = selfHeight
                height = selfWidth
            }
            minHeight = seekBarMinMaxValue
            maxHeight = seekBarMinMaxValue
            minWidth = seekBarMinMaxValue
            maxWidth = seekBarMinMaxValue

            if (selfProgress >= 0) progress = selfProgress
            if (selfMax >= 0) max = selfMax
        }

        // 设置icon距离底部位置,以及icon背景
        icon = parentView.findViewById<View>(R.id.icon).apply {
            (layoutParams as LayoutParams).bottomMargin = seekBarMinMaxValue / 4
            iconDrawable?.let { setBackgroundDrawable(it) }
        }
    }

}

1.3.1 对外提供客制化

        public修饰放开parentView、seekBar以及icon,让应用开发者可自定义相关属性方法。

1.3.2 支持代码中动态添加使用

        提供构造方法constructor(context: Context, width: Int, height: Int),可在代码中动态使用SeekBarVertical。

1.3.3 布局依赖selfWidth/selfHeight

        以selfWidth与selfHeight为基准参考,代码中动态控制SeekBarVertical相关尺寸和位置。

 

2 使用竖直SeekBar

    <com.metabounds.ui.SeekBarVertical
        android:layout_width="96dp"
        android:layout_height="216dp"/>

        可在xml中指定相关属性值

        app:UISeekBarIcon="@drawable/ic_user_head"
        app:UISeekBarMax="12"
        app:UISeekBarProgress="3"

        也可在代码中指定

binding.seekBar.icon.setBackgroundResource(R.drawable.ic_user_head)
binding.seekBar.seekBar.max = 12
binding.seekBar.seekBar.progress = 3

        代码中动态添加SeekBarVertical

binding.root.addView(SeekBarVertical(this, 96, 216))

3 小结

        本文的实现依赖于Android11及以上的rotation属性,如果要兼容低版本Android,还是建议采用方案1的自定义View或方案2的canvas旋转+平移。


目录

0 前言

关于自定义View

1 实现竖直SeekBar

1.1 XML布局解析

1.1.1 套一层FrameLayout

1.1.2 SeekBar去除左右间距

1.1.3 SeekBar高度无法设置

1.1.4 SeekBar背景设置

1.1.5 底部View尺寸和距底部距离不硬编码

1.2 自定义样式属性与主题

1.2.1 自定义样式属性

1.2.2 自定义主题指定属性

1.3 编码实现

1.3.1 对外提供客制化

1.3.2 支持代码中动态添加使用

1.3.3 布局依赖selfWidth/selfHeight

2 使用竖直SeekBar

3 小结


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

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

相关文章

应急管理大屏助力暴雨天气下的水灾防范

随着气候变化和城市化进程的加剧&#xff0c;暴雨天气引发的水灾风险日益凸显。在面对这种自然灾害时&#xff0c;如何高效、及时地应对、减轻损失成为了当务之急。水灾应急管理平台的可视化大屏为相关部门和决策者提供了实时、全面的信息展示和决策支持&#xff0c;大大提升了…

每天5个好用的实用工具链接分享(第1弹)

每天5个好用的实用工具链接分享&#xff08;第1弹&#xff09; 1、免费PPT模板网站2、科研狗租用GPU跑模型网站3、在线正则测试网站4、免费数据集下载网站5、在线curl命令转代码网站6、号外 1、免费PPT模板网站 【链接】&#xff1a;https://www.ypppt.com/ 【网站名】&#x…

性能测试工具 Jmeter 做 Http 接口测试 :编写自定义函数

目录 一、 前言 二、 编写自定义函数的步骤 1. 新建一个工程&#xff0c;导入 jmeter jar 包。 2. 新建 package&#xff1a;stressTest.functions 3. 新建一个类继承 AbstractFunction&#xff0c;重写以下方法&#xff1a; 4. 打包 5. 将打出来的 jar 包拷贝至 jmeter…

学习记录——BiSeNetV1、BiSeNetV2、BiSeNetV3、PIDNet、CMNeXt

BiSeNetV1 BiSeNetV1为了在不影响速度的情况下&#xff0c;同时收集到空间信息和语义信息&#xff0c;设计了两条路&#xff1a; Spatial Path: 用了三层stride为 2 的卷积&#xff0c;卷积BNRELU模块。最后提取了相当于原图像 1/8 的输出特征图。由于它利用了较大尺度的特征图…

怎样把手机录音转换成文字免费?分享3个免费方法给给大家!

将手机录音转换为文字可以提高工作和学习效率&#xff0c;但很多人不知道如何实现。在本文中&#xff0c;我将分享三个免费的方法来帮助您将手机录音转换为文字&#xff0c;分别是使用记灵在线工具&#xff08;网页&#xff09;、微信和剪映。无论您是需要转录会议记录、课堂笔…

界面控件DevExtreme UI组件——增强的API功能

虽然DevExtreme刚刚发布了v23.1&#xff0c;但今天我们仍然要继续总结一下之前的主要更新&#xff08;v22.2&#xff09;中发布的一些与DevExtreme API相关的重要特性。 DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff…

C语言 — 指针进阶篇(上)

前言 指针基础篇回顾可以详见&#xff1a; 指针基础篇&#xff08;1&#xff09;指针基础篇&#xff08;2&#xff09; 指针进阶篇分为上下两篇,上篇介绍1 — 4&#xff0c;下篇介绍5 — 6 字符指针数组指针指针数组数组传参和指针传参函数指针函数指针数组指向函数指针数组的…

Leetcode-每日一题【147.对链表进行插入排序】

题目 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的&#xff0c;每次只移动一个元素&#xff0c;直到所有元素可以形成一个有序的输出列表。 每次迭代中&#xff0c;插入排序…

MySQL分区表详解

目录 分区表介绍 分区适用场景 分区方式 分区策略 常见分区命令 1. 分区表介绍 MySQL 数据库中的数据是以文件的形势存在磁盘上的&#xff0c;默认放在 /var/lib/mysql/ 目录下面&#xff0c;我们可以通过 show variables like %datadir%; 命令来查看&#xff1a; 我们进入…

a标签form表单,转发,重定向

a标签需要写项目名,form表单需要写项目名,转发写请求路径,重定向需要写项目名 // window.location.href"请求路径" 需要写项目名 // window.location"请求路径" 需要写项目名 // document.location.href"请求路径" 需要写项目名 …

业务流程图怎么画?这几种绘制方法看一看

业务流程图怎么画&#xff1f;流程图提供了对业务流程的清晰概述&#xff0c;帮助人们理解工作流程中涉及的活动、决策和步骤。它定义了任务的顺序和依赖关系&#xff0c;使人们能够更好地了解整个流程。通过绘制流程图&#xff0c;可以更容易地识别出潜在的问题、瓶颈和延迟。…

SOPC之NiosⅡ系统(一)

1. 基础概念 1.1 CPU软核与硬核 简单来说 CPU硬核就是在FPGA上的一颗硬件结构固定并且用户不能对其结构进行任何更改、只能进行编程控制的芯片。 CPU软核则是FPGA上本来不存在这样的硬件结构&#xff0c;用户可根据硬件描述语言利用NIOS Ⅱ软核搭建出一个CPU。 1.2 SOC和S…

面试题大杂烩-记不住

1、分库分表图啥 分库是为了解决单库io连接数的瓶颈 分表是为了解决单表效率瓶颈 2、分表后如何limit分页 如果是根据xxx字段进行分表的话 那么shardingjdbc会根据字段进行笛卡尔积计算 去对应表里面执行sql到内存中计算&#xff0c;比如根据用户id进行hash算法进行查表&…

如何选择软文发布平台?软文发布平台的分类、特点及推广策略

在现今的市场竞争中&#xff0c;软文作为一种重要的推广方式&#xff0c;受到了越来越多企业的关注和运用。而软文发布平台&#xff0c;则是软文营销过程中不可或缺的一个环节&#xff0c;不同的软文发布平台会对软文的传播效果产生重要影响。本文将从软文发布平台的分类、特点…

淘宝APP商品详情源数据接口代码分享(API接口系列可高并发)

电商平台APP商品详情源数据接口代码分享如下&#xff1a; 商品数据&#xff1a;淘宝提供了商品的基本信息&#xff0c;包括商品名称、描述、规格、价格、销量、库存等信息。此外&#xff0c;也可以通过淘宝提供的API接口来获取商品的图片、评价、物流信息等详细数据。 公共参数…

深入理解DRF中的Mixin类

DRF官网地址&#xff1a; Home - Django REST framework Generic views - Django REST framework 一、Mixin类介绍 1.1 Mixin类介绍 Mixin类是一种常见的设计模式&#xff0c;在多个类之间共享功能或行为时非常有用。 一个Mixin类通常包含一组方法或属性&#xff0c;可以被…

unity 使用vrtk4的插件 打包htv vive VR客户端包,手柄不生效

背景&#xff1a; 目的&#xff1a;u3d使用vrtk开发pico应用(vrtk是为了到时候无缝衔接后续要买的htc vive pro 2) 先导入了tilia importer&#xff08;也就是vrtk4.0&#xff0c;根据教程模块化使用功能&#xff09;和pico官网下的“PICO Unity IntegrationSDK-214-20230302…

Leetcode-每日一题【138.复制带随机指针的链表】

题目 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节…

手捏Java知识点

今天开始面向对象了 面向对象基础 类&#xff1a;由属性和行为组成属性&#xff1a;在类中通过成员变量来体现行为&#xff1a;在类中通过成员方法来体现public class 类名{//成员变量变量1的数据类型 变量1;变量2的数据类型 变量2;...//成员方法方法1;方法2;...}有几个重要的概…

百度iOS端长连接组件建设及应用实践

作者 | 百度消息中台团队 导读 在过去的十年里&#xff0c;移动端技术飞速发展&#xff0c;移动应用逐渐成为主要的便捷访问和使用互联网的方式&#xff0c;承接了越来越多的业务和功能&#xff0c;这也意味着对移动端和服务器之间的通信效率和稳定性提出了更高的要求。为了实现…