android自定义View之复合控件

下面是关于 "android自定义View之复合控件" 的完整攻略。

什么是复合控件?

复合控件是指由多个基本控件组成的控件,它通常会具有一定的业务逻辑和自定义配置属性等特征。复合控件开发的一般过程是将多个基本控件组合在一起,并对组合后的控件进行一些额外的封装,以便于在项目中重复使用。

实现复合控件的步骤

开发自定义的复合控件通常需要以下步骤:

  1. 继承 ViewGroup 或已有的组合控件(如 ConstraintLayout 等),定义自己的组合控件。
  2. 在构造方法中初始化控件,并加载布局文件(可选)。
  3. 实现 onMeasure() 方法测量自定义控件的尺寸。
  4. 实现 onLayout() 方法对内部控件进行布局。
  5. 实现 onDraw() 方法绘制控件以及相应效果。
  6. 对外提供属性和方法,允许用户配置自定义控件的样式和行为。

示例演练

示例一:封装SwitchButton

下面是一个简单的示例代码,我们将会用代码实现自定义开关按钮:

<com.example.myview.SwitchButton
    android:id="@+id/switch_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:checked="true"
    app:switch_color="@color/colorAccent"
    app:switch_radius="15dp" />

在布局文件中,我们定义了一个自定义的 SwitchButton 控件,在属性中设置了开关状态、按钮颜色和半径等参数。

class SwitchButton(context: Context, attrs: AttributeSet?) : RelativeLayout(context, attrs) {

    private lateinit var switchView: View
    private var isChecked = false
    private var switchRadius = 0
    private var switchColor = 0

    init {
        initView(context, attrs)
    }

    private fun initView(context: Context, attrs: AttributeSet?) {
        LayoutInflater.from(context).inflate(R.layout.layout_switch_button, this, true)
        switchView = findViewById(R.id.switch_button)
        attrs?.let {
            val typedArray =
                context.obtainStyledAttributes(it, R.styleable.SwitchButton)
            isChecked = typedArray.getBoolean(R.styleable.SwitchButton_checked, false)
            switchColor =
                typedArray.getColor(R.styleable.SwitchButton_switch_color, context.getColor(R.color.colorAccent))
            switchRadius = typedArray.getDimensionPixelSize(
                R.styleable.SwitchButton_switch_radius,
                0
            )
            typedArray.recycle()
        }
        setSwitchView()
        setOnClickListener {
            isChecked = !isChecked
            setSwitchView()
        }
    }

    private fun setSwitchView() {
        val layoutParams = switchView.layoutParams as LayoutParams
        if (isChecked) {
            switchView.setBackgroundResource(R.drawable.bg_switch_on)
            layoutParams.leftMargin = 0
        } else {
            switchView.setBackgroundResource(R.drawable.bg_switch_off)
            layoutParams.leftMargin = switchRadius*3
        }
        switchView.layoutParams = layoutParams
    }
}

在代码中我们继承自 RelativeLayout,并对控件进行了初始化操作。我们通过上面的代码可以看到,我们在initView()方法中获取用户对控件传递过来的属性参数,并设置对应的属性,如checked、switch_color、switch_radius等。我们通过代码设置了开关状态,并提供了自定义开关颜色和宽度的属性,用户可以自由选择设置。最后通过onClickListener()监听器对开关状态进行控制,并刷新自定义开关。

示例二:封装微信底部导航栏效果

下面是另外一个示例代码,我们将会用代码实现自定义微信底部导航栏效果:

<com.example.myview.WechatBottomBar
    android:id="@+id/bottom_bar"
    android:layout_width="match_parent"
    android:layout_height="56dp"
    android:background="@color/colorPrimary"
    app:text_color_normal="@color/colorPrimaryDark"
    app:text_color_selected="@android:color/white"
    app:item_text_color="#ffffff"
    app:item_text_size="12sp"
    app:item_margin="0dp"
    app:bar_padding="12dp"
    app:bar_margin_left="0dp"
    app:bar_margin_right="0dp"
    app:bar_margin_top="0dp"
    app:bar_margin_bottom="0dp"
    app:bottom_item_count="4" />

在布局文件中,我们定义了一个自定义的 WechatBottomBar 控件,在属性中设置了背景颜色、大小、字体颜色和间距等参数。

