Android如何通过组合的方式自定义View
作者:东土也 发布时间:2022-11-01 04:33:46
前言:
自定义View可以分为两种方式:
第一种通过继承ViewGroup,内部通过addView的方式将其他的View组合到一起。
第二种则是通过继承View,重启View的onMeasure,onLayout,onDraw方法来绘制不规则图形,如折线图等。
本文介绍的是第一种方式通过组合的方式去实现自定义View。
实现自定义View首先要自定义属性。对于自定义属性,第一步是在项目res/values文件夹中新建attrs.xml文件,在文件中设置自定义属性的名称和类型,
代码如下:
<resources>
<declare-styleable name="InputItemLayout">
<attr name="hint" format="string"></attr>
<attr name="title" format="string"/>
<attr name="inputType" format="enum">
<enum name="text" value="0"/>
<enum name="password" value="1"/>
<enum name="number" value="2"/>
</attr>
<attr name="inputTextAppearance" format="reference"/>
<attr name="titleTextAppearance" format="reference"/>
<attr name="topLineAppearance" format="reference"/>
<attr name="bottomLineAppearance" format="reference"/>
</declare-styleable>
<declare-styleable name="inputTextAppearance">
<attr name="hintColor" format="color" />
<attr name="inputColor" format="color" />
<attr name="textSize" format="dimension" />
<attr name="maxInputLength" format="integer" />
</declare-styleable>
<declare-styleable name="titleTextAppearance">
<attr name="titleColor" format="color" />
<attr name="titleSize" format="dimension" />
<attr name="minWidth" format="dimension" />
</declare-styleable>
<declare-styleable name="lineAppearance">
<attr name="color" format="color" />
<attr name="height" format="dimension" />
<attr name="leftMargin" format="dimension" />
<attr name="rightMargin" format="dimension" />
<attr name="enable" format="boolean" />
</declare-styleable>
</resources>
自定义属性都需要包裹在declare-styleable标签中,name属性标志这个属性集合的名字,其中的attr标志属性。对于自定义属性的类型,主要的有以下几种
string字符串类型 reference引用类型,一般是指向另外的一个资源属性 color颜色代码 dimension尺寸 float浮点型 boolean布尔型 integer整型 enum枚举型
当你定义完上面的文件,接下来我们需要在自定义View中解析它们,从而获得用户传递进来的属性。 属性的解析可以使用以下代码完成
val array = context.obtainStyledAttributes(attributeSet, R.styleable.InputItemLayout)
val title = array.getString(R.styleable.InputItemLayout_title)
val titleResId = array.getResourceId(R.styleable.InputItemLayout_titleTextAppearance, 0)
上面的代码中,第一句是通过obtainStyledAttributes解析上面XML文件中属性名为InputItemLayout的属性内容,并返回TypedArray,后续该命名空间中的所有属性都可以通过TypedArray.getXX()来获得XX是属性类型。
但是引用类型除外,因为引用类型中还包含了其他属性,所以需要如下代码去提取属性。
val array1 = context.obtainStyledAttributes(attributeSet, R.styleable.InputItemLayout)
val titleResId = array1.getResourceId(R.styleable.InputItemLayout_titleTextAppearance, 0)
val array = context.obtainStyledAttributes(titleResId, R.styleable.titleTextAppearance)
val titleColor = array.getColor(
R.styleable.titleTextAppearance_titleColor,
resources.getColor(R.color.color_565)
)
如上代码,我们先获取在InputItemLayout属性中titleTextAppearance的属性,这时候发现titleTextAppearance是一个引用类型的属性,在使用 context.obtainStyledAttributes(titleResId, R.styleable.titleTextAppearance)获取titleTextAppearance中的属性值,第一个参数titleResId是titleTextAppearance的资源ID。 最终我们获取了所有的属性,这时候就可以开始自定义你的View了。
当我们最终完成了所有的代码,怎么在布局文件中使用呢。对于普通的属性,如String Int等就和平常一样,但是对于引用类型,我们需要在style.xml文件中定义资源文件
<com.slowtd.tcommon.InputItemLayout
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="55dp"
app:hint="请输入密码"
app:title="密码"
app:inputType="text"
app:titleTextAppearance="@style/titleTextAppearance"
app:inputTextAppearance="@style/inputTextAppearance_limitLength"
app:topLineAppearance="@style/lineAppearance"
app:bottomLineAppearance="@style/lineAppearance"
/>
<style name="inputTextAppearance">
<item name="hintColor">@color/color_C1B</item>
<item name="inputColor">@color/color_565</item>
<item name="textSize">15sp</item>
</style>
<style name="inputTextAppearance_limitLength" parent="inputTextAppearance">
<item name="maxInputLength">4</item>
</style>
<style name="titleTextAppearance">
<item name="titleColor">@color/color_565</item>
<item name="titleSize">15sp</item>
<item name="minWidth">100dp</item>
</style>
<style name="lineAppearance">
<item name="color">@color/black</item>
<item name="height">2dp</item>
<item name="leftMargin">0dp</item>
<item name="rightMargin">0dp</item>
<item name="enable">true</item>
</style>
下面的代码是一个简单的自定义输入框代码,供大家参考,配合上面的XML属性资源就可以使用了。
class InputItemLayout : LinearLayout {
private lateinit var titleView: TextView
private lateinit var editText: EditText
private var bottomLine: Line
private var topLine: Line
private var topPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var bottomPaint = Paint(Paint.ANTI_ALIAS_FLAG)
constructor(context: Context) : this(context, null)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(
context,
attributeSet,
defStyle
) {
dividerDrawable = ColorDrawable()
showDividers = SHOW_DIVIDER_BEGINNING
//去加载 去读取 自定义sytle属性
orientation = HORIZONTAL
val array = context.obtainStyledAttributes(attributeSet, R.styleable.InputItemLayout)
//解析title 属性
val title = array.getString(R.styleable.InputItemLayout_title)
val titleResId = array.getResourceId(R.styleable.InputItemLayout_titleTextAppearance, 0)
parseTitleStyle(title, titleResId)
//解析右侧的输入框属性
val hint = array.getString(R.styleable.InputItemLayout_hint)
val inputResId = array.getResourceId(R.styleable.InputItemLayout_inputTextAppearance, 0)
val inputType = array.getInteger(R.styleable.InputItemLayout_inputType, 0)
parseInputStyle(hint, inputResId, inputType)
//上下分割线属性
val topResId = array.getResourceId(R.styleable.InputItemLayout_topLineAppearance, 0)
val bottomResId = array.getResourceId(R.styleable.InputItemLayout_bottomLineAppearance, 0)
topLine = parseLineStyle(topResId)
bottomLine = parseLineStyle(bottomResId)
if (topLine.enable) {
topPaint.color = topLine.color
topPaint.style = Paint.Style.FILL_AND_STROKE
topPaint.strokeWidth = topLine.height
}
if (bottomLine.enable) {
bottomPaint.color = bottomLine.color
bottomPaint.style = Paint.Style.FILL_AND_STROKE
bottomPaint.strokeWidth = bottomLine.height
}
array.recycle()
}
@SuppressLint("CustomViewStyleable")
private fun parseLineStyle(resId: Int): Line {
val line = Line()
val array = context.obtainStyledAttributes(resId, R.styleable.lineAppearance)
line.color =
array.getColor(
R.styleable.lineAppearance_color,
ContextCompat.getColor(context, R.color.color_d1d2)
)
line.height = array.getDimensionPixelOffset(R.styleable.lineAppearance_height, 0).toFloat()
line.leftMargin =
array.getDimensionPixelOffset(R.styleable.lineAppearance_leftMargin, 0).toFloat()
line.rightMargin =
array.getDimensionPixelOffset(R.styleable.lineAppearance_rightMargin, 0).toFloat()
line.enable = array.getBoolean(R.styleable.lineAppearance_enable, false)
array.recycle()
return line
}
inner class Line {
var color = 0
var height = 0f
var leftMargin = 0f
var rightMargin = 0f;
var enable: Boolean = false
}
@SuppressLint("CustomViewStyleable")
private fun parseInputStyle(hint: String?, resId: Int, inputType: Int) {
val array = context.obtainStyledAttributes(resId, R.styleable.inputTextAppearance)
val hintColor = array.getColor(
R.styleable.inputTextAppearance_hintColor,
ContextCompat.getColor(context, R.color.color_d1d2)
)
val inputColor = array.getColor(
R.styleable.inputTextAppearance_inputColor,
ContextCompat.getColor(context, R.color.color_565)
)
//px
val textSize = array.getDimensionPixelSize(
R.styleable.inputTextAppearance_textSize,
applyUnit(TypedValue.COMPLEX_UNIT_SP, 15f)
)
val maxInputLength = array.getInteger(R.styleable.inputTextAppearance_maxInputLength, 0)
editText = EditText(context)
if (maxInputLength > 0) {
editText.filters = arrayOf(InputFilter.LengthFilter(maxInputLength))//最多可输入的字符数
}
editText.setPadding(0, 0, 0, 0)
val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)
params.weight = 1f
editText.layoutParams = params
editText.hint = hint
editText.setTextColor(inputColor)
editText.setHintTextColor(hintColor)
editText.gravity = LEFT or (CENTER)
editText.setBackgroundColor(Color.TRANSPARENT)
editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize.toFloat())
/**
* <enum name="text" value="0"></enum>
* <enum name="password" value="1"></enum>
* <enum name="number" value="2"></enum>
*/
if (inputType == 0) {
editText.inputType = InputType.TYPE_CLASS_TEXT
} else if (inputType == 1) {
editText.inputType =
InputType.TYPE_TEXT_VARIATION_PASSWORD or (InputType.TYPE_CLASS_TEXT)
} else if (inputType == 2) {
editText.inputType = InputType.TYPE_CLASS_NUMBER
}
addView(editText)
array.recycle()
}
@SuppressLint("CustomViewStyleable")
private fun parseTitleStyle(title: String?, resId: Int) {
val array = context.obtainStyledAttributes(resId, R.styleable.titleTextAppearance)
val titleColor = array.getColor(
R.styleable.titleTextAppearance_titleColor,
resources.getColor(R.color.color_565)
)
//px
val titleSize = array.getDimensionPixelSize(
R.styleable.titleTextAppearance_titleSize,
applyUnit(TypedValue.COMPLEX_UNIT_SP, 15f)
)
val minWidth = array.getDimensionPixelOffset(R.styleable.titleTextAppearance_minWidth, 0)
titleView = TextView(context)
titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize.toFloat()) //sp---当做sp在转换一次
titleView.setTextColor(titleColor)
titleView.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)
titleView.minWidth = minWidth
titleView.gravity = LEFT or (CENTER)
titleView.text = title
addView(titleView)
array.recycle()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//巨坑
if (topLine.enable) {
canvas!!.drawLine(
topLine.leftMargin,
0f,
measuredWidth - topLine.rightMargin,
0f,
topPaint
)
}
if (bottomLine.enable) {
canvas!!.drawLine(
bottomLine.leftMargin,
height - bottomLine.height,
measuredWidth - bottomLine.rightMargin,
height - bottomLine.height,
bottomPaint
)
}
}
private fun applyUnit(applyUnit: Int, value: Float): Int {
return TypedValue.applyDimension(applyUnit, value, resources.displayMetrics).toInt()
}
fun getTitleView(): TextView {
return titleView
}
fun getEditText(): EditText {
return editText
}
}
来源:https://juejin.cn/post/7142758407844397063
猜你喜欢
- 环境操作系统windows10JDKjdk1.8.0_192IDEEclipse IDE for Enterprise Java Devel
- IDE工具之IDEA2022.2的简介、下载与安装、初步配置IDEA简介概述IDEA全称是IntelliJ,是JetBrains公司推出一个
- 前言前段时间一直使用到word文档转pdf或者pdf转word,寻思着用Java应该是可以实现的,于是花了点时间写了个文件转换工具源码wel
- 1、采用MapperScannerConfigurer,它将会查找类路径下的映射器并自动将它们创建成MapperFactoryBean。sp
- 这篇文章主要介绍了如何基于java语言实现八皇后问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友
- Java数组的定义和使用如果希望保存一组有相同类型的数据,可以使用数组。数组的定义和内存分配Java 中定义数组的语法有两种:
- 本文介绍了struts2的国际化实现网站整体中英文切换实例代码,分享给大家,具体如下:环境要求:Struts2框架环境搭建成功为了实现程序的
- 1.springboot启动过程中,首先会收集需要加载的bean的定义,作为BeanDefinition对象,添加到BeanFactory中
- 一、什么是轻量级锁轻量级锁是JDK 6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用moni
- 本文实例为大家分享了unity鼠标或者手指点击模型播放动的具体代码,供大家参考,具体内容如下using UnityEngine;using
- 这两天遇到一个服务假死的问题,具体现象就是服务不再接收任何请求,客户端会抛出Broken Pipe。检查系统状态执行top,发现CPU和内存
- java 二分法算法的实例1、前提:二分查找的前提是需要查找的数组必须是已排序的,我们这里的实现默认为升序2、原理:将数组分为三部分,依次是
- 一、项目概述之前有不少粉丝私信我说,能不能用Android原生的语言开发一款在手机上运行的游戏呢?说实话,使用java语言直接开发游戏这个需
- 赛马下周一就要去做java实验了,还记得上一次实验还有一个程序没写完,匆匆交了实验报告的半成品(希望老师没发现www)。为了下周一能有更充裕
- 本文实例讲述了Java中public static void main(String args[])的来龙去脉。分享给大家供大家参考,具体如
- java 中HttpClient传输xml字符串实例详解介绍:我现在有一个对象page,需要将page对象转换为xml格式并以binary方
- 这篇文章主要介绍了java 读取系统Properties代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值
- 01.点明观点C#中,非托管资源使用之后必须释放,而using()是使用非托管资源的最佳方式,可以确保资源在代码块结束之后被正确释放,并且代
- 一、SpringMVC使用1.工程创建创建maven工程。添加java、resources目录。引入Spring-webmvc 依赖。<
- 项目地址:gitee.com/baojh123/rp…netty-study 这个项目是没用到的,可以删掉,主要是测试