├── .circleci
└── config.yml
├── .editorconfig
├── .github
└── workflows
│ ├── build.yml
│ ├── docs.yml
│ └── issues-stale.yml
├── .gitignore
├── .travis.yml.bak
├── CHANGELOG.md
├── GIF.gif
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── release
│ ├── app-release.apk
│ └── output-metadata.json
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── king
│ │ └── compose
│ │ └── codetextfield
│ │ └── app
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── king
│ │ │ └── compose
│ │ │ └── codetextfield
│ │ │ └── app
│ │ │ ├── MainActivity.kt
│ │ │ └── ui
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── king
│ └── compose
│ └── codetextfield
│ └── app
│ └── ExampleUnitTest.kt
├── build.gradle
├── build_docs.sh
├── codetextfield
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── gradle.properties
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── king
│ │ └── compose
│ │ └── codetextfield
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── king
│ │ └── compose
│ │ └── codetextfield
│ │ ├── CodeTextField.kt
│ │ └── TextFieldCursor.kt
│ └── test
│ └── java
│ └── com
│ └── king
│ └── compose
│ └── codetextfield
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── mkdocs.yml
├── settings.gradle
└── versions.gradle
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | android: circleci/android@0.2.0
5 |
6 | jobs:
7 | build:
8 | docker:
9 | - image: cimg/android:2023.02
10 | steps:
11 | - checkout
12 | - run:
13 | command: ./gradlew build
14 |
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.{kt, kts}]
11 | ij_kotlin_imports_layout = *
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up JDK 11
16 | uses: actions/setup-java@v1
17 | with:
18 | java-version: 11
19 | - name: Build with Gradle
20 | run: ./gradlew build
21 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | env:
9 | JAVA_VERSION: 11
10 | PYTHON_VERSION: 3.x
11 | GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
12 |
13 | permissions:
14 | contents: write
15 | id-token: write
16 | pages: write
17 |
18 | jobs:
19 | docs:
20 | environment:
21 | name: github-pages
22 | url: ${{ steps.deployment.outputs.page_url }}
23 | runs-on: ubuntu-latest
24 | if: github.ref == 'refs/heads/master'
25 |
26 | steps:
27 | - name: Checkout
28 | uses: actions/checkout@v4
29 | with:
30 | fetch-depth: 0
31 |
32 | - name: Configure JDK
33 | uses: actions/setup-java@v4
34 | with:
35 | distribution: 'zulu'
36 | java-version: ${{ env.JAVA_VERSION }}
37 |
38 | - name: Install Python
39 | uses: actions/setup-python@v5
40 | with:
41 | python-version: ${{ env.PYTHON_VERSION }}
42 |
43 | - name: Install MkDocs Material
44 | run: pip install mkdocs-material
45 |
46 | - name: Generate Docs
47 | run: ./build_docs.sh
48 |
49 | - name: Upload to GitHub Pages
50 | uses: actions/upload-pages-artifact@v3
51 | with:
52 | path: site
53 |
54 | - name: Deploy to GitHub Pages
55 | id: deployment
56 | uses: actions/deploy-pages@v4
57 |
--------------------------------------------------------------------------------
/.github/workflows/issues-stale.yml:
--------------------------------------------------------------------------------
1 | name: 'Close stale issues and PRs'
2 | on:
3 | schedule:
4 | - cron: '0 16 * * *'
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | issues: write
11 | pull-requests: write
12 | steps:
13 | - uses: actions/stale@v5
14 | with:
15 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
16 | days-before-stale: 30
17 | days-before-close: 5
18 | exempt-all-pr-milestones: true
19 | exempt-issue-labels: 'help wanted'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 |
--------------------------------------------------------------------------------
/.travis.yml.bak:
--------------------------------------------------------------------------------
1 | language: android
2 | dist: trusty
3 | jdk: oraclejdk11
4 | sudo: false
5 |
6 | env:
7 | global:
8 | - ANDROID_API_LEVEL=32
9 | - ANDROID_BUILD_TOOLS_VERSION=32.0.0
10 | - TRAVIS_SECURE_ENV_VARS=true
11 |
12 | before_install:
13 | - chmod +x gradlew
14 | - mkdir "$ANDROID_HOME/licenses" || true
15 | # Hack to accept Android licenses
16 | - yes | sdkmanager "platforms;android-$ANDROID_API_LEVEL"
17 |
18 | android:
19 | components:
20 | # The BuildTools version used by your project
21 | - tools
22 | - platform-tools
23 | - build-tools-$ANDROID_BUILD_TOOLS_VERSION
24 | - extra-android-m2repository
25 | - extra-google-android-support
26 |
27 | # The SDK version used to compile your project
28 | - android-$ANDROID_API_LEVEL
29 |
30 | script:
31 | - ./gradlew clean
32 | - ./gradlew assembleDebug
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 版本日志
2 |
3 | #### v1.0.0:2022-11-20
4 | * CodeTextField初始版本
5 |
--------------------------------------------------------------------------------
/GIF.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/GIF.gif
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022 Jenly Yu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CodeTextField
2 |
3 | [](https://repo1.maven.org/maven2/com/github/jenly1314/CodeTextField)
4 | [](https://jitpack.io/#jenly1314/CodeTextField)
5 | [](https://github.com/jenly1314/CodeTextField/actions/workflows/build.yml)
6 | [](https://raw.githubusercontent.com/jenly1314/CodeTextField/master/app/release/app-release.apk)
7 | [](https://developer.android.com/guide/topics/manifest/uses-sdk-element#ApiLevels)
8 | [](https://opensource.org/licenses/mit)
9 |
10 | CodeTextField for Jetpack Compose;一个使用 Compose 实现的验证码输入框。
11 |
12 | > 原生Android View实现类似的输入框可参见 [SplitEditText](https://github.com/jenly1314/SplitEditText)
13 |
14 | ## 效果展示
15 | 
16 |
17 | > 你也可以直接下载 [演示App](https://raw.githubusercontent.com/jenly1314/CodeTextField/master/app/release/app-release.apk) 体验效果
18 |
19 | ## 引入
20 |
21 | ### Gradle:
22 |
23 | 1. 在Project的 **build.gradle** 或 **setting.gradle** 中添加远程仓库
24 |
25 | ```gradle
26 | repositories {
27 | //...
28 | mavenCentral()
29 | }
30 | ```
31 |
32 | 2. 在Module的 **build.gradle** 中添加依赖项
33 |
34 | ```gradle
35 | implementation 'com.github.jenly1314:codetextfield:1.0.0'
36 | ```
37 |
38 | ## 使用
39 |
40 | ### 代码示例
41 |
42 | ```kotlin
43 | var text by remember {
44 | mutableStateOf("")
45 | }
46 | // 验证码输入框
47 | CodeTextField(value = text, onValueChange = {
48 | text = it
49 | })
50 |
51 | ```
52 |
53 | 更多使用详情,请查看[app](app)中的源码使用示例或直接查看 [API帮助文档](https://jenly1314.github.io/CodeTextField/api/)
54 |
55 | ## 相关推荐
56 |
57 | - [SplitEditText](https://github.com/jenly1314/SplitEditText) 一个灵活的分割可编辑框;常常应用于 **验证码输入** 、**密码输入** 等场景。
58 | - [KingKeyboard](https://github.com/jenly1314/KingKeyboard) 一个自定义键盘,满足各种不同场景的键盘输入需求。
59 | - [compose-component](https://github.com/jenly1314/compose-component) 一个Jetpack Compose的组件库;主要提供了一些小组件,便于快速使用。
60 |
61 |
62 |
63 | ## 版本日志
64 |
65 | #### v1.0.0:2022-11-20
66 | * CodeTextField初始版本
67 |
68 | ---
69 |
70 | 
71 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk build_versions.compileSdk
8 |
9 | defaultConfig {
10 | applicationId "com.king.compose.codetextfield.app"
11 | minSdk build_versions.minSdk
12 | targetSdk build_versions.targetSdk
13 | versionCode app_version.versionCode
14 | versionName app_version.versionName
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | vectorDrawables {
18 | useSupportLibrary true
19 | }
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility JavaVersion.VERSION_1_8
30 | targetCompatibility JavaVersion.VERSION_1_8
31 | }
32 | kotlinOptions {
33 | jvmTarget = JavaVersion.VERSION_1_8
34 | }
35 | buildFeatures {
36 | compose true
37 | }
38 | composeOptions {
39 | kotlinCompilerExtensionVersion versions.compose
40 | }
41 | packagingOptions {
42 | resources {
43 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
44 | }
45 | }
46 | lintOptions {
47 | abortOnError false
48 | warning 'InvalidPackage'
49 | }
50 | }
51 |
52 | dependencies {
53 |
54 | implementation "androidx.core:core-ktx:$versions.coreKtx"
55 | implementation "androidx.compose.ui:ui:$versions.compose"
56 | implementation "androidx.compose.material:material:$versions.compose"
57 | implementation "androidx.compose.ui:ui-tooling-preview:$versions.compose"
58 | // implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
59 | implementation "androidx.activity:activity-compose:$versions.activityCompose"
60 |
61 | testImplementation "junit:junit:$versions.junit"
62 | androidTestImplementation "androidx.test.ext:junit:$versions.androidExtJunit"
63 | androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCore"
64 |
65 | implementation project(":codetextfield")
66 | }
67 |
--------------------------------------------------------------------------------
/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/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/release/app-release.apk
--------------------------------------------------------------------------------
/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "com.king.compose.codetextfield.app",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 1,
15 | "versionName": "1.0.0",
16 | "outputFile": "app-release.apk"
17 | }
18 | ],
19 | "elementType": "File"
20 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/king/compose/codetextfield/app/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield.app
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("com.king.compose.codetextfield.app", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/compose/codetextfield/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield.app
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.BorderStroke
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.*
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material.*
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.runtime.setValue
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.graphics.SolidColor
19 | import androidx.compose.ui.res.stringResource
20 | import androidx.compose.ui.text.TextStyle
21 | import androidx.compose.ui.text.font.FontWeight
22 | import androidx.compose.ui.tooling.preview.Preview
23 | import androidx.compose.ui.unit.dp
24 | import androidx.compose.ui.unit.sp
25 | import com.king.compose.codetextfield.CodeTextField
26 | import com.king.compose.codetextfield.app.ui.theme.CodeTextFieldTheme
27 |
28 | /**
29 | * 演示 CodeTextField
30 | *
31 | * @author Jenly
32 | */
33 | class MainActivity : ComponentActivity() {
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | setContent {
37 | CodeTextFieldTheme {
38 | MainScreen()
39 | }
40 | }
41 | }
42 | }
43 |
44 | @Composable
45 | private fun MainScreen() {
46 | Scaffold(topBar = {
47 | TopAppBar {
48 | Text(
49 | text = stringResource(id = R.string.app_name),
50 | style = TextStyle(fontSize = 24.sp, color = Color.White),
51 | modifier = Modifier.padding(horizontal = 16.dp)
52 | )
53 | }
54 | }) {
55 | Surface(
56 | modifier = Modifier
57 | .fillMaxSize()
58 | .padding(bottom = it.calculateBottomPadding()),
59 | color = MaterialTheme.colors.background
60 | ) {
61 | CodeTextFiledDemo()
62 | }
63 | }
64 |
65 | }
66 |
67 | /**
68 | * 验证码示例
69 | */
70 | @Composable
71 | private fun CodeTextFiledDemo() {
72 | Column() {
73 | var text1 by remember {
74 | mutableStateOf("")
75 | }
76 | // 验证码矩形输入框;默认
77 | CodeTextField(
78 | value = text1,
79 | modifier = Modifier
80 | .padding(10.dp)
81 | .fillMaxWidth(),
82 | onValueChange = {
83 | text1 = it
84 | })
85 |
86 | var text2 by remember {
87 | mutableStateOf("")
88 | }
89 | // 验证码矩形输入框;移除光标颜色
90 | CodeTextField(
91 | value = text2,
92 | modifier = Modifier
93 | .padding(10.dp)
94 | .fillMaxWidth(),
95 | onValueChange = {
96 | text2 = it
97 | },
98 | cursorBrush = SolidColor(Color.Unspecified)
99 | )
100 |
101 | var text3 by remember {
102 | mutableStateOf("")
103 | }
104 | // 验证码圆角输入框;自定义输入框颜色
105 | CodeTextField(
106 | value = text3,
107 | modifier = Modifier
108 | .padding(10.dp)
109 | .fillMaxWidth(),
110 | onValueChange = {
111 | text3 = it
112 | },
113 | cursorBrush = SolidColor(Color.Unspecified),
114 | boxShape = RoundedCornerShape(10.dp),
115 | boxFocusedBorderStroke = BorderStroke(
116 | TextFieldDefaults.FocusedBorderThickness,
117 | color = Color.Red
118 | ),
119 | )
120 |
121 | var text4 by remember {
122 | mutableStateOf("")
123 | }
124 | // 验证码圆角带背景输入框;突出输入的验证码以加密“*”的方式显示
125 | CodeTextField(
126 | value = text4,
127 | modifier = Modifier
128 | .padding(10.dp)
129 | .fillMaxWidth(),
130 | textStyle = TextStyle(
131 | fontSize = 24.sp,
132 | color = Color.Black,
133 | fontWeight = FontWeight.Bold
134 | ),
135 | onValueChange = {
136 | text4 = it
137 | },
138 | cursorBrush = SolidColor(Color.Unspecified),
139 | boxShape = RoundedCornerShape(10.dp),
140 | boxBackgroundColor = Color.LightGray,
141 | cipherMask = "*"
142 | )
143 |
144 | var text5 by remember {
145 | mutableStateOf("")
146 | }
147 | // 验证码矩形无间距输入框
148 | CodeTextField(
149 | value = text5,
150 | modifier = Modifier
151 | .padding(horizontal = 30.dp, vertical = 10.dp)
152 | .border(BorderStroke(2.dp, color = Color.LightGray)),
153 | onValueChange = {
154 | text5 = it
155 | },
156 | cursorBrush = SolidColor(Color.Red),
157 | boxBorderStroke = BorderStroke(1.dp, color = Color.LightGray),
158 | boxFocusedBorderStroke = BorderStroke(
159 | 1.dp,
160 | color = Color.LightGray
161 | ),
162 | boxMargin = 0.dp
163 | )
164 |
165 | }
166 | }
167 |
168 |
169 | @Preview(showBackground = true)
170 | @Composable
171 | fun DefaultPreview() {
172 | CodeTextFieldTheme {
173 | MainScreen()
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/app/src/main/java/com/king/compose/codetextfield/app/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield.app.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
--------------------------------------------------------------------------------
/app/src/main/java/com/king/compose/codetextfield/app/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield.app.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/king/compose/codetextfield/app/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield.app.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorPalette = darkColors(
10 | primary = Purple200,
11 | primaryVariant = Purple700,
12 | secondary = Teal200
13 | )
14 |
15 | private val LightColorPalette = lightColors(
16 | primary = Purple500,
17 | primaryVariant = Purple700,
18 | secondary = Teal200
19 |
20 | /* Other default colors to override
21 | background = Color.White,
22 | surface = Color.White,
23 | onPrimary = Color.White,
24 | onSecondary = Color.Black,
25 | onBackground = Color.Black,
26 | onSurface = Color.Black,
27 | */
28 | )
29 |
30 | @Composable
31 | fun CodeTextFieldTheme(
32 | darkTheme: Boolean = isSystemInDarkTheme(),
33 | content: @Composable () -> Unit
34 | ) {
35 | val colors = if (darkTheme) {
36 | DarkColorPalette
37 | } else {
38 | LightColorPalette
39 | }
40 |
41 | MaterialTheme(
42 | colors = colors,
43 | typography = Typography,
44 | shapes = Shapes,
45 | content = content
46 | )
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/king/compose/codetextfield/app/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield.app.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/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/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CodeTextField
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/king/compose/codetextfield/app/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield.app
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 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | apply from: 'versions.gradle'
3 | }// Top-level build file where you can add configuration options common to all sub-projects/modules.
4 | plugins {
5 | id 'com.android.application' version '7.2.1' apply false
6 | id 'com.android.library' version '7.2.1' apply false
7 | id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
8 | id 'org.jetbrains.dokka' version '1.8.20' apply false
9 | id 'com.vanniktech.maven.publish' version '0.22.0' apply false
10 | }
11 |
12 | //task clean(type: Delete) {
13 | // delete rootProject.buildDir
14 | //}
15 |
--------------------------------------------------------------------------------
/build_docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -ex
4 |
5 | # Generate the API docs
6 | ./gradlew dokkaHtml
7 |
8 | mkdir -p docs/api
9 | mv codetextfield/build/dokka/html/* docs/api
10 |
11 | # Copy in special files that GitHub wants in the project root.
12 | GITHUB_URL=https://github.com/jenly1314/CodeTextField/
13 | echo $GITHUB_URL
14 | sed "//q" README.md > docs/index.md
15 | # sed -i "s|app/src/main/ic_launcher-web.png|ic_logo.png|g" docs/index.md
16 | sed -i "s|](app|](${GITHUB_URL}blob/master/app|g" docs/index.md
17 | cat CHANGELOG.md | grep -v '## 版本日志' > docs/changelog.md
18 |
19 | cp GIF.gif docs/GIF.gif
20 | # cp app/src/main/ic_launcher-web.png docs/ic_logo.png
21 |
22 | # Build the site locally
23 | mkdocs build
24 |
--------------------------------------------------------------------------------
/codetextfield/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/codetextfield/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'org.jetbrains.dokka'
5 | id 'com.vanniktech.maven.publish'
6 | }
7 |
8 | android {
9 | compileSdk build_versions.compileSdk
10 |
11 | defaultConfig {
12 | minSdk build_versions.minSdk
13 | targetSdk build_versions.targetSdk
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles "consumer-rules.pro"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = JavaVersion.VERSION_1_8
31 | }
32 | buildFeatures {
33 | compose true
34 | }
35 | composeOptions {
36 | kotlinCompilerExtensionVersion versions.compose
37 | }
38 | lintOptions {
39 | abortOnError false
40 | warning 'InvalidPackage'
41 | }
42 | }
43 |
44 | dependencies {
45 |
46 | implementation "androidx.core:core-ktx:$versions.coreKtx"
47 | implementation "androidx.compose.ui:ui:$versions.compose"
48 | implementation "androidx.compose.material:material:$versions.compose"
49 |
50 | testImplementation "junit:junit:$versions.junit"
51 | androidTestImplementation "androidx.test.ext:junit:$versions.androidExtJunit"
52 | androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCore"
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/codetextfield/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/codetextfield/consumer-rules.pro
--------------------------------------------------------------------------------
/codetextfield/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=CodeTextField
2 | POM_ARTIFACT_ID=codetextfield
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/codetextfield/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
--------------------------------------------------------------------------------
/codetextfield/src/androidTest/java/com/king/compose/codetextfield/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield
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("com.king.compose.codetextfield.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/codetextfield/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/codetextfield/src/main/java/com/king/compose/codetextfield/CodeTextField.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.foundation.text.BasicTextField
8 | import androidx.compose.foundation.text.KeyboardActions
9 | import androidx.compose.foundation.text.KeyboardOptions
10 | import androidx.compose.material.*
11 | import androidx.compose.runtime.*
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.draw.clip
15 | import androidx.compose.ui.focus.onFocusChanged
16 | import androidx.compose.ui.geometry.Rect
17 | import androidx.compose.ui.graphics.*
18 | import androidx.compose.ui.text.TextRange
19 | import androidx.compose.ui.text.TextStyle
20 | import androidx.compose.ui.text.input.KeyboardType
21 | import androidx.compose.ui.text.input.TextFieldValue
22 | import androidx.compose.ui.unit.Dp
23 | import androidx.compose.ui.unit.dp
24 | import androidx.compose.ui.unit.sp
25 |
26 | /**
27 | * 验证码输入框
28 | *
29 | * @author Jenly
30 | */
31 | @Composable
32 | fun CodeTextField(
33 | value: String,
34 | onValueChange: (String) -> Unit,
35 | modifier: Modifier = Modifier.padding(10.dp),
36 | textColor: Color = Color.Unspecified,
37 | length: Int = 6,
38 | boxWidth: Dp = 48.dp,
39 | boxHeight: Dp = 48.dp,
40 | boxMargin: Dp = 10.dp,
41 | boxShape: Shape = RectangleShape,
42 | boxBackgroundColor: Color = Color.Unspecified,
43 | boxBorderStroke: BorderStroke = BorderStroke(
44 | width = TextFieldDefaults.UnfocusedBorderThickness,
45 | color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
46 | ),
47 | boxFocusedBorderStroke: BorderStroke = BorderStroke(
48 | width = TextFieldDefaults.FocusedBorderThickness,
49 | color = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high)
50 | ),
51 | enabled: Boolean = true,
52 | textStyle: TextStyle = TextStyle(fontSize = 20.sp),
53 | horizontalArrangement: Arrangement.Horizontal = Arrangement.Center,
54 | keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
55 | keyboardActions: KeyboardActions = KeyboardActions.Default,
56 | cursorBrush: Brush = SolidColor(TextFieldDefaults.textFieldColors().cursorColor(false).value),
57 | cipherMask: String = "",
58 | ) {
59 | val focusState = remember {
60 | mutableStateOf(false)
61 | }
62 | val textFieldValue = TextFieldValue(text = value, selection = TextRange(value.length))
63 | var lastTextValue by remember { mutableStateOf(value) }
64 |
65 | BasicTextField(
66 | value = textFieldValue,
67 | onValueChange = {
68 | val newText = if (it.text.length <= length) {
69 | it.text
70 | } else {// 输入超长时,截取前面的部分
71 | it.text.substring(0, length)
72 | }
73 | if (lastTextValue != newText) {
74 | lastTextValue = newText
75 | onValueChange(newText)
76 | }
77 | },
78 | modifier = Modifier.onFocusChanged {
79 | focusState.value = it.hasFocus
80 | },
81 | enabled = enabled,
82 | keyboardOptions = keyboardOptions,
83 | keyboardActions = keyboardActions,
84 | singleLine = true,
85 | cursorBrush = SolidColor(Color.Unspecified)
86 | ) {
87 | CodeText(
88 | value = textFieldValue.text,
89 | modifier = modifier,
90 | textColor = textColor,
91 | hasFocus = focusState.value,
92 | length = length,
93 | boxWidth = boxWidth,
94 | boxHeight = boxHeight,
95 | boxMargin = boxMargin,
96 | boxShape = boxShape,
97 | boxBackgroundColor = boxBackgroundColor,
98 | boxBorderStroke = boxBorderStroke,
99 | boxFocusedBorderStroke = boxFocusedBorderStroke,
100 | textStyle = textStyle,
101 | cursorBrush = cursorBrush,
102 | horizontalArrangement = horizontalArrangement,
103 | cipherMask = cipherMask
104 | )
105 | }
106 |
107 | }
108 |
109 | /**
110 | * 验证码文本
111 | */
112 | @Composable
113 | private fun CodeText(
114 | value: String,
115 | modifier: Modifier = Modifier,
116 | textColor: Color = Color.Unspecified,
117 | hasFocus: Boolean = false,
118 | length: Int = 6,
119 | boxWidth: Dp = 48.dp,
120 | boxHeight: Dp = 48.dp,
121 | boxMargin: Dp = 10.dp,
122 | boxShape: Shape = RectangleShape,
123 | boxBackgroundColor: Color = Color.Gray,
124 | boxBorderStroke: BorderStroke = BorderStroke(1.dp, color = Color.Unspecified),
125 | boxFocusedBorderStroke: BorderStroke = boxBorderStroke,
126 | textStyle: TextStyle = TextStyle.Default,
127 | cursorBrush: Brush = SolidColor(Color.Unspecified),
128 | horizontalArrangement: Arrangement.Horizontal = Arrangement.Center,
129 | cipherMask: String = "",
130 | ) {
131 | Row(modifier = modifier, horizontalArrangement = horizontalArrangement) {
132 | repeat(length) {
133 | if (boxMargin.value > 0 && it != 0) {
134 | // 框与框之间的间距
135 | Spacer(modifier = Modifier.width(boxMargin))
136 | }
137 | val selection = it == value.length
138 |
139 | // 验证码的框
140 | Box(
141 | modifier = Modifier
142 | .background(color = boxBackgroundColor, boxShape)
143 | .clip(boxShape)
144 | .border(
145 | if (hasFocus && selection) boxFocusedBorderStroke else boxBorderStroke,
146 | boxShape
147 | )
148 | .size(width = boxWidth, height = boxHeight),
149 | contentAlignment = Alignment.Center
150 | ) {
151 |
152 | val text = value.getOrNull(it)?.toString() ?: ""
153 |
154 | val cursorRectState = remember {
155 | mutableStateOf(Rect(0f, 0f, 0f, 0f))
156 | }
157 |
158 | // 框的文本内容
159 | Text(
160 | text = if (cipherMask.isNotEmpty() && text.isNotEmpty()) cipherMask else text,
161 | modifier = Modifier.cursor(
162 | cursorBrush = cursorBrush,
163 | cursorRect = cursorRectState.value,
164 | enabled = hasFocus && selection
165 | ),
166 | color = textColor,
167 | onTextLayout = { textLayoutResult ->
168 | cursorRectState.value = textLayoutResult.getCursorRect(0)
169 | },
170 | style = textStyle
171 | )
172 | }
173 | }
174 | }
175 | }
176 |
177 |
178 |
--------------------------------------------------------------------------------
/codetextfield/src/main/java/com/king/compose/codetextfield/TextFieldCursor.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield
2 |
3 | import androidx.compose.animation.core.Animatable
4 | import androidx.compose.animation.core.AnimationSpec
5 | import androidx.compose.animation.core.infiniteRepeatable
6 | import androidx.compose.animation.core.keyframes
7 | import androidx.compose.runtime.LaunchedEffect
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.composed
11 | import androidx.compose.ui.draw.drawWithContent
12 | import androidx.compose.ui.geometry.Offset
13 | import androidx.compose.ui.geometry.Rect
14 | import androidx.compose.ui.graphics.Brush
15 | import androidx.compose.ui.graphics.SolidColor
16 | import androidx.compose.ui.graphics.isUnspecified
17 | import androidx.compose.ui.unit.dp
18 |
19 | /**
20 | * 光标
21 | * @author Jenly
22 | */
23 | @Suppress("ModifierInspectorInfo")
24 | internal fun Modifier.cursor(
25 | cursorBrush: Brush,
26 | cursorRect: Rect,
27 | enabled: Boolean
28 | ) = if (enabled) composed {
29 | val cursorAlpha = remember { Animatable(1f) }
30 | val isBrushSpecified = !(cursorBrush is SolidColor && cursorBrush.value.isUnspecified)
31 | if (isBrushSpecified) {
32 | LaunchedEffect(cursorBrush) {
33 | cursorAlpha.animateTo(0f, cursorAnimationSpec)
34 | }
35 |
36 | drawWithContent {
37 | this.drawContent()
38 | val cursorAlphaValue = cursorAlpha.value.coerceIn(0f, 1f)
39 | if (cursorAlphaValue != 0f) {
40 | val cursorWidth = DefaultCursorThickness.toPx()
41 | val cursorX = (cursorRect.left + cursorWidth / 2)
42 | .coerceAtMost(size.width - cursorWidth / 2)
43 | drawLine(
44 | cursorBrush,
45 | Offset(cursorX, cursorRect.top),
46 | Offset(cursorX, cursorRect.bottom),
47 | alpha = cursorAlphaValue,
48 | strokeWidth = cursorWidth
49 | )
50 | }
51 | }
52 | } else {
53 | Modifier
54 | }
55 | } else this
56 |
57 | /**
58 | * 光标动画
59 | */
60 | private val cursorAnimationSpec: AnimationSpec
61 | get() = infiniteRepeatable(
62 | animation = keyframes {
63 | durationMillis = 1000
64 | 1f at 0
65 | 1f at 499
66 | 0f at 500
67 | 0f at 999
68 | }
69 | )
70 |
71 | /**
72 | * 光标的宽度
73 | */
74 | private val DefaultCursorThickness = 2.dp
75 |
--------------------------------------------------------------------------------
/codetextfield/src/test/java/com/king/compose/codetextfield/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.king.compose.codetextfield
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 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs = -Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX = true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style = official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass = true
24 |
25 | android.disableAutomaticComponentCreation=true
26 |
27 | VERSION_NAME=1.0.0
28 | VERSION_CODE=1
29 | GROUP=com.github.jenly1314
30 |
31 | POM_DESCRIPTION=CodeTextField for Jetpack Compose
32 | POM_URL=https://github.com/jenly1314/CodeTextField
33 |
34 | POM_SCM_URL=https://github.com/jenly1314/codeTextField
35 | POM_SCM_CONNECTION=scm:git@github.com:jenly1314/CodeTextField.git
36 | POM_SCM_DEV_CONNECTION=scm:git@github.com:jenly1314/CodeTextField.git
37 |
38 | POM_LICENCE_NAME=The MIT License
39 | POM_LICENCE_URL=https://opensource.org/licenses/mit-license.php
40 | #POM_LICENCE_NAME=The Apache Software License, Version 2.0
41 | #POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
42 | POM_LICENSE_DIST=repo
43 |
44 | POM_DEVELOPER_ID=jenly
45 | POM_DEVELOPER_NAME=Jenly Yu
46 | POM_DEVELOPER_URL=https://github.com/jenly1314/
47 |
48 | #RELEASE_REPOSITORY_URL=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
49 | #SNAPSHOT_REPOSITORY_URL=https://s01.oss.sonatype.org/content/repositories/snapshots/
50 |
51 | SONATYPE_HOST=S01
52 |
53 | RELEASE_SIGNING_ENABLED=false
54 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenly1314/CodeTextField/e1c794631c9bf2d8cbe1c4f68655e238cb8c6059/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Nov 19 21:45:43 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | # Project information
2 | site_name: CodeTextField
3 | site_url: https://jenly1314.github.io/CodeTextField/
4 | site_description: "CodeTextField for Android"
5 | site_author: Jenly
6 | remote_branch: gh-pages
7 | edit_uri: ""
8 |
9 | # Repository
10 | repo_name: CodeTextField
11 | repo_url: https://github.com/jenly1314/CodeTextField
12 |
13 | # Copyright
14 | copyright: 'Copyright © 2016 - 2024 Jenly'
15 |
16 | # Configuration
17 | theme:
18 | name: 'material'
19 | favicon: https://jenly1314.github.io/favicon.png
20 | logo: https://jenly1314.github.io/medias/logo.png
21 | icon:
22 | repo: fontawesome/brands/github
23 | language: zh
24 | palette:
25 | - media: "(prefers-color-scheme: light)"
26 | scheme: default
27 | primary: teal
28 | accent: blue
29 | toggle:
30 | icon: octicons/sun-24
31 | name: "切换到深色模式"
32 | - media: "(prefers-color-scheme: dark)"
33 | scheme: slate
34 | primary: teal
35 | accent: blue
36 | toggle:
37 | icon: octicons/moon-24
38 | name: "切换到浅色模式"
39 | features:
40 | - navigation.instant
41 | - navigation.instant.progress
42 | - navigation.tabs
43 | - content.code.copy
44 |
45 | extra:
46 | social:
47 | - icon: material/home-circle
48 | link: https://jenly1314.github.io/
49 | - icon: simple/github
50 | link: https://github.com/jenly1314/
51 | - icon: simple/gitee
52 | link: https://gitee.com/jenly1314/
53 | - icon: fontawesome/solid/paper-plane
54 | link: mailto:jenly1314@gmail.com
55 |
56 | markdown_extensions:
57 | - smarty
58 | - footnotes
59 | - meta
60 | - toc:
61 | permalink: true
62 | - attr_list
63 | - pymdownx.betterem:
64 | smart_enable: all
65 | - pymdownx.caret
66 | - pymdownx.emoji:
67 | emoji_index: !!python/name:materialx.emoji.twemoji
68 | emoji_generator: !!python/name:materialx.emoji.to_svg
69 | - pymdownx.inlinehilite
70 | - pymdownx.magiclink
71 | - pymdownx.smartsymbols
72 | - pymdownx.superfences
73 | - pymdownx.tilde
74 | - pymdownx.tabbed:
75 | alternate_style: true
76 | - tables
77 |
78 | # Plugins
79 | plugins:
80 | - search
81 |
82 | nav:
83 | - '概览': index.md
84 | - 'API文档': api/index.html
85 | - '版本日志': changelog.md
86 |
87 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "CodeTextField"
16 | include ':app'
17 | include ':codetextfield'
18 |
--------------------------------------------------------------------------------
/versions.gradle:
--------------------------------------------------------------------------------
1 |
2 | //App
3 | def app_version = [:]
4 | app_version.versionCode = 1
5 | app_version.versionName = "1.0.0"
6 | ext.app_version = app_version
7 |
8 | //build version
9 | def build_versions = [:]
10 | build_versions.minSdk = 21
11 | build_versions.targetSdk =32
12 | build_versions.compileSdk = 32
13 | build_versions.buildTools = "32.0.0"
14 | ext.build_versions = build_versions
15 |
16 | def versions = [:]
17 | versions.mavenPublish = '0.22.0'
18 | versions.dokka = "1.7.20"
19 | versions.gradle = "7.2.1"
20 | versions.kotlin = "1.7.20"
21 | versions.coreKtx = "1.7.0"
22 | versions.compose = "1.2.0"
23 | versions.activityCompose = "1.3.1"
24 |
25 | versions.junit = "4.13.2"
26 | versions.androidExtJunit = "1.1.3"
27 | versions.espressoCore = "3.4.0"
28 |
29 | ext.versions = versions
--------------------------------------------------------------------------------