前言
Kotlin
作为谷歌力推的Android
一级开发语言,当然是有它的独到之处的,Kotlin
也使用了一段时间了,今天心血来潮,用它来自定义一个搜索框控件,话不多说,先上图
思路
从效果图可以看出,无非就是三个控件组合而成的:一个文本编辑框EditText
和两个ImageView
。所以这个自定义控件得继承ViewGroup
,而整个布局,要么是用自定义好的一个XML
布局文件,再要么就是自己摆放控件位置,这里我选择自己来摆放控件位置
初始化
格式化的步骤,唯一需要注意的是,在这里,将三个控件添加到父控件中去,以及不能因为Kotlin
中函数有默认值,就偷懒只写一个包含所有参数的构造函数
class Search : ViewGroup, TextWatcher, TextView.OnEditorActionListener, ValueAnimator.AnimatorUpdateListener, View.OnFocusChangeListener {
private var mSearchListener: OnSearchListener? = null
interface OnSearchListener {
fun search(s: String)
}
fun setOnSearchListener(listener: OnSearchListener) {
mSearchListener = listener
}
private var mText: String? = null
private var mCollapsed = false
private var percent = 1f
private var mWidth = -1
private var mHeight = -1
private var mAnimator: ValueAnimator? = null
private var mEndElement: AnimatorElement? = null
private var mStartElement: AnimatorElement? = null
private val mEvaluator = AnimatorEvaluator()
private var clearIconPadding = 0
private var editPaddingLeft = 0
private var editPaddingTop = 0
private var editPaddingRight = 0
private var editPaddingBottom = 0
private var parentPaddingLeft = 0
private var parentPaddingTop = 0
private var parentPaddingRight = 0
private var parentPaddingBottom = 0
private var clearDrawable: Drawable? = null
private var searchDrawable: Drawable? = null
private lateinit var clear: ImageView
private lateinit var search: ImageView
private lateinit var edit: AutoCompleteTextView
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
clear = ImageView(context)
search = ImageView(context)
edit = AutoCompleteTextView(context)
edit.addTextChangedListener(this)
edit.setOnEditorActionListener(this)
edit.setSingleLine(true)
edit.imeOptions = EditorInfo.IME_ACTION_SEARCH
edit.onFocusChangeListener = this
edit.gravity = Gravity.CENTER_VERTICAL
search.setOnClickListener { search() }
clear.setOnClickListener { edit.text.clear() }
attachViewToParent(edit, -1, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
attachViewToParent(clear, -1, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
attachViewToParent(search, -1, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
val ta = context.obtainStyledAttributes(attrs, R.styleable.Search)
clearIconPadding = ta.getDimensionPixelOffset(R.styleable.Search_searchClearIconPadding, 0)
editPaddingLeft = ta.getDimensionPixelOffset(R.styleable.Search_searchEditPaddingLeft, 0)
editPaddingTop = ta.getDimensionPixelOffset(R.styleable.Search_searchEditPaddingTop, 0)
editPaddingRight = ta.getDimensionPixelOffset(R.styleable.Search_searchEditPaddingRight, 0)
editPaddingBottom = ta.getDimensionPixelOffset(R.styleable.Search_searchEditPaddingBottom, 0)
clearDrawable = ta.getDrawable(R.styleable.Search_searchClearIcon)
?: ResourcesCompat.getDrawable(resources, R.drawable.ic_clear_icon, null)
searchDrawable = ta.getDrawable(R.styleable.Search_searchSearchIcon)
?: ResourcesCompat.getDrawable(resources, R.drawable.ic_search_icon, null)
ViewCompat.setBackground(edit, ta.getDrawable(R.styleable.Search_searchBackground))
val textSize = ta.getDimensionPixelOffset(R.styleable.Search_searchTextSize, 0)
if (textSize > 0) edit.textSize = textSize.toFloat()
val textColor = ta.getColor(R.styleable.Search_searchTextColor, 0)
if (textColor > 0) edit.setTextColor(ta.getColor(R.styleable.Search_searchTextColor, 0))
ta.recycle()
clear.setImageDrawable(clearDrawable)
search.setImageDrawable(searchDrawable)
edit.setPadding(editPaddingLeft, editPaddingTop, editPaddingRight + (clearDrawable?.intrinsicWidth
?: 0), editPaddingBottom)
parentPaddingLeft = paddingLeft
parentPaddingTop = paddingTop
parentPaddingRight = paddingRight
parentPaddingBottom = paddingBottom
}
......
}
这里添加进父控件用的是
attachViewToParent
,而非addViewInLayout
,具体原因,官方文档说得很清楚
onMeasure
主要就是计算自适应时的高度,即wrap_content
,也就是说计算三个控件中的最大高度值加上间距值
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
measureChildren(widthMeasureSpec, heightMeasureSpec)// 测量子View尺寸信息
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val suggestWidth = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val suggestHeight = MeasureSpec.getSize(heightMeasureSpec)
when (widthMode) {
MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED -> {
}
MeasureSpec.EXACTLY -> {
}
}
var height = edit.textSize.toInt()
val searchH = searchDrawable?.intrinsicHeight ?: 0
if (searchH > height) height = searchH
val clearH = clearDrawable?.intrinsicHeight ?: 0
if (clearH > height) height = clearH
when (heightMode) {
MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED -> height += parentPaddingTop + parentPaddingBottom + editPaddingTop + editPaddingBottom
MeasureSpec.EXACTLY -> height = suggestHeight
}
setMeasuredDimension(suggestWidth, height)
}
onLayout
摆放位置,唯一需要注意的是(0,0)
坐标点是以自定义控件的左上角为基准,而非是屏幕的左上角为基准
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val w = r - l
val h = b - t
println("layout -> w:$w,h:$h")
if (mWidth == -1) mWidth = w
if (mHeight == -1) mHeight = h
val searchW = searchDrawable?.intrinsicWidth ?: 0
val clearW = clearDrawable?.intrinsicWidth ?: 0
val searchL = searchLeft(w, searchW).toInt()
search.layout(searchL, parentPaddingTop, searchL + searchW, h - parentPaddingBottom)
clear.layout(
w - parentPaddingRight - searchW - clearW - editPaddingRight,
parentPaddingTop + editPaddingTop,
w - parentPaddingRight - searchW - editPaddingRight,
h - parentPaddingBottom - editPaddingBottom
)
edit.layout(
(percent * parentPaddingLeft).toInt(),
(percent * parentPaddingTop).toInt(),
editRightForCollapse(w).toInt(),
h - (percent * parentPaddingBottom).toInt()
)
}
以上,动画,属性动画,注意下始末值即可