用Kotlin自定义一个搜索框控件

前言

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()
        )
    }

以上,动画,属性动画,注意下始末值即可

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值