热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android自定义View之随机数验证码(仿写鸿洋)

前言本文面向自定义view新手,但是希望你最好有一定的理论知识,或基础概念,有的地方可能会一笔带过并不会细讲,细讲篇幅就太

前言

本文面向自定义view新手,但是希望你最好有一定的理论知识,或基础概念,有的地方可能会一笔带过并不会细讲,细讲篇幅就太长了。


本文仿写自鸿洋的自定义View (一),尽管过去了将近快7年之久,我觉得依然有学习价值。



效果

在这里插入图片描述


自定义View分类

简单介绍一下自定义View分类:


  • 组合控件,继承自已有的layout,比如LinearLayout,然后通过LayoutInflater引入布局,然后处理相关事件,这种方式的好处在于,不需要过度关注view内部的绘制机制,而且扩展性也很强。
  • 继承自现有的系统控件,修改或扩展某些属性或方法即可,比如AppCompatTextView
  • 继承自viewviewgroup,这种相对要复杂一些,因为我们要自己控制绘制流程,但是相对的,也有更大的想象空间。

步骤

先分析一下上图中的效果:


  • 带颜色的矩形背景
  • 居中的文本

比较简单,老手稍微想一下就已经有思路了:


  • 1.自定义属性
  • 2.添加构造方法
  • 3.在构造里获取自定义样式
  • 4.重写onDraw计算坐标绘制
  • 5.重写onMeasure测量宽高
  • 6.设置点击事件

先分析效果图,然后构思,随后不断的调整优化。


1.自定义属性


这一步也不一定非要写在前面,可能有些人觉得不一定就能事先知道会用到哪些属性,由于例子比较简单,暂且放在前面吧,看个人习惯。



  • res/values/ 下建立一个attrs.xml文件 , 在里面定义我们的属性和声明我们的整个样式

<?xml version&#61;"1.0" encoding&#61;"utf-8"?>
<resources><attr name&#61;"randomText" format&#61;"string"/><attr name&#61;"randomTextColor" format&#61;"color"/><attr name&#61;"randomTextSize" format&#61;"dimension"/><declare-styleable name&#61;"RandomTextView" ><attr name&#61;"randomText"/><attr name&#61;"randomTextColor"/><attr name&#61;"randomTextSize"/></declare-styleable></resources>

format是值该属性的取值类型:
一共有&#xff1a;string&#xff0c;color&#xff0c;demension&#xff0c;integer&#xff0c;enum&#xff0c;reference&#xff0c;float&#xff0c;boolean&#xff0c;fraction&#xff0c;flag


  • xml布局中的引用&#xff1a;

<com.yechaoa.customviews.randomtext.RandomTextViewandroid:layout_width&#61;"wrap_content"android:layout_height&#61;"wrap_content"android:padding&#61;"20dp"app:randomText&#61;"1234"app:randomTextColor&#61;"&#64;color/colorAccent"app:randomTextSize&#61;"50sp" />

注意引入命名空间&#xff1a;

xmlns:app&#61;"http://schemas.android.com/apk/res-auto"

2.添加构造方法

新建一个RandomTextView类&#xff0c;继承View&#xff0c;并添加3个构造方法

class RandomTextView : View {//文本private var mRandomText: String//文本颜色private var mRandomTextColor: Int &#61; 0//文本字体大小private var mRandomTextSize: Int &#61; 0private var paint &#61; Paint()private var bounds &#61; Rect()//调用两个参数的构造constructor(context: Context) : this(context, null)//xml默认调用两个参数的构造&#xff0c;再调用三个参数的构造&#xff0c;在三个参数构造里获取自定义属性constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {...}...
}

这里要注意的是&#xff0c;所有的构造方法&#xff0c;都指向的是第三个构造方法&#xff0c;前两个构造的继承是this&#xff0c;而不是super

第一个构造比如我们可以是new创建的&#xff0c;第二个是xml中默认调用的&#xff0c;我们在第三个构造中去获取自定义属性。


3.在构造里获取自定义样式

constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {//获取自定义属性val typedArray &#61; context.theme.obtainStyledAttributes(attributeSet,R.styleable.RandomTextView,defStyle,0)mRandomText &#61; typedArray.getString(R.styleable.RandomTextView_randomText).toString()mRandomTextColor &#61; typedArray.getColor(R.styleable.RandomTextView_randomTextColor, Color.BLACK)//默认黑色mRandomTextSize &#61; typedArray.getDimensionPixelSize(R.styleable.RandomTextView_randomTextSize,TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16F, resources.displayMetrics ).toInt())//获取完回收typedArray.recycle()paint.textSize &#61; mRandomTextSize.toFloat()//返回文本边界&#xff0c;即包含文本的最小矩形&#xff0c;没有所谓“留白”&#xff0c;返回比measureText()更精确的text宽高&#xff0c;数据保存在bounds里paint.getTextBounds(mRandomText, 0, mRandomText.length, bounds)}

通过obtainStyledAttributes获取自定义属性&#xff0c;返回一个TypedArray&#xff0c;这里用到了我们在attrs.xml文件中声明的样式&#xff08;R.styleable.RandomTextView&#xff09;&#xff0c;返回的TypedArray即包含了这里面的属性。

拿到自定义view属性集合&#xff0c;然后赋值&#xff0c;赋值之后就可以用paint去画了。

然后用到了paint的getTextBounds方法&#xff1a;

paint.getTextBounds(mRandomText, 0, mRandomText.length, bounds)

简单理解就是&#xff0c;把文字放在一个矩形里&#xff0c;通过矩形的宽高即可知道文字的宽高&#xff0c;所以宽高会保存在bounds里&#xff0c;bounds是一个矩形Rect&#xff0c;为什么要这么做呢&#xff0c;因为后面我们要计算文字居中的时候会用到。

ok&#xff0c;接下来开始画布局。


4.重写onDraw计算坐标绘制

&#64;SuppressLint("DrawAllocation")override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)/*** 自定义View时&#xff0c;需要我们自己在onDraw中处理padding&#xff0c;否则是不生效的* 自定义ViewGroup时&#xff0c;子view的padding放在onMeasure中处理*//*** 矩形背景*/paint.color &#61; Color.YELLOW//计算坐标&#xff0c;因为原点是在文字的左下角&#xff0c;左边要是延伸出去就还要往左边去&#xff0c;所以是减&#xff0c;右边和下边是正&#xff0c;所以是加canvas?.drawRect((0 - paddingLeft).toFloat(),(0 - paddingTop).toFloat(),(measuredWidth &#43; paddingRight).toFloat(),(measuredHeight &#43; paddingBottom).toFloat(),paint)/*** 文本*/paint.color &#61; mRandomTextColor//注意这里的坐标xy不是左上角&#xff0c;而是左下角&#xff0c;所以高度是相加的&#xff0c;在自定义view中&#xff0c;坐标轴右下为正//getWidth 等于 measuredWidthcanvas?.drawText(mRandomText,(width / 2 - bounds.width() / 2).toFloat(),(height / 2 &#43; bounds.height() / 2).toFloat(),paint)}

上面的代码就是在onDraw里面显示绘制了一个YELLOW颜色的矩形背景&#xff0c;然后绘制了一个自定义属性颜色的居中的文本。

这里要注意我们计算位置时的坐标&#xff0c;在自定义view中&#xff0c;原点是view的左上角&#xff0c;而在数学坐标系中&#xff0c;原点&#xff08;0,0&#xff09;是在中间的&#xff0c;二者是有区别的。

其次&#xff0c;假如xml布局中有padding&#xff0c;或者预判会使用到padding&#xff0c;在重写onDraw的时候也要把padding的数据加上&#xff0c;否则padding是不生效的。如果是继承ViewGroup时&#xff0c;子view的padding放在onMeasure中处理。

来看此时的效果&#xff1a;

在这里插入图片描述

此时是不是有疑惑&#xff0c;xml里面的宽高明明是wrap_content&#xff0c;为什么会充满父布局呢&#xff1f;

这就涉及到onMeasure的知识点了&#xff0c;往下看。


5.重写onMeasure测量宽高

我们在xml设置view宽高有3种方式&#xff1a;


  • match_parent
  • wrap_content
  • 具体数据&#xff0c;比如100dp

onMeasureMeasureSpecmode也有3种模式&#xff1a;


  • EXACTLY&#xff1a;一般是设置了明确的值或者是MATCH_PARENT
  • AT_MOST&#xff1a;表示子布局限制在一个最大值内&#xff0c;一般为WARP_CONTENT
  • UNSPECIFIED&#xff1a;表示子布局想要多大就多大&#xff0c;很少使用

由于我们xml用的是wrap_content&#xff0c;也就是对应AT_MOST&#xff0c;所以效果就是会占满父布局中的可用空间&#xff0c;而父布局是填充屏幕&#xff0c;所以我们自定义的view也会占满全屏。

而我们实际想要的效果是view包裹自己&#xff0c;而不是铺满全屏&#xff0c;所以我们需要在onMeasure中进行处理

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)/*** EXACTLY&#xff1a;一般是设置了明确的值或者是MATCH_PARENT* AT_MOST&#xff1a;表示子布局限制在一个最大值内&#xff0c;一般为WARP_CONTENT* UNSPECIFIED&#xff1a;表示子布局想要多大就多大&#xff0c;很少使用*/val widthMode &#61; MeasureSpec.getMode(widthMeasureSpec)val widthSize &#61; MeasureSpec.getSize(widthMeasureSpec)val heightMode &#61; MeasureSpec.getMode(heightMeasureSpec)val heightSize &#61; MeasureSpec.getSize(heightMeasureSpec)var width &#61; 0var height &#61; 0//如果指定了宽度&#xff0c;或不限制宽度&#xff0c;用可用宽度即可&#xff0c;如果是WARP_CONTENT&#xff0c;则用文本宽度&#xff0c;再加上左右paddingwhen (widthMode) {MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY -> {width &#61; widthSize &#43; paddingLeft &#43; paddingRight}MeasureSpec.AT_MOST -> {width &#61; bounds.width() &#43; paddingLeft &#43; paddingRight}}//如果指定了高度&#xff0c;或不限制高度&#xff0c;用可用高度即可&#xff0c;如果是WARP_CONTENT&#xff0c;则用文本高度&#xff0c;再加上上下paddingwhen (heightMode) {MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY -> {height &#61; heightSize &#43; paddingTop &#43; paddingBottom}MeasureSpec.AT_MOST -> {height &#61; bounds.height() &#43; paddingTop &#43; paddingBottom}}//保存测量的宽高setMeasuredDimension(width, height)}

上面的代码呢&#xff0c;主要做了两件事&#xff1a;


  • 获取view宽高的模式
  • 针对不同的模式&#xff0c;对宽高进行重新测量

最后别忘记调用setMeasuredDimension保存新测量的宽高&#xff0c;否则没用哦。

此时再看效果就是效果图中的样子了。


6.设置点击事件

ok&#xff0c;到这&#xff0c;view已经绘制完成了&#xff0c;但是还没有事件&#xff0c;我们在构造中加一个点击事件

constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {.../*** 添加点击事件*/this.setOnClickListener {mRandomText &#61; randomText()//更新postInvalidate()}}

randomText方法&#xff1a;

/*** 根据文本长度 随意数字*/private fun randomText(): String {val list &#61; mutableListOf<Int>()for (index in mRandomText.indices) {list.add(Random.nextInt(10))}val stringBuffer &#61; StringBuffer()for (i in list) {stringBuffer.append("" &#43; i)}return stringBuffer.toString()}

触发事件之后&#xff0c;文字更新&#xff0c;然后view重绘更新页面即可。

关于数据获取&#xff0c;也就是变化后的数字&#xff0c;可以写个onTextChanged接口&#xff0c;也可以写个开放方法获取。


总结

其实看效果的话&#xff0c;还不如TextView来的简单&#xff0c;而且TextView也可以轻松的实现效果图中的效果。

所以本文的重点并不是实现效果&#xff0c;而是学习理解自定义View以及其绘制流程

理论看的再多也需要实践才行&#xff0c;不如跟着敲两遍&#xff0c;理解消化一下。

注释还是非常详细的&#xff0c;甚至有点啰嗦。。

如果对你有一点帮助&#xff0c;点个赞呗 ^ _ ^


Github

https://github.com/yechaoa/CustomViews


参考


  • Android 自定义View (一)
  • 自定义 View 1-1 绘制基础

推荐阅读
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
author-avatar
霸气胡志刚_你活埋了木有
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有