Android如何通过组合的方式自定义View

前言:

自定义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
   }
}

到此这篇关于Android如何通过组合的方式自定义View的文章就介绍到这了,更多相关Android自定义View内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android自定义View实现遥控器按钮

    本文实例为大家分享了Android自定义View实现遥控器按钮的具体代码,供大家参考,具体内容如下 效果图: 原理: onSizeChanged拿到控件宽高,进行path和region的计算(此处,path和region的坐标值都是以viewWidth/2,viewHeight/2为坐标原点进行计算的) 画布平移,绘制5个path 点击事件,判断是否处于相应的region区域内,进行控件的重绘 点击事件motionEvent的原始坐标(getX和getY),是以viewParent的左上角为坐标

  • Android自定义View实现跟随手指移动

    对View的移动,实现的方法有好几种,原理是通过改变View的位置来移动View,下面来实现这样的效果 动画的方法 通过改变View的tranlationX和tranlationY的值来实现移动,首先来写一个自定义View类,重写onTouchEvent方法,实现构造方法 public class MyView extends View {     public MyView(Context context) {         super(context);     }     public

  • Android自定义View实现QQ消息气泡

    本文实例为大家分享了Android自定义View实现QQ消息气泡的具体代码,供大家参考,具体内容如下 效果图: 原理: 控件源码: public class DragView extends View {     private int defaultZoomSize = 8;     //初始化圆的大小     private int initRadius;     //圆1的圆心位置     private PointF center1;     private PointF center2

  • Android自定义View绘制居中文本

    本文实例为大家分享了Android自定义View绘制居中文本的具体代码,供大家参考,具体内容如下 自定义view的步骤: 1.自定义View的属性2.在View的构造方法中获得我们自定义的属性3.重写onMesure(非必须)4.重写onDraw 1.自定义View的属性,首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性,只定义三个,有文本.颜色和字体大小: <!--CustomTextView-->     <declare-styleable na

  • Android自定义View实现球形动态加速球

    本文实例为大家分享了Android自定义View实现球形动态加速球的具体代码,供大家参考,具体内容如下 利用贝塞尔曲线画波浪线封闭黄色矩形,使用PorterDuffXfermode的SRC_ATOP只显示圆和交叠部分,利用Handler发送消息模拟进度形成动态效果. 代码: public class CircleView extends View{ private int width; private int height; private Bitmap mBitmap; private Can

  • Android自定义View绘制贝塞尔曲线的方法

    本文实例为大家分享了Android自定义View绘制贝塞尔曲线的具体代码,供大家参考,具体内容如下 在平面内任选 3 个不共线的点,依次用线段连接. 在第一条线段上任选一个点 D.计算该点到线段起点的距离 AD,与该线段总长 AB 的比例. 根据上一步得到的比例,从第二条线段上找出对应的点 E,使得 AD:AB = BE:BC. 连接这两点 DE. 从新的线段 DE 上再次找出相同比例的点 F,使得 DF:DE = AD:AB = BE:BC. 到这里,我们就确定了贝塞尔曲线上的一个点 F.接下

  • 利用Android实现比较炫酷的自定义View

    目录 一.背景 1.1.控件效果 1.2.从功能上分析一下这个控件,大致有以下特点 1.3.从结构上分析 二. 背景圆实现 2.1.实现粒子运动 2.2.实现渐变色圆 2.3.展示背景圆的扇形区域 2.4.实现指针变色 2.5.实现背景圆颜色随扇形角度变化 三.跳动数字动画实现 3.1.属性动画+2个TextView实现数字上下切换动画 四.项目源码 总结 一.背景 1.1.控件效果 要实现的自定义控件效果大致如下,实现过程中用到了比较多的自定义View的API,觉得比较有代表性,就分享出来也当

  • Android嵌套RecyclerView左右滑动替代自定义view

    以前的左右滑动效果采用自定义scrollview或者linearlayout来实现,recyclerview可以很好的做这个功能,一般的需求就是要么一个独立的左右滑动效果,要么在一个列表里的中间部分一个左右滑动效果 而列表里面也容易,只是需要解决一点小问题,个人认为值得一提的就是高度问题,一般的人采用固定死的高度,可是在列表里面展示和机型的不同,固定死的话很难保证美观,动态的高度才能解决问题的所在 首先在一个列表控件布局上添加一个recyclerview控件 <android.support.v

  • Android创建外部lib库及自定义View的图文教程

    前言 随着插件化/组件化的快速发展,现在大部分的项目开发中都会提取公共的代码制作成 Library module,根据具体的业务需求进行拆分.小菜也学习一下如何拆分 lib 包,实际操作很简单,整理一下操作步骤. 拆分创建 Library 在当前 Project 下,File -> New Module,选择 Android Library,进行下一步: 设置具体的 Library/Module/Package 等名称,注意:Module 名称与 Library 相匹配默认为小写,需要的话手动调

  • Android 自定义View步骤

    例子如下:Android 自定义View 密码框 例子 1 良好的自定义View 易用,标准,开放. 一个设计良好的自定义view和其他设计良好的类很像.封装了某个具有易用性接口的功能组合,这些功能能够有效地使用CPU和内存,并且十分开放的.但是,除了开始一个设计良好的类之外,一个自定义view应该: l 符合安卓标准 l 提供能够在Android XML布局中工作的自定义样式属性 l 发送可访问的事件 l 与多个Android平台兼容. Android框架提供了一套基本的类和XML标签来帮您创

  • Android自定义View实现开关按钮

    前言:Android自定义View对于刚入门乃至工作几年的程序员来说都是非常恐惧的,但也是Android进阶学习的必经之路,平时项目中经常会有一些苛刻的需求,我们可以在GitHub上找到各种各样的效果,能用则用,不能用自己花功夫改改也能草草了事.不过随着工作经验和工作性质,越来越觉得自定义View是时候有必要自己花点功夫研究一下. 一.经过这两天的努力,自己也尝试着写了一个Demo,效果很简单,就是开关按钮的实现. 可能有的人会说这效果so easy,找UI切三张图就完事了,何必大费周折自定义.

  • Android自定义View仿QQ健康界面

    最近一直在学习自定义View相关的知识,今天给大家带来的是QQ健康界面的实现.先看效果图: 可以设置数字颜色,字体颜色,运动步数,运动排名,运动平均步数,虚线下方的蓝色指示条的长度会随着平均步数改变而进行变化.整体效果还是和QQ运动健康界面很像的. 自定义View四部曲,一起来看看怎么实现的. 1.自定义view的属性: <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定

  • Android自定义View实现左右滑动选择出生年份

    自定义view的第三篇,模仿的是微博运动界面的个人出生日期设置view,先看看我的效果图: 支持设置初始年份,左右滑动选择出生年份,对应的TextView的值也会改变.这个动画效果弄了好久,感觉还是比较生硬,与微博那个还是有点区别.大家有改进的方案,欢迎一起交流. 自定义View四部曲,这里依旧是这个套路,看看怎么实现的. 1.自定义view的属性: 在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性以及声明我们的整个样式. <?xml version="1.

  • Android自定义View实现shape图形绘制

    概述 之前曾写过一篇文章介绍了Android中drawable使用Shape资源,通过定义drawable中的shape资源能够绘制简单的图形效果,如矩形,椭圆形,线形和圆环等.后来我在项目中正好遇到这样一个需求,要在特定的位置上显示一条垂直的虚线.正当我胸有成竹的把上面的资源文件放入进去的时候,我才发现它并不能符合我的要求.使用shape画出的垂直虚线,其实就是将一条水平的线,旋转90度.但这样做的弊端就是,该View有效区域为旋转90度后与原来位置相重合的区域,还不能随意的改动,这样的效果根

  • Android自定义View仿微博运动积分动画效果

    自定义View一直是自己的短板,趁着公司项目不紧张的时候,多加强这方面的练习.这一系列文章主要记录自己在自定义View的学习过程中的心得与体会. 刷微博的时候,发现微博运动界面,运动积分的显示有一个很好看的动画效果.OK,就从这个开始我的自定义view之路! 看一下最后的效果图: 分数颜色,分数大小,外圆的颜色,圆弧的颜色都支持自己设置,整体还是和微博那个挺像的.一起看看自定义View是怎样一步一步实现的: 1.自定义view的属性: 在res/values/ 下建立一个attrs.xml ,

  • Android自定义View的三种实现方式总结

    在毕设项目中多处用到自定义控件,一直打算总结一下自定义控件的实现方式,今天就来总结一下吧.在此之前学习了郭霖大神博客上面关于自定义View的几篇博文,感觉受益良多,本文中就参考了其中的一些内容. 总结来说,自定义控件的实现有三种方式,分别是:组合控件.自绘控件和继承控件.下面将分别对这三种方式进行介绍. (一)组合控件 组合控件,顾名思义就是将一些小的控件组合起来形成一个新的控件,这些小的控件多是系统自带的控件.比如很多应用中普遍使用的标题栏控件,其实用的就是组合控件,那么下面将通过实现一个简单

随机推荐