文化袁探索专栏——Activity、Window和View三者间关系
文化袁探索专栏——View三大流程#Measure
文化袁探索专栏——View三大流程#Layout
文化袁探索专栏——消息分发机制
文化袁探索专栏——事件分发机制
文化袁探索专栏——Launcher进程启动流程’VS’APP进程启动流程
文化袁探索专栏——Activity启动流程
文化袁探索专栏——自定义View实现细节
文化袁探索专栏——线程池执行原理|线程复用|线程回收
文化袁探索专栏——React Native启动流程

这里介绍以继承布局实现方式,来探索自定义View的实现细节 ~

文件attrs.xml-属性字段定义 format
枚举类型 enum
引用类型/参考某资源id refrence
font-size、view-measure dimension
颜色类型 color
布尔类型 boolean
单精度浮点类型 float
整型 Integer
百分数 fraction

自定义顶部导航:继承RelativeLayout实现

自定义通用的页面顶部导航组件。左侧按钮以返回按钮为主,右侧至少会有两个按钮(更更多、分享)。在顶部导航栏中间位置则是标题,包含副标题,标题字数超过一定宽度以末尾省略结束。

关键代码:如何定义View样式属性、如何对已定义的样式属性进行解析

定义View样式属性
定义View样式属性,需要依据需求明确自定义属性的字段,如按钮的大小、颜色,主副标题大小等。并为该组件通用性定义其默认属性样式的配置。

<!-- attrs.xml,自定义View属性集合 -->
<declare-styleable name="UINavigationBar"><!--顶部导航的背景定义 android:navBackground = "@drawable/图片ID"--><atty name="navBackground" format="refrence"><!--按钮的大小颜色,使用iconfont--><attr name="text_btn_text_size" format="dimension" /><attr name="text_btn_text_color" format="color" /><!--标题大小颜色,出现副标题时主标题的大小--><attr name="title_text_size" format="dimension" /><attr name="title_text_size_with_subTitle" format="dimension" /><attr name="title_text_color" format="color" /><!--副标题大小颜色--><attr name="subTitle_text_size" format="dimension" /><attr name="subTitle_text_color" format="color" /><!--按钮的横向内间距--><attr name="hor_padding" format="dimension"/><!--返回按钮iconfont文本  主副标题文本--><attr name="nav_icon" format="string" /><attr name="nav_title" format="string" /><attr name="nav_subtitle" format="string" />
</declare-styleable><!-- 用作默认属性集合配置-->
<style name="defNavigationStyle"><item name="hor_padding">8dp</item><item name="nav_icon">&#xe607;</item><item name="text_btn_text_size">16sp</item><item name="text_btn_text_color">#666666</item><item name="title_text_size">18sp</item><item name="title_text_color">#000000</item><item name="subTitle_text_size">14sp</item><item name="title_text_size_with_subTitle">16sp</item><item name="subTitle_text_color">#717882</item>
</style>

解析已定义的样式属性

// UINavigationHeader.kt
/**抽取几行具有代表性代码,介绍如何使用自定义属性*/
// 获取样式属性信息的集合;若没有对所自定义属性配置对应值。
// 则会使用默认的属性集合defNavigationStyle
val array = context.obtainStyledAttributes(attrs,R.styleable.UINavigationBar,defStyleAttr,R.style.defNavigationStyle)
// 获取样式属性集合中单个字符串类型样式值
val navIcon = array.getString(R.styleable.UINavigationBar_nav_icon)
// 获取样式自定义属性集合中(或默认)颜色值,且有默认颜色值
val navIconColor = array.getColor(R.styleable.UINavigationBar_nav_icon_color, Color.BLACK)
// 或者获取自定义样式属性中(或默认)颜色值,返回的是ColorStateList
// 这里使用的目的是配合Button点击效果的文字颜色变化
val btnTextColor = array.getColorStateList(R.styleable.UINavigationBar_text_btn_text_color)
// 获取配置的自定义属性(或默认)-title的字体大小且设置默认尺寸
val titleTextSize = array.getDimensionPixelSize(R.styleable.UINavigationBar_title_text_size, applyUnit(TypedValue.COMPLEX_UNIT_SP, 16f))
// 获取配置的自定属性(或默认)-分割线高度
val lineHeight = array.getDimensionPixelOffset(R.styleable.UINavigationBar_nav_line_height, 0)

在这里主要介绍在Attrs.xml文件中,<declare-styleable/><style />的使用方式;以及如何设置默认的样式。

