├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable-v23
│ │ │ │ └── backpic.jpg
│ │ │ ├── drawable-v26
│ │ │ │ └── backpic.jpg
│ │ │ ├── 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
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable
│ │ │ │ ├── green_dropdown_arrow.xml
│ │ │ │ ├── orange_dropdown_arrow.xml
│ │ │ │ ├── check_icon_gray.xml
│ │ │ │ ├── check_icon_green.xml
│ │ │ │ ├── back_icon_white.xml
│ │ │ │ ├── ic_baseline_close_24.xml
│ │ │ │ ├── ic_baseline_flight_24.xml
│ │ │ │ ├── ic_baseline_volume_up_24.xml
│ │ │ │ ├── ic_baseline_translate_24.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── layout
│ │ │ │ ├── voice_layout.xml
│ │ │ │ └── webrtcview.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── nico
│ │ │ │ └── voiceview
│ │ │ │ ├── VoiceviewStatus.kt
│ │ │ │ ├── VoiceViewCallback.kt
│ │ │ │ ├── VoiceOprCallback.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── VoiceArcView.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── nico
│ │ │ └── voiceview
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── nico
│ │ └── voiceview
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── voiceview.jks
├── README.md
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── .gitignore
├── gradlew.bat
├── .github
└── workflows
│ └── android.yml
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/voiceview.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/voiceview.jks
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VoiceView
2 | a custom view used for send voice message. the style like wechat
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | VoiceView
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v23/backpic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/drawable-v23/backpic.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v26/backpic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/drawable-v26/backpic.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicochofly/VoiceView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/java/com/nico/voiceview/VoiceviewStatus.kt:
--------------------------------------------------------------------------------
1 | enum class VoiceviewStatus {
2 | INIT,
3 | LEFT,
4 | RIGHT,
5 | CENTER,
6 | RESULT,
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nico/voiceview/VoiceViewCallback.kt:
--------------------------------------------------------------------------------
1 | package com.nico.voiceview
2 |
3 | interface VoiceViewCallback {
4 | fun cancelAction()
5 | fun sendAction(message: String)
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nico/voiceview/VoiceOprCallback.kt:
--------------------------------------------------------------------------------
1 | package com.nico.voiceview
2 |
3 | interface VoiceOprCallback {
4 | fun startRecord()
5 | fun stopRecord()
6 | fun cancelRecord()
7 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 12 22:16:32 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/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/drawable/green_dropdown_arrow.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/orange_dropdown_arrow.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/check_icon_gray.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/check_icon_green.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/back_icon_white.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_close_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/nico/voiceview/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.nico.voiceview
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 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_flight_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_volume_up_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_translate_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/nico/voiceview/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.nico.voiceview
2 |
3 | import android.support.test.InstrumentationRegistry
4 | import android.support.test.runner.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.getTargetContext()
22 | assertEquals("com.nico.myapplication", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 |
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | android {
8 | compileSdkVersion 29
9 | buildToolsVersion "29.0.3"
10 | defaultConfig {
11 | applicationId "com.nico.voiceview"
12 | minSdkVersion 15
13 | targetSdkVersion 29
14 | versionCode 11
15 | versionName "1.1"
16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
17 | vectorDrawables.useSupportLibrary = true
18 | }
19 |
20 | signingConfigs {
21 | release{
22 | storeFile file("../voiceview.jks")
23 | storePassword '111111'
24 | keyAlias 'voiceview'
25 | keyPassword '111111'
26 | v2SigningEnabled false
27 | }
28 | }
29 | buildTypes {
30 | release {
31 | minifyEnabled false
32 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
33 | signingConfig signingConfigs.release
34 | }
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(dir: 'libs', include: ['*.jar'])
40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
41 | implementation 'com.android.support:appcompat-v7:+'
42 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
43 | testImplementation 'junit:junit:4.12'
44 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
45 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/voice_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
22 |
23 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/gradle.xml
44 | .idea/assetWizardSettings.xml
45 | .idea/dictionaries
46 | .idea/libraries
47 | # Android Studio 3 in .gitignore file.
48 | .idea/caches
49 | .idea/modules.xml
50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
51 | .idea/navEditor.xml
52 |
53 | # Keystore files
54 | # Uncomment the following lines if you do not want to check your keystore files in.
55 | #*.jks
56 | #*.keystore
57 |
58 | # External native build folder generated in Android Studio 2.2 and later
59 | .externalNativeBuild
60 | .cxx/
61 |
62 | # Google Services (e.g. APIs or Firebase)
63 | # google-services.json
64 |
65 | # Freeline
66 | freeline.py
67 | freeline/
68 | freeline_project_description.json
69 |
70 | # fastlane
71 | fastlane/report.xml
72 | fastlane/Preview.html
73 | fastlane/screenshots
74 | fastlane/test_output
75 | fastlane/readme.md
76 |
77 | # Version control
78 | vcs.xml
79 |
80 | # lint
81 | lint/intermediates/
82 | lint/generated/
83 | lint/outputs/
84 | lint/tmp/
85 | # lint/reports/
86 |
--------------------------------------------------------------------------------
/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/layout/webrtcview.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
25 |
26 |
34 |
35 |
41 |
42 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: set up JDK 11
17 | uses: actions/setup-java@v2
18 | with:
19 | java-version: '11'
20 | distribution: 'adopt'
21 |
22 | - name: Grant execute permission for gradlew
23 | run: chmod +x gradlew
24 | - name: Build with Gradle
25 | run: ./gradlew assembleRelease
26 |
27 |
28 |
29 | # 获取apk版本号
30 | - name: Get Version Name
31 | uses: actions/github-script@v3
32 | id: get-version
33 | with:
34 | script: |
35 | const str=process.env.GITHUB_REF;
36 | return str.substring(str.indexOf("v"));
37 | result-encoding: string
38 |
39 | # 创建release
40 | - name: Create Release
41 | id: create_release
42 | uses: actions/create-release@v1
43 | env:
44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 |
46 | with:
47 | tag_name: ${{steps.get-version.outputs.result}}
48 | release_name: Release ${{steps.get-version.outputs.result}}
49 | # ${{ github.ref }}
50 | draft: false
51 | prerelease: false
52 |
53 |
54 | # - name: Upload APK
55 | # uses: actions/upload-artifact@v1
56 | # env:
57 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58 | # with:
59 | # name: app
60 | # path: app/build/outputs/apk/release/app-release.apk
61 |
62 |
63 |
64 |
65 |
66 |
67 | # 上传至release的资源
68 | # - name: Upload Release Asset
69 | # id: upload-release-asset
70 | # uses: actions/upload-release-asset@v1
71 | # env:
72 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
73 | # with:
74 | # upload_url: ${{ steps.create_release.outputs.upload_url }} # 上传网址,无需改动
75 | # asset_path: app/build/outputs/apk/release/app-release.apk # 上传路径
76 | # asset_name: voiceview-${{steps.get-version.outputs.result}}.apk # 资源名
77 | # asset_content_type: application/vnd.android.package-archiv #资源类型
78 | # # 存档打包的文件
79 | # - name: Archive production artifacts
80 | # uses: actions/upload-artifact@v2
81 | # with:
82 | # name: build
83 | # path: app/build/outputs #将打包之后的文件全部上传(里面会有混淆的map文件)
84 |
85 |
86 |
87 | # - name: mvfiles
88 | # run: |
89 | # mv /home/runner/work/VoiceView/VoiceView/app/build/outputs/apk/release/app-release.apk app.apk
90 | # ls
91 | # cd /home/runner/work/VoiceView/VoiceView~/local/rubiesapp/build/outputs/apk/release/
92 |
93 | - uses: actions/checkout@v1
94 | id: cache
95 | with:
96 | path: ~/local/rubies
97 | key: ruby-2.6.5
98 | clean: false
99 | #files: app/build/outputs/apk/release/app-release.apk
100 |
101 |
102 | - name: showfiles
103 | run: |
104 | cd /home/runner/work/VoiceView/~/local/rubies/app/build/outputs/apk/release/
105 | ls
106 |
107 | # - uses: actions/checkout@v1
108 | # - uses: actions/cache@preview
109 | # id: cache
110 | # with:
111 | # path: ~/local/rubies
112 | # key: ruby-2.6.5
113 | # - uses: clupprich/ruby-build-action@master
114 | # id: ruby
115 | # with:
116 | # ruby-version: 2.6.5
117 | # cache-available: ${{ steps.cache.outputs.cache-hit == 'true' }}
118 | # - name: Print version
119 | # run: ${{ steps.ruby.outputs.ruby-path }} --version
120 |
121 |
122 |
123 | # - uses: actions/cache@preview
124 | # id: cache
125 | # with:
126 | # path: ~/local/rubies
127 | # key: ruby-2.6.5
128 | # - uses: clupprich/ruby-build-action@master
129 | # id: ruby
130 | # with:
131 | # ruby-version: 2.6.5
132 | # files: app/build/outputs/apk/release/app-release.apk
133 |
134 |
135 |
136 |
137 |
138 |
139 | # - name: install fir.im
140 | # run: sudo gem install fir-cli
141 | # - name: Upload to Fir.im
142 | # run: fir login ${{ secrets.FIRTOKEN }}
143 | # #- run: cd ~
144 | # - run: fir publish app/build/outputs/apk/release/app-release.apk
145 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nico/voiceview/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.nico.voiceview
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.os.Handler
6 | import android.util.Log
7 | import android.view.MotionEvent
8 | import android.view.View
9 | import android.widget.TextView
10 |
11 | class MainActivity : Activity(), VoiceOprCallback, VoiceViewCallback {
12 | lateinit var voiceArcView: VoiceArcView
13 |
14 |
15 | var isPress = false
16 |
17 |
18 | private lateinit var btn: TextView
19 | private var handler = Handler()
20 |
21 |
22 | private var mCurrentRecString = StringBuilder()
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 |
26 |
27 | setContentView(R.layout.voice_layout)
28 |
29 | btn = findViewById(R.id.btn)
30 |
31 | voiceArcView = findViewById(R.id.voicearcview)
32 | // voiceArcView.setCallback(this)
33 |
34 | voiceArcView.setOprCallback(this)
35 | voiceArcView.setViewCallback(this)
36 |
37 | btn.setOnClickListener {
38 | if (voiceArcView.visibility == View.GONE) {
39 | voiceArcView.visibility = View.VISIBLE
40 | }
41 | }
42 |
43 |
44 | btn.setOnTouchListener { v, event ->
45 | when (event.action) {
46 | MotionEvent.ACTION_DOWN -> {
47 |
48 | if (voiceArcView.visibility == View.GONE) {
49 | voiceArcView.visibility = View.VISIBLE
50 | voiceArcView.responseTouchEvent(event)
51 | } else {
52 | }
53 | }
54 | MotionEvent.ACTION_MOVE -> {
55 |
56 | if (voiceArcView.visibility == View.VISIBLE) {
57 | voiceArcView.responseTouchEvent(event)
58 | }
59 | }
60 | MotionEvent.ACTION_UP -> {
61 | if (voiceArcView.visibility == View.VISIBLE) {
62 | voiceArcView.responseTouchEvent(event)
63 | }
64 | }
65 | }
66 | return@setOnTouchListener true
67 | }
68 | // btn.setOnTouchListener { v, event ->
69 | // {
70 | // var act = event.action
71 | // when(act)
72 | // {
73 | // MotionEvent.ACTION_DOWN ->""
74 | // MotionEvent.ACTION_UP ->""
75 | // MotionEvent.ACTION_MOVE -> ""
76 | // }
77 | // }
78 | // }
79 | }
80 |
81 | // override fun onStatusChanged(isPressed: Boolean) {
82 | // isPress = isPressed
83 | // if (isPress) {
84 | // doRunnable()
85 | // }
86 | // }
87 | //
88 | // override fun cancelHandler() {
89 | // handler.removeCallbacksAndMessages(null)
90 | // }
91 |
92 | fun doRunnable() {
93 | var runnable: Runnable = object : Runnable {
94 | override fun run() {
95 | if (isPress) {
96 | val result = (0..30).random()
97 | voiceArcView.newRefreshElement(result)
98 | handler.postDelayed(this, 300)
99 | }
100 | }
101 | }
102 | handler.postDelayed(runnable, 300)
103 | }
104 |
105 | // override fun onTouchDown() {
106 | // handler.post(Runnable {
107 | // voiceArcView.visibility = View.VISIBLE
108 | // voiceArcView.postInvalidate()
109 | // })
110 | //
111 | // }
112 | //
113 | // override fun onTouchUp() {
114 | // handler.post(Runnable { voiceArcView.visibility = View.GONE })
115 | // }
116 |
117 | override fun startRecord() {
118 | Log.e(javaClass.simpleName, "startRecord")
119 | }
120 |
121 | override fun stopRecord() {
122 | Log.e(javaClass.simpleName, "stopRecord")
123 | }
124 |
125 | override fun cancelRecord() {
126 | Log.e(javaClass.simpleName, "cancelRecord")
127 | }
128 |
129 | override fun cancelAction() {
130 | Log.e(javaClass.simpleName, "cancelAction")
131 | resetVoiceView()
132 | btn.visibility = View.VISIBLE
133 | }
134 |
135 | override fun sendAction(message: String) {
136 | Log.e(javaClass.simpleName, "sendAction")
137 | resetVoiceView()
138 | btn.visibility = View.VISIBLE
139 | }
140 |
141 | private fun resetVoiceView() {
142 | mCurrentRecString.clear()
143 | voiceArcView.doReset()
144 | voiceArcView.visibility = View.GONE
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nico/voiceview/VoiceArcView.kt:
--------------------------------------------------------------------------------
1 | package com.nico.voiceview
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.support.constraint.ConstraintLayout
6 | import android.support.graphics.drawable.VectorDrawableCompat
7 | import android.text.*
8 | import android.util.AttributeSet
9 | import android.util.Log
10 | import android.util.TypedValue
11 | import android.view.MotionEvent
12 | import android.view.View
13 | import android.view.ViewGroup
14 | import android.view.WindowManager
15 | import android.view.inputmethod.EditorInfo
16 | import android.widget.EditText
17 | import java.lang.Math.atan2
18 | import java.util.*
19 | import kotlin.math.roundToInt
20 |
21 | class VoiceArcView : ViewGroup {
22 | private var screenWidth: Int = 0
23 | private var screenHeight: Int = 0
24 |
25 | /**
26 | * 屏幕的中点坐标
27 | */
28 | private var hLine = 0
29 | private var vLine = 0
30 |
31 | private var mLeft = 0
32 | private var mTop = 0
33 | private var mBottom = 0
34 | private var mRight = 0
35 | private var mBackColor = 0
36 |
37 | /**
38 | * 真实底部框的path
39 | */
40 | private var mRealPath: Path? = null
41 |
42 | /**
43 | * 选中选项后底部框的path
44 | */
45 | private var mInnerPath: Path? = null
46 |
47 | /**
48 | * 用于绘制左右两个选项框角度的path
49 | */
50 | private var mPath: Path? = null
51 |
52 | /**
53 | * 右选项框的path
54 | */
55 | private var mRightPath: Path? = null
56 |
57 | /**
58 | * 左选项框的path
59 | */
60 | private var mLeftPath: Path? = null
61 |
62 | /**
63 | * 初始化的框
64 | */
65 | private var mRect: RectF? = null
66 |
67 | /**
68 | * 底部 初始化的框
69 | */
70 | private var mRealRect: RectF? = null
71 |
72 | /**
73 | * 底部 操作有目标对象的 框
74 | */
75 | private var mInnerRect: RectF? = null
76 |
77 |
78 | /**
79 | * 底部 操作有目标对象的 框
80 | */
81 | private var mLeftVoiceRect: RectF? = null
82 |
83 | /**
84 | * 底部 操作有目标对象的 框
85 | */
86 | private var mCenterVoiceRect: RectF? = null
87 |
88 | /**
89 | * 底部 操作有目标对象的 框
90 | */
91 | private var mRightVoiceRect: RectF? = null
92 |
93 |
94 | /**
95 | * 展示语音识别文本 框
96 | */
97 | private var mResultContentRect: RectF? = null
98 |
99 | private var mResultTextRect: RectF = RectF()
100 |
101 | /**
102 | * 发送按钮区域
103 | */
104 | private var mResultSendRect: Rect? = null
105 |
106 | /**
107 | * 取消按钮区域
108 | */
109 | private var mResultCancelRect: Rect? = null
110 |
111 |
112 | /**
113 | * 获取左右两个选项path结果的状态值
114 | */
115 | private var mLeftStatus = false
116 | private var mRightStatus = false
117 |
118 | /**
119 | * 左边选项 的 坐标以及角度
120 | */
121 | private var mLeftPosArray = floatArrayOf(0.0f, 0.0f)
122 | private var mLeftTanArray = floatArrayOf(0.0f, 0.0f)
123 |
124 | /**
125 | * 右边选项的坐标以及角度
126 | */
127 | private var mRightPosArray = floatArrayOf(0.0f, 0.0f)
128 | private var mRightTanArray = floatArrayOf(0.0f, 0.0f)
129 |
130 | /**
131 | * 0 初始
132 | * 1 左边
133 | * 2 右边
134 | * 3 中间
135 | * 4 展示结果
136 | */
137 | // private var mSelectStatus = 0;
138 |
139 | /**
140 | * 当前识别的字符
141 | */
142 | private var mCurrentString = ""
143 |
144 | /**
145 | * 页面当前状态
146 | */
147 | private var mCurrentStatus = VoiceviewStatus.INIT
148 |
149 | /**
150 | * 是否有内容
151 | */
152 | private var hasResultContent = false;
153 |
154 | /**
155 | * 圆环 初始状态和放大状态半径
156 | */
157 | private var cRadiusOrigin = 80.0f
158 | private var cRadiusScale = 100.0f
159 |
160 | private var leftMatrix: Matrix? = null
161 | private var rightMatrix: Matrix? = null
162 |
163 |
164 | //矩形波纹颜色
165 | // private val lineColor: Int = Color.parseColor("#00faf7")
166 | // private val LINE_W = 5//默认矩形波纹的宽度,9像素, 原则上从layout的attr获得
167 |
168 | //矩形波纹宽度
169 | private val lineWidth: Float = 5.0f
170 | private val textSize: Float = 40.0f
171 | private val DEFAULT_TEXT = ""
172 | private var voiceMessage = DEFAULT_TEXT
173 | // private val textColor: Int = Color.parseColor("#666666")
174 | // private var isStart = false
175 |
176 | private val MIN_WAVE_H = 3//最小的矩形线高,是线宽的2倍,线宽从lineWidth获得
177 | private val MAX_WAVE_H = 8//最高波峰,是线宽的4倍
178 |
179 | //默认矩形波纹的高度,总共10个矩形,左右各有10个
180 | private val DEFAULT_WAVE_HEIGHT = intArrayOf(3, 3, 3, 3, 3, 3, 3, 3, 3, 3)
181 | // private val mWaveList: LinkedList = LinkedList()
182 |
183 | private val rectRight = RectF()//右边波纹矩形的数据,10个矩形复用一个rectF
184 | private val rectLeft = RectF()//左边波纹矩形的数据
185 |
186 | var list: LinkedList = LinkedList()
187 | // private val UPDATE_INTERVAL_TIME = 100//100ms更新一次
188 |
189 |
190 | private lateinit var mVoiceOprCallback: VoiceOprCallback
191 | private lateinit var mVoiceViewCallback: VoiceViewCallback
192 |
193 |
194 | /**
195 | * 三角形箭头 的长度
196 | */
197 | private val mArrowTriangle = 20;
198 |
199 | /**
200 | * 结果 点击事件
201 | * 0 初始状态
202 | * 1 发送
203 | * 2 取消
204 | */
205 | private var resultTouchStatus = 0
206 |
207 | /**
208 | * 三个图标
209 | */
210 | private val bottomVoiceIcon =
211 | VectorDrawableCompat.create(context.resources, R.drawable.ic_baseline_volume_up_24, null);
212 | private val leftVoiceIcon =
213 | VectorDrawableCompat.create(context.resources, R.drawable.ic_baseline_close_24, null);
214 | private val rightVoiceIcon =
215 | VectorDrawableCompat.create(context.resources, R.drawable.ic_baseline_translate_24, null);
216 |
217 | private val resultSendEnableIcon =
218 | VectorDrawableCompat.create(context.resources, R.drawable.check_icon_green, null);
219 | private val resultSendUnableIcon =
220 | VectorDrawableCompat.create(context.resources, R.drawable.check_icon_gray, null);
221 |
222 | private val resultCancelIcon =
223 | VectorDrawableCompat.create(context.resources, R.drawable.back_icon_white, null);
224 |
225 | constructor(context: Context) : super(context) {
226 | }
227 |
228 | constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
229 |
230 | }
231 |
232 | constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(
233 | context,
234 | attributeSet,
235 | defStyleAttr
236 | ) {
237 |
238 | }
239 |
240 |
241 | init {
242 | val windowManager =
243 | context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
244 | var dis = windowManager.defaultDisplay;
245 | var point: Point = Point()
246 | dis.getSize(point)
247 | screenWidth = point.x
248 | screenHeight = point.y
249 |
250 |
251 | hLine = screenWidth / 2
252 | vLine = screenHeight * 3 / 4;
253 |
254 |
255 | mLeft = -screenWidth / 2
256 | mRight = screenWidth * 3 / 2
257 | mTop = screenHeight * 3 / 4
258 | mBottom = (screenHeight * 3 / 4) + (2 * screenWidth)
259 |
260 |
261 | mRect =
262 | RectF(mLeft.toFloat(), mTop.toFloat() - 200, mRight.toFloat(), mBottom.toFloat() - 200)
263 | mRealRect =
264 | RectF(mLeft.toFloat(), mTop.toFloat() - 100, mRight.toFloat(), mBottom.toFloat() - 100)
265 | mInnerRect =
266 | RectF(mLeft.toFloat(), mTop.toFloat() - 80, mRight.toFloat(), mBottom.toFloat() - 80)
267 |
268 | mBackColor = Color.DKGRAY
269 | //mPath是为了绘制左右两个 圆选项准备的
270 | mPath = Path()
271 | var centerX = (screenWidth / 2).toFloat()
272 | var centerY = (screenWidth + (mTop)).toFloat() - 200
273 | mPath!!.addCircle(
274 | centerX, centerY,
275 | screenWidth.toFloat()
276 | , Path.Direction.CW
277 | )
278 | //mRealPath 用于绘制初始化的底部圆弧
279 | mRealPath = Path()
280 | var realcenterX = (screenWidth / 2).toFloat()
281 | var realcenterY = (screenWidth + (mTop)).toFloat() - 50
282 | mRealPath!!.addCircle(
283 | realcenterX, realcenterY,
284 | screenWidth.toFloat()
285 | , Path.Direction.CW
286 | )
287 |
288 |
289 | //mInnerPath 用于绘制操作后的底部圆弧
290 | mInnerPath = Path();
291 | var innerCenterX = (screenWidth / 2).toFloat()
292 | var innerCenterY = (screenWidth + (mTop)).toFloat() - 20
293 | mInnerPath!!.addCircle(
294 | innerCenterX, innerCenterY,
295 | screenWidth.toFloat()
296 | , Path.Direction.CW
297 | )
298 |
299 |
300 |
301 | mLeftPath = Path()
302 | mLeftPath!!.addArc(mRect, 250.0f, 10.0f)
303 | var leftpathMeasure: PathMeasure = PathMeasure()
304 | leftpathMeasure.setPath(mLeftPath, true)
305 | mLeftStatus = leftpathMeasure.getPosTan(0.0f, mLeftPosArray, mLeftTanArray)
306 |
307 |
308 |
309 | mRightPath = Path()
310 | mRightPath!!.arcTo(mRect, 290.0f, -10.0f)//得反向来绘制
311 | var rightpathMeasure: PathMeasure = PathMeasure()
312 | rightpathMeasure.setPath(mRightPath, true)
313 | mRightStatus = rightpathMeasure.getPosTan(0.0f, mRightPosArray, mRightTanArray)
314 |
315 | leftMatrix = initMatrix(mLeftPosArray, mLeftTanArray, true);
316 | rightMatrix = initMatrix(mRightPosArray, mRightTanArray, false);
317 |
318 | // 绘制左边 语音 框
319 | mLeftVoiceRect = RectF()
320 | var leftVoiceX = mLeftPosArray[0]
321 | var leftVoiceY = mLeftPosArray[1] /*-200*/
322 |
323 | mLeftVoiceRect!!.left = 50.0f
324 | mLeftVoiceRect!!.right = leftVoiceX + 100
325 |
326 | mLeftVoiceRect!!.top = leftVoiceY - 400
327 | mLeftVoiceRect!!.bottom = leftVoiceY - 250
328 |
329 |
330 | // 绘制 中间 语音框
331 | mCenterVoiceRect = RectF()
332 | var centerVoiceX = screenWidth / 2
333 | var centerVoiceY = mRightPosArray[1] /*-200*/
334 | mCenterVoiceRect!!.left = centerVoiceX - 150.0f
335 | mCenterVoiceRect!!.right = centerVoiceX + 150.0f
336 |
337 | mCenterVoiceRect!!.top = centerVoiceY - 400
338 | mCenterVoiceRect!!.bottom = centerVoiceY - 250
339 |
340 |
341 | //绘制 右边 语音框
342 | mRightVoiceRect = RectF()
343 |
344 | var rightVoiceX = mRightPosArray[0]
345 | var rightVoiceY = mRightPosArray[1] /*-200*/
346 |
347 | mRightVoiceRect!!.left = 150.0f
348 | mRightVoiceRect!!.right = screenWidth - 50.0f
349 |
350 | mRightVoiceRect!!.top = rightVoiceY - 400
351 | mRightVoiceRect!!.bottom = rightVoiceY - 250
352 |
353 |
354 | //绘制语音识别结果框
355 | mResultContentRect = RectF()
356 |
357 | mResultContentRect!!.left = 250.0f
358 | mResultContentRect!!.right = screenWidth - 50.0f
359 |
360 | mResultContentRect!!.top = rightVoiceY - 500
361 | mResultContentRect!!.bottom = rightVoiceY - 250
362 |
363 | //绘制 取消按钮 框
364 | mResultCancelRect = Rect()
365 | var resultCancelX = mLeftPosArray[0].toInt()
366 | var resultCancelY = mLeftPosArray[1].toInt() /*-200*/
367 | mResultCancelRect!!.left = 50
368 | mResultCancelRect!!.right = resultCancelX + 100
369 |
370 | mResultCancelRect!!.top = resultCancelY /*- 400*/
371 | mResultCancelRect!!.bottom = resultCancelY/* - 250*/ + (cRadiusScale * 2).toInt()
372 |
373 |
374 | //绘制 发送按钮 框
375 | mResultSendRect = Rect()
376 | var resultSendX = mRightPosArray[0].toInt()
377 | var resultSendY = mRightPosArray[1].toInt() /*-200*/
378 |
379 | mResultSendRect!!.left = resultSendX - cRadiusScale.toInt()
380 | mResultSendRect!!.right = resultSendX + cRadiusScale.toInt()
381 |
382 | mResultSendRect!!.top = resultSendY - cRadiusScale.toInt()
383 | mResultSendRect!!.bottom = resultSendY + cRadiusScale.toInt()
384 |
385 | resetList(list, DEFAULT_WAVE_HEIGHT);
386 |
387 | }
388 |
389 |
390 | override fun onDraw(canvas: Canvas?) {
391 | super.onDraw(canvas)
392 | var paint = Paint();
393 | // Log.e(javaClass.simpleName, "onDraw mSelectStatus=$mSelectStatus")
394 | // mSelectStatus
395 | when (mCurrentStatus) {
396 | VoiceviewStatus.INIT, VoiceviewStatus.LEFT, VoiceviewStatus.CENTER, VoiceviewStatus.RIGHT -> {
397 | updateLeftItem(canvas!!, paint)
398 | updateRightItem(canvas!!, paint)
399 | updateBottomLayout(canvas!!, paint)
400 | updateVoiceLayout(canvas!!, paint)
401 | }
402 |
403 |
404 | VoiceviewStatus.RESULT -> {
405 | updateResultLayout(canvas!!, paint)
406 | }
407 | }
408 | // if (mSelectStatus < 4) {
409 | // updateLeftItem(canvas!!, paint)
410 | // updateRightItem(canvas!!, paint)
411 | // updateBottomLayout(canvas!!, paint)
412 | // updateVoiceLayout(canvas!!, paint)
413 | // } else {
414 | // updateResultLayout(canvas!!, paint)
415 | // }
416 | }
417 |
418 |
419 | /**
420 | * 绘制箭头
421 | */
422 | private fun drawTrigAngle(xPos: Float, rectF: RectF, paint: Paint, canvas: Canvas) {
423 | //绘制底部三角
424 | var path = Path();
425 | path.moveTo(xPos - mArrowTriangle, rectF!!.bottom)
426 | path.lineTo(xPos + mArrowTriangle, rectF!!.bottom)
427 | path.lineTo(xPos, rectF!!.bottom + mArrowTriangle * 2)
428 | path.lineTo(xPos - 5, rectF!!.bottom + mArrowTriangle * 2)
429 | path.lineTo(xPos - mArrowTriangle, rectF!!.bottom);
430 | canvas.drawPath(path, paint)
431 | }
432 |
433 | /**
434 | * 更新结果布局
435 | */
436 | private fun updateResultLayout(canvas: Canvas, paint: Paint) {
437 | paint.isAntiAlias = true;
438 | var rightPosX = mRightPosArray[0]
439 | var rightPosY = mRightPosArray[1]
440 |
441 | var leftPosX = mLeftPosArray[0]
442 | var leftPosY = mLeftPosArray[1]
443 |
444 | drawResultTarget(canvas, paint)
445 |
446 | if (hasResultContent) {
447 | paint.color = Color.WHITE
448 | var circlePath = Path()
449 | circlePath.addCircle(rightPosX, rightPosY, cRadiusScale, Path.Direction.CW)
450 | canvas.drawPath(circlePath, paint)
451 |
452 | var cancelPath = Path()
453 | paint.color = Color.DKGRAY
454 | cancelPath.addCircle(leftPosX, leftPosY, cRadiusScale, Path.Direction.CW)
455 | canvas.drawPath(cancelPath, paint)
456 |
457 | canvas.save()
458 | var width = resultSendEnableIcon!!.intrinsicWidth
459 | var height = resultSendEnableIcon!!.intrinsicHeight
460 | resultSendEnableIcon!!.setBounds(0, 0, width, height);
461 | var rMatrix = initResultMatrix(mRightPosArray, mRightTanArray, false)
462 | canvas.setMatrix(rMatrix)
463 | resultSendEnableIcon.draw(canvas)
464 | canvas.restore()
465 |
466 |
467 |
468 | canvas.save()
469 | var cancelwidth = resultCancelIcon!!.intrinsicWidth
470 | var cancelheight = resultCancelIcon!!.intrinsicHeight
471 | resultCancelIcon!!.setBounds(0, 0, cancelwidth, cancelheight)
472 | var lMatrix = initResultMatrix(mLeftPosArray, mLeftTanArray, false)
473 | canvas.setMatrix(lMatrix)
474 | resultCancelIcon.draw(canvas)
475 | canvas.restore()
476 |
477 |
478 | } else {
479 | paint.color = Color.LTGRAY
480 | var circlePath = Path()
481 | circlePath.addCircle(rightPosX, rightPosY, cRadiusScale, Path.Direction.CW)
482 | canvas.drawPath(circlePath, paint)
483 |
484 | var cancelPath = Path()
485 | cancelPath.addCircle(leftPosX, leftPosY, cRadiusScale, Path.Direction.CW)
486 | paint.color = Color.DKGRAY
487 | canvas.drawPath(cancelPath, paint)
488 |
489 | canvas.save()
490 |
491 | var width = resultSendUnableIcon!!.intrinsicWidth
492 | var height = resultSendUnableIcon!!.intrinsicHeight
493 | resultSendUnableIcon!!.setBounds(0, 0, width, height);
494 | var rMatrix = initResultMatrix(mRightPosArray, mRightTanArray, false)
495 | canvas.setMatrix(rMatrix)
496 | resultSendUnableIcon!!.draw(canvas)
497 | canvas.restore()
498 |
499 |
500 | canvas.save()
501 | var cancelwidth = resultCancelIcon!!.intrinsicWidth
502 | var cancelheight = resultCancelIcon!!.intrinsicHeight
503 | resultCancelIcon!!.setBounds(0, 0, cancelwidth, cancelheight);
504 | var lMatrix = initResultMatrix(mLeftPosArray, mLeftTanArray, false)
505 |
506 | canvas.setMatrix(lMatrix)
507 | resultCancelIcon.draw(canvas)
508 | canvas.restore()
509 | }
510 | }
511 |
512 | private fun computeFontHeight(paint: Paint): Int {
513 | val fontMetrics: Paint.FontMetrics = paint.fontMetrics
514 | return (fontMetrics.bottom - fontMetrics.top).toInt()
515 | }
516 |
517 |
518 | private fun computeFontWidth(paint: Paint, str: String): Int {
519 | var iRet = 0
520 | if (str != null && str.isNotEmpty()) {
521 | val len: Int = str.length
522 | val widths = FloatArray(len)
523 | paint.getTextWidths(str, widths)
524 | for (j in 0 until len) {
525 | iRet += Math.ceil(widths[j].toDouble()).toInt()
526 | }
527 | }
528 | return iRet
529 | }
530 |
531 |
532 | /**
533 | * 更新底部布局
534 | */
535 | private fun updateBottomLayout(canvas: Canvas, paint: Paint) {
536 | paint.isAntiAlias = true
537 |
538 | when (mCurrentStatus) {
539 | VoiceviewStatus.LEFT -> {
540 | paint.color = Color.DKGRAY
541 | canvas!!.drawPath(mInnerPath!!, paint)
542 | }
543 | VoiceviewStatus.RIGHT -> {
544 | paint.color = Color.DKGRAY
545 | canvas!!.drawPath(mInnerPath!!, paint)
546 | }
547 | else -> {
548 | paint.color = Color.LTGRAY
549 | canvas!!.drawPath(mRealPath!!, paint)
550 | paint.color = Color.WHITE
551 | paint.style = Paint.Style.STROKE
552 | paint.strokeWidth = 10.0f
553 | canvas.drawPath(mRealPath!!, paint)
554 | }
555 | }
556 |
557 | when (mCurrentStatus) {
558 | VoiceviewStatus.INIT -> {
559 | }
560 | else -> {
561 | canvas.save()
562 | var width = bottomVoiceIcon!!.intrinsicWidth
563 | var height = bottomVoiceIcon!!.intrinsicHeight
564 | bottomVoiceIcon!!.setBounds(0, 0, width, height);
565 | canvas.translate(
566 | (hLine - width / 2).toFloat(),
567 | mInnerRect!!.top + screenHeight / 8
568 | );
569 | bottomVoiceIcon.draw(canvas);
570 | canvas.restore();
571 | }
572 | }
573 | // if (mSelectStatus == 1 || mSelectStatus == 2) {
574 | //
575 | // paint.color = Color.DKGRAY
576 | // canvas!!.drawPath(mInnerPath!!, paint)
577 | // } else {
578 | // paint.color = Color.LTGRAY
579 | // canvas!!.drawPath(mRealPath!!, paint)
580 | //
581 | //
582 | // paint.color = Color.WHITE
583 | // paint.style = Paint.Style.STROKE
584 | // paint.strokeWidth = 10.0f
585 | // canvas.drawPath(mRealPath!!, paint)
586 | //
587 | // }
588 |
589 |
590 | // if (mSelectStatus != 0) {
591 | //
592 | // canvas.save()
593 | // var width = bottomVoiceIcon!!.intrinsicWidth
594 | // var height = bottomVoiceIcon!!.intrinsicHeight
595 | // bottomVoiceIcon!!.setBounds(0, 0, width, height);
596 | // canvas.translate(
597 | // (hLine - width / 2).toFloat(),
598 | // mInnerRect!!.top + screenHeight / 8
599 | // );
600 | // bottomVoiceIcon.draw(canvas);
601 | // canvas.restore();
602 | // }
603 |
604 |
605 | }
606 |
607 | /**
608 | * 更新右边的item项
609 | */
610 | private fun updateRightItem(canvas: Canvas, paint: Paint) {
611 | paint.reset();
612 | paint.isAntiAlias = true;
613 | var realPosX = mRightPosArray[0]
614 | var realPosY = mRightPosArray[1]
615 | if (mRightStatus) {
616 | // if (mSelectStatus == 2) {
617 | // paint.color = Color.GREEN
618 | // canvas.drawCircle(realPosX, realPosY, cRadiusScale, paint)
619 | // } else {
620 | // paint.color = Color.LTGRAY
621 | // canvas.drawCircle(realPosX, realPosY, cRadiusOrigin, paint)
622 | // }
623 |
624 | when (mCurrentStatus) {
625 | VoiceviewStatus.RIGHT -> {
626 | paint.color = Color.GREEN
627 | canvas.drawCircle(realPosX, realPosY, cRadiusScale, paint)
628 | }
629 | else -> {
630 | paint.color = Color.LTGRAY
631 | canvas.drawCircle(realPosX, realPosY, cRadiusOrigin, paint)
632 | }
633 | }
634 | canvas.save()
635 | var width = rightVoiceIcon!!.intrinsicWidth
636 | var height = rightVoiceIcon!!.intrinsicHeight
637 | rightVoiceIcon!!.setBounds(0, 0, width, height)
638 | canvas.setMatrix(rightMatrix)
639 | rightVoiceIcon.draw(canvas);
640 | canvas.restore();
641 | }
642 | }
643 |
644 | /**
645 | * 更新左边的item项
646 | */
647 | private fun updateLeftItem(canvas: Canvas, paint: Paint) {
648 | paint.reset()
649 | paint.isAntiAlias = true;
650 | var realPosX = mLeftPosArray[0]
651 | var realPosY = mLeftPosArray[1]
652 | if (mLeftStatus) {
653 | // if (mSelectStatus == 1) {
654 | // paint.color = Color.YELLOW
655 | // canvas.drawCircle(realPosX, realPosY, cRadiusScale, paint)
656 | // } else {
657 | // paint.color = Color.LTGRAY
658 | // canvas.drawCircle(realPosX, realPosY, cRadiusOrigin, paint)
659 | // }
660 | when (mCurrentStatus) {
661 | VoiceviewStatus.LEFT -> {
662 | paint.color = Color.YELLOW
663 | canvas.drawCircle(realPosX, realPosY, cRadiusScale, paint)
664 | }
665 | else -> {
666 | paint.color = Color.LTGRAY
667 | canvas.drawCircle(realPosX, realPosY, cRadiusOrigin, paint)
668 | }
669 | }
670 | canvas.save()
671 | var width = leftVoiceIcon!!.intrinsicWidth
672 | var height = leftVoiceIcon!!.intrinsicHeight
673 | leftVoiceIcon!!.setBounds(0, 0, width, height);
674 | canvas.setMatrix(leftMatrix)
675 | leftVoiceIcon.draw(canvas)
676 | canvas.restore()
677 | }
678 | }
679 |
680 |
681 | private fun updateVoiceLayout(canvas: Canvas, paint: Paint) {
682 | paint.reset()
683 | // when (mSelectStatus) {
684 | // 1 -> {
685 | // //左边选中
686 | // paint.color = Color.YELLOW
687 | // canvas.drawRoundRect(mLeftVoiceRect!!, 10.0f, 10.0f, paint)
688 | // drawTrigAngle(mLeftPosArray[0], mLeftVoiceRect!!, paint, canvas)
689 | //
690 | // var centerX = (mLeftVoiceRect!!.right + mLeftVoiceRect!!.left) / 2
691 | // var centerY = (mLeftVoiceRect!!.bottom + mLeftVoiceRect!!.top) / 2
692 | //
693 | // drawWaveLine(centerX.toInt(), centerY.toInt(), canvas, paint)
694 | //
695 | //
696 | // paint.color = Color.LTGRAY
697 | // paint.textSize = 40.0f
698 | // canvas.drawText("松开取消", mLeftPosArray[0] - 80, mLeftPosArray[1] - 120, paint)
699 | //
700 | // }
701 | // 2 -> {
702 | // //右边选中
703 | // paint.color = Color.GREEN
704 | // canvas.drawRoundRect(mRightVoiceRect!!, 10.0f, 10.0f, paint)
705 | //
706 | // drawTrigAngle(mRightPosArray[0], mRightVoiceRect!!, paint, canvas)
707 | //
708 | // var centerX = (mRightVoiceRect!!.right + mRightVoiceRect!!.left) / 2
709 | // var centerY = (mRightVoiceRect!!.bottom + mRightVoiceRect!!.top) / 2
710 | // drawWaveLine(centerX.toInt(), centerY.toInt(), canvas, paint)
711 | //
712 | // paint.color = Color.LTGRAY
713 | // paint.textSize = textSize
714 | // canvas.drawText("转文字", mRightPosArray[0] - 40, mRightPosArray[1] - 120, paint)
715 | // }
716 | // 3 -> {
717 | // //中间选中
718 | //
719 | // paint.color = Color.GREEN
720 | // canvas.drawRoundRect(mCenterVoiceRect!!, 10.0f, 10.0f, paint)
721 | // drawTrigAngle(hLine.toFloat(), mCenterVoiceRect!!, paint, canvas)
722 | //
723 | // var centerX = (mCenterVoiceRect!!.right + mCenterVoiceRect!!.left) / 2
724 | // var centerY = (mCenterVoiceRect!!.bottom + mCenterVoiceRect!!.top) / 2
725 | // drawWaveLine(centerX.toInt(), centerY.toInt(), canvas, paint)
726 | // }
727 | // else -> {
728 | // invalidate()
729 | // }
730 | // }
731 | when (mCurrentStatus) {
732 | VoiceviewStatus.LEFT -> {
733 | //左边选中
734 | paint.color = Color.YELLOW
735 | canvas.drawRoundRect(mLeftVoiceRect!!, 10.0f, 10.0f, paint)
736 | drawTrigAngle(mLeftPosArray[0], mLeftVoiceRect!!, paint, canvas)
737 |
738 | var centerX = (mLeftVoiceRect!!.right + mLeftVoiceRect!!.left) / 2
739 | var centerY = (mLeftVoiceRect!!.bottom + mLeftVoiceRect!!.top) / 2
740 |
741 | drawWaveLine(centerX.toInt(), centerY.toInt(), canvas, paint)
742 |
743 |
744 | paint.color = Color.LTGRAY
745 | paint.textSize = 40.0f
746 | canvas.drawText("松开取消", mLeftPosArray[0] - 80, mLeftPosArray[1] - 120, paint)
747 |
748 | }
749 | VoiceviewStatus.RIGHT -> {
750 | //右边选中
751 | // paint.color = Color.GREEN
752 | // canvas.drawRoundRect(mRightVoiceRect!!, 10.0f, 10.0f, paint)
753 |
754 | drawRightRecoContent(canvas, paint);
755 |
756 |
757 | // drawTrigAngle(mRightPosArray[0], mRightVoiceRect!!, paint, canvas)
758 |
759 |
760 | // paint.color = Color.LTGRAY
761 | // paint.textSize = textSize
762 | // canvas.drawText("转文字", mRightPosArray[0] - 40, mRightPosArray[1] - 120, paint)
763 | }
764 | VoiceviewStatus.CENTER -> {
765 | //中间选中
766 |
767 | paint.color = Color.GREEN
768 | canvas.drawRoundRect(mCenterVoiceRect!!, 10.0f, 10.0f, paint)
769 | drawTrigAngle(hLine.toFloat(), mCenterVoiceRect!!, paint, canvas)
770 |
771 | var centerX = (mCenterVoiceRect!!.right + mCenterVoiceRect!!.left) / 2
772 | var centerY = (mCenterVoiceRect!!.bottom + mCenterVoiceRect!!.top) / 2
773 | drawWaveLine(centerX.toInt(), centerY.toInt(), canvas, paint)
774 | }
775 | else -> {
776 | invalidate()
777 | }
778 | }
779 |
780 | }
781 |
782 |
783 | override fun onTouchEvent(event: MotionEvent?): Boolean {
784 | var action = event!!.action
785 | when (action) {
786 | MotionEvent.ACTION_DOWN -> {
787 | // Log.e(javaClass.simpleName, "==view=ACTION_DOWN==mSelectStatus = $mSelectStatus");
788 | // if (mSelectStatus < 4) {
789 | // return false
790 | // } else {
791 | // if (inTarget(event!!, mResultSendRect!!)) {
792 | // resultTouchStatus = 1;
793 | // } else if (inTarget(event!!, mResultCancelRect!!)) {
794 | // resultTouchStatus = 2;
795 | // }
796 | // }
797 |
798 |
799 | when (mCurrentStatus) {
800 | VoiceviewStatus.LEFT, VoiceviewStatus.RIGHT, VoiceviewStatus.INIT, VoiceviewStatus.CENTER -> {
801 | return false
802 | }
803 | else -> {
804 | if (inTarget(event!!, mResultSendRect!!)) {
805 | resultTouchStatus = 1;
806 | } else if (inTarget(event!!, mResultCancelRect!!)) {
807 | resultTouchStatus = 2;
808 | }
809 | }
810 | }
811 |
812 | }
813 | MotionEvent.ACTION_UP -> {
814 | // if (mSelectStatus >= 4) {
815 | // when (resultTouchStatus) {
816 | // 0 -> {
817 | // }
818 | // 1 -> {
819 | // if (inTarget(event, mResultSendRect!!)) {
820 | // //发送
821 | // Log.e(javaClass.simpleName, "===发送消息===")
822 | // resultTouchStatus = 0
823 | // mSelectStatus = 0
824 | // visibility = GONE
825 | // mVoiceViewCallback.sendAction(voiceMessage)
826 | // }
827 | // }
828 | // 2 -> {
829 | // if (inTarget(event, mResultCancelRect!!)) {
830 | // //发送
831 | // Log.e(javaClass.simpleName, "===取消消息===")
832 | // resultTouchStatus = 0
833 | // mSelectStatus = 0
834 | // visibility = GONE
835 | // mVoiceViewCallback.cancelAction()
836 | // }
837 | // }
838 | // }
839 | // }
840 |
841 |
842 | when (mCurrentStatus) {
843 | VoiceviewStatus.RESULT -> {
844 | when (resultTouchStatus) {
845 | 0 -> {
846 | }
847 | 1 -> {
848 | if (inTarget(event, mResultSendRect!!)) {
849 | //发送
850 | Log.e(javaClass.simpleName, "===发送消息===")
851 | resultTouchStatus = 0
852 | mCurrentStatus = VoiceviewStatus.INIT
853 | visibility = GONE
854 | mVoiceViewCallback.sendAction(voiceMessage)
855 | }
856 | }
857 | 2 -> {
858 | if (inTarget(event, mResultCancelRect!!)) {
859 | //发送
860 | Log.e(javaClass.simpleName, "===取消消息===")
861 | resultTouchStatus = 0
862 | mCurrentStatus = VoiceviewStatus.INIT
863 | visibility = GONE
864 | mVoiceViewCallback.cancelAction()
865 | }
866 | }
867 | }
868 | }
869 | else -> {
870 | }
871 | }
872 | }
873 | MotionEvent.ACTION_MOVE -> {
874 |
875 | }
876 | MotionEvent.ACTION_CANCEL -> mVoiceViewCallback.cancelAction()
877 | }
878 | return true
879 | }
880 |
881 |
882 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
883 | val childCount = childCount
884 | Log.e(javaClass.simpleName, "childCount $childCount")
885 | for (i in 0 until childCount) {
886 | val view: View = getChildAt(i)
887 |
888 | var showMsg: String = ""
889 |
890 | showMsg = if (hasResultContent) {
891 | voiceMessage
892 | } else {
893 | "未识别到文字"
894 | }
895 | var paint = Paint()
896 | paint.textSize = textSize
897 | var fontHeight = computeFontHeight(paint);
898 | var fontWidth = computeFontWidth(paint, showMsg)
899 | var singleWidth = mResultContentRect!!.width() - 120
900 | var lines = ((fontWidth / singleWidth) + 1).toInt()
901 | var rectHeight = fontHeight * lines + 120
902 |
903 |
904 | //绘制结果框的宽高
905 | mResultTextRect.left = mResultContentRect!!.left
906 | mResultTextRect.right = mResultContentRect!!.right
907 | mResultTextRect.bottom = mResultContentRect!!.bottom
908 | mResultTextRect.top = mResultContentRect!!.bottom - rectHeight
909 | Log.e(javaClass.simpleName, "drawCustomText mResultTextRect $mResultTextRect")
910 | view.layout(
911 | (mResultTextRect.left + 60).toInt(), (mResultTextRect.top + 30).toInt(),
912 | (mResultTextRect.right - 60).toInt(),
913 | (mResultTextRect.bottom).toInt()
914 | );
915 | Log.e(javaClass.simpleName, "onLayout mResultTextRect $mResultTextRect")
916 | }
917 | }
918 |
919 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
920 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
921 | val count = childCount
922 | for (i in 0 until count) {
923 | //这个很重要,没有就不显示
924 | getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec)
925 | }
926 | }
927 |
928 | fun responseTouchEvent(event: MotionEvent?) {
929 | var action = event!!.action
930 | when (action) {
931 | MotionEvent.ACTION_DOWN -> {
932 | mVoiceOprCallback!!.startRecord()
933 | }
934 |
935 | MotionEvent.ACTION_UP -> doUpAction(event)
936 | MotionEvent.ACTION_MOVE -> doMoveAction(event)
937 | }
938 | }
939 |
940 |
941 | private fun doMoveAction(event: MotionEvent?) {
942 | Log.e(javaClass.simpleName, "==view=ACTION_MOVE==");
943 |
944 | var xPos = event!!.rawX
945 | var yPos = event!!.rawY
946 | if (yPos > vLine) {
947 | if (xPos >= mRightPosArray[0]) {
948 | // mSelectStatus = 2
949 | mCurrentStatus = VoiceviewStatus.RIGHT
950 | } else {
951 | // mSelectStatus = 3
952 | mCurrentStatus = VoiceviewStatus.CENTER
953 |
954 | }
955 | } else {
956 | if (xPos >= hLine) {
957 | // mSelectStatus = 2
958 | mCurrentStatus = VoiceviewStatus.RIGHT
959 |
960 | } else {
961 | // mSelectStatus = 1
962 | mCurrentStatus = VoiceviewStatus.LEFT
963 |
964 | }
965 | }
966 | invalidate()
967 | }
968 |
969 |
970 | private fun doUpAction(event: MotionEvent?) {
971 | mVoiceOprCallback!!.stopRecord()
972 | recognizeStringBuilder.clear()
973 | when (mCurrentStatus) {
974 | VoiceviewStatus.LEFT, VoiceviewStatus.INIT -> {
975 | mVoiceViewCallback.cancelAction()
976 | return
977 | }
978 | }
979 | mCurrentStatus = VoiceviewStatus.RESULT
980 | invalidate()
981 | }
982 |
983 |
984 | /**
985 | * view 重置
986 | */
987 | fun doReset() {
988 | resultTouchStatus = 0
989 | // mSelectStatus = 0
990 | mCurrentStatus = VoiceviewStatus.INIT
991 | hasResultContent = false
992 | voiceMessage = ""
993 | mCurrentString = ""
994 | removeAllViews()
995 | }
996 |
997 |
998 | /**
999 | *
1000 | */
1001 | private fun initMatrix(posArray: FloatArray, tanArray: FloatArray, isLeft: Boolean): Matrix {
1002 |
1003 | var imageWidth = 0
1004 | var imageHeight = 0
1005 | var realPosX = posArray[0]
1006 | var realPosY = posArray[1]
1007 |
1008 | var degreePlus: Int = 0
1009 | degreePlus = if (isLeft) {
1010 | imageWidth = leftVoiceIcon!!.intrinsicWidth
1011 | imageHeight = leftVoiceIcon!!.intrinsicHeight
1012 | 0
1013 | } else {
1014 | imageWidth = rightVoiceIcon!!.intrinsicWidth
1015 | imageHeight = rightVoiceIcon!!.intrinsicHeight
1016 | 180
1017 | }
1018 | var matrix = Matrix()
1019 | val degrees = (atan2(
1020 | tanArray[1].toDouble(),
1021 | tanArray[0].toDouble()
1022 | ) * 180.0 / Math.PI).toFloat()
1023 | // 旋转图片
1024 | matrix.postRotate(
1025 | degrees + degreePlus,
1026 | (imageWidth / 2).toFloat(),
1027 | (imageHeight / 2).toFloat()
1028 | )
1029 | // 将图片中心绘制到当前点
1030 | matrix.postTranslate(
1031 | realPosX.toFloat() - (imageWidth / 2).toFloat(),
1032 | realPosY.toFloat() - (imageHeight / 2).toFloat()
1033 | )
1034 |
1035 | return matrix
1036 | }
1037 |
1038 |
1039 | /**
1040 | * 发送,取消按钮位置
1041 | */
1042 | private fun initResultMatrix(
1043 | posArray: FloatArray,
1044 | tanArray: FloatArray,
1045 | isLeft: Boolean
1046 | ): Matrix {
1047 |
1048 | var imageWidth = 0
1049 | var imageHeight = 0
1050 | var realPosX = posArray[0]
1051 | var realPosY = posArray[1]
1052 |
1053 |
1054 | // var degreePlus: Int = 0
1055 | /*degreePlus =*/ if (isLeft) {
1056 | imageWidth = leftVoiceIcon!!.intrinsicWidth
1057 | imageHeight = leftVoiceIcon!!.intrinsicHeight
1058 | // 0
1059 | } else {
1060 | imageWidth = rightVoiceIcon!!.intrinsicWidth
1061 | imageHeight = rightVoiceIcon!!.intrinsicHeight
1062 | // 180
1063 | }
1064 | var matrix = Matrix()
1065 | // 将图片中心绘制到当前点
1066 | matrix.postTranslate(
1067 | realPosX - (imageWidth / 2).toFloat(),
1068 | realPosY - (imageHeight / 2).toFloat()
1069 | )
1070 | return matrix
1071 | }
1072 |
1073 | /**
1074 | * 刷新音量
1075 | * @param volume
1076 | */
1077 | @Synchronized
1078 | fun newRefreshElement(volume: Int) {
1079 | var maxAmp = volume.toFloat() / 30
1080 | if (maxAmp > 1.0) {
1081 | maxAmp = 1.0f
1082 | }
1083 | val waveH = MIN_WAVE_H + (maxAmp * (MAX_WAVE_H - 3)).roundToInt()//wave 在 4 ~ 9 之间
1084 | list.add(0, waveH)
1085 | list.removeLast()
1086 | postInvalidate()
1087 | }
1088 |
1089 | private fun resetList(list: LinkedList, array: IntArray) {
1090 | list.clear()
1091 | for (i in array.indices) {
1092 | list.add(array[i])
1093 | }
1094 | }
1095 |
1096 | /**
1097 | * 绘制 声波 波纹
1098 | */
1099 | private fun drawWaveLine(widthCenter: Int, heightCenter: Int, canvas: Canvas, paint: Paint) {
1100 | val textWidth = 0f
1101 | paint.reset()
1102 |
1103 | //更新左右两边的波纹矩形
1104 | paint.color = Color.BLACK
1105 | paint.style = Paint.Style.FILL
1106 | paint.strokeWidth = lineWidth
1107 | paint.isAntiAlias = true
1108 | for (i in 0..9) {
1109 | //右边矩形
1110 | rectRight.left = widthCenter + 2 * i * lineWidth + textWidth / 2 + lineWidth
1111 | rectRight.top = heightCenter - list[i] * lineWidth / 2
1112 | rectRight.right =
1113 | widthCenter + 2 * i * lineWidth + 2 * lineWidth + textWidth / 2 /*- 10*/
1114 | rectRight.bottom = heightCenter + list[i] * lineWidth / 2
1115 |
1116 |
1117 | //左边矩形
1118 | rectLeft.left = widthCenter - (2 * i * lineWidth + textWidth / 2 + 2 * lineWidth)
1119 | rectLeft.top = heightCenter - list[i] * lineWidth / 2
1120 | rectLeft.right = widthCenter - (2 * i * lineWidth + textWidth / 2 + lineWidth) /*- 10*/
1121 | rectLeft.bottom = heightCenter + list[i] * lineWidth / 2
1122 |
1123 | canvas.drawRoundRect(rectRight, 5.0f, 5.0f, paint)
1124 | canvas.drawRoundRect(rectLeft, 5.0f, 5.0f, paint)
1125 | }
1126 | }
1127 |
1128 |
1129 | fun setOprCallback(callback: VoiceOprCallback) {
1130 | mVoiceOprCallback = callback
1131 | }
1132 |
1133 | fun setViewCallback(callback: VoiceViewCallback) {
1134 | mVoiceViewCallback = callback
1135 | }
1136 |
1137 |
1138 | /**
1139 | * 判断点击是否在对应的区域
1140 | */
1141 | private fun inTarget(event: MotionEvent, rect: Rect): Boolean {
1142 |
1143 | var targetRect = Rect()
1144 | targetRect.left = rect.left - 25
1145 | targetRect.right = rect.right + 25
1146 | targetRect.top = rect.top - 25
1147 | targetRect.bottom = rect.bottom + 25
1148 |
1149 | return targetRect.contains(event.rawX.toInt(), event.rawY.toInt())
1150 | }
1151 |
1152 |
1153 | /**
1154 | * 绘制 结果 框
1155 | */
1156 | private fun drawResultTarget(canvas: Canvas, paint: Paint) {
1157 |
1158 |
1159 | if (hasResultContent) {
1160 | //右边选中
1161 | paint.color = Color.GREEN
1162 | } else {
1163 | paint.color = Color.YELLOW
1164 | }
1165 | var showMsg: String = ""
1166 |
1167 | showMsg = if (hasResultContent) {
1168 | voiceMessage
1169 | } else {
1170 | "未识别到文字"
1171 | }
1172 | paint.textSize = textSize
1173 | var fontHeight = computeFontHeight(paint);
1174 | var fontWidth = computeFontWidth(paint, showMsg)
1175 | var singleWidth = mResultContentRect!!.width() - 120
1176 |
1177 | var lines = ((fontWidth / singleWidth) + 1).toInt()
1178 | var rectHeight = fontHeight * lines + 120
1179 |
1180 |
1181 | //绘制结果框的宽高
1182 | mResultTextRect.left = mResultContentRect!!.left
1183 | mResultTextRect.right = mResultContentRect!!.right
1184 | mResultTextRect.bottom = mResultContentRect!!.bottom
1185 | mResultTextRect.top = mResultContentRect!!.bottom - rectHeight
1186 |
1187 |
1188 | Log.e(javaClass.simpleName, "drawCustomText mResultTextRect $mResultTextRect")
1189 |
1190 | canvas.drawRoundRect(mResultTextRect!!, 10.0f, 10.0f, paint)
1191 | drawTrigAngle(mRightPosArray[0], mResultTextRect!!, paint, canvas)
1192 |
1193 | val textPaint = TextPaint()
1194 | textPaint.isAntiAlias = true
1195 | if (!hasResultContent) {
1196 | textPaint.setARGB(0xFF, 0xFF, 0x00, 0x00)
1197 | textPaint.textSize = textSize
1198 | val layout = StaticLayout(
1199 | showMsg,
1200 | textPaint!!,
1201 | singleWidth.toInt(),
1202 | Layout.Alignment.ALIGN_NORMAL,
1203 | 1.0f,
1204 | 0.0f,
1205 | true
1206 | )
1207 | canvas.save()
1208 | canvas.translate(mResultTextRect.left + 60, mResultTextRect.top + 60)
1209 | layout.draw(canvas)
1210 | canvas.restore()
1211 | }
1212 | }
1213 |
1214 |
1215 | fun showResult(voiceMessage: String) {
1216 | hasResultContent = if (TextUtils.isEmpty(voiceMessage)) {
1217 | showNoContentView()
1218 | false
1219 | } else {
1220 | this.voiceMessage = voiceMessage
1221 | showEditTextView(voiceMessage)
1222 | true
1223 | }
1224 | }
1225 |
1226 | private fun showNoContentView() {
1227 | //do nothing
1228 | }
1229 |
1230 |
1231 | /**
1232 | * 在 输入框 中 展示 结果
1233 | */
1234 | private fun showEditTextView(str: String) {
1235 | val editText = EditText(context)
1236 | editText.setBackgroundColor(Color.parseColor("#00000000"))
1237 | editText.setText(str)
1238 | editText.setTextColor(Color.BLACK)
1239 | editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
1240 | editText.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
1241 |
1242 | editText.addTextChangedListener(object : TextWatcher {
1243 | override fun afterTextChanged(s: Editable?) {
1244 | voiceMessage = s.toString()
1245 | }
1246 |
1247 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
1248 | }
1249 |
1250 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
1251 | }
1252 | })
1253 | addView(
1254 | editText,
1255 | (mResultContentRect!!.width() - 120).toInt(),
1256 | (mResultContentRect!!.height() - 30).toInt()
1257 | )
1258 | }
1259 |
1260 |
1261 | fun updateRecognizeText(str: String) {
1262 | mCurrentString = str
1263 | postInvalidate()
1264 | }
1265 |
1266 |
1267 | /**
1268 | * 绘制识别时候 右边 文本框, 内容
1269 | */
1270 | private fun drawRightRecoContent(canvas: Canvas, paint: Paint) {
1271 |
1272 | paint.reset()
1273 | var currentString = ""
1274 | paint.textSize = textSize
1275 | var fontHeight = computeFontHeight(paint);
1276 | var fontWidth = computeFontWidth(paint, currentString)
1277 | var singleWidth = mRightVoiceRect!!.width() - 120 //框的宽度
1278 |
1279 | var lines = ((fontWidth / singleWidth) + 1).toInt()
1280 |
1281 | var rectHeight = fontHeight * lines + 120 //框的高度
1282 |
1283 |
1284 | paint.color = Color.GREEN
1285 | paint.isAntiAlias = true
1286 | //绘制结果框的宽高
1287 | var currentTextRectF = RectF()
1288 | currentTextRectF.left = mRightVoiceRect!!.left
1289 | currentTextRectF.right = mRightVoiceRect!!.right
1290 | currentTextRectF.bottom = mRightVoiceRect!!.bottom
1291 | currentTextRectF.top = mRightVoiceRect!!.bottom - rectHeight
1292 | Log.e(javaClass.simpleName, "drawCustomText mResultTextRect $mResultTextRect")
1293 | canvas.drawRoundRect(currentTextRectF!!, 10.0f, 10.0f, paint)
1294 | drawTrigAngle(mRightPosArray[0], currentTextRectF, paint, canvas)
1295 |
1296 | // val textPaint = TextPaint()
1297 | // textPaint.isAntiAlias = true
1298 | // textPaint.setARGB(0xFF, 0xFF, 0x00, 0x00)
1299 | // textPaint.color = Color.WHITE
1300 | // textPaint.textSize = textSize
1301 | // val layout = StaticLayout(
1302 | // mCurrentString,
1303 | // textPaint!!,
1304 | // singleWidth.toInt(),
1305 | // Layout.Alignment.ALIGN_NORMAL,
1306 | // 1.0f,
1307 | // 0.0f,
1308 | // true
1309 | // )
1310 | // canvas.save()
1311 | // canvas.translate(currentTextRectF.left + 60, currentTextRectF.top + 60)
1312 | // layout.draw(canvas)
1313 | // canvas.restore()
1314 |
1315 |
1316 | drawMutipleText(
1317 | canvas,
1318 | currentTextRectF,
1319 | singleWidth.toInt(),
1320 | loadRecognizeText(mCurrentString),
1321 | Color.BLACK
1322 | )
1323 | paint.reset()
1324 | // 正在识音时候,音量条需要挪到右下角
1325 | var rightBottomRectF = RectF()
1326 | rightBottomRectF.left = mRightVoiceRect!!.right - 300
1327 | rightBottomRectF.right = mRightVoiceRect!!.right
1328 | rightBottomRectF.top = mRightVoiceRect!!.bottom - 100
1329 | rightBottomRectF.bottom = mRightVoiceRect!!.bottom
1330 | var centerX = (rightBottomRectF!!.right + rightBottomRectF!!.left) / 2
1331 | var centerY = (rightBottomRectF!!.bottom + rightBottomRectF!!.top) / 2
1332 | drawWaveLine(centerX.toInt(), centerY.toInt(), canvas, paint)
1333 |
1334 | paint.reset()
1335 | paint.color = Color.LTGRAY
1336 | paint.textSize = textSize
1337 | canvas.drawText("转文字", mRightPosArray[0] - 40, mRightPosArray[1] - 120, paint)
1338 |
1339 | }
1340 |
1341 |
1342 | //绘制多行文本
1343 | private fun drawMutipleText(
1344 | canvas: Canvas,
1345 | rectF: RectF,
1346 | singleWidth: Int,
1347 | message: String,
1348 | textColor: Int
1349 | ) {
1350 | val textPaint = TextPaint()
1351 | textPaint.isAntiAlias = true
1352 | // textPaint.setARGB(0xFF, 0xFF, 0x00, 0x00)
1353 | textPaint.color = textColor
1354 | textPaint.textSize = textSize
1355 | val layout = StaticLayout(
1356 | message,
1357 | textPaint!!,
1358 | singleWidth.toInt(),
1359 | Layout.Alignment.ALIGN_NORMAL,
1360 | 1.0f,
1361 | 0.0f,
1362 | true
1363 | )
1364 | canvas.save()
1365 | canvas.translate(rectF.left + 60, rectF.top + 60)
1366 | layout.draw(canvas)
1367 | canvas.restore()
1368 | }
1369 |
1370 | private var recognizeStringBuilder: StringBuilder = StringBuilder()
1371 | private var loadDots = 0
1372 |
1373 |
1374 | private var lastLoadTime = 0L
1375 |
1376 | private var refreshPeroid = 1000L
1377 |
1378 | /**
1379 | * 识别语音的效果
1380 | */
1381 | private fun loadRecognizeText(message: String): String {
1382 | if (System.currentTimeMillis() - lastLoadTime > refreshPeroid) {
1383 | lastLoadTime = System.currentTimeMillis()
1384 | recognizeStringBuilder.clear()
1385 | recognizeStringBuilder.append(message)
1386 | loadDots += 1;
1387 | var time = loadDots % 3
1388 | for (i in 0..time) {
1389 | recognizeStringBuilder.append(".")
1390 | }
1391 | }
1392 | return recognizeStringBuilder.toString();
1393 |
1394 | }
1395 | }
1396 |
--------------------------------------------------------------------------------