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日

相关文章

  • 详解Golang中字符串的使用

    详解Golang中字符串的使用 在 Golang 中,字符串是一种常用的数据类型,本文将详细讲解 Golang 中字符串的使用,包括字符串的定义、常用操作和注意事项。 字符串定义 在 Golang 中,字符串的定义方式有以下两种: 双引号方式 双引号中的内容即为字符串 s1 := "Hello World!" fmt.Println(s1…

    other 2023年6月20日
    00
  • 织梦dedecms 忘记管理员后台密码的解决技巧

    下面我会给出”织梦DedeCMS 忘记管理员后台密码的解决技巧”的完整攻略,包含两条示例说明。 背景 当我们使用DedeCMS作为网站内容管理系统时,由于种种原因,可能会忘记了管理员后台的密码。这个时候如何找回或重置密码就是大家关心的问题。 解决办法 解决办法一:通过数据库重置管理员密码 使用phpmyadmin等数据库管理工具登录网站web服务器上的mys…

    other 2023年6月27日
    00
  • iphone6s死机如何重启?iphone6s死机问题的解决方法

    iPhone6s死机如何重启?iPhone6s死机问题的解决方法 如果您的iPhone6s死机(即卡死、无响应),不要慌张,可以尝试以下方法来重启它,或者解决死机问题。 重启iPhone6s的方法 硬重启。按住iPhone6s的电源键和Home键,直到出现苹果标志。这通常可以解决一些普通的死机问题。 使用iTunes重启。如果硬重启不起作用,可以尝试连接您的…

    other 2023年6月27日
    00
  • 详解android adb常见用法

    详解Android ADB常见用法 ADB(Android Debug Bridge)是Android开发工具包(SDK)中的一个命令行工具,用于与连接的Android设备进行通信和调试。以下是ADB的常见用法及示例说明: 查看已连接设备列表 使用以下命令可以查看当前连接的Android设备列表: adb devices 示例输出: List of devi…

    other 2023年10月13日
    00
  • transactionscope是什么

    Transactionscope 是什么? TransactionScope 是 .NET Framework 中的一个类,用于管理事务的范围。它提供了一种简单的方法来处理跨多个资源的事务,例如数据库、消息队列和文件系统等。使用 TransactionScope 可以确保所有资源都在同一个事务中提交或回滚,从而保证数据的一致性和完整性。 Transactio…

    other 2023年5月6日
    00
  • tomcat双击startup.bat闪退的原因及解决方式

    问题描述 当我们想要启动Tomcat时,双击startup.bat后,发现窗口一闪即退,无法启动Tomcat。这个问题在开发Web应用程序时经常会遇到。 原因分析 引起这个问题的原因可能有很多,比如Java环境配置不正确、Tomcat版本不兼容、系统缺失必要的动态链接库等等。但最常见的原因是Java环境配置不正确。 解决方案 环境变量配置 确保系统中已正确配…

    其他 2023年4月16日
    00
  • TF卡和UFS存储卡有什么区别 UFS存储卡和TF卡定义及全面区别对比深度评测

    TF卡和UFS存储卡的区别: 定义不同:TF卡是一种用于存储数据的嵌入式闪存卡,也被称为Micro SD卡。而UFS存储卡是一种新型的高速存储卡,用于替代SD卡和TF卡等传统存储卡。 传输速度不同:UFS存储卡支持最高的传输速度达到1GB/s,远高于TF卡的传输速度。这意味着UFS存储卡可以更快地读写数据,使得数据传输更加高效。 容量不同:TF卡的容量普遍在…

    other 2023年6月27日
    00
  • JavaScript中new操作符的原理示例详解

    JavaScript中new操作符的原理示例详解 前言 在JavaScript中使用new操作符可以实例化一个对象,但是其具体的原理有很多人不太清楚。因此,在本文中,我们将详细介绍JavaScript中new操作符的原理,并通过实例说明其使用方法。 new操作符的原理 在JavaScript中,我们可以使用构造函数来定义一个类,构造函数内部通常会定义各个属性…

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