Rv adapter EditText 复用问题扩展函数封装

35 阅读3分钟
package com.example.testdemo.kt

import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_CALL
import android.content.res.Resources
import android.net.Uri
import android.text.Editable
import android.text.Spannable
import android.text.TextWatcher
import android.text.style.ForegroundColorSpan
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import java.text.SimpleDateFormat
import java.util.*

```
//Rv adapter EditText 复用问题扩展函数封装
eg:使用方式如下
```
/**etCmpProfitAmount.superAddTextChangedListener(item.profitAmount) {
    Log.d("CmpHKSetProfitDetailEditAdapter", "convert: superAddTextChangedListener")
    item.profitAmount = it.toString()
    item.labelErrorMsg = checkChangeError(etCmpProfitAmount, etCmpProfitPercent, tvError)
}**/
```
fun EditText.superAddTextChangedListener(content: String,afterTextChanged: (s: Editable?) -> Unit) {
    //todo 1 先移除监听
    if (this.tag is TextWatcher) {
        // 赋值之前取消监听
        this.removeTextChangedListener(this.tag as TextWatcher)
        this.clearFocus()
    }
    //todo 2 声明一个TextWatcher监听器
    val watcher = object : TextWatcher {
        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

        }

        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

        }

        override fun afterTextChanged(p0: Editable?) {
            afterTextChanged.invoke(p0)
        }

    }
    //todo 在更新UI前,可以先移除监听器,更新完后再重新添加。这样可以确保在更新期间不会触发监听器:
    this.setText(content)
    //todo 3 重新添加
    this.addTextChangedListener(watcher)
    //todo 4 设置tag
    this.tag = watcher
}
```

/**
 * Rv adapter EditText 复用问题扩展函数封装
 * 通过设置setOnFocusChangeListener来监听焦点就能完美的解决了RecyclerView中EditText数据监听textWatcher导致的数据错乱问题了。目前来说是最优雅的一种方式了,也不需要用到Tag了
 */
fun EditText.superAddTextChangedListener2(afterTextChanged: (s: Editable?) -> Unit) {
    //todo 1 声明一个TextWatcher监听器
    val watcher = object : TextWatcher {
        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }

        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }

        override fun afterTextChanged(p0: Editable?) {
            //todo 判断当前EditText是否有焦点在
            if (this@superAddTextChangedListener2.hasFocus()) {
                afterTextChanged.invoke(p0)
            }
        }
    }
    //todo 设置setOnFocusChangeListener监听器来判断焦点的变化从而设置addTextChangedListener和removeTextChangedListener。
    this.setOnFocusChangeListener { view, hasFocus ->
        if (hasFocus) {
            this.addTextChangedListener(watcher)
        } else {
            this.removeTextChangedListener(watcher)
        }
    }
}

/**
 *  Rv adapter CheckBox 复用问题扩展函数封装
 *  Recycler中 多Item中CheckBox焦点混乱,数据错乱的解决方法:
 */
fun CheckBox.superOnCheckedChangeListener(OnCheckedChangeListened: (CompoundButton, Boolean) -> Unit) {
//    if (this.tag is CompoundButton.OnCheckedChangeListener) {
//        //赋值之前取消CheckBox监听
//        this.setOnCheckedChangeListener(null)
//    }
    //赋值之前取消CheckBox监听
    this.setOnCheckedChangeListener(null)
    val checkedChange = CompoundButton.OnCheckedChangeListener { p0, p1 -> OnCheckedChangeListened.invoke(p0, p1) }
    this.setOnCheckedChangeListener(checkedChange)
//    this.tag = checkedChange
}

/**
 * Recycler中 多Item中RadioGroup ,RadioButton焦点混乱,数据错乱的解决方法
 */
fun RadioGroup.superOnCheckedChangeListener(block: (RadioGroup, Int) -> Unit) {
    //避免复用先设置null
    this.setOnCheckedChangeListener(null)
    this.setOnCheckedChangeListener { radioGroup, id ->
        block.invoke(radioGroup, id)
    }
}
```

/**
 * EditText的文本为空
 */
fun EditText.isEmpty(): Boolean {
    return this.text.isNullOrEmpty()
}

/**
 * 将光标移动至末尾
 */
fun EditText.selectionEnd() {
    this.setSelection(this.text.length)
}

/**
 * 设置文字,并将将光标移动至末尾
 */
fun EditText.setTextAndSelectionEnd(@StringRes resId: Int) {
    this.setText(resId)
    selectionEnd()
}

/**
 * 设置文字,并将将光标移动至末尾
 */
fun EditText.setTextAndSelectionEnd(text: CharSequence) {
    this.setText(text)
    selectionEnd()
}

/**
 * 监听文本变化
 */
fun EditText.listenerTextChange(onChange: (s: CharSequence?, count: Int) -> Unit) {
    this.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            onChange(s, count)
        }
    })
}
```

/**
 *  user.printToLog() // 使用默认日志标签
user.printToLog(tag = "USER_INFO" ) / / 带有自定义日志标签
 */
fun Any?.printToLog(tag: String = "DEBUG_LOG") {
    Log.d(tag, toString())
}

//是否为null判断
val Any?.isNull get() = this == null

//在特定对象为空时运行一些代码
fun Any?.ifNull(block: () -> Unit) = run {
    if (this == null) {
        block()
    }
}

//获取子View集合
val ViewGroup.children: List<View>
    get() = (0 until childCount).map { getChildAt(it) }


//// Convert px to dp
//val Int.px: Int
//    get() = (this / Resources.getSystem().displayMetrics.density).toInt()
//
////Convert dp to px
//val Int.dp: Int
//    get() = (this * Resources.getSystem().displayMetrics.density).toInt()
/**
 * dp转px,px转dp, kotlin 扩展方法,不需要context
 */
val Float.px
    get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, this, Resources.getSystem().displayMetrics)

val Float.dp
    get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics)

val Float.sp
    get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics)

val Int.dp
    get() = this.toFloat().dp

//fun Float.dp2px() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics)


//fun Int.dp2px() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt()


//根据手机的分辨率从 px(像素) 的单位 转成为 dp
//fun Int.px2dp(): Int {
//    val scale = BaseApplication.getAppContext().resources.displayMetrics.density
//    return (this / scale + 0.5f).toInt()
//}


//日期装好
fun String.toDate(format: String = "yyyy-MM-dd HH:mm:ss"): Date? {
    val dateFormatter = SimpleDateFormat(format, Locale.getDefault())
    return dateFormatter.parse(this)
}

fun Date.toStringFormat(format: String = "yyyy-MM-dd HH:mm:ss"): String {
    val dateFormatter = SimpleDateFormat(format, Locale.getDefault())
    return dateFormatter.format(this)
}

//数字及字母检查
//此属性允许您检查字符串。当您只想验证数字、字母或字符串文本中的字母数字时,这会很有用。检查示例以获得更多理解。它基于正则表达式。
val String.isDigitOnly: Boolean
    get() = matches(Regex("^\d*$"))

val String.isAlphabeticOnly: Boolean
    get() = matches(Regex("^[a-zA-Z]*$"))

val String.isAlphanumericOnly: Boolean
    get() = matches(Regex("^[a-zA-Z\d]*$"))

//下面是例子
//val isValidNumber = "1234" .isDigitOnly // return true
//val isValid = "1234abc" .isDigitOnly // return false
//val isOnlyAlphabetic = "abcABC" .isAlphabeticOnly // return true
//val isOnlyAlphabetic2 = "abcABC123" .isAlphabeticOnly // return false
//val isOnlyAlphanumeric = "abcABC123" .isAlphanumericOnly // return true
//val isOnlyAlphanumeric2 = "abcABC@123." .isAlphanumericOnly // return false
//隐藏键盘
fun Activity.hideKeyboard() {
    val imm: InputMethodManager =
        getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
    val view = currentFocus ?: View(this)
    imm.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}

//隐藏键盘
fun Fragment.hideKeyboard() {
    activity?.apply {
        val imm: InputMethodManager =
            getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
        val view = currentFocus ?: View(this)
        imm.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
    }
}

/**
 * Toast扩展函数
 */

fun Fragment.showToast(message: String) {
    Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
}

fun Fragment.showToast(@StringRes message: Int) {
    Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
}

fun Activity.showToast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}

fun Activity.showToast(@StringRes message: Int) {
    Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}


/**
 * 获取在窗口上的坐标
 */
fun View.getLInWindow(): IntArray {
    val locations = IntArray(2)
    getLocationInWindow(locations)
    return locations
}

fun View.getLocationXInWindow(): Int {
    return getLInWindow()[0]
}

fun View.getLocationYInWindow(): Int {
    return getLInWindow()[1]
}

/**
 * 获取在屏幕上的坐标
 */
fun View.getLOnScreen(): IntArray {
    val locations = IntArray(2)
    getLocationOnScreen(locations)
    return locations
}


fun View.getLocationXOnScreen(): Int {
    return getLOnScreen()[0]
}

fun View.getLocationYOnScreen(): Int {
    return getLOnScreen()[1]
}

/**
 * 获取屏幕宽/高(px)
 */
val Context.screenWidthPx: Int
    get() = resources.displayMetrics.widthPixels

val Context.screenHeightPx: Int
    get() = resources.displayMetrics.heightPixels


//val View.screenWidthDp: Int
//    get() = screenWidthPx.px2dp()
//
//val View.screenHeightDp: Int
//    get() = screenHeightPx.px2dp()

/**
 * 跳转到拨号界面
 */
fun Context.makeCall(number: String): Boolean {
    try {
        val intent = Intent(ACTION_CALL, Uri.parse("tel:$number"))
        startActivity(intent)
        return true
    } catch (e: Exception) {
        return false
    }
}

/**
 * 设置部分文本字体颜色高亮
 */
fun TextView.setColorOfSubstring(substring: String, color: Int) {
    try {
        val spannable = android.text.SpannableString(text)
        val start = text.indexOf(substring)
        spannable.setSpan(ForegroundColorSpan(ContextCompat.getColor(context, color)), start, start + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        text = spannable
    } catch (e: Exception) {
        Log.d("ViewExtensions", "exception in setColorOfSubstring, text=$text, substring=$substring", e)
    }
}

/**
 * 拷贝
 */
fun TextView.copyStr(): Boolean {
    return try {
        //获取剪贴板管理器
        val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
        // 创建普通字符型ClipData
        val mClipData = ClipData.newPlainText("Label", this.text)
        // 将ClipData内容放到系统剪贴板里。
        cm.setPrimaryClip(mClipData)
        true
    } catch (e: java.lang.Exception) {
        false
    }
}