上述attrs.xml文件中在定义样式属性时,属性字段的类型标志format 分别有这么几个属性类型

  • dimension - {一般用来表示字体尺寸、layout宽高大小}
  • color - {用来表示颜色类型}
  • string - {用来表示字符串}
  • reference -{用来表示引用类型/参考某资源ID}

自定义顶部导航的核心代码
在左右两侧添加按钮(View)时,关键判断逻辑是如何得知左右两侧是否已经添加过按钮。从而能确定当前将要添加的按钮(View)落到哪个位置,并据此设定样式。如何得知左右两侧是否已经添加过?通过分别定义两个View集合【mLeftViewList、mRightViewList】关联左右两侧按钮的添加状态。
核心代码中,navAttrs对象属于NavAttr类型(定义的内部类)的对象实例,为封装已解析的样式属性。

 // UINavigationHeader.kt/**添加导航栏左侧按钮*/private fun addLeftTextButton(@StringRes stringRes: Int, viewId: Int): Button {return addLeftTextButton(resources.getString(stringRes), viewId)}private fun addLeftTextButton(navIconStr: String?, viewId: Int): Button {val button:Button = genTextButton()button.text = navIconStrbutton.id = viewIdif (mLeftViewList.isEmpty()) {//判断集合中右侧按钮没有则说明当前按钮是第一个被添加//然后设置相应的padding距离button.setPadding(navAttrs.horPadding*2,0, navAttrs.horPadding, 0)} else {//若已有添加按钮,则当前按钮从右到左排列并色荷治相应padding距离button.setPadding(navAttrs.horPadding,0, navAttrs.horPadding, 0)}addLeftView(button, genTextButtonLayoutParams())//添加左侧按钮到当前RelativeLayoutreturn button}private fun addLeftView(view: View, params:LayoutParams) {val viewId = view.idif (viewId == View.NO_ID) {throw IllegalStateException("左侧view必须设置id")}if (mLeftLastViewId == View.NO_ID) {params.addRule(ALIGN_PARENT_LEFT, viewId)//落在父布局左侧靠边} else {params.addRule(RIGHT_OF, mLeftLastViewId)//落在以mLeftLastViewId为锚点,在mLeftLastViewId的右侧}mLeftLastViewId = viewIdparams.alignWithParent = true //alignParentIfMissing 自动靠边排列mLeftViewList.add(view)addView(view, params)}
     // UINvigationHeader.kt/**添加导航栏右侧按钮关键代码*/private fun addRightTextButton(@StringRes stringRes: Int, viewId: Int):Button {return addRightTextButton(resources.getString(stringRes), viewId)}private fun addRightTextButton(btnText: String, viewId: Int):Button {val button:Button = genTextButton()button.text = btnTextbutton.id = viewIdif (mRightViewList.isEmpty()) {//判断集合中右侧按钮没有则说明当前按钮是第一个被添加//然后设置相应的padding距离button.setPadding(navAttrs.horPadding,0,navAttrs.horPadding*2,0)} else {//若已有添加按钮,则当前按钮从右到左排列并色荷治相应padding距离button.setPadding(navAttrs.horPadding,0,navAttrs.horPadding,0)}addRightView(button, genTextButtonLayoutParams())return button}private fun addRightView(button: Button,params: LayoutParams) {val viewId = button.idif (viewId == View.NO_ID) {throw IllegalStateException("右侧view必须设置id")}if (mRightLastViewId == View.NO_ID) {//落在父布局贴边靠右侧params.addRule(ALIGN_PARENT_RIGHT, viewId)} else {//RelativeLayout.LEFT_OF以mLeftLastViewId为锚点,落在mLeftLastViewId的左侧params.addRule(LEFT_OF, mLeftLastViewId)}mLeftLastViewId = viewIdparams.alignWithParent = true //alignParentIfMissingmRightViewList.add(button)addView(button, params)}

标题居中核心逻辑
促使标题始终居中,重写当前RelativeLayout的onMeasure方法。以左右两侧按钮所占据的宽度,计算出中间标题所占据空间val centerSpace = this.measuredWidth - Math.max(leftUsedSpace,rightUsedSpace)*2,根据centerSpace得到新的new_widthMeasureSpec(测量规则)重新测量标题父布局(titleContainer)。

// UINavigationHeader.kt
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)if(null == titleContainer) return// 计算左侧按钮,占据的空间var leftUsedSpace = paddingLeft+marginLeftfor(leftView in mLeftViewList) {leftUsedSpace+=leftView.measuredWidth}// 计算右侧按钮,占据的空间var rightUsedSpace = paddingRight+marginRightfor(rightView in mRightViewList) {rightUsedSpace+=rightView.measuredWidth}// 使得 标题能够居中的宽度 (导航栏宽度 - 左右两侧最宽View宽度尺寸的两倍)val centerSpace = this.measuredWidth - Math.max(leftUsedSpace,rightUsedSpace)*2if (centerSpace < titleContainer!!.measuredWidth) {val new_widthMeasureSpec= MeasureSpec.makeMeasureSpec(centerSpace, MeasureSpec.EXACTLY)titleContainer!!.measure(new_widthMeasureSpec, heightMeasureSpec)}
}

自定义输入框:继承LinearLayout实现

组合View,自定义出上截图中效果。LinearLayout(TextView+EditText) ~

自定义View实现登录、注册等输入框的公共使用View。定义View样式属性,依据需求明确自定义属性的字段,如组件标题、输入框输入内容格式、输入框样式等定义属性文件xml。

   <!--attrs.xml--><declare-styleable name="InputItemLayout"><attr name="hint" format="string"></attr><!-- 输入框标题 --><attr name="title" format="string"></attr><!-- app:inputType="text|password|number" 输入框输入内容格式--><attr name="inputType" format="enum"><enum name="text" value="0"/><enum name="password" value="1"/><enum name="number" value="2"/></attr><!-- app:inputTextAppearance="@style/inputTextAppearance" --><attr name="inputTextAppearance" format="reference"></attr><attr name="titleTextAppearance" format="reference"></attr><attr name="topLineAppearance" format="reference"></attr><attr name="bottomLineAppearance" format="reference"></attr></declare-styleable><!--输入框内容输入字体、默认提示字体,颜色、大小--><declare-styleable name="inputTextAppearance"><attr name="hintColor" format="color"/><attr name="inputColor" format="color"/><attr name="textSize" format="dimension"/></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>

上述attrs.xml文件中在定义样式属性时,属性字段的类型标志format 分别有这么几个属性类型

  • dimension - {一般用来表示字体尺寸、layout宽高大小}
  • color - {用来表示颜色类型}
  • string - {用来表示字符串}
  • boolean -{用来表示布尔类型}
  • enum -{用来表示枚举}
  • reference -{用来表示引用类型/参考某资源ID}

定义样式时同样使用标签<declare-styleable/>,这里应该多关注enum(枚举)和refrence(引用类型)。举例说明:

refrence(引用类型)
自定义属性文件解析第一步,获取到样式属性集合的实例~

val array = context.obtainStyledAttributes(attributeSet, R.styleable.InputItemLayout)

且在R.styleable.InputItemLayout样式集合中已定义有四个引用类型。

<attr name="inputTextAppearance" format="reference"></attr>
<attr name="titleTextAppearance" format="reference"></attr>
<attr name="topLineAppearance" format="reference"></attr>
<attr name="bottomLineAppearance" format="reference"></attr>

那么如何从R.styleable.InputItemLayout引用属性集合列表中获取titleTextAppearance集合的实例?val titleStyleId = array.getResourceId 结合 val titleArray = context.obtainStyledAttributes(titleStyleId, R.styleable.titleTextAppearance)

// InputItemLayout.kt
// 解析 <declare-styleable name="titleTextAppearance"> 中的属性
// getResourceId 获取引用属性集合实例的方法
val titleStyleId = array.getResourceId(R.styleable.InputItemLayout_titleTextAppearance, 0)
val title = array.getString(R.styleable.InputItemLayout_title)
parseTitleStyle(titleStyleId, title)private fun parseTitleStyle(titleStyleId: Int, title: String?) {// obtainStyledAttributes 获取属性集合实例的方法val array = context.obtainStyledAttributes(titleStyleId, 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 = minWidthtitleView.gravity = Gravity.LEFT or (Gravity.CENTER)titleView.text = titleaddView(titleView)array.recycle() // 资源回收
}

enum(枚举)
使用时,枚举类型inputType在布局文件中定义为app:inputType="text|password|number"
解析时,通过array.getInteger(R.styleable.InputItemLayout_inputType, 0)获取使用时定义的枚举值,并对editText.inputType在执行逻辑上设置对应文本格式。

// InputItemLayout.kt
// 解析 <declare-styleable name="inputTextAppearance"> 中的属性
val inputStyleId = array.getResourceId(R.styleable.InputItemLayout_inputTextAppearance, 0)
val hint = array.getString(R.styleable.InputItemLayout_hint)
val inputType = array.getInteger(R.styleable.InputItemLayout_inputType, 0)parseInputStyle(inputStyleId, hint, inputType)private fun parseInputStyle(inputStyleId: Int, hint: String?, inputType: Int) {val typeArray =context.obtainStyledAttributes(inputStyleId, R.styleable.inputTextAppearance)val hintColor = typeArray.getColor(R.styleable.inputTextAppearance_hintColor, resources.getColor(R.color.color_d1d2))val inputColor = typeArray.getColor(R.styleable.inputTextAppearance_inputColor, resources.getColor(R.color.color_565))val textSize = typeArray.getDimensionPixelSize(R.styleable.inputTextAppearance_textSize, applyUnit(TypedValue.COMPLEX_UNIT_SP, 14f))editText = EditText(context)val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)params.weight = 1feditText.layoutParams = paramseditText.setHintTextColor(hintColor)editText.setHint(hint)editText.setTextColor(inputColor)editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize.toFloat())editText.setBackgroundColor(Color.TRANSPARENT)editText.gravity = Gravity.LEFT or Gravity.CENTERif (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)typeArray.recycle() // 资源回收}
// 绘制输入框下的分割线
override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)if (topLine.enable) {canvas?.drawLine(topLine.leftMargin.toFloat(), 0f, (measuredWidth - topLine.rightMargin).toFloat(), 0f, topPaint)}if (bottomLine.enable) {canvas?.drawLine(bottomLine.leftMargin.toFloat(), height - bottomLine.height.toFloat(), (measuredWidth - bottomLine.rightMargin).toFloat(), height - bottomLine.height.toFloat(), bottomPaint)}
}

文化袁探索专栏——自定义View实现细节相关推荐

  1. 文化袁探索专栏——Activity、Window和View三者间关系

    文化袁探索专栏--Activity.Window和View三者间关系 <文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--H ...

  2. 文化袁探索专栏——事件分发机制

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  3. 文化袁探索专栏——消息分发机制

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  4. 文化袁探索专栏——React Native启动流程

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  5. 文化袁探索专栏——Activity|Application启动流程

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  6. 文化袁探索专栏——线程池执行原理|线程复用|线程回收

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  7. 《Android开发艺术探索》自定义View中关于“HorizontalScrollViewEx”的改进

    在<Android开发艺术探索>一书中自定义View一节中提到了关于一个类似横向滑动List的自定义ViewGroup:HorizontalScrollViewEx.如果你使用过的话就会发 ...

  8. Android自定义View探索—生命周期

    Activity代码: public class FiveActivity extends AppCompatActivity {private MyView myView;@Overrideprot ...

  9. Android自定义view之measure、layout、draw三大流程

    自定义view之measure.layout.draw三大流程 一个view要显示出来,需要经过测量.布局和绘制这三个过程,本章就这三个流程详细探讨一下.View的三大流程具体分析起来比较复杂,本文不 ...

最新文章

  1. jQuery快速学习
  2. java反射机制的原理与简单使用
  3. mergesort_Mergesort算法的功能方法
  4. 什么是eager loading
  5. jq如何获取选中option的值_【分享】如何获取变量token的值
  6. Foxmail安装以及使用
  7. 三星Galaxy S10 5G版手机国行版或今日发布 在韩销量已超100万部
  8. itchat key
  9. odoo tree视图过滤数据_OpenERP Odoo 搜索视图设置默认过滤按钮(filter button)
  10. 使用javascript生成的植物显示过程特效
  11. 程序基本编写方法python_python程序设计基本编写方法
  12. python写小说阅读器_用python实现自己的小说阅读器
  13. [线程安全问题] 多线程到底可能会带来哪些风险?
  14. AUTOSAR实战教程pdf版
  15. 【计算机组成原理】实验2:十六位数据总线实验
  16. 从Oppo手机拍照无法展示谈图片压缩
  17. 星巴克在东京开设四层楼的全沉浸式优质咖啡体验门店
  18. centos 6 python django mysql_CentOS 6.5中部署django+uwsgi+nginx+mysql项目
  19. Vue全家桶基础设施环境搭建
  20. matlab语言在天线设计,matlab语言在天线设计中的运用

热门文章

  1. 李宏毅老师课程:Unsupervised Learning - Word Embedding
  2. linux 串口4g ppp,在ARM-linux上实现4G模块PPP拨号上网
  3. 苹果xsmax是什么接口_液态硅胶手机壳,媲美苹果官方同款,拿手里太舒服了~
  4. 大量ICON图标下载网站汇总
  5. 英语笔记12.29.2021-2
  6. 平板电脑里安装python_10 个平板电脑上的 Python 编辑器
  7. 计算机数据在经济学的应用论文,数学在经济学中的运用论文
  8. SzNOI c007小鼠迷宫
  9. 面试谈薪有技巧,学会这2个方法,再也不怕被压工资了
  10. APP渗透测试通过burp抓取https包