最近项目要求需要实现一个常见的功能,就是点击Edittext 的编辑图标则为可输入状态,再点击图标切换为类似TextView的样式。这个看上去实现是比较简单,原本打算找个代码Copy一下,但发现找了半天都没找到满意的,所以还是自己实现一下做个笔记。先看个Gif效果图:
效果看上去也就比较简单,Edittext 内部右边有个小图标,点击一次则为编辑状态,再点击一次变为不可编辑状态。之前只要是自定义控件都是Java来写,现在刚好项目用的是Kotlin ,这次就改用Kotlin 练习一下。下面就来看一下实现流程:
1. 第一步 :创建自定义Edittext
class EditableTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.editTextStyle //不这样写可能有些属性用不了
) : AppCompatEditText(context, attrs, defStyleAttr) {
init {
initView() //在此开始初始化EditableTextView的属性
}
}
上面简短的Kotlin代码则完成创建了一个自定义Edittext了。可以看到与Java不同,Kotlin使用构造函数的默认参数就不需要重载那么多个构造方法了,前提是记得别遗漏构造方法前的注解 @JvmOverloads ,如果不使用 @JvmOverloads 的话则会运行报错提示未实现构造方法。
如果上面代码未使用 @JvmOverloads 标注,则默认只重载了一个含三参数的构造方法
使用了 @JvmOverloads 标注,相当于重载了三个构造函数
2.第二步:设置可编辑和不可编辑时的样式
首先要有默认的开始编辑和完成编辑2个图片素材(根据需要而定),我这里以有2个SVG图 ,点击图标后相互切换,目前只实现了XML更改开始编辑的图标。若需要实现提供更改开始编辑/完成编辑/编辑时背景/未编辑时背景效果更改或其他功能则自己去自定义declare-styleable,这里就省了。
private var editInputIcon: Drawable? = null // 编辑图标
private var editCompleteIcon: Drawable? = null // 完成图标
private var layoutBackground: Drawable? = null // 布局背景
private var isEditable = false //是否编辑状态
private fun initView() {
// compoundDrawables 保存left top right bottom 4个 drawable
//获取编辑图标,若XML不设置使用默认;
editInputIcon = compoundDrawables[2] ?: resources.getDrawable(R.drawable.ic_edit, null)
//setBounds 设置drawable位置,必须设置才显示;intrinsicWidth 代表显示后的图片大小
editInputIcon?.apply { setBounds(0, 0, intrinsicWidth, intrinsicHeight) }
//输入完成的图标
editCompleteIcon = resources.getDrawable(R.drawable.ic_edit_complete, null)
editCompleteIcon?.apply { setBounds(0, 0, intrinsicWidth, intrinsicHeight) }
//获取XML布局的背景
layoutBackground = background
//设置默认DrawablePadding
compoundDrawablePadding = 16.dp2px()
setEditStyle(isEditable)
setOnFocusChangeListener { _, hasFocus ->
//失去焦点时自动变为不可编辑状态
if (!hasFocus) setEditStyle(false)
}
}
//设置编辑时和不可编辑时的样式
private fun setEditStyle(isEditable: Boolean) {
this.isEditable = isEditable
isFocusable = isEditable
isFocusableInTouchMode = isEditable
if (isEditable) {
requestFocus()
setIconDrawable(editCompleteIcon)
background = layoutBackground
} else {
setIconDrawable(editInputIcon)
hideKeyborad()
clearFocus()
background = null //不可编辑时清空背景
}
}
//设置drawableRight图标图片
private fun setIconDrawable(drawable: Drawable?) {
setCompoundDrawables(
compoundDrawables[0],
compoundDrawables[1],
drawable,
compoundDrawables[3]
)
}
//隐藏虚拟键盘
private fun hideKeyborad() {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(
windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}
3. 第三步:通过onTouchEvent判断 Drawable 的区域实现点击事件
需要判断drawable的点击区域,则可通过判断 当前点击Edittext 的X坐标值 ,在 Drawable 左边界到 EditableTextView 的右边界的坐标区间范围内则为点击了图标。
这里涉及到 EditableTextView 的 getWidth() , getPaddingRight() , getTotalPaddingRight() , getCompoundDrawablePadding() 四个获取距离的方法。这4个方法作用直接画个图描述了下:
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP && editInputIcon != null) {
//图标左边的X坐标值,不包含点击了左部的compoundDrawablePadding
val left = width - totalPaddingRight + compoundDrawablePadding
if (event.x.toInt() in left..width) { //点击了图标
setEditStyle(!isEditable)
}
}
return super.onTouchEvent(event)
}
以上就是实现的整个流程,整个代码也不超过100行
下面附完成代码:
class EditableTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.editTextStyle
) : AppCompatEditText(context, attrs, defStyleAttr) {
private var editInputIcon: Drawable? = null
private var editCompleteIcon: Drawable? = null
private var layoutBackground: Drawable? = null
private var isEditable = false
init {
initView()
}
private fun initView() {
editInputIcon = compoundDrawables[2] ?: resources.getDrawable(R.drawable.ic_edit, null)
editInputIcon?.apply { setBounds(0, 0, intrinsicWidth, intrinsicHeight) }
editCompleteIcon = resources.getDrawable(R.drawable.ic_edit_complete, null)
editCompleteIcon?.apply { setBounds(0, 0, intrinsicWidth, intrinsicHeight) }
layoutBackground = background
compoundDrawablePadding = 16.dp2px()
setEditStyle(isEditable)
setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) setEditStyle(false)
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP && editInputIcon != null) {
val left =
width - totalPaddingRight + compoundDrawablePadding
if (event.x.toInt() in left..width) {
setEditStyle(!isEditable)
}
}
return super.onTouchEvent(event)
}
private fun setEditStyle(isEditable: Boolean) {
this.isEditable = isEditable
isFocusable = isEditable
isFocusableInTouchMode = isEditable
if (isEditable) {
requestFocus()
setIconDrawable(editCompleteIcon)
background = layoutBackground
} else {
setIconDrawable(editInputIcon)
hideKeyborad()
clearFocus()
background = null
}
}
private fun setIconDrawable(drawable: Drawable?) {
setCompoundDrawables(
compoundDrawables[0],
compoundDrawables[1],
drawable,
compoundDrawables[3]
)
}
private fun hideKeyborad() {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(
windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}
fun Int.dp2px(): Int {
val scale = context.applicationContext.resources.displayMetrics.density
return (this * scale + 0.5f).toInt()
}
}
Demo中使用的布局文件参考:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<RelativeLayout
android:id="@+id/rl_test"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/t1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="默认样式"
android:textColor="@android:color/black"
android:textSize="18sp" />
<com.zzh.editabletextview.EditableTextView
android:id="@+id/et1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_toRightOf="@id/t1"
android:singleLine="true"
android:text="Hello World 1" />
<View
android:id="@+id/lineview0"
style="@style/gray_line"
android:layout_below="@id/t1"
android:layout_marginTop="16dp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_test2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/t2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="自定义样式"
android:textColor="@android:color/black"
android:textSize="18sp" />
<com.zzh.editabletextview.EditableTextView
android:id="@+id/et2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_toRightOf="@id/t2"
android:background="@drawable/edittextbg"
android:gravity="end"
android:padding="8dp"
android:singleLine="true"
android:text="Hello World 2" />
<View
android:id="@+id/lineview1"
style="@style/gray_line"
android:layout_below="@id/t2"
android:layout_marginTop="16dp" />
</RelativeLayout>
</LinearLayout>