软件编程
位置:首页>> 软件编程>> Android编程>> Android如何通过组合的方式自定义View

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

作者:东土也  发布时间:2022-11-01 04:33:46 

标签: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
  }
}

来源:https://juejin.cn/post/7142758407844397063

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com