class WechatBottomBar @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    private val radioButtons: MutableList<RadioButton> = mutableListOf()

    var onCheckedChangeListener: OnCheckedChangeListener? = null

    init {
        initView(context, attrs)
    }


    override fun onFinishInflate() {
        super.onFinishInflate()
        radioButtons.forEach {
            it.setOnCheckedChangeListener { _, isChecked ->
                if (isChecked) {
                    onCheckedChangeListener?.onCheckedChange(it)
                    radioButtons.filter { r -> r != it }.forEach { r -> r.isChecked = false }
                }
            }
        }
    }

    private fun initView(
        context: Context,
        attrs: AttributeSet?
    ) {
        attrs?.let {
            val ta = context.obtainStyledAttributes(it, R.styleable.WechatBottomBar)
            val bottomItemCount = ta.getInt(R.styleable.WechatBottomBar_bottom_item_count, 5)
            val barPadding = ta.getDimension(R.styleable.WechatBottomBar_bar_padding, 0f)
            val barMarginLeft = ta.getDimension(R.styleable.WechatBottomBar_bar_margin_left, 0f)
            val barMarginRight = ta.getDimension(R.styleable.WechatBottomBar_bar_margin_right, barMarginLeft)
            val barMarginTop = ta.getDimension(R.styleable.WechatBottomBar_bar_margin_top, 0f)
            val barMarginBottom = ta.getDimension(R.styleable.WechatBottomBar_bar_margin_bottom, 0f)
            val itemTextSize = ta.getDimension(R.styleable.WechatBottomBar_item_text_size, 0f)
            val itemTextColor = ta.getColor(
                R.styleable.WechatBottomBar_item_text_color,
                resources.getColor(android.R.color.black)
            )
            val itemMargin = ta.getDimension(R.styleable.WechatBottomBar_item_margin, 0f)
            val normalTextColor =
                ta.getColor(R.styleable.WechatBottomBar_text_color_normal, itemTextColor)
            val p = ta.getColor(R.styleable.WechatBottomBar_text_color_selected, normalTextColor)
            val count = bottomItemCount.min(5).max(2)
            val itemWidth = (width - paddingLeft - paddingRight) / bottomItemCount
            LayoutInflater.from(context).inflate(R.layout.custom_wechat_bottom_bar, this, true)
            gravity = Gravity.CENTER_HORIZONTAL
            orientation = HORIZONTAL
            for (i in 0 until count) {
                val rb = RadioButton(context).apply {
                    layoutParams = LayoutParams(itemWidth, LayoutParams.MATCH_PARENT).apply {
                        leftMargin = itemMargin.toInt()
                        rightMargin = itemMargin.toInt()
                        topMargin = barPadding.toInt()
                        bottomMargin = barPadding.toInt()
                    }
                    setTextSize(TypedValue.COMPLEX_UNIT_PX, itemTextSize)
                    setTextColor(ColorStateList(
                        arrayOf(
                            intArrayOf(android.R.attr.state_checked),
                            intArrayOf(-android.R.attr.state_checked)
                        ),
                        intArrayOf(p, normalTextColor)
                    ))
                    this.buttonDrawable = null
                    isChecked = i == 0
                    text = "Item ${i + 1}"
                    id = i + 1
                }
                radioButtons.add(rb)
                addView(rb)
            }
            ta.recycle()
            setMargins(barMarginLeft.toInt(), barMarginTop.toInt(), barMarginRight.toInt(), barMarginBottom.toInt())
        }
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams =
        LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)

    interface OnCheckedChangeListener {
        fun onCheckedChange(button: RadioButton)
    }
}

在代码中我们依然是继承自 LinearLayout,将 RadioButton 作为子控件添加到我们自定义的 WechatBottomBar 控件中。在 initView() 方法中获取了用户传递的属性值,包括底部控件的个数、内边距等属性。在代码中需要实现 OnCheckedChangeListener 接口,主要是实现选中状态的切换。在 onFinishInflate() 方法中,我们遍历获取到的 radioButtons 集合,监听 CheckedChangeListener 并为其指定具体实现。最终实现了微信底部导航栏的样式,并将传递过来的属性值指派到 RadioButton 控件。

结语

上面的两个示例都展示了如何实现一个复合控件。自定义 View 的过程需要掌握各个方法的含义,更需要面向对象的设计思想。希望通过本文的讲解,大家能够对自定义控件有一定的认识,也能够在实际项目中灵活应用。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:android自定义View之复合控件 - Python技术站

(0)
上一篇 2023年6月25日
下一篇 2023年6月25日

相关文章

  • C语言单链表遍历与求和示例解读

    C语言单链表遍历与求和示例解读是一个重要的程序开发技能,它能帮助程序员更好地理解链表的操作方法,并能有效完成链表求和等需求。下面,我们将从以下几个方面进行详细讲解。 1. 单链表的创建与初始化 在正式开始单链表遍历与求和的过程前,需要先创建并初始化单链表。一般而言,单链表的初始化主要包括链表的头节点初始化以及节点的申请和赋值。下面是单链表的创建示例代码: s…

    other 2023年6月27日
    00
  • cny是什么货币?

    CNY是什么货币? CNY是中国货币的简写,全称为“人民币”。人民币是中国的法定货币,在国内有广泛的流通。人民币由中国人民银行发行,目前有纸币和硬币两种形式。 人民币的历史 人民币起源于1948年,当时新成立的中国人民银行开始发行人民币。初期的人民币以等价交换的方式发行,即原先流通的旧钞换取等额新钞。后来,人民银行逐渐完善了货币体系,发行了一系列新的货币,如…

    其他 2023年4月16日
    00
  • PHP根据IP地址获取所在城市具体实现

    PHP根据IP地址获取所在城市具体实现攻略 1. 获取IP地址 首先,我们需要获取用户的IP地址。在PHP中,可以使用$_SERVER[‘REMOTE_ADDR’]来获取用户的IP地址。例如: $ip = $_SERVER[‘REMOTE_ADDR’]; 2. 发送请求获取城市信息 接下来,我们需要向IP地址查询接口发送请求,以获取IP地址对应的城市信息。有…

    other 2023年7月30日
    00
  • sqlexec命令用法

    以下是sqlexec命令用法的完整攻略: 1. sqlexec命令简介 sqlexec是一个命令行工具,用于在命令行中执行SQL语句。它可以连接到各种数据库,包括MySQL、Oracle、SQL Server等,并执行SQL查询、插入、更新和删除等操作。 2. sqlexec命令语法 sqlexec命令的基本语法如下: sqlexec [options] […

    other 2023年5月8日
    00
  • 查看vue-cli脚手架的版本号和vue真实版本号及详细操作命令

    查看vue-cli脚手架的版本号和vue真实版本号及详细操作命令攻略 1. 查看vue-cli脚手架的版本号 要查看vue-cli脚手架的版本号,可以使用以下命令: vue –version 这将输出vue-cli的版本号,例如: @vue/cli 4.5.13 2. 查看vue真实版本号 要查看vue的真实版本号,可以在项目的根目录下找到package.…

    other 2023年8月3日
    00
  • pd.excelwrite的用法

    当然,我可以为您提供有关“pd.ExcelWriter的用法”的攻略,以下是详细说明: pd.ExcelWriter的用法 在Pandas中,pd.ExcelWriter用于将数据写入Excel文件。该函数可以多个数据框写入同一个Excel文件的不同工作表中。在本教程中,我们将介绍如何使用pd.ExcelWriter函数及的用法。 语法 pd.ExcelWr…

    other 2023年5月7日
    00
  • Laravel中Trait的用法实例详解

    下面就是对「Laravel中Trait的用法实例详解」的完整攻略: 什么是Trait? Trait 是 PHP 5.4 引入的一个新语言特性,它是 PHP 中实现代码重用的一种方式。在 Laravel 中,Trait 是非常常用的一种技巧,可以用它来解决代码冗余、功能扩展等问题。 Laravel中Trait的用法 Laravel 中的 Trait 主要用于增…

    other 2023年6月27日
    00
  • linuxcp直接覆盖不提示信息解决方法

    以下是关于“Linux cp 直接覆盖不提示信息解决方法”的完整攻略: 问题描述 在Linux系统中,使用cp命令复制文件时,如果目标文件已经存在,cp命令会直接覆盖目标文件,而不会提示用户是否确认。这可能会导致用户误操作,造成数据丢失等问题。 解决方法 为了避免这种情况的发生,可以使用以下两种方法: 方法1:使用-i选项 -i选项可以让cp命令在覆盖目标文…

    other 2023年5月6日
    00
合作推广
合作推广
分享本页
返回顶部