├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── kotlinc.xml ├── misc.xml └── vcs.xml ├── MentionLibrary ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── studio │ │ └── kio │ │ └── mentionlibrary │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── studio │ │ └── kio │ │ └── mentionlibrary │ │ ├── MentionEditText.kt │ │ ├── MentionEditTextHandler.kt │ │ ├── MentionHandlerBuilder.kt │ │ └── MentionUtil.kt │ └── test │ └── java │ └── studio │ └── kio │ └── mentionlibrary │ └── ExampleUnitTest.kt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── studio │ │ └── kio │ │ └── demo │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── studio │ │ │ └── kio │ │ │ └── demo │ │ │ ├── MainActivity.kt │ │ │ ├── MentionListActivity.kt │ │ │ └── User.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_mention_list.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── studio │ └── kio │ └── demo │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── preview.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MentionLibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | publishConfig.gradle -------------------------------------------------------------------------------- /MentionLibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'com.github.panpf.bintray-publish' 5 | 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.3" 9 | 10 | compileOptions { 11 | sourceCompatibility = 1.8 12 | targetCompatibility = 1.8 13 | } 14 | 15 | kotlinOptions { 16 | jvmTarget = "1.8" 17 | } 18 | 19 | defaultConfig { 20 | minSdkVersion 14 21 | targetSdkVersion 29 22 | versionCode 1 23 | versionName "1.0" 24 | 25 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 26 | consumerProguardFiles "consumer-rules.pro" 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation 'androidx.appcompat:appcompat:1.2.0' 39 | testImplementation 'junit:junit:4.12' 40 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 42 | } 43 | 44 | apply from: 'publishConfig.gradle' -------------------------------------------------------------------------------- /MentionLibrary/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Elip-kio/MentionEditText/1e106d4f6f5fec0a7f72a0b6775e416fb5c86f84/MentionLibrary/consumer-rules.pro -------------------------------------------------------------------------------- /MentionLibrary/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /MentionLibrary/src/androidTest/java/studio/kio/mentionlibrary/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.mentionlibrary 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("studio.kio.mentionlibrary.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /MentionLibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MentionLibrary/src/main/java/studio/kio/mentionlibrary/MentionEditText.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.mentionlibrary 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.KeyEvent 6 | import android.widget.Toast 7 | 8 | /** 9 | * created by KIO on 2020/10/10 10 | */ 11 | 12 | open class MentionEditText(context: Context, attributeSet: AttributeSet?) : 13 | androidx.appcompat.widget.AppCompatEditText(context, attributeSet) { 14 | 15 | private var onSelectionChangedListener: OnSelectionChangedListener? = null 16 | 17 | constructor(context: Context) : this(context, null) 18 | 19 | override fun onSelectionChanged(selStart: Int, selEnd: Int) { 20 | super.onSelectionChanged(selStart, selEnd) 21 | onSelectionChangedListener?.onSelectionChanged(selStart, selEnd) 22 | } 23 | 24 | fun setOnSelectionChangedListener(onSelectionChangedListener: OnSelectionChangedListener) { 25 | this.onSelectionChangedListener = onSelectionChangedListener 26 | } 27 | 28 | 29 | interface OnSelectionChangedListener { 30 | fun onSelectionChanged(selStart: Int, selEnd: Int) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /MentionLibrary/src/main/java/studio/kio/mentionlibrary/MentionEditTextHandler.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.mentionlibrary 2 | 3 | import android.text.Editable 4 | import android.text.Spannable 5 | import android.text.TextWatcher 6 | import android.util.Log 7 | import java.lang.IllegalStateException 8 | 9 | /** 10 | * created by KIO on 2020/10/10 11 | */ 12 | class MentionEditTextHandler(private val handlerBuilder: MentionHandlerBuilder) { 13 | 14 | private var mentions = mutableListOf>() 15 | 16 | init { 17 | handlerBuilder.editText?.addTextChangedListener(object : TextWatcher { 18 | 19 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} 20 | 21 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { 22 | Log.e( 23 | "onTextChanged:", 24 | "s:${s.toString()} start:${start} before:${before} count:${count}" 25 | ) 26 | if (s == null) 27 | return 28 | if (count == 1 && s[start] == handlerBuilder.mentionTag) {//用户输入了@ 29 | handlerBuilder.onMentionInsertListener?.onMentionInserted(start) 30 | } 31 | 32 | //长度变小了或者有被替换的部分 33 | if (count < before || before != 0) { 34 | var i = 0 35 | while (i < mentions.size) { 36 | val it = mentions[i] 37 | val mStart = handlerBuilder.editText?.editableText?.getSpanStart(it.span)!! 38 | val mEnd = handlerBuilder.editText?.editableText?.getSpanEnd(it.span)!! 39 | 40 | val needRemove: Boolean 41 | Log.e("onTextChanged", "mStart:$mStart , mEnd:$mEnd") 42 | needRemove = if (mStart == -1 || mEnd == -1) { 43 | true //已没有span 44 | } else (start == mEnd && mEnd - mStart == it.label.length + 1) //删除span中最右边一个字符 45 | 46 | if (needRemove) { 47 | handlerBuilder.editText?.editableText?.removeSpan(it.span) 48 | mentions.remove(it) 49 | } else { 50 | i++ 51 | } 52 | 53 | } 54 | } 55 | } 56 | 57 | override fun afterTextChanged(s: Editable?) {} 58 | }) 59 | handlerBuilder.editText?.setOnSelectionChangedListener(object : 60 | MentionEditText.OnSelectionChangedListener { 61 | override fun onSelectionChanged(selStart: Int, selEnd: Int) { 62 | Log.e("selectionChange", "${selStart}::${selEnd}") 63 | //碰撞检测,不允许光标在span中 64 | mentions.forEach { 65 | val start = handlerBuilder.editText?.editableText?.getSpanStart(it.span) 66 | val end = handlerBuilder.editText?.editableText?.getSpanEnd(it.span) 67 | if (start != null && start != -1 && end != null && end != -1) { 68 | var adjustSelStart = selStart 69 | var adjustSelEnd = selEnd 70 | var needAdjust = true 71 | 72 | if (selStart > start && selEnd < end) {//span包含了光标 73 | adjustSelStart = start 74 | adjustSelEnd = end 75 | } else if (selEnd > start && selEnd < end) {//光标与span的左边相交 76 | adjustSelEnd = end 77 | } else if (selStart > start && selStart < end) {//光标与span的右边相交 78 | adjustSelStart = start 79 | } else { 80 | needAdjust = false 81 | } 82 | if (needAdjust) { 83 | handlerBuilder.editText?.requestFocus() 84 | handlerBuilder.editText?.setSelection(adjustSelStart, adjustSelEnd) 85 | } 86 | } 87 | 88 | } 89 | } 90 | }) 91 | 92 | } 93 | 94 | fun insert(obj: T, label: String, labelStart: Int) { 95 | 96 | val e = handlerBuilder.editText?.editableText 97 | val span = handlerBuilder.decorator?.getSpan() 98 | 99 | val node = MentionNode( 100 | obj, 101 | label, 102 | span 103 | ) 104 | 105 | mentions.add(node) 106 | e?.insert(labelStart + 1, "${node.label} ") 107 | 108 | e?.setSpan( 109 | span, 110 | labelStart, node.label.length + labelStart + 2,//这里的1是加上了前面的@与后面的空格 111 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 112 | ) 113 | handlerBuilder.editText?.requestFocus() 114 | } 115 | 116 | fun automaticallyAppend() { 117 | val et = handlerBuilder.editText 118 | val e = et?.editableText 119 | val length = e?.length 120 | val tag = handlerBuilder.mentionTag 121 | if (length != null) { 122 | e.insert(et.selectionEnd, "$tag") 123 | } 124 | } 125 | 126 | fun getMentionItems(): List> { 127 | val list = mutableListOf>() 128 | mentions.forEach { 129 | val start = handlerBuilder.editText?.editableText?.getSpanStart(it.span) 130 | val end = handlerBuilder.editText?.editableText?.getSpanEnd(it.span) 131 | if (start == null || start == -1 || end == null || end == -1) {//找不到记录 132 | throw IllegalStateException("Could Not Find Out MentionItem for Input Text!") 133 | } 134 | list.add(MentionItem(it.obj, it.label, start, end)) 135 | } 136 | return list 137 | } 138 | 139 | private data class MentionNode( 140 | val obj: T, 141 | var label: String, 142 | val span: Any? 143 | ) 144 | 145 | data class MentionItem( 146 | val obj: T, 147 | val label: String, 148 | var start: Int, 149 | val end: Int 150 | ) 151 | 152 | } -------------------------------------------------------------------------------- /MentionLibrary/src/main/java/studio/kio/mentionlibrary/MentionHandlerBuilder.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.mentionlibrary 2 | 3 | /** 4 | * created by KIO on 2020/10/10 5 | */ 6 | 7 | class MentionHandlerBuilder { 8 | 9 | var onMentionInsertListener: OnMentionInsertedListener? = null 10 | private set 11 | 12 | var mentionTag = '@' 13 | private set 14 | 15 | var editText: MentionEditText? = null 16 | private set 17 | 18 | var decorator: MentionDecorator? = null 19 | private set 20 | 21 | fun onMention(onMentionInsertListener: OnMentionInsertedListener): MentionHandlerBuilder { 22 | this.onMentionInsertListener = onMentionInsertListener 23 | return this 24 | } 25 | 26 | fun decorate(decorator: MentionDecorator): MentionHandlerBuilder { 27 | this.decorator = decorator 28 | return this 29 | } 30 | 31 | fun tag(mentionTag: Char): MentionHandlerBuilder { 32 | this.mentionTag = mentionTag 33 | return this 34 | } 35 | 36 | fun attach(editText: MentionEditText): MentionEditTextHandler { 37 | this.editText = editText 38 | return MentionEditTextHandler(this) 39 | } 40 | 41 | interface OnMentionInsertedListener { 42 | fun onMentionInserted(position: Int) 43 | } 44 | 45 | interface MentionDecorator { 46 | fun getSpan(): Any 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /MentionLibrary/src/main/java/studio/kio/mentionlibrary/MentionUtil.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.mentionlibrary 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * created by KIO on 2020/10/10 7 | */ 8 | object MentionUtil { 9 | 10 | fun withType(type: KClass): MentionHandlerBuilder { 11 | return MentionHandlerBuilder() 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /MentionLibrary/src/test/java/studio/kio/mentionlibrary/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.mentionlibrary 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MentionEditText 2 | 3 | ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/Eclipse-kio/MentionEditText?label=Latest) 4 | 5 | 一个可定制性优良支持@的输入框组件。支持定制响应字符,自定义高亮Span,输入与数据及时同步。 6 | 7 | ## 预览 8 | 9 | ![preview.gif](./images/preview.gif) 10 | 11 | ## 开始使用 12 | 13 | ### 引入依赖 14 | 15 | 在module下的build.gradle文件中添加如下依赖 16 | 17 | ```groovy 18 | implementation 'studio.kio:MentionLibrary:1.0.0' 19 | ``` 20 | 21 | ### 使用MentionEditText控件 22 | 23 | ```xml 24 | 28 | ``` 29 | 30 | ```kotlin 31 | val myMentionEditText = MentionEditText(context) 32 | ``` 33 | 34 | ### 自定义一个可被关联的数据类 35 | 36 | ```kotlin 37 | data class User( 38 | val Id: Int, 39 | val name: String 40 | ) 41 | ``` 42 | 43 | ### 启用@功能 44 | 45 | ```kotlin 46 | MentionUtil.withType(User::class)//返回MentionHandlerBuilder以支持mention事件及定制 47 | .attach(et_sample)//返回MentionEditTextHandler以支持插入及获取已关联的mentionItems 48 | ``` 49 | 50 | ### 定制 51 | 52 | ```kotlin 53 | mentionHandlerBuilder.tag('@')//设置响应的字符 54 | .onMention(object : OnMentionInsertedListener { 55 | override fun onMentionInserted(position: Int) { 56 | //用户输入了@时这里会被回调 57 | } 58 | }) 59 | .decorate(object : MentionHandlerBuilder.MentionDecorator { 60 | override fun getSpan(): Any { 61 | //自定义高亮样式 62 | return ForegroundColorSpan((0xff000000 or (Math.random() * Int.MAX_VALUE).toLong()).toInt()) 63 | } 64 | }) 65 | ``` 66 | 67 | ### 插入 68 | 69 | ```kotlin 70 | mentionHandler.insert(user, label, position) 71 | ``` 72 | 73 | ### 获取关联的MentionItem 74 | 75 | ```kotlin 76 | mentionHandler.getMentionItems()//List 77 | ``` 78 | 79 | ## 完整Demo 80 | 81 | ```kotlin 82 | this.mentionHandler = MentionUtil.withType(User::class) 83 | .onMention(object : OnMentionInsertedListener { 84 | override fun onMentionInserted(position: Int) { 85 | mentionHandler.insert(user, user.name, position) 86 | } 87 | }) 88 | .decorate(object : MentionHandlerBuilder.MentionDecorator { 89 | override fun getSpan(): Any { 90 | return ForegroundColorSpan((0xff000000 or (Math.random() * Int.MAX_VALUE).toLong()).toInt()) 91 | } 92 | }) 93 | .tag('@') 94 | .attach(et_sample) 95 | 96 | //触发@ 97 | mentionHandler.automaticallyAppend() 98 | 99 | //获取关联的用户 100 | mentionHandler.getMentionItems() 101 | 102 | ``` 103 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.3" 8 | 9 | defaultConfig { 10 | applicationId "studio.kio.demo" 11 | minSdkVersion 21 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | // implementation project(path: ":MentionLibrary") 29 | implementation 'studio.kio:MentionLibrary:1.0.0' 30 | implementation fileTree(dir: "libs", include: ["*.jar"]) 31 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 32 | implementation 'androidx.core:core-ktx:1.3.1' 33 | implementation 'androidx.appcompat:appcompat:1.2.0' 34 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 38 | 39 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/studio/kio/demo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.demo 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("studio.kio.demo", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/studio/kio/demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.demo 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.text.style.ForegroundColorSpan 6 | import androidx.appcompat.app.AppCompatActivity 7 | import kotlinx.android.synthetic.main.activity_main.* 8 | import studio.kio.mentionlibrary.MentionEditTextHandler 9 | import studio.kio.mentionlibrary.MentionHandlerBuilder 10 | import studio.kio.mentionlibrary.MentionHandlerBuilder.OnMentionInsertedListener 11 | import studio.kio.mentionlibrary.MentionUtil 12 | 13 | class MainActivity : AppCompatActivity() { 14 | 15 | private lateinit var mentionHandler: MentionEditTextHandler 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | 20 | setContentView(R.layout.activity_main) 21 | this.mentionHandler = MentionUtil.withType(User::class) 22 | .onMention(object : OnMentionInsertedListener { 23 | override fun onMentionInserted(position: Int) { 24 | val intent = Intent(this@MainActivity, MentionListActivity::class.java) 25 | intent.putExtra("position", position) 26 | startActivityForResult(intent, 0) 27 | } 28 | }) 29 | .decorate(object : MentionHandlerBuilder.MentionDecorator { 30 | override fun getSpan(): Any { 31 | return ForegroundColorSpan((0xff000000 or (Math.random() * Int.MAX_VALUE).toLong()).toInt()) 32 | } 33 | }) 34 | .tag('@') 35 | .attach(et_sample) 36 | bt_show.setOnClickListener { 37 | tv_mentions.text = mentionHandler.getMentionItems().toString() 38 | } 39 | 40 | bt_mention.setOnClickListener { 41 | mentionHandler.automaticallyAppend() 42 | } 43 | } 44 | 45 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 46 | super.onActivityResult(requestCode, resultCode, data) 47 | if (data != null) { 48 | val user = data.getSerializableExtra("user") as User 49 | mentionHandler.insert(user, user.name, data.getIntExtra("position", 0)) 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/studio/kio/demo/MentionListActivity.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.demo 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.widget.ArrayAdapter 6 | import kotlinx.android.synthetic.main.activity_mention_list.* 7 | 8 | class MentionListActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_mention_list) 12 | 13 | val users = arrayOf( 14 | User(1, "Daniel"), 15 | User(2, "Lee"), 16 | User(3, "Ronald"), 17 | User(4, "Vaughan"), 18 | User(5, "Gerald"), 19 | User(6, "Erik"), 20 | User(7, "Strawberry"), 21 | User(8, "Gerald"), 22 | User(9, "Ives"), 23 | User(10, "Royal"), 24 | User(11, "Crown"), 25 | User(12, "Warren"), 26 | User(13, "Philip"), 27 | User(14, "Gilbert"), 28 | User(15, "Rosemary"), 29 | User(16, "Timothea") 30 | ) 31 | 32 | val userLabels = mutableListOf() 33 | 34 | users.forEach { 35 | userLabels.add(it.name) 36 | } 37 | 38 | lv_users.adapter = 39 | ArrayAdapter(this, android.R.layout.simple_list_item_1, userLabels) 40 | title = "Select User" 41 | 42 | lv_users.setOnItemClickListener { _, _, position, _ -> 43 | intent.putExtra("user", users[position]) 44 | setResult(0, intent) 45 | finish() 46 | } 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/studio/kio/demo/User.kt: -------------------------------------------------------------------------------- 1 | package studio.kio.demo 2 | 3 | import java.io.Serializable 4 | 5 | /** 6 | * created by KIO on 2020/10/10 7 | */ 8 | 9 | 10 | data class User( 11 | val Id: Int, 12 | val name: String 13 | ) : Serializable -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 20 | 21 |