update
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
6
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="17" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/deploymentTargetDropDown.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<value>
|
||||||
|
<entry key="app">
|
||||||
|
<State />
|
||||||
|
</entry>
|
||||||
|
</value>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
19
.idea/gradle.xml
generated
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
<option name="resolveExternalAnnotations" value="false" />
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KotlinJpsPluginSettings">
|
||||||
|
<option name="version" value="1.8.20" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/migrations.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/misc.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
BIN
.safedk/api/SafeDKAndroid-6.0.7.jar
Normal file
17
.safedk/app_sdks.lst
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
106f9be0e66f52f36eaaaff4dd231971
|
||||||
|
75939c4ce23c53ad9534d43be176b3e9
|
||||||
|
217e8f437c9fc4244d6e74653ac8a8c7
|
||||||
|
66b774de6608db14a84e972fba1ec954
|
||||||
|
e1c9ddef73e5621f62c717badf1be3f2
|
||||||
|
daaea35726ab7cd457ab61d4538fb822
|
||||||
|
1be9e72506f3307ce6a9e78d26d65bd0
|
||||||
|
4df96d3bc9afd17b812e65e6c6add1ef
|
||||||
|
eb3214f29c0a52815b41977d6cc9a46e
|
||||||
|
becf75b2cc99e82716da2e6697879509
|
||||||
|
7eec7b9476b99b3ce94533da4f2eb987
|
||||||
|
974322f19d813702ea048d95288d2b8c
|
||||||
|
29015bbfcc182d80e7f75bd2c38e4521
|
||||||
|
86a0d598cde251321e21a0da4ab94065
|
||||||
|
74616804a7dc29147dfb0afe122a9fd2
|
||||||
|
35695de726f6044576c830bf197f36f7
|
||||||
|
|
||||||
BIN
.safedk/dex/SafeDKAndroid-6.0.7.dex
Normal file
BIN
.safedk/dex/android-support-multidex.dex
Normal file
2
.safedk/hashes.safedk
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#Wed Jul 17 17:05:37 CST 2024
|
||||||
|
json=521354371
|
||||||
1
.safedk/list.enc
Normal file
5
.safedk/plugin.properties
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
#Wed Jul 17 17:05:37 CST 2024
|
||||||
|
BoZtE6LMU2QaUEowq3SoQFO_HqwztZQdgF3VZGmNRR17TGv0XhXSwlT6LiaRllitI7yAsCkSGo_pfE0yfipADf=wt2KAZMCf_SkV_coMIB7GWtaOQtCd2ZFRK8hFAQo7zbXpIGpG5iI0fZ0sMJr5n_cCO3LEVU66gpxe099OFXXvv
|
||||||
|
sdk_analysis_plugin_version=5.0.7
|
||||||
|
set_multidex=true
|
||||||
34
.safedk/proguard-safedk.pro
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
-keep class androidx.multidex.** { *; }
|
||||||
|
-keep class androidx.browser.customtabs.CustomTabsIntent { *; }
|
||||||
|
-keep class androidx.** {
|
||||||
|
*** startActivityForResult(***);
|
||||||
|
*** startActivity(***);
|
||||||
|
}
|
||||||
|
-keep class android.support.multidex.** { *; }
|
||||||
|
-keep class android.support.v4.app.** { *; }
|
||||||
|
-keep class com.google.android.gms.location.FusedLocationProviderApi { *; }
|
||||||
|
-keep class com.google.android.gms.location.LocationListener { *; }
|
||||||
|
-keep class io.fabric.sdk.android.** { *; }
|
||||||
|
-keep class okio.** { *; }
|
||||||
|
-keep class retrofit2.** { *; }
|
||||||
|
-keep class okhttp3.** { *; }
|
||||||
|
-keep class com.squareup.okhttp.** { *; }
|
||||||
|
-keep class com.android.volley.** { *; }
|
||||||
|
-keep class com.flurry.** { *; }
|
||||||
|
-keep class org.apache.** { *; }
|
||||||
|
-keep class com.applovin.** { *; }
|
||||||
|
-keep class com.google.android.gms.ads.** { *; }
|
||||||
|
-keep class com.ironsource.** { *; }
|
||||||
|
-keep class com.fyber.inneractive.** { *; }
|
||||||
|
-keep class com.vungle.** { *; }
|
||||||
|
-keep class com.unity3d.ads.** { *; }
|
||||||
|
-keep class com.unity3d.services.** { *; }
|
||||||
|
-keep class com.mintegral.msdk.** { *; }
|
||||||
|
-keep class com.mbridge.msdk.** { *; }
|
||||||
|
-keep class com.adcolony.sdk.** { *; }
|
||||||
|
-keep class com.inmobi.** { *; }
|
||||||
|
-keep class com.five_corp.** { *; }
|
||||||
|
-keep class com.bytedance.** { *; }
|
||||||
|
-keep class com.smaato.** { *; }
|
||||||
|
-keep class com.safedk.** { *; }
|
||||||
|
-keep class com.applovin.quality.** { *; }
|
||||||
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
85
app/build.gradle
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
id 'applovin-quality-service'
|
||||||
|
id 'com.google.gms.google-services'
|
||||||
|
id 'com.google.firebase.crashlytics'
|
||||||
|
}
|
||||||
|
|
||||||
|
applovin {
|
||||||
|
apiKey = "BoZtE6LMU2QaUEowq3SoQFO_HqwztZQdgF3VZGmNRR17TGv0XhXSwlT6LiaRllitI7yAsCkSGo_pfE0yfipADf"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'com.keyboard.journey'
|
||||||
|
compileSdk 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.keyboards.journey"
|
||||||
|
minSdk 23
|
||||||
|
targetSdk 34
|
||||||
|
versionCode 5
|
||||||
|
versionName "1.0.5"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation 'androidx.core:core-ktx:1.8.0'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
|
implementation 'com.google.android.material:material:1.11.0'
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
|
//banner
|
||||||
|
implementation 'io.github.youth5201314:banner:2.2.2'
|
||||||
|
//沉浸式
|
||||||
|
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2'
|
||||||
|
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.2'
|
||||||
|
//图片加载
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||||
|
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||||
|
//网络请求
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
|
||||||
|
//上拉下拉
|
||||||
|
implementation 'io.github.scwang90:refresh-layout-kernel:2.1.0'
|
||||||
|
implementation 'io.github.scwang90:refresh-header-classics:2.1.0'
|
||||||
|
implementation 'io.github.scwang90:refresh-footer-classics:2.1.0'
|
||||||
|
//pag
|
||||||
|
implementation 'com.tencent.tav:libpag:4.0.5.10'
|
||||||
|
//json动画
|
||||||
|
implementation 'com.airbnb.android:lottie:6.0.0'
|
||||||
|
//7z
|
||||||
|
implementation 'com.github.omicronapps:7-Zip-JBinding-4Android:Release-16.02-2.02'
|
||||||
|
|
||||||
|
implementation 'com.applovin:applovin-sdk:12.1.0'
|
||||||
|
implementation 'com.applovin.mediation:vungle-adapter:6.12.0.0'
|
||||||
|
implementation 'com.applovin.mediation:mintegral-adapter:16.2.31.0'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
|
// implementation 'com.applovin.mediation:bytedance-adapter:4.7.0.8.0'
|
||||||
|
implementation 'com.applovin.mediation:unityads-adapter:4.4.1.0'
|
||||||
|
implementation(platform("com.google.firebase:firebase-bom:32.2.2"))
|
||||||
|
implementation("com.google.firebase:firebase-analytics-ktx")
|
||||||
|
implementation("com.google.firebase:firebase-crashlytics-ktx")
|
||||||
|
implementation("com.google.firebase:firebase-config-ktx")
|
||||||
|
}
|
||||||
29
app/google-services.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "651805746416",
|
||||||
|
"project_id": "keyboard-journey",
|
||||||
|
"storage_bucket": "keyboard-journey.appspot.com"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:651805746416:android:51f4a6799c2fc2dac88e35",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.keyboards.journey"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyCIX2Rq8AOsnmGDWsoeYFb67rzgauVidpc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
26
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
-keep class org.libpag.** {*;}
|
||||||
|
-keep class androidx.exifinterface.** {*;}
|
||||||
|
-keep class com.omicronapplications.** { *; }
|
||||||
|
-keep class net.sf.sevenzipjbinding.** { *; }
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("com.keyboard.journey", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".JourneyApp"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/journey_app_logo"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/journey_app_round_logo"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.KeyboardJourney"
|
||||||
|
tools:targetApi="31">
|
||||||
|
|
||||||
|
|
||||||
|
<meta-data android:name="applovin.sdk.key"
|
||||||
|
android:value="wt2KAZMCf_SkV_coMIB7GWtaOQtCd2ZFRK8hFAQo7zbXpIGpG5iI0fZ0sMJr5n_cCO3LEVU66gpxe099OFXXvv"/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".JourneyStartActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".JourneyMActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".KeyboardService"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:permission="android.permission.BIND_INPUT_METHOD">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.view.im"
|
||||||
|
android:resource="@xml/method" />
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.view.InputMethod" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".JourneyCategoryDetailsActivity"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
<activity
|
||||||
|
android:name=".JourneyDetailsActivity"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
<activity
|
||||||
|
android:name=".PreviewActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
|
||||||
|
</manifest>
|
||||||
BIN
app/src/main/assets/loading.pag
Normal file
18
app/src/main/java/com/keyboard/journey/JourneyApp.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.applovin.sdk.AppLovinSdk
|
||||||
|
import com.applovin.sdk.AppLovinSdkConfiguration
|
||||||
|
|
||||||
|
class JourneyApp : Application() {
|
||||||
|
companion object {
|
||||||
|
lateinit var app: JourneyApp
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
app = this
|
||||||
|
AppLovinSdk.getInstance(this).mediationProvider = "max"
|
||||||
|
AppLovinSdk.getInstance(this).initializeSdk()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import com.gyf.immersionbar.ktx.immersionBar
|
||||||
|
import com.keyboard.journey.ad.MaxAdsInsUtil
|
||||||
|
import com.keyboard.journey.adapter.CategoryDetailsDataAdapter
|
||||||
|
import com.keyboard.journey.bean.CategoryDataBean
|
||||||
|
import com.keyboard.journey.bean.ItemDataBean
|
||||||
|
import com.keyboard.journey.bean.MainDataBean
|
||||||
|
import com.keyboard.journey.databinding.JourneyCategoryDetailsActivityBinding
|
||||||
|
import com.keyboard.journey.util.NetworkCallback
|
||||||
|
import com.keyboard.journey.util.NetworkUtil
|
||||||
|
import org.libpag.PAGFile
|
||||||
|
|
||||||
|
class JourneyCategoryDetailsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CATEGORY_DETAILS_BEAN_KEY = "category_details_bean_key"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: JourneyCategoryDetailsActivityBinding
|
||||||
|
private var offset = 0
|
||||||
|
private var pageSize = 20
|
||||||
|
|
||||||
|
private var bean: MainDataBean? = null
|
||||||
|
|
||||||
|
private var adapter: CategoryDetailsDataAdapter? = null
|
||||||
|
private var contentBeans: MutableList<ItemDataBean> = mutableListOf()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = JourneyCategoryDetailsActivityBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
MaxAdsInsUtil.showAdRandomMode(this)
|
||||||
|
bean = intent.getSerializableExtra(CATEGORY_DETAILS_BEAN_KEY) as MainDataBean?
|
||||||
|
if (bean == null) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
initBar()
|
||||||
|
initView()
|
||||||
|
initData()
|
||||||
|
|
||||||
|
MaxAdsInsUtil.loadAllAdIsNotCached(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initBar() {
|
||||||
|
immersionBar {
|
||||||
|
statusBarDarkFont(true)
|
||||||
|
statusBarView(binding.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
binding.titleTv.text = bean?.title
|
||||||
|
binding.backBtn.setOnClickListener { finish() }
|
||||||
|
|
||||||
|
binding.refreshLayout.setEnableRefresh(false)
|
||||||
|
binding.refreshLayout.setOnLoadMoreListener {
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
adapter = CategoryDetailsDataAdapter(this, contentBeans)
|
||||||
|
binding.rv.layoutManager = GridLayoutManager(this, 2)
|
||||||
|
binding.rv.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initData() {
|
||||||
|
loadingPlay()
|
||||||
|
offset = 0
|
||||||
|
NetworkUtil().fetchCategory(
|
||||||
|
bean?.key!!,
|
||||||
|
offset,
|
||||||
|
pageSize,
|
||||||
|
object : NetworkCallback<List<CategoryDataBean>> {
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
override fun onSuccess(data: List<CategoryDataBean>) {
|
||||||
|
contentBeans.clear()
|
||||||
|
contentBeans.addAll(data[0].items)
|
||||||
|
runOnUiThread {
|
||||||
|
if (!isFinishing) {
|
||||||
|
offset += pageSize
|
||||||
|
loadingClose()
|
||||||
|
adapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(errorMessage: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshData() {
|
||||||
|
NetworkUtil().fetchCategory(
|
||||||
|
bean?.key!!,
|
||||||
|
offset,
|
||||||
|
pageSize,
|
||||||
|
object : NetworkCallback<List<CategoryDataBean>> {
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
override fun onSuccess(data: List<CategoryDataBean>) {
|
||||||
|
contentBeans.addAll(data[0].items)
|
||||||
|
runOnUiThread {
|
||||||
|
if (!isFinishing) {
|
||||||
|
binding.refreshLayout.finishLoadMore()
|
||||||
|
val startPosition = contentBeans.size
|
||||||
|
contentBeans.addAll(data[0].items)
|
||||||
|
offset += pageSize
|
||||||
|
adapter?.notifyItemRangeInserted(startPosition, data[0].items.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(errorMessage: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadingPlay(){
|
||||||
|
binding.loadingLayout.visibility = View.VISIBLE
|
||||||
|
val pagFile = PAGFile.Load(assets, "loading.pag")
|
||||||
|
binding.pagView.composition = pagFile
|
||||||
|
binding.pagView.setRepeatCount(0)
|
||||||
|
binding.pagView.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadingClose(){
|
||||||
|
binding.loadingLayout.visibility = View.GONE
|
||||||
|
if(binding.pagView.isPlaying){
|
||||||
|
binding.pagView.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
154
app/src/main/java/com/keyboard/journey/JourneyDetailsActivity.kt
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.gyf.immersionbar.ktx.immersionBar
|
||||||
|
import com.keyboard.journey.ad.MaxAdsInsUtil
|
||||||
|
import com.keyboard.journey.bean.DetailsBean
|
||||||
|
import com.keyboard.journey.bean.ItemDataBean
|
||||||
|
import com.keyboard.journey.databinding.JourneyDetailsActivityBinding
|
||||||
|
import com.keyboard.journey.util.AppSharedPreferences
|
||||||
|
import com.keyboard.journey.util.NetworkCallback
|
||||||
|
import com.keyboard.journey.util.NetworkUtil
|
||||||
|
import com.keyboard.journey.util.OnDownloadListener
|
||||||
|
import com.keyboard.journey.util.ResourceDownloadUtil
|
||||||
|
import com.keyboard.journey.util.fileIsDownload
|
||||||
|
import com.keyboard.journey.util.loadRoundedImage
|
||||||
|
import org.libpag.PAGFile
|
||||||
|
|
||||||
|
class JourneyDetailsActivity : AppCompatActivity(), OnDownloadListener {
|
||||||
|
companion object {
|
||||||
|
const val KEY_JOURNEY_DETAILS_BEAN = "key_journey_details_bean"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: JourneyDetailsActivityBinding
|
||||||
|
private var bean: ItemDataBean? = null
|
||||||
|
private val appSharedPreferences = lazy { AppSharedPreferences(this) }
|
||||||
|
private var detailsBean: DetailsBean? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = JourneyDetailsActivityBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
MaxAdsInsUtil.showAdRandomMode(this)
|
||||||
|
bean = intent.getSerializableExtra(KEY_JOURNEY_DETAILS_BEAN) as ItemDataBean?
|
||||||
|
initBar()
|
||||||
|
initView()
|
||||||
|
initData()
|
||||||
|
MaxAdsInsUtil.loadAllAdIsNotCached(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initBar() {
|
||||||
|
immersionBar {
|
||||||
|
statusBarDarkFont(true)
|
||||||
|
statusBarView(binding.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
binding.titleTv.text = bean?.title
|
||||||
|
binding.backBtn.setOnClickListener { finish() }
|
||||||
|
|
||||||
|
binding.downBtn.setOnClickListener {
|
||||||
|
detailsBean?.let {
|
||||||
|
updateDownloadBtn(it)
|
||||||
|
val resourceHandler = ResourceDownloadUtil(this) // 传入当前上下文
|
||||||
|
resourceHandler.setOnDownloadListener(this)
|
||||||
|
val imageUrl = it.themeContent.androidRawZipUrl
|
||||||
|
val b = fileIsDownload(this, imageUrl)
|
||||||
|
if (b) {
|
||||||
|
AppSharedPreferences(this).setCurrentlyThemeUrl(imageUrl)
|
||||||
|
|
||||||
|
val intent = Intent(this, PreviewActivity::class.java)
|
||||||
|
intent.putExtra(PreviewActivity.KEY_PREVIEW_URL, imageUrl)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
binding.loadingBar.visibility = View.VISIBLE
|
||||||
|
binding.downIcon.visibility = View.GONE
|
||||||
|
resourceHandler.downloadAndExtractResources(imageUrl)//文件不存在则下载
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initData() {
|
||||||
|
loadingPlay()
|
||||||
|
NetworkUtil().getResourceRequest(bean?.key!!, object : NetworkCallback<DetailsBean> {
|
||||||
|
override fun onSuccess(data: DetailsBean) {
|
||||||
|
detailsBean = data
|
||||||
|
updateUi(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(errorMessage: String) {
|
||||||
|
runOnUiThread {
|
||||||
|
if (!isFinishing && !isDestroyed) {
|
||||||
|
loadingClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUi(data: DetailsBean) {
|
||||||
|
runOnUiThread {
|
||||||
|
if (!isFinishing && !isDestroyed) {
|
||||||
|
loadingClose()
|
||||||
|
|
||||||
|
updateDownloadBtn(data)
|
||||||
|
|
||||||
|
var url = data.themeContent.imgGif
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
url = data.themeContent.img
|
||||||
|
}
|
||||||
|
loadRoundedImage(this, url, binding.imageView, 40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadingPlay() {
|
||||||
|
binding.loadingLayout.visibility = View.VISIBLE
|
||||||
|
val pagFile = PAGFile.Load(assets, "loading.pag")
|
||||||
|
binding.pagView.composition = pagFile
|
||||||
|
binding.pagView.setRepeatCount(0)
|
||||||
|
binding.pagView.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadingClose() {
|
||||||
|
binding.loadingLayout.visibility = View.GONE
|
||||||
|
if (binding.pagView.isPlaying) {
|
||||||
|
binding.pagView.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDownloadBtn(data: DetailsBean) {
|
||||||
|
if (fileIsDownload(this, data.themeContent.androidRawZipUrl)) {
|
||||||
|
if (AppSharedPreferences(this).getCurrentlyThemeUrl() == data.themeContent.androidRawZipUrl) {
|
||||||
|
binding.downTv.text = getString(R.string.applied)
|
||||||
|
binding.downBtn.isClickable = false
|
||||||
|
binding.downBtn.isFocusable = false
|
||||||
|
binding.downIcon.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.downTv.text = getString(R.string.apply_it)
|
||||||
|
binding.downBtn.isClickable = true
|
||||||
|
binding.downBtn.isFocusable = true
|
||||||
|
binding.downIcon.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.downTv.text = getString(R.string.download)
|
||||||
|
binding.downBtn.isClickable = true
|
||||||
|
binding.downBtn.isFocusable = true
|
||||||
|
binding.downIcon.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDownloadComplete(isDownloaded: Boolean) {
|
||||||
|
binding.loadingBar.visibility = View.GONE
|
||||||
|
binding.downIcon.visibility = View.GONE
|
||||||
|
|
||||||
|
updateUi(detailsBean!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
321
app/src/main/java/com/keyboard/journey/JourneyMActivity.kt
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.GravityCompat
|
||||||
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.gyf.immersionbar.ktx.immersionBar
|
||||||
|
import com.keyboard.journey.ad.MaxAdsInsUtil
|
||||||
|
import com.keyboard.journey.adapter.BannerImgAdapter
|
||||||
|
import com.keyboard.journey.adapter.MainDataAdapter
|
||||||
|
import com.keyboard.journey.bean.CategoryDataBean
|
||||||
|
import com.keyboard.journey.bean.MainDataBean
|
||||||
|
import com.keyboard.journey.databinding.JourneyActivityMainBinding
|
||||||
|
import com.keyboard.journey.util.NetworkCallback
|
||||||
|
import com.keyboard.journey.util.NetworkUtil
|
||||||
|
import com.keyboard.journey.util.getRandomInt
|
||||||
|
import com.keyboard.journey.util.isMyInputMethodDefault
|
||||||
|
import com.keyboard.journey.util.isMyInputMethodEnabled
|
||||||
|
import com.keyboard.journey.util.openPrivacyPolicy
|
||||||
|
import com.keyboard.journey.util.shareAppInfo
|
||||||
|
import com.youth.banner.listener.OnPageChangeListener
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.libpag.PAGFile
|
||||||
|
import java.io.IOException
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
|
|
||||||
|
class JourneyMActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: JourneyActivityMainBinding
|
||||||
|
private var imgList: MutableList<Int> = mutableListOf()
|
||||||
|
private var dataList: MutableList<MainDataBean> = mutableListOf()
|
||||||
|
private var mainAdapter: MainDataAdapter? = null
|
||||||
|
private var backPressedOnce = false
|
||||||
|
private var currentBannerType = 0
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = JourneyActivityMainBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
initBar()
|
||||||
|
initView()
|
||||||
|
initData()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
MaxAdsInsUtil.loadAllAdIsNotCached(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getFetchCategory(
|
||||||
|
key: String,
|
||||||
|
offset: Int,
|
||||||
|
pageSize: Int,
|
||||||
|
): CategoryDataBean? {
|
||||||
|
return try {
|
||||||
|
suspendCancellableCoroutine<CategoryDataBean> { continuation ->
|
||||||
|
NetworkUtil().fetchCategory(
|
||||||
|
key,
|
||||||
|
offset,
|
||||||
|
pageSize,
|
||||||
|
object : NetworkCallback<List<CategoryDataBean>> {
|
||||||
|
override fun onSuccess(data: List<CategoryDataBean>) {
|
||||||
|
continuation.resume(data[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(errorMessage: String) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
private fun initData() {
|
||||||
|
loadingPlay()
|
||||||
|
NetworkUtil().fetchData(object : NetworkCallback<List<MainDataBean>> {
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
override fun onSuccess(data: List<MainDataBean>) {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val jobs = data.map { bean ->
|
||||||
|
if (bean.title == "For You") {
|
||||||
|
bean.title = "Recommend"
|
||||||
|
}
|
||||||
|
GlobalScope.async(Dispatchers.IO) {
|
||||||
|
val resultCategory = getFetchCategory(
|
||||||
|
bean.key, getRandomInt(1, 15),
|
||||||
|
getRandomInt(5, 16),
|
||||||
|
)
|
||||||
|
bean.items = resultCategory?.items!!
|
||||||
|
bean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val list = awaitAll(*jobs.toTypedArray())
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
dataList.clear()
|
||||||
|
dataList.addAll(list)
|
||||||
|
mainAdapter?.notifyDataSetChanged()
|
||||||
|
loadingClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(errorMessage: String) {
|
||||||
|
LogUtil.logMsgD(errorMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initBar() {
|
||||||
|
immersionBar {
|
||||||
|
statusBarDarkFont(true)
|
||||||
|
statusBarView(binding.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupDrawerListener() {
|
||||||
|
binding.drawer.addDrawerListener(object : DrawerLayout.DrawerListener {
|
||||||
|
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
|
||||||
|
|
||||||
|
override fun onDrawerOpened(drawerView: View) {
|
||||||
|
drawerView.isClickable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDrawerClosed(drawerView: View) {}
|
||||||
|
|
||||||
|
override fun onDrawerStateChanged(newState: Int) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initClick() {
|
||||||
|
binding.menuBtn.setOnClickListener {
|
||||||
|
if (binding.drawer.isDrawerOpen(GravityCompat.START)) {
|
||||||
|
binding.drawer.closeDrawer(GravityCompat.START)
|
||||||
|
} else {
|
||||||
|
binding.drawer.openDrawer(GravityCompat.START)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.bannerBtn.setOnClickListener {
|
||||||
|
|
||||||
|
val recommend = dataList.filter { it.title == "Recommend" }
|
||||||
|
val cool = dataList.filter { it.title == "Cool" }
|
||||||
|
if (currentBannerType == 0) {
|
||||||
|
val intent = Intent(this, JourneyCategoryDetailsActivity::class.java)
|
||||||
|
intent.putExtra(JourneyCategoryDetailsActivity.CATEGORY_DETAILS_BEAN_KEY, recommend[0])
|
||||||
|
startActivity(intent)
|
||||||
|
} else if (currentBannerType == 1) {
|
||||||
|
val intent = Intent(this, JourneyCategoryDetailsActivity::class.java)
|
||||||
|
intent.putExtra(JourneyCategoryDetailsActivity.CATEGORY_DETAILS_BEAN_KEY, cool[0])
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.applyKeyboardBtn.setOnClickListener {
|
||||||
|
val enabled = isMyInputMethodEnabled(this)
|
||||||
|
val default = isMyInputMethodDefault(this)
|
||||||
|
if (enabled && default) {
|
||||||
|
binding.dialogStepLayout.visibility = View.GONE
|
||||||
|
|
||||||
|
Toast.makeText(this,"The keyboard has been set up successfully!",Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
binding.dialogStepLayout.visibility = View.VISIBLE
|
||||||
|
updateSetMyInputMethod()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.step1Btn.setOnClickListener {
|
||||||
|
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.step2Btn.setOnClickListener {
|
||||||
|
val imeManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imeManager.showInputMethodPicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.dialogStepLayout.setOnClickListener {
|
||||||
|
binding.dialogStepLayout.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.privacyBtn.setOnClickListener {
|
||||||
|
val privacyPolicyUrl = "https://sites.google.com/view/privacy-policy-html-app"
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.data = Uri.parse(privacyPolicyUrl)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
binding.shareBtn.setOnClickListener {
|
||||||
|
shareAppInfo(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (binding.drawer.isDrawerOpen(GravityCompat.START)) {
|
||||||
|
binding.drawer.closeDrawer(GravityCompat.START);
|
||||||
|
} else {
|
||||||
|
if (backPressedOnce) {
|
||||||
|
super.onBackPressed()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
backPressedOnce = true
|
||||||
|
Toast.makeText(this, "Press again to exit!", Toast.LENGTH_SHORT).show()
|
||||||
|
// 两秒钟内再次按返回键取消退出操作
|
||||||
|
val handler = android.os.Handler()
|
||||||
|
handler.postDelayed({ backPressedOnce = false }, 2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
binding.rv.layoutManager = LinearLayoutManager(this)
|
||||||
|
mainAdapter = MainDataAdapter(this, dataList)
|
||||||
|
binding.rv.adapter = mainAdapter
|
||||||
|
|
||||||
|
imgList.add(R.mipmap.recommend_top_banner_img)
|
||||||
|
imgList.add(R.mipmap.cool_top_banner_img)
|
||||||
|
val adapter = BannerImgAdapter(imgList)
|
||||||
|
binding.banner.setAdapter(adapter).addBannerLifecycleObserver(this) //添加生命周期观察者
|
||||||
|
binding.banner.addOnPageChangeListener(object : OnPageChangeListener {
|
||||||
|
override fun onPageScrolled(
|
||||||
|
position: Int,
|
||||||
|
positionOffset: Float,
|
||||||
|
positionOffsetPixels: Int
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
when (position) {
|
||||||
|
0 -> {
|
||||||
|
currentBannerType = 0
|
||||||
|
binding.indicatorLayout.setBackgroundColor(getColor(R.color.banner_indicator_one_bg))
|
||||||
|
binding.indicatorImg.setImageResource(R.mipmap.top_banner_one_img)
|
||||||
|
binding.indicatorTxt.text = getString(R.string.recommend)
|
||||||
|
binding.indicatorGoBg.setCardBackgroundColor(getColor(R.color.banner_indicator_one_go_bg_color))
|
||||||
|
binding.indicatorLineOneBg.setBackgroundColor(getColor(R.color.banner_indicator_one_go_bg_color))
|
||||||
|
binding.indicatorLineTowBg.setBackgroundColor(getColor(R.color.black_10))
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> {
|
||||||
|
currentBannerType = 1
|
||||||
|
binding.indicatorLayout.setBackgroundColor(getColor(R.color.banner_indicator_tow_bg))
|
||||||
|
binding.indicatorImg.setImageResource(R.mipmap.top_banner_tow_img)
|
||||||
|
binding.indicatorTxt.text = getString(R.string.cool)
|
||||||
|
binding.indicatorGoBg.setCardBackgroundColor(getColor(R.color.banner_indicator_tow_go_bg_color))
|
||||||
|
binding.indicatorLineOneBg.setBackgroundColor(getColor(R.color.black_10))
|
||||||
|
binding.indicatorLineTowBg.setBackgroundColor(getColor(R.color.banner_indicator_tow_go_bg_color))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setupDrawerListener()
|
||||||
|
initClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadingPlay() {
|
||||||
|
binding.loadingLayout.visibility = View.VISIBLE
|
||||||
|
binding.indicatorGoBg.visibility = View.GONE
|
||||||
|
val pagFile = PAGFile.Load(assets, "loading.pag")
|
||||||
|
binding.pagView.composition = pagFile
|
||||||
|
binding.pagView.setRepeatCount(0)
|
||||||
|
binding.pagView.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadingClose() {
|
||||||
|
binding.loadingLayout.visibility = View.GONE
|
||||||
|
binding.indicatorGoBg.visibility = View.VISIBLE
|
||||||
|
if (binding.pagView.isPlaying) {
|
||||||
|
binding.pagView.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSetMyInputMethod() {
|
||||||
|
val enabled = isMyInputMethodEnabled(this)
|
||||||
|
val default = isMyInputMethodDefault(this)
|
||||||
|
if (enabled) {
|
||||||
|
binding.step1Btn.background = getDrawable(R.drawable.drw_gray_select_bg)
|
||||||
|
binding.step1Btn.text = "Step 1:Enabled"
|
||||||
|
binding.step1Btn.setTextColor(Color.parseColor("#999999"))
|
||||||
|
} else {
|
||||||
|
binding.step1Btn.background = getDrawable(R.mipmap.activate_btn_bg)
|
||||||
|
binding.step1Btn.text = "Step 1:Select"
|
||||||
|
binding.step1Btn.setTextColor(Color.parseColor("#000000"))
|
||||||
|
}
|
||||||
|
if (default) {
|
||||||
|
binding.step2Btn.background = getDrawable(R.drawable.drw_gray_select_bg)
|
||||||
|
binding.step2Btn.text = "Step 2:Enabled"
|
||||||
|
binding.step1Btn.setTextColor(Color.parseColor("#999999"))
|
||||||
|
} else {
|
||||||
|
binding.step2Btn.background = getDrawable(R.mipmap.activate_btn_bg)
|
||||||
|
binding.step2Btn.text = "Step 2:Select"
|
||||||
|
binding.step1Btn.setTextColor(Color.parseColor("#000000"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
140
app/src/main/java/com/keyboard/journey/JourneyStartActivity.kt
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.applovin.mediation.MaxError
|
||||||
|
import com.gyf.immersionbar.ktx.immersionBar
|
||||||
|
import com.keyboard.journey.ad.AdLoadListener
|
||||||
|
import com.keyboard.journey.ad.AdShowFailed
|
||||||
|
import com.keyboard.journey.ad.AdShowListener
|
||||||
|
import com.keyboard.journey.ad.MaxAdsInsUtil
|
||||||
|
import com.keyboard.journey.databinding.JourneyStartActivityBinding
|
||||||
|
import org.libpag.PAGFile
|
||||||
|
import java.util.Timer
|
||||||
|
import java.util.TimerTask
|
||||||
|
|
||||||
|
class JourneyStartActivity : AppCompatActivity() {
|
||||||
|
companion object {
|
||||||
|
const val SPLASH_TIME_OUT: Long = 15000
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: JourneyStartActivityBinding
|
||||||
|
private var timer: DelayTimer? = null // 可变变量,初始化为 null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = JourneyStartActivityBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
immersionBar {
|
||||||
|
fullScreen(true)
|
||||||
|
statusBarDarkFont(false)
|
||||||
|
}
|
||||||
|
timer = DelayTimer(SPLASH_TIME_OUT, callback = {
|
||||||
|
LogUtil.logMsgD("loaded ing")
|
||||||
|
if (adCanShow()) {
|
||||||
|
timer?.stop()
|
||||||
|
showAd()
|
||||||
|
}
|
||||||
|
}, com = {
|
||||||
|
startMain()
|
||||||
|
})
|
||||||
|
loadAd()
|
||||||
|
if (timer != null) {
|
||||||
|
timer?.start()
|
||||||
|
}
|
||||||
|
loadingPlay()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadAd() {
|
||||||
|
MaxAdsInsUtil.loadMaxAd(
|
||||||
|
this,
|
||||||
|
MaxAdsInsUtil.Placement.SPLASH_AD_ID, object : AdLoadListener {
|
||||||
|
override fun loaded() {
|
||||||
|
LogUtil.logMsgD("loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadFailed(error: MaxError?) {
|
||||||
|
LogUtil.logMsgD(error.toString())
|
||||||
|
startMain()
|
||||||
|
timer?.stop()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
MaxAdsInsUtil.loadSplashAllAd(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun adCanShow(): Boolean {
|
||||||
|
return MaxAdsInsUtil.canShowAd(MaxAdsInsUtil.Placement.SPLASH_AD_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showAd() {
|
||||||
|
MaxAdsInsUtil.showMaxAd(
|
||||||
|
this,
|
||||||
|
MaxAdsInsUtil.Placement.SPLASH_AD_ID,
|
||||||
|
object : AdShowListener {
|
||||||
|
override fun onAdShown() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdShowFailed(error: AdShowFailed?) {
|
||||||
|
startMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdClosed() {
|
||||||
|
startMain()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startMain() {
|
||||||
|
val intent = Intent(this, JourneyMActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadingPlay() {
|
||||||
|
val pagFile = PAGFile.Load(assets, "loading.pag")
|
||||||
|
binding.pagView.composition = pagFile
|
||||||
|
binding.pagView.setRepeatCount(0)
|
||||||
|
binding.pagView.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadingClose() {
|
||||||
|
if (binding.pagView.isPlaying) {
|
||||||
|
binding.pagView.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
loadingClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DelayTimer(
|
||||||
|
private val totalDelayInMillis: Long,
|
||||||
|
private val callback: () -> Unit,
|
||||||
|
private val com: () -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val timer = Timer()
|
||||||
|
private var elapsedTime: Long = 0
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
timer.scheduleAtFixedRate(object : TimerTask() {
|
||||||
|
override fun run() {
|
||||||
|
if (elapsedTime < totalDelayInMillis) {
|
||||||
|
callback.invoke()
|
||||||
|
elapsedTime += 1000 // 每秒钟递增
|
||||||
|
} else {
|
||||||
|
com.invoke()
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 1000) // 每秒钟执行一次
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
timer.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
359
app/src/main/java/com/keyboard/journey/KeyboardService.kt
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.inputmethodservice.InputMethodService
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.view.inputmethod.InputConnection
|
||||||
|
import android.widget.VideoView
|
||||||
|
import com.keyboard.journey.util.AppSharedPreferences
|
||||||
|
import com.keyboard.journey.util.colorsXmlPullParser
|
||||||
|
import com.keyboard.journey.util.currentlyThemeUFileString
|
||||||
|
import com.keyboard.journey.util.getBitmapDrawable
|
||||||
|
import com.keyboard.journey.util.getPath
|
||||||
|
import com.keyboard.journey.util.getRawVideoPath
|
||||||
|
import com.keyboard.journey.util.getStateDrawable
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView
|
||||||
|
import com.keyboard.journey.view.Keyboard
|
||||||
|
import com.keyboard.journey.view.KeyboardView
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboard
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class KeyboardService : InputMethodService() {
|
||||||
|
|
||||||
|
private lateinit var keyboardView: JourneyKeyboardView
|
||||||
|
private lateinit var currentKeyboard: Keyboard
|
||||||
|
private var isAllCaps = false
|
||||||
|
private var isCap = false
|
||||||
|
private lateinit var rootView: View
|
||||||
|
|
||||||
|
override fun onCreateInputView(): View {
|
||||||
|
rootView = layoutInflater.inflate(R.layout.custom_keyboard, null) // 这里使用了一个容器布局
|
||||||
|
keyboardView = rootView.findViewById(R.id.keyboardView) // 在容器布局中找到 KeyboardView 的引用
|
||||||
|
currentKeyboard = Keyboard(this, R.xml.my_custom_keyboard_layout)
|
||||||
|
keyboardView.keyboard = currentKeyboard
|
||||||
|
keyboardView.setOnKeyboardActionListener(onKeyboardActionListener)
|
||||||
|
|
||||||
|
isCap = keyboardView.isCap()
|
||||||
|
isAllCaps = keyboardView.isAllCaps()
|
||||||
|
|
||||||
|
return rootView // 返回包含 KeyboardView 的容器布局
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWindowShown() {
|
||||||
|
super.onWindowShown()
|
||||||
|
updateConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateConfig() {
|
||||||
|
val currentlyUrl = AppSharedPreferences(this).getCurrentlyThemeUrl()
|
||||||
|
if (currentlyUrl.isNotEmpty()) {
|
||||||
|
val currentlyThemeUFile = currentlyThemeUFileString(this, currentlyUrl)
|
||||||
|
val config = JourneyKeyboardView.Config(this)
|
||||||
|
config.keyBackground = getStateDrawable(this,
|
||||||
|
getPath(currentlyThemeUFile, "btn_keyboard_key_normal_normal.9.png"),
|
||||||
|
getPath(currentlyThemeUFile, "btn_keyboard_key_normal_pressed.9.png")
|
||||||
|
)
|
||||||
|
config.specialKeyBackground = getStateDrawable(this,
|
||||||
|
getPath(currentlyThemeUFile, "btn_keyboard_spacekey_normal_normal.9.png"),
|
||||||
|
getPath(currentlyThemeUFile, "btn_keyboard_spacekey_normal_pressed.9.png")
|
||||||
|
)
|
||||||
|
config.deleteDrawable = getStateDrawable(this,
|
||||||
|
getPath(currentlyThemeUFile, "sym_keyboard_delete_normal.png"),
|
||||||
|
getPath(currentlyThemeUFile, "sym_keyboard_delete_pressed.png")
|
||||||
|
)
|
||||||
|
config.toggleKeyBackground = getStateDrawable(this,
|
||||||
|
getPath(currentlyThemeUFile, "btn_keyboard_key_toggle_normal_on.9.png"),
|
||||||
|
getPath(currentlyThemeUFile, "btn_keyboard_key_toggle_pressed_on.9.png")
|
||||||
|
)
|
||||||
|
|
||||||
|
val colorMap = colorsXmlPullParser(currentlyThemeUFile)
|
||||||
|
colorMap.forEach { (name, value) ->
|
||||||
|
if (name == "key_text_color_normal") {
|
||||||
|
config.keyTextColor = value
|
||||||
|
config.keySpecialTextColor = value
|
||||||
|
}
|
||||||
|
if (name == "key_text_color_functional") {
|
||||||
|
config.toggleKeyTextColor = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.capitalDrawable =
|
||||||
|
getBitmapDrawable(this,currentlyThemeUFile, "sym_keyboard_shift_locked.png")
|
||||||
|
config.lowerDrawable = getBitmapDrawable(this,currentlyThemeUFile, "sym_keyboard_shift.png")
|
||||||
|
config.capitalLockDrawable =
|
||||||
|
getBitmapDrawable(this,currentlyThemeUFile, "sym_keyboard_shift_locked.png")
|
||||||
|
|
||||||
|
val videoView = rootView.findViewById<VideoView>(R.id.videoView)
|
||||||
|
val videoPath = getRawVideoPath(currentlyThemeUFile)
|
||||||
|
if (File(videoPath).exists()) {
|
||||||
|
// 设置视频路径并启动播放
|
||||||
|
videoView.setVideoURI(Uri.parse(videoPath))
|
||||||
|
videoView.setOnPreparedListener { mp ->
|
||||||
|
mp.isLooping = true // 循环播放
|
||||||
|
videoView.start()
|
||||||
|
}
|
||||||
|
videoView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
val imagePath =
|
||||||
|
"$currentlyThemeUFile/res/drawable-xxhdpi-v4/keyboard_background.jpg"
|
||||||
|
val bitmap = BitmapFactory.decodeFile(imagePath)
|
||||||
|
val background = BitmapDrawable(resources, bitmap)
|
||||||
|
rootView.background = background
|
||||||
|
videoView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
keyboardView.setConfig(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onKeyboardActionListener = object : KeyboardView.OnKeyboardActionListener {
|
||||||
|
override fun onKey(primaryCode: Int, keyCodes: IntArray?) {
|
||||||
|
val ic = currentInputConnection
|
||||||
|
performKey(ic, primaryCode, keyCodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPress(primaryCode: Int) {}
|
||||||
|
|
||||||
|
override fun onRelease(primaryCode: Int) {}
|
||||||
|
|
||||||
|
override fun onText(text: CharSequence?) {}
|
||||||
|
|
||||||
|
override fun swipeLeft() {}
|
||||||
|
|
||||||
|
override fun swipeRight() {}
|
||||||
|
|
||||||
|
override fun swipeDown() {}
|
||||||
|
|
||||||
|
override fun swipeUp() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var keyboardType = KeyboardType.NORMAL
|
||||||
|
|
||||||
|
private val keyboardNormal by lazy { Keyboard(this, R.xml.my_custom_keyboard_layout) }
|
||||||
|
private val keyboardNormalModeChange by lazy { Keyboard(this, R.xml.keyboard_mode_change) }
|
||||||
|
private val keyboardNormalMore by lazy { Keyboard(this, R.xml.keyboard_more_symbol) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 primaryCode去做相应的处理
|
||||||
|
*/
|
||||||
|
private fun performKey(ic: InputConnection, primaryCode: Int, keyCodes: IntArray?) {
|
||||||
|
when (primaryCode) {
|
||||||
|
JourneyKeyboard.KEYCODE_SHIFT -> keyShift()
|
||||||
|
JourneyKeyboard.KEYCODE_MODE_CHANGE -> keyModeChange()
|
||||||
|
JourneyKeyboard.KEYCODE_CANCEL -> keyCancel(primaryCode)
|
||||||
|
JourneyKeyboard.KEYCODE_DONE -> keyDone(primaryCode)
|
||||||
|
JourneyKeyboard.KEYCODE_DELETE -> keyDelete(ic)
|
||||||
|
JourneyKeyboard.KEYCODE_MODE_BACK -> keyBack(false)
|
||||||
|
JourneyKeyboard.KEYCODE_BACK -> keyBack(true)
|
||||||
|
JourneyKeyboard.KEYCODE_MORE -> keyMore()
|
||||||
|
//无效的按键值,打印相关日志
|
||||||
|
else -> commitText(ic, primaryCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun commitText(ic: InputConnection, primaryCode: Int) {
|
||||||
|
val code = primaryCode.toChar().toString()
|
||||||
|
ic.commitText(code, 1)
|
||||||
|
|
||||||
|
if (isCap && !isAllCaps) {//如果当前是大写键盘,并且并且没有锁定,则自动变换成小写键盘
|
||||||
|
isCap = false
|
||||||
|
isAllCaps = false
|
||||||
|
toLowerCaseKey(currentKeyboard)
|
||||||
|
|
||||||
|
keyboardView.run {
|
||||||
|
setCap(isCap)
|
||||||
|
setAllCaps(isAllCaps)
|
||||||
|
keyboard = currentKeyboard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 触发删除
|
||||||
|
*/
|
||||||
|
private fun keyDelete(ic: InputConnection) {
|
||||||
|
ic.deleteSurroundingText(1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发Shift,切换大小字母键盘
|
||||||
|
*/
|
||||||
|
private fun keyShift() {
|
||||||
|
//将键盘进行大小写键盘切换
|
||||||
|
if (isAllCaps) {//上次状态为大写锁定时,转换为小写
|
||||||
|
toLowerCaseKey(currentKeyboard)
|
||||||
|
} else {//反之上次状态即为小写时,转换为大写
|
||||||
|
toUpperCaseKey(currentKeyboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
isAllCaps -> {//上次状态为锁定时,此次状态将改变为小写,将变量状态改变
|
||||||
|
isAllCaps = false
|
||||||
|
isCap = false
|
||||||
|
}
|
||||||
|
|
||||||
|
isCap -> {//上次状态为非锁定,此次状态改变为锁定
|
||||||
|
isAllCaps = true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {//上次状态为小写(默认),此次状态改变为大写
|
||||||
|
isCap = true
|
||||||
|
isAllCaps = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboardView.let {
|
||||||
|
it.setCap(isCap)
|
||||||
|
it.setAllCaps(isAllCaps)
|
||||||
|
it.keyboard = currentKeyboard
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为大写
|
||||||
|
*/
|
||||||
|
private fun toUpperCaseKey(keyboard: Keyboard) {
|
||||||
|
keyboard.run {
|
||||||
|
for (key in keys) {
|
||||||
|
if (key.label?.length == 1) {// 一个字符
|
||||||
|
var c = key.label.toString()[0]
|
||||||
|
if (c.isLowerCase()) { //是小写字母
|
||||||
|
//转换为大写
|
||||||
|
val letter = c.toUpperCase()
|
||||||
|
key.label = letter.toString()
|
||||||
|
key.codes[0] = letter.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为小写
|
||||||
|
*/
|
||||||
|
private fun toLowerCaseKey(keyboard: Keyboard) {
|
||||||
|
keyboard.run {
|
||||||
|
for (key in keys) {
|
||||||
|
if (key.label?.length == 1) {// 一个字符
|
||||||
|
var c = key.label.toString()[0]
|
||||||
|
if (c.isUpperCase()) { //是大写字母
|
||||||
|
//转换为小写
|
||||||
|
val letter = c.toLowerCase()
|
||||||
|
key.label = letter.toString()
|
||||||
|
key.codes[0] = letter.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模式改变,切换键盘
|
||||||
|
*/
|
||||||
|
private fun keyModeChange() {
|
||||||
|
when (keyboardType) {
|
||||||
|
JourneyKeyboard.KeyboardType.NORMAL -> {
|
||||||
|
keyboardType = KeyboardType.NORMAL_MODE_CHANGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchKeyboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun switchKeyboard() {
|
||||||
|
when (keyboardType) {
|
||||||
|
KeyboardType.NORMAL -> {
|
||||||
|
currentKeyboard = keyboardNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardType.NORMAL_MODE_CHANGE -> {
|
||||||
|
currentKeyboard = keyboardNormalModeChange
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardType.NORMAL_MORE -> {
|
||||||
|
currentKeyboard = keyboardNormalMore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboardView.run {
|
||||||
|
keyboard = currentKeyboard
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消,关闭键盘
|
||||||
|
*/
|
||||||
|
private fun keyCancel(primaryCode: Int) {
|
||||||
|
val ic = currentInputConnection
|
||||||
|
ic?.performEditorAction(EditorInfo.IME_ACTION_DONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成
|
||||||
|
*/
|
||||||
|
private fun keyDone(primaryCode: Int) {
|
||||||
|
val ic = currentInputConnection
|
||||||
|
ic?.performEditorAction(EditorInfo.IME_ACTION_DONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回
|
||||||
|
*/
|
||||||
|
private fun keyBack(isBack: Boolean) {
|
||||||
|
when (keyboardType) {
|
||||||
|
KeyboardType.NORMAL_MODE_CHANGE -> {
|
||||||
|
keyboardType = KeyboardType.NORMAL
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardType.NORMAL_MORE -> {
|
||||||
|
keyboardType = if (isBack) KeyboardType.NORMAL else KeyboardType.NORMAL_MODE_CHANGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchKeyboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更多
|
||||||
|
*/
|
||||||
|
private fun keyMore() {
|
||||||
|
when (keyboardType) {
|
||||||
|
KeyboardType.NORMAL -> {
|
||||||
|
keyboardType = KeyboardType.NORMAL_MORE
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardType.NORMAL_MODE_CHANGE -> {
|
||||||
|
keyboardType = KeyboardType.NORMAL_MORE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchKeyboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
object KeyboardType {
|
||||||
|
/**
|
||||||
|
* 默认键盘 - 字母带符号
|
||||||
|
*/
|
||||||
|
const val NORMAL = 0x00000001
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认键盘 - 切换键盘
|
||||||
|
*/
|
||||||
|
internal const val NORMAL_MODE_CHANGE = 0x00000002
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认键盘 - 更多
|
||||||
|
*/
|
||||||
|
internal const val NORMAL_MORE = 0x00000003
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
app/src/main/java/com/keyboard/journey/LogUtil.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
object LogUtil {
|
||||||
|
private const val LogTag = "Journey-keyboard"
|
||||||
|
|
||||||
|
fun logMsgD(msg: String) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(LogTag, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
174
app/src/main/java/com/keyboard/journey/PreviewActivity.kt
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package com.keyboard.journey
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.gyf.immersionbar.ktx.immersionBar
|
||||||
|
import com.keyboard.journey.ad.MaxAdsInsUtil
|
||||||
|
import com.keyboard.journey.databinding.JourneyPreviewActivityBinding
|
||||||
|
import com.keyboard.journey.util.currentlyThemeUFileString
|
||||||
|
import com.keyboard.journey.util.getBitmapXXDrawable
|
||||||
|
import com.keyboard.journey.util.isMyInputMethodDefault
|
||||||
|
import com.keyboard.journey.util.isMyInputMethodEnabled
|
||||||
|
import com.keyboard.journey.util.loadAndBlurImage
|
||||||
|
|
||||||
|
class PreviewActivity : AppCompatActivity() {
|
||||||
|
companion object {
|
||||||
|
const val KEY_PREVIEW_URL = "key_preview_url"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: JourneyPreviewActivityBinding
|
||||||
|
|
||||||
|
private var themeUrl = ""
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = JourneyPreviewActivityBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
MaxAdsInsUtil.loadAllAdIsNotCached(this)
|
||||||
|
initBar()
|
||||||
|
themeUrl = intent.getStringExtra(KEY_PREVIEW_URL).toString()
|
||||||
|
registerReceiver()
|
||||||
|
initView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initBar() {
|
||||||
|
immersionBar {
|
||||||
|
statusBarDarkFont(true)
|
||||||
|
fitsSystemWindows(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
updateSetMyInputMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
MaxAdsInsUtil.showAdRandomMode(this)
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
val currentlyThemeUFile = currentlyThemeUFileString(this, themeUrl)
|
||||||
|
loadAndBlurImage(
|
||||||
|
this,
|
||||||
|
getBitmapXXDrawable(this, currentlyThemeUFile, "keyboard_preview_screenshot.jpg"),
|
||||||
|
binding.previewIv
|
||||||
|
)
|
||||||
|
|
||||||
|
val imagePath =
|
||||||
|
"$currentlyThemeUFile/res/drawable-xxhdpi-v4/keyboard_preview.jpg"
|
||||||
|
val bitmap = BitmapFactory.decodeFile(imagePath)
|
||||||
|
val background = BitmapDrawable(resources, bitmap)
|
||||||
|
binding.keyboardIv.background = background
|
||||||
|
|
||||||
|
binding.backBtn.setOnClickListener { onBackPressed() }
|
||||||
|
|
||||||
|
binding.activateBtn.setOnClickListener {
|
||||||
|
binding.dialogStepLayout.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.step1Btn.setOnClickListener {
|
||||||
|
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.step2Btn.setOnClickListener {
|
||||||
|
val imeManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imeManager.showInputMethodPicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.dialogStepLayout.setOnClickListener {
|
||||||
|
binding.dialogStepLayout.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSetMyInputMethod() {
|
||||||
|
val enabled = isMyInputMethodEnabled(this)
|
||||||
|
val default = isMyInputMethodDefault(this)
|
||||||
|
if (enabled) {
|
||||||
|
binding.step1Btn.background = getDrawable(R.drawable.drw_gray_select_bg)
|
||||||
|
binding.step1Btn.text = "Step 1:Enabled"
|
||||||
|
binding.step1Btn.setTextColor(Color.parseColor("#999999"))
|
||||||
|
} else {
|
||||||
|
binding.step1Btn.background = getDrawable(R.mipmap.activate_btn_bg)
|
||||||
|
binding.step1Btn.text = "Step 1:Select"
|
||||||
|
binding.step1Btn.setTextColor(Color.parseColor("#000000"))
|
||||||
|
}
|
||||||
|
if (default) {
|
||||||
|
binding.step2Btn.background = getDrawable(R.drawable.drw_gray_select_bg)
|
||||||
|
binding.step2Btn.text = "Step 2:Enabled"
|
||||||
|
binding.step1Btn.setTextColor(Color.parseColor("#999999"))
|
||||||
|
} else {
|
||||||
|
binding.step2Btn.background = getDrawable(R.mipmap.activate_btn_bg)
|
||||||
|
binding.step2Btn.text = "Step 2:Select"
|
||||||
|
binding.step1Btn.setTextColor(Color.parseColor("#000000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled && default) {
|
||||||
|
binding.edit.visibility = View.VISIBLE
|
||||||
|
binding.tv.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.edit.visibility = View.GONE
|
||||||
|
binding.tv.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled && default) {
|
||||||
|
binding.unauthorizedLayout.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
showKeyboard(binding.edit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var receiver: BroadcastReceiver? = null
|
||||||
|
private fun registerReceiver() {
|
||||||
|
val filter = IntentFilter(Intent.ACTION_INPUT_METHOD_CHANGED)
|
||||||
|
receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
updateSetMyInputMethod()
|
||||||
|
val enabled = isMyInputMethodEnabled(this@PreviewActivity)
|
||||||
|
val default = isMyInputMethodDefault(this@PreviewActivity)
|
||||||
|
if (enabled && default) {
|
||||||
|
binding.dialogStepLayout.visibility = View.GONE
|
||||||
|
binding.edit.visibility = View.VISIBLE
|
||||||
|
binding.tv.visibility = View.GONE
|
||||||
|
showKeyboard(binding.edit)
|
||||||
|
} else {
|
||||||
|
binding.dialogStepLayout.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerReceiver(receiver, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unregisterReceiver() {
|
||||||
|
if (receiver != null) {
|
||||||
|
unregisterReceiver(receiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
unregisterReceiver()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showKeyboard(view: View) {
|
||||||
|
view.postDelayed({
|
||||||
|
view.requestFocus()
|
||||||
|
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}, 1000) // 设置延迟时间
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.keyboard.journey.ad
|
||||||
|
|
||||||
|
import com.applovin.mediation.MaxError
|
||||||
|
|
||||||
|
interface AdLoadListener {
|
||||||
|
fun loadFailed(error: MaxError?) {}
|
||||||
|
fun loaded() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.keyboard.journey.ad
|
||||||
|
|
||||||
|
data class AdShowFailed(
|
||||||
|
val msg: String = "",
|
||||||
|
)
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.keyboard.journey.ad
|
||||||
|
|
||||||
|
interface AdShowListener {
|
||||||
|
fun onAdShown() {}
|
||||||
|
fun onAdShowFailed(error: AdShowFailed?) {}
|
||||||
|
fun onAdClosed() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.keyboard.journey.ad
|
||||||
|
|
||||||
|
import com.applovin.mediation.ads.MaxInterstitialAd
|
||||||
|
|
||||||
|
class InstAdCacheManager {
|
||||||
|
private val mMaxAdCacheDict: MutableMap<String, MaxInterstitialAd> = mutableMapOf()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val instance: InstAdCacheManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||||
|
InstAdCacheManager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAdCache(place: String, adCache: MaxInterstitialAd) {
|
||||||
|
mMaxAdCacheDict[place] = adCache
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAdCache(place: String): MaxInterstitialAd? {
|
||||||
|
return mMaxAdCacheDict[place]
|
||||||
|
}
|
||||||
|
}
|
||||||
59
app/src/main/java/com/keyboard/journey/ad/MaxAdInstLoad.kt
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package com.keyboard.journey.ad
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import com.applovin.mediation.MaxAd
|
||||||
|
import com.applovin.mediation.MaxAdListener
|
||||||
|
import com.applovin.mediation.MaxError
|
||||||
|
import com.applovin.mediation.ads.MaxInterstitialAd
|
||||||
|
|
||||||
|
class MaxAdInstLoad {
|
||||||
|
private var mPlace: String
|
||||||
|
private var adLoadListener: AdLoadListener? = null
|
||||||
|
private var activity: Activity? = null
|
||||||
|
|
||||||
|
constructor(activity: Activity, place: String, listener: AdLoadListener?) {
|
||||||
|
this.mPlace = place
|
||||||
|
this.adLoadListener = listener
|
||||||
|
this.activity = activity
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(place: String, listener: AdLoadListener?) {
|
||||||
|
this.mPlace = place
|
||||||
|
this.adLoadListener = listener
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
val interstitialAd = MaxInterstitialAd(mPlace, activity)
|
||||||
|
interstitialAd.setListener(object : MaxAdListener {
|
||||||
|
override fun onAdLoaded(p0: MaxAd) {
|
||||||
|
// Handle ad loaded event
|
||||||
|
InstAdCacheManager.instance.setAdCache(mPlace, interstitialAd)
|
||||||
|
adLoadListener?.loaded()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdDisplayed(p0: MaxAd) {
|
||||||
|
// Handle ad displayed event
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdHidden(p0: MaxAd) {
|
||||||
|
// Handle ad hidden event
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdClicked(p0: MaxAd) {
|
||||||
|
// Handle ad clicked event
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdLoadFailed(p0: String, p1: MaxError) {
|
||||||
|
// Handle ad load failure event
|
||||||
|
adLoadListener?.loadFailed(p1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdDisplayFailed(p0: MaxAd, p1: MaxError) {
|
||||||
|
// Handle ad display failure event
|
||||||
|
}
|
||||||
|
})
|
||||||
|
interstitialAd.loadAd()
|
||||||
|
}
|
||||||
|
}
|
||||||
58
app/src/main/java/com/keyboard/journey/ad/MaxAdInstShower.kt
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package com.keyboard.journey.ad
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import com.applovin.mediation.MaxAd
|
||||||
|
import com.applovin.mediation.MaxAdListener
|
||||||
|
import com.applovin.mediation.MaxError
|
||||||
|
|
||||||
|
class MaxAdInstShower {
|
||||||
|
private var mPlace: String
|
||||||
|
private var showListener: AdShowListener? = null
|
||||||
|
private var activity: Activity? = null
|
||||||
|
|
||||||
|
constructor(activity: Activity, place: String, showListener: AdShowListener?) {
|
||||||
|
this.mPlace = place
|
||||||
|
this.showListener = showListener
|
||||||
|
this.activity = activity
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(place: String, showListener: AdShowListener?) {
|
||||||
|
this.mPlace = place
|
||||||
|
this.showListener = showListener
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
val interstitialAd = InstAdCacheManager.instance.getAdCache(mPlace)
|
||||||
|
interstitialAd?.setListener(object : MaxAdListener {
|
||||||
|
override fun onAdLoaded(p0: MaxAd) {
|
||||||
|
// Handle ad loaded event
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdDisplayed(p0: MaxAd) {
|
||||||
|
// Handle ad displayed event
|
||||||
|
showListener?.onAdShown()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdHidden(p0: MaxAd) {
|
||||||
|
// Handle ad hidden event
|
||||||
|
showListener?.onAdClosed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdClicked(p0: MaxAd) {
|
||||||
|
// Handle ad clicked event
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdLoadFailed(p0: String, p1: MaxError) {
|
||||||
|
// Handle ad load failure event
|
||||||
|
showListener?.onAdShowFailed(AdShowFailed(p1.message))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdDisplayFailed(p0: MaxAd, p1: MaxError) {
|
||||||
|
// Handle ad display failure event
|
||||||
|
}
|
||||||
|
})
|
||||||
|
interstitialAd?.showAd()
|
||||||
|
}
|
||||||
|
}
|
||||||
82
app/src/main/java/com/keyboard/journey/ad/MaxAdsInsUtil.kt
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package com.keyboard.journey.ad
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import java.util.Random
|
||||||
|
|
||||||
|
object MaxAdsInsUtil {
|
||||||
|
|
||||||
|
object Placement {
|
||||||
|
const val SPLASH_AD_ID = "f17d90b8ffcd8c43"
|
||||||
|
const val MAX_AD_ID_ONE = "b940838b4e632b48"
|
||||||
|
const val MAX_AD_ID_TOW = "872bcabcd258447b"
|
||||||
|
|
||||||
|
val adPlaceAllList = listOf(
|
||||||
|
SPLASH_AD_ID,
|
||||||
|
MAX_AD_ID_ONE,
|
||||||
|
MAX_AD_ID_TOW
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSplashAllAd(act: Activity) {
|
||||||
|
Placement.adPlaceAllList.drop(1).forEach { placement ->
|
||||||
|
loadAdIfNotCached(act, placement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadAllAdIsNotCached(act: Activity) {
|
||||||
|
Placement.adPlaceAllList.forEach { placement ->
|
||||||
|
loadAdIfNotCached(act, placement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadAdIfNotCached(act: Activity, placement: String, listener: AdLoadListener? = null) {
|
||||||
|
if (act.isFinishing || canShowAd(placement)) return
|
||||||
|
loadMaxAd(act, placement, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showAdRandomMode(act: Activity) {
|
||||||
|
val adPlace: MutableList<String> = ArrayList()
|
||||||
|
Placement.adPlaceAllList.forEach { placement ->
|
||||||
|
if (canShowAd(placement)) {
|
||||||
|
adPlace.add(placement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (adPlace.isNotEmpty()) {
|
||||||
|
val placeId = Random().nextInt(adPlace.size)
|
||||||
|
val place = adPlace[placeId]
|
||||||
|
showAdIfCached(act, place)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showAdIfCached(act: Activity, placement: String, listener: AdShowListener? = null) {
|
||||||
|
if (act.isFinishing || !canShowAd(placement)) {
|
||||||
|
listener?.onAdShowFailed(AdShowFailed("No cache for ads"))
|
||||||
|
} else {
|
||||||
|
showMaxAd(act, placement, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadMaxAd(
|
||||||
|
act: Activity,
|
||||||
|
adID: String,
|
||||||
|
loadListener: AdLoadListener?
|
||||||
|
): MaxAdInstLoad {
|
||||||
|
return MaxAdInstLoad(act, adID, loadListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showMaxAd(
|
||||||
|
act: Activity,
|
||||||
|
adID: String,
|
||||||
|
listener: AdShowListener?
|
||||||
|
): MaxAdInstShower {
|
||||||
|
return MaxAdInstShower(act, adID, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun canShowAd(adID: String): Boolean {
|
||||||
|
InstAdCacheManager.instance.getAdCache(adID)?.let {
|
||||||
|
return it.isReady
|
||||||
|
} ?: let {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.keyboard.journey.adapter
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.youth.banner.adapter.BannerAdapter
|
||||||
|
|
||||||
|
class BannerImgAdapter(data: MutableList<Int>) :
|
||||||
|
BannerAdapter<Int, BannerImgAdapter.BannerViewHolder>(
|
||||||
|
data
|
||||||
|
) {
|
||||||
|
|
||||||
|
class BannerViewHolder(var imageView: ImageView) :
|
||||||
|
RecyclerView.ViewHolder(imageView)
|
||||||
|
|
||||||
|
override fun onCreateHolder(parent: ViewGroup?, viewType: Int): BannerViewHolder {
|
||||||
|
val imageView = ImageView(parent!!.context)
|
||||||
|
imageView.layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
return BannerViewHolder(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindView(holder: BannerViewHolder, data: Int, position: Int, size: Int) {
|
||||||
|
holder.imageView.setImageResource(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package com.keyboard.journey.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.keyboard.journey.JourneyCategoryDetailsActivity
|
||||||
|
import com.keyboard.journey.JourneyCategoryDetailsActivity.Companion.CATEGORY_DETAILS_BEAN_KEY
|
||||||
|
import com.keyboard.journey.JourneyDetailsActivity
|
||||||
|
import com.keyboard.journey.bean.CategoryDataBean
|
||||||
|
import com.keyboard.journey.bean.ItemDataBean
|
||||||
|
import com.keyboard.journey.bean.MainDataBean
|
||||||
|
import com.keyboard.journey.databinding.AdapterCategoryDetailsItemBinding
|
||||||
|
import com.keyboard.journey.databinding.MainRvItemBinding
|
||||||
|
|
||||||
|
class CategoryDetailsDataAdapter(private val context: Context, private val mainDataList: List<ItemDataBean>) :
|
||||||
|
RecyclerView.Adapter<CategoryDetailsDataAdapter.MainDataViewHolder>() {
|
||||||
|
|
||||||
|
inner class MainDataViewHolder(private val binding: AdapterCategoryDetailsItemBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(data: ItemDataBean) {
|
||||||
|
binding.apply {
|
||||||
|
var url = data.thumbUrlGif
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
url = data.thumbUrl
|
||||||
|
}
|
||||||
|
Glide.with(context)
|
||||||
|
.load(url)
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainDataViewHolder {
|
||||||
|
val binding = AdapterCategoryDetailsItemBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
return MainDataViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: MainDataViewHolder, position: Int) {
|
||||||
|
val currentItem = mainDataList[position]
|
||||||
|
holder.bind(currentItem)
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
val intent = Intent(context, JourneyDetailsActivity::class.java)
|
||||||
|
intent.putExtra(JourneyDetailsActivity.KEY_JOURNEY_DETAILS_BEAN,currentItem)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = mainDataList.size
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package com.keyboard.journey.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.keyboard.journey.JourneyDetailsActivity
|
||||||
|
import com.keyboard.journey.JourneyDetailsActivity.Companion.KEY_JOURNEY_DETAILS_BEAN
|
||||||
|
import com.keyboard.journey.LogUtil
|
||||||
|
import com.keyboard.journey.bean.ItemDataBean
|
||||||
|
import com.keyboard.journey.databinding.HorizontalScrollingRvItemBinding
|
||||||
|
|
||||||
|
class CategoryItemDataAdapter(
|
||||||
|
private val context: Context,
|
||||||
|
private val mainDataList: List<ItemDataBean>
|
||||||
|
) :
|
||||||
|
RecyclerView.Adapter<CategoryItemDataAdapter.MainDataViewHolder>() {
|
||||||
|
|
||||||
|
inner class MainDataViewHolder(private val binding: HorizontalScrollingRvItemBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(bean: ItemDataBean) {
|
||||||
|
binding.apply {
|
||||||
|
var url = bean.thumbUrlGif
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
url = bean.thumbUrl
|
||||||
|
}
|
||||||
|
LogUtil.logMsgD("url->${url}")
|
||||||
|
Glide.with(context)
|
||||||
|
.load(url)
|
||||||
|
.into(hsRvImg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainDataViewHolder {
|
||||||
|
val binding = HorizontalScrollingRvItemBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
return MainDataViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: MainDataViewHolder, position: Int) {
|
||||||
|
val currentItem = mainDataList[position]
|
||||||
|
holder.bind(currentItem)
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
val intent = Intent(context, JourneyDetailsActivity::class.java)
|
||||||
|
intent.putExtra(KEY_JOURNEY_DETAILS_BEAN,currentItem)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = mainDataList.size
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.keyboard.journey.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.keyboard.journey.JourneyCategoryDetailsActivity
|
||||||
|
import com.keyboard.journey.JourneyCategoryDetailsActivity.Companion.CATEGORY_DETAILS_BEAN_KEY
|
||||||
|
import com.keyboard.journey.bean.MainDataBean
|
||||||
|
import com.keyboard.journey.databinding.MainRvItemBinding
|
||||||
|
|
||||||
|
class MainDataAdapter(private val context: Context, private val mainDataList: List<MainDataBean>) :
|
||||||
|
RecyclerView.Adapter<MainDataAdapter.MainDataViewHolder>() {
|
||||||
|
|
||||||
|
inner class MainDataViewHolder(private val binding: MainRvItemBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(mainData: MainDataBean) {
|
||||||
|
binding.apply {
|
||||||
|
titleTextView.text = mainData.title
|
||||||
|
val adapter = CategoryItemDataAdapter(context, mainData.items!!)
|
||||||
|
itemRv.layoutManager =
|
||||||
|
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||||
|
itemRv.adapter = adapter
|
||||||
|
|
||||||
|
moreData.setOnClickListener {
|
||||||
|
val intent = Intent(context, JourneyCategoryDetailsActivity::class.java)
|
||||||
|
intent.putExtra(CATEGORY_DETAILS_BEAN_KEY, mainData)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainDataViewHolder {
|
||||||
|
val binding = MainRvItemBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
return MainDataViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: MainDataViewHolder, position: Int) {
|
||||||
|
val currentItem = mainDataList[position]
|
||||||
|
holder.bind(currentItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = mainDataList.size
|
||||||
|
}
|
||||||
10
app/src/main/java/com/keyboard/journey/bean/Author.kt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class Author(
|
||||||
|
val name: String,
|
||||||
|
val key: String,
|
||||||
|
val photoUrl: String,
|
||||||
|
val homeUrl: String
|
||||||
|
):Serializable
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class CategoryDataBean(
|
||||||
|
val layout: Int,
|
||||||
|
val grid: Int,
|
||||||
|
val type: Int,
|
||||||
|
val title: String,
|
||||||
|
val key: String,
|
||||||
|
var items: List<ItemDataBean>
|
||||||
|
) : Serializable
|
||||||
7
app/src/main/java/com/keyboard/journey/bean/Content.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class Content(
|
||||||
|
val imageUrl: String
|
||||||
|
) : Serializable
|
||||||
16
app/src/main/java/com/keyboard/journey/bean/DetailsBean.kt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class DetailsBean(
|
||||||
|
val key: String,
|
||||||
|
val title: String,
|
||||||
|
val type: Int,
|
||||||
|
val thumbUrl: String,
|
||||||
|
val pkgName: String,
|
||||||
|
val thumbUrlGif: String,
|
||||||
|
val content: Content,
|
||||||
|
val themeContent: ThemeDetailsContent,
|
||||||
|
val author: Author,
|
||||||
|
val lock: LockBean
|
||||||
|
) : Serializable
|
||||||
14
app/src/main/java/com/keyboard/journey/bean/ItemDataBean.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class ItemDataBean(
|
||||||
|
val key: String,
|
||||||
|
val title: String,
|
||||||
|
val type: Int,
|
||||||
|
val thumbUrl: String,
|
||||||
|
val thumbUrlGif: String,
|
||||||
|
val themeContentBean: ThemeContentBean,
|
||||||
|
val lockBean: LockBean
|
||||||
|
) : Serializable
|
||||||
|
|
||||||
7
app/src/main/java/com/keyboard/journey/bean/LockBean.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class LockBean(
|
||||||
|
val type: Int
|
||||||
|
) : Serializable
|
||||||
13
app/src/main/java/com/keyboard/journey/bean/MainDataBean.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class MainDataBean(
|
||||||
|
val layout: Int,
|
||||||
|
val grid: Int,
|
||||||
|
val type: Int,
|
||||||
|
var title: String,
|
||||||
|
val key: String,
|
||||||
|
val mainItemDataBean: MainItemDataBean,
|
||||||
|
var items: List<ItemDataBean>? = null,
|
||||||
|
) : Serializable
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class MainItemDataBean(
|
||||||
|
val key: String,
|
||||||
|
val title: String,
|
||||||
|
val type: Int,
|
||||||
|
val thumbUrl: String
|
||||||
|
) : Serializable
|
||||||
|
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class ThemeContentBean(
|
||||||
|
val pushIcon: String
|
||||||
|
) : Serializable
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.keyboard.journey.bean
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class ThemeDetailsContent(
|
||||||
|
val img: String,
|
||||||
|
val imgBanner: String,
|
||||||
|
val imgPreviewGif: String,
|
||||||
|
val pushIcon: String,
|
||||||
|
val pushBanner: String,
|
||||||
|
val androidRawZipUrl: String,
|
||||||
|
val imgGif: String
|
||||||
|
) : Serializable
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
package com.keyboard.journey.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
class AppSharedPreferences(context: Context) {
|
||||||
|
companion object {
|
||||||
|
private const val KEY_CURRENTLY_THEME_URL = "key_journey_currently_theme_url"
|
||||||
|
private const val KEY_CURRENTLY_THEME_GIF_URL = "key_journey_currently_theme_gif_url"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val sharedPreferences: SharedPreferences =
|
||||||
|
context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun getCurrentlyThemeUrl(): String {
|
||||||
|
return getString(KEY_CURRENTLY_THEME_URL, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentlyThemeUrl(string: String) {
|
||||||
|
saveString(KEY_CURRENTLY_THEME_URL, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentlyThemeGifUrl(): String {
|
||||||
|
return getString(KEY_CURRENTLY_THEME_GIF_URL, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentlyThemeGifUrl(string: String) {
|
||||||
|
saveString(KEY_CURRENTLY_THEME_GIF_URL, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveString(key: String, value: String) {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.putString(key, value)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getString(key: String, defaultValue: String = ""): String {
|
||||||
|
return sharedPreferences.getString(key, defaultValue) ?: defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveInt(key: String, value: Int) {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.putInt(key, value)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInt(key: String, defaultValue: Int = 0): Int {
|
||||||
|
return sharedPreferences.getInt(key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 删除特定键对应的值
|
||||||
|
fun removeKey(key: String) {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.remove(key)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空 SharedPreferences 中的所有数据
|
||||||
|
fun clearAll() {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.clear()
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
234
app/src/main/java/com/keyboard/journey/util/AppUtil.kt
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
package com.keyboard.journey.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.StateListDrawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Xml
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.core.app.ShareCompat
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
import java.io.File
|
||||||
|
import java.io.StringReader
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
fun getRandomInt(from: Int, until: Int): Int {
|
||||||
|
var num = 0
|
||||||
|
val random = Random.Default
|
||||||
|
num = random.nextInt(from, until)
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFileNameFromUrl(urlString: String): String {
|
||||||
|
val uri = Uri.parse(urlString)
|
||||||
|
val path = uri.path ?: urlString // 如果没有路径,将使用整个 URL
|
||||||
|
val file = File(path)
|
||||||
|
return file.name // 获取文件名部分
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSubDirectories(directory: File): List<String> {
|
||||||
|
val subDirectories = mutableListOf<String>()
|
||||||
|
|
||||||
|
if (directory.exists() && directory.isDirectory) {
|
||||||
|
val subDirectoryFiles = directory.listFiles { file -> file.isDirectory }
|
||||||
|
subDirectoryFiles?.forEach { subDir ->
|
||||||
|
subDirectories.add(subDir.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subDirectories
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFileExtension(fileName: String): String {
|
||||||
|
return fileName.substringBeforeLast(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fileIsDownload(context: Context, zipUrl: String): Boolean {
|
||||||
|
val destinationFolder = context.filesDir.absolutePath
|
||||||
|
val childString = getFileNameFromUrl(zipUrl)
|
||||||
|
val file = File(destinationFolder, removeFileExtension(childString))
|
||||||
|
var getSubDirectories = ""
|
||||||
|
if (file.exists() && file.isDirectory) {
|
||||||
|
getSubDirectories = getSubDirectories(file)[0]
|
||||||
|
}
|
||||||
|
val sub = removeFileExtension(childString) + "/" + getSubDirectories
|
||||||
|
val subFile = File(destinationFolder, sub)
|
||||||
|
return subFile.exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentlyThemeUFileString(context: Context, zipUrl: String): String {
|
||||||
|
val destinationFolder = context.filesDir.absolutePath
|
||||||
|
val childString = getFileNameFromUrl(zipUrl)
|
||||||
|
val file = File(destinationFolder, removeFileExtension(childString))
|
||||||
|
var getSubDirectories = ""
|
||||||
|
if (file.exists() && file.isDirectory) {
|
||||||
|
getSubDirectories = getSubDirectories(file)[0]
|
||||||
|
}
|
||||||
|
val sub = removeFileExtension(childString) + "/" + getSubDirectories
|
||||||
|
return File(destinationFolder, sub).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
//输入法是否被启用
|
||||||
|
fun isMyInputMethodEnabled(context: Context): Boolean {
|
||||||
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
val ids = imm.enabledInputMethodList.map { it.id }
|
||||||
|
return ids.any { it.startsWith(context.packageName) }
|
||||||
|
}
|
||||||
|
|
||||||
|
//输入法是否被设置为默认输入法
|
||||||
|
fun isMyInputMethodDefault(context: Context): Boolean {
|
||||||
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
val id =
|
||||||
|
Settings.Secure.getString(context.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD)
|
||||||
|
return id != null && id.startsWith(context.packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadRoundedImage(
|
||||||
|
context: Context,
|
||||||
|
imageUrl: String,
|
||||||
|
imageView: ImageView,
|
||||||
|
cornerRadius: Int = 10
|
||||||
|
) {
|
||||||
|
val requestOptions = RequestOptions().transform(RoundedCorners(cornerRadius))
|
||||||
|
|
||||||
|
Glide.with(context)
|
||||||
|
.load(imageUrl)
|
||||||
|
.apply(requestOptions)
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载并给图片添加模糊效果的函数
|
||||||
|
fun loadAndBlurImage(
|
||||||
|
context: Context,
|
||||||
|
drawable: Drawable,
|
||||||
|
imageView: ImageView,
|
||||||
|
blurRadius: Int = 25
|
||||||
|
) {
|
||||||
|
Glide.with(context)
|
||||||
|
.load(drawable)
|
||||||
|
.apply(RequestOptions.bitmapTransform(BlurTransformation(blurRadius)))
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBitmapDrawable(context: Context, currentlyThemeUFile: String, name: String): Drawable {
|
||||||
|
val imagePath = "$currentlyThemeUFile/res/drawable-xhdpi-v4/$name"
|
||||||
|
val bitmap = BitmapFactory.decodeFile(imagePath)
|
||||||
|
return BitmapDrawable(context.resources, bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBitmapXXDrawable(context: Context, currentlyThemeUFile: String, name: String): Drawable {
|
||||||
|
val imagePath = "$currentlyThemeUFile/res/drawable-xxhdpi-v4/$name"
|
||||||
|
val bitmap = BitmapFactory.decodeFile(imagePath)
|
||||||
|
return BitmapDrawable(context.resources, bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRawVideoPath(currentlyThemeUFile: String): String {
|
||||||
|
return "$currentlyThemeUFile/res/raw/keyboard_background_video.mp4"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPath(currentlyThemeUFile: String, name: String): String {
|
||||||
|
return "$currentlyThemeUFile/res/drawable-xhdpi-v4/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getResColorsPath(currentlyThemeUFile: String): String {
|
||||||
|
return "$currentlyThemeUFile/res/colors.xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun colorsXmlPullParser(currentlyThemeUFile: String): Map<String, Int> {
|
||||||
|
val colorsFile = File(getResColorsPath(currentlyThemeUFile)) // 构建文件对象
|
||||||
|
val colorsMap = mutableMapOf<String, Int>()
|
||||||
|
if (colorsFile.exists()) {
|
||||||
|
val xmlContent = colorsFile.readText() // 读取文件内容为字符串
|
||||||
|
val parser: XmlPullParser = Xml.newPullParser()
|
||||||
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||||
|
parser.setInput(StringReader(xmlContent))
|
||||||
|
|
||||||
|
var eventType = parser.eventType
|
||||||
|
|
||||||
|
|
||||||
|
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||||
|
if (eventType == XmlPullParser.START_TAG && (parser.name == "color" || parser.name == "item")) {
|
||||||
|
val colorName = parser.getAttributeValue(null, "name")
|
||||||
|
val colorValue = parser.nextText()
|
||||||
|
|
||||||
|
// 只添加你需要的颜色到 map 中
|
||||||
|
if (colorName == "key_text_color_normal" ||
|
||||||
|
colorName == "key_text_color_functional"
|
||||||
|
) {
|
||||||
|
colorsMap[colorName] = Color.parseColor(colorValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eventType = parser.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
return colorsMap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStateDrawable(
|
||||||
|
context: Context,
|
||||||
|
defaultPath: String,
|
||||||
|
pressedPath: String
|
||||||
|
): StateListDrawable {
|
||||||
|
val defaultBitmap = BitmapFactory.decodeFile(defaultPath)
|
||||||
|
val pressedBitmap = BitmapFactory.decodeFile(pressedPath)
|
||||||
|
// 创建 StateListDrawable
|
||||||
|
val stateListDrawable = StateListDrawable()
|
||||||
|
val defaultDrawable = BitmapDrawable(context.resources, defaultBitmap)
|
||||||
|
val pressedDrawable = BitmapDrawable(context.resources, pressedBitmap)
|
||||||
|
// 添加按下状态
|
||||||
|
val pressedState = intArrayOf(android.R.attr.state_pressed)
|
||||||
|
stateListDrawable.addState(pressedState, pressedDrawable)
|
||||||
|
// 添加默认状态
|
||||||
|
val normalState = intArrayOf()
|
||||||
|
stateListDrawable.addState(normalState, defaultDrawable)
|
||||||
|
return stateListDrawable
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openPrivacyPolicy(context: Context) {
|
||||||
|
val privacyPolicyUrl = "https://sites.google.com/view/privacy-policy-html-app"
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.data = Uri.parse(privacyPolicyUrl)
|
||||||
|
|
||||||
|
if (intent.resolveActivity(context.packageManager) != null) {
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun shareAppInfo(context: Context) {
|
||||||
|
val appPackageName = context.packageName
|
||||||
|
val appInfo = context.packageManager.getApplicationInfo(appPackageName, PackageManager.GET_META_DATA)
|
||||||
|
val appName = context.getString(appInfo.labelRes)
|
||||||
|
|
||||||
|
val appPlayStoreLink = "https://play.google.com/store/apps/details?id=$appPackageName"
|
||||||
|
|
||||||
|
val shareMessage = "Check out $appName on Google Play: $appPlayStoreLink"
|
||||||
|
|
||||||
|
val shareIntent = ShareCompat.IntentBuilder.from(context as androidx.appcompat.app.AppCompatActivity)
|
||||||
|
.setType("text/plain")
|
||||||
|
.setText(shareMessage)
|
||||||
|
.intent
|
||||||
|
|
||||||
|
// 判断是否有可以处理分享的应用程序
|
||||||
|
if (shareIntent.resolveActivity(context.packageManager) != null) {
|
||||||
|
context.startActivity(shareIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
286
app/src/main/java/com/keyboard/journey/util/NetworkUtil.kt
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
package com.keyboard.journey.util
|
||||||
|
|
||||||
|
import com.keyboard.journey.bean.Author
|
||||||
|
import com.keyboard.journey.bean.CategoryDataBean
|
||||||
|
import com.keyboard.journey.bean.Content
|
||||||
|
import com.keyboard.journey.bean.DetailsBean
|
||||||
|
import com.keyboard.journey.bean.ItemDataBean
|
||||||
|
import com.keyboard.journey.bean.LockBean
|
||||||
|
import com.keyboard.journey.bean.MainDataBean
|
||||||
|
import com.keyboard.journey.bean.MainItemDataBean
|
||||||
|
import com.keyboard.journey.bean.ThemeContentBean
|
||||||
|
import com.keyboard.journey.bean.ThemeDetailsContent
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
interface NetworkCallback<T> {
|
||||||
|
fun onSuccess(data: T)
|
||||||
|
fun onFailure(errorMessage: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkUtil {
|
||||||
|
companion object {
|
||||||
|
private const val HEADER_AGENT = "User-Agent"
|
||||||
|
private const val HEADER_KEY = "User-Key"
|
||||||
|
private const val HEADER_KEY_VALUE =
|
||||||
|
"f_M2HePkT1-XKO1y9MsJzt:APA91bG_JRru9-AuBlcAE7TwyhQf4POchj2nHGH6dqsL4nZd-2HhyMzZe1eIVy9TBCMG3avCVUYxj4dcN5FgbTv4_LGV-mAtb4x-FEMLztx79vySuH5gpBV7SdPCeBsB-4NmS5OElFwe"
|
||||||
|
private const val GET_MAIN_URL = "https://backend-wallpaper.kika-backend.com/v1/api/theme/"
|
||||||
|
private const val GET_CATEGORY_URL =
|
||||||
|
GET_MAIN_URL + "page/kbtheme_main?offset=0&fetch_size=100&sign=a28aadc61c76c754944f6ddb48962c9c"
|
||||||
|
private const val GET_CATEGORY_DETAILS_URL =
|
||||||
|
GET_MAIN_URL + "category/"
|
||||||
|
private const val GET_DETAILS_URL =
|
||||||
|
GET_MAIN_URL + "resource/"
|
||||||
|
|
||||||
|
private val HEADER_AGENT_VALUE =
|
||||||
|
"com.ikeyboard.theme.neon.love/200 (2102cf82e4624c3bb94f30359139b1d4/d7b10839af1ba26bc4b64881501e6df0) Country/US Language/en System/android Version/${33} Screen/${560}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val client = OkHttpClient()
|
||||||
|
|
||||||
|
fun fetchData(callback: NetworkCallback<List<MainDataBean>>) {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(GET_CATEGORY_URL)
|
||||||
|
.get()
|
||||||
|
.addHeader(HEADER_KEY, HEADER_KEY_VALUE)
|
||||||
|
.addHeader(HEADER_AGENT, HEADER_AGENT_VALUE)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(object : Callback {
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val jsonData = response.body?.string()
|
||||||
|
jsonData?.let {
|
||||||
|
val resultObj = JSONObject(it)
|
||||||
|
if (resultObj.optBoolean("success")) {
|
||||||
|
val dataObj = resultObj.optJSONObject("data")
|
||||||
|
dataObj?.let {
|
||||||
|
val sectionsArray = dataObj.optJSONArray("sections")
|
||||||
|
sectionsArray?.let {
|
||||||
|
callback.onSuccess(parseMainDataList(sectionsArray))
|
||||||
|
} ?: callback.onFailure("sections array null")
|
||||||
|
} ?: callback.onFailure("Empty Data ")
|
||||||
|
} else {
|
||||||
|
callback.onFailure("success != true")
|
||||||
|
}
|
||||||
|
} ?: callback.onFailure("Empty response body")
|
||||||
|
} else {
|
||||||
|
callback.onFailure("Error: ${response.code}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
callback.onFailure("Exception: ${e.message}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMainDataList(jsonArray: JSONArray): List<MainDataBean> {
|
||||||
|
val list = mutableListOf<MainDataBean>()
|
||||||
|
|
||||||
|
for (i in 0 until jsonArray.length()) {
|
||||||
|
val jsonObject = jsonArray.getJSONObject(i)
|
||||||
|
val mainDataBean = MainDataBean(
|
||||||
|
jsonObject.getInt("layout"),
|
||||||
|
jsonObject.getInt("grid"),
|
||||||
|
jsonObject.getInt("type"),
|
||||||
|
jsonObject.getString("title"),
|
||||||
|
jsonObject.getString("key"),
|
||||||
|
parseMainItemDataBean(jsonObject.optJSONArray("items"))
|
||||||
|
)
|
||||||
|
list.add(mainDataBean)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMainItemDataBean(jsonArray: JSONArray): MainItemDataBean {
|
||||||
|
val list = mutableListOf<MainItemDataBean>()
|
||||||
|
for (i in 0 until jsonArray.length()) {
|
||||||
|
val jsonObject = jsonArray.getJSONObject(i)
|
||||||
|
list.add(
|
||||||
|
MainItemDataBean(
|
||||||
|
jsonObject.optString("key"),
|
||||||
|
jsonObject.optString("title"),
|
||||||
|
jsonObject.optInt("type"),
|
||||||
|
jsonObject.optString("thumbUrl")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return list[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun fetchCategory(
|
||||||
|
key: String,
|
||||||
|
offset: Int,
|
||||||
|
pageSize: Int,
|
||||||
|
callback: NetworkCallback<List<CategoryDataBean>>
|
||||||
|
) {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(GET_CATEGORY_DETAILS_URL + "${key}/resources?offset=${offset}&pageSize=${pageSize}&sign=a28aadc61c76c754944f6ddb48962c9c")
|
||||||
|
.get()
|
||||||
|
.addHeader(HEADER_KEY, HEADER_KEY_VALUE)
|
||||||
|
.addHeader(HEADER_AGENT, HEADER_AGENT_VALUE)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(object : Callback {
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val jsonData = response.body?.string()
|
||||||
|
jsonData?.let {
|
||||||
|
val resultObj = JSONObject(it)
|
||||||
|
if (resultObj.optBoolean("success")) {
|
||||||
|
val dataObj = resultObj.optJSONObject("data")
|
||||||
|
dataObj?.let {
|
||||||
|
val sectionsArray = dataObj.optJSONArray("sections")
|
||||||
|
sectionsArray?.let {
|
||||||
|
callback.onSuccess(parseCategoryDataBeanList(sectionsArray))
|
||||||
|
} ?: callback.onFailure("sections array null")
|
||||||
|
} ?: callback.onFailure("Empty Data ")
|
||||||
|
} else {
|
||||||
|
callback.onFailure("success != true")
|
||||||
|
}
|
||||||
|
} ?: callback.onFailure("Empty response body")
|
||||||
|
} else {
|
||||||
|
callback.onFailure("Error: ${response.code}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
callback.onFailure("Exception: ${e.message}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseCategoryDataBeanList(jsonArray: JSONArray): List<CategoryDataBean> {
|
||||||
|
val sectionsList = mutableListOf<CategoryDataBean>()
|
||||||
|
|
||||||
|
for (i in 0 until jsonArray.length()) {
|
||||||
|
val sectionObj = jsonArray.getJSONObject(i)
|
||||||
|
val layout = sectionObj.getInt("layout")
|
||||||
|
val grid = sectionObj.getInt("grid")
|
||||||
|
val type = sectionObj.getInt("type")
|
||||||
|
val sectionTitle = sectionObj.getString("title")
|
||||||
|
val sectionKey = sectionObj.getString("key")
|
||||||
|
|
||||||
|
val itemsArray = sectionObj.getJSONArray("items")
|
||||||
|
val itemsList = mutableListOf<ItemDataBean>()
|
||||||
|
|
||||||
|
for (j in 0 until itemsArray.length()) {
|
||||||
|
val itemObj = itemsArray.getJSONObject(j)
|
||||||
|
val itemKey = itemObj.getString("key")
|
||||||
|
val itemTitle = itemObj.getString("title")
|
||||||
|
val itemType = itemObj.getInt("type")
|
||||||
|
val thumbUrl = itemObj.getString("thumbUrl")
|
||||||
|
val thumbUrlGif = itemObj.getString("thumbUrlGif")
|
||||||
|
|
||||||
|
val themeContentObj = itemObj.getJSONObject("themeContent")
|
||||||
|
val pushIcon = themeContentObj.getString("pushIcon")
|
||||||
|
val themeContent = ThemeContentBean(pushIcon)
|
||||||
|
|
||||||
|
val lockObj = itemObj.getJSONObject("lock")
|
||||||
|
val lockType = lockObj.getInt("type")
|
||||||
|
val lock = LockBean(lockType)
|
||||||
|
|
||||||
|
val item = ItemDataBean(
|
||||||
|
itemKey,
|
||||||
|
itemTitle,
|
||||||
|
itemType,
|
||||||
|
thumbUrl,
|
||||||
|
thumbUrlGif,
|
||||||
|
themeContent,
|
||||||
|
lock
|
||||||
|
)
|
||||||
|
itemsList.add(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
val section = CategoryDataBean(layout, grid, type, sectionTitle, sectionKey, itemsList)
|
||||||
|
sectionsList.add(section)
|
||||||
|
}
|
||||||
|
return sectionsList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getResourceRequest(
|
||||||
|
key: String,
|
||||||
|
callback: NetworkCallback<DetailsBean>
|
||||||
|
) {
|
||||||
|
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("$GET_DETAILS_URL$key?sign=a28aadc61c76c754944f6ddb48962c9c")
|
||||||
|
.get()
|
||||||
|
.addHeader(HEADER_KEY, HEADER_KEY_VALUE)
|
||||||
|
.addHeader(HEADER_AGENT, HEADER_AGENT_VALUE)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(object : Callback {
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val jsonData = response.body?.string()
|
||||||
|
jsonData?.let {
|
||||||
|
val resultObj = JSONObject(it)
|
||||||
|
if (resultObj.optBoolean("success")) {
|
||||||
|
val dataObject = resultObj.getJSONObject("data")
|
||||||
|
val itemObject = dataObject.getJSONObject("item")
|
||||||
|
callback.onSuccess(parseDetailsBean(itemObject))
|
||||||
|
} else {
|
||||||
|
callback.onFailure("success != true")
|
||||||
|
}
|
||||||
|
} ?: callback.onFailure("Empty response body")
|
||||||
|
} else {
|
||||||
|
callback.onFailure("Error: ${response.code}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
callback.onFailure("Exception: ${e.message}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDetailsBean(itemObject: JSONObject): DetailsBean {
|
||||||
|
val key = itemObject.getString("key")
|
||||||
|
val title = itemObject.getString("title")
|
||||||
|
val type = itemObject.getInt("type")
|
||||||
|
val thumbUrl = itemObject.getString("thumbUrl")
|
||||||
|
val pkgName = itemObject.getString("pkgName")
|
||||||
|
val thumbUrlGif = itemObject.optString("thumbUrlGif")
|
||||||
|
|
||||||
|
val contentObject = itemObject.getJSONObject("content")
|
||||||
|
val imageUrl = contentObject.getString("imageUrl")
|
||||||
|
|
||||||
|
val themeContentObject = itemObject.getJSONObject("themeContent")
|
||||||
|
val img = themeContentObject.getString("img")
|
||||||
|
val imgBanner = themeContentObject.getString("imgBanner")
|
||||||
|
val pushIcon = themeContentObject.getString("pushIcon")
|
||||||
|
val pushBanner = themeContentObject.getString("pushBanner")
|
||||||
|
val androidRawZipUrl = themeContentObject.getString("androidRawZipUrl")
|
||||||
|
|
||||||
|
val authorObject = itemObject.getJSONObject("author")
|
||||||
|
val authorName = authorObject.getString("name")
|
||||||
|
val authorKey = authorObject.getString("key")
|
||||||
|
val photoUrl = authorObject.getString("photoUrl")
|
||||||
|
val homeUrl = authorObject.getString("homeUrl")
|
||||||
|
|
||||||
|
val lockObject = itemObject.getJSONObject("lock")
|
||||||
|
val lockType = lockObject.getInt("type")
|
||||||
|
|
||||||
|
return DetailsBean(
|
||||||
|
key,
|
||||||
|
title,
|
||||||
|
type,
|
||||||
|
thumbUrl,
|
||||||
|
pkgName,
|
||||||
|
thumbUrlGif,
|
||||||
|
Content(imageUrl),
|
||||||
|
ThemeDetailsContent(img, imgBanner, "", pushIcon, pushBanner, androidRawZipUrl, ""),
|
||||||
|
Author(authorName, authorKey, photoUrl, homeUrl),
|
||||||
|
LockBean(lockType)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
package com.keyboard.journey.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import net.sf.sevenzipjbinding.ExtractAskMode
|
||||||
|
import net.sf.sevenzipjbinding.ExtractOperationResult
|
||||||
|
import net.sf.sevenzipjbinding.IArchiveExtractCallback
|
||||||
|
import net.sf.sevenzipjbinding.IInArchive
|
||||||
|
import net.sf.sevenzipjbinding.ISequentialOutStream
|
||||||
|
import net.sf.sevenzipjbinding.PropID
|
||||||
|
import net.sf.sevenzipjbinding.SevenZip
|
||||||
|
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream
|
||||||
|
import okio.IOException
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.RandomAccessFile
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
interface OnDownloadListener {
|
||||||
|
fun onDownloadComplete(isDownloaded: Boolean)
|
||||||
|
}
|
||||||
|
class ResourceDownloadUtil(private val context: Context) {
|
||||||
|
private val destinationFolder = context.filesDir.absolutePath
|
||||||
|
|
||||||
|
// 声明回调接口变量
|
||||||
|
private var downloadListener: OnDownloadListener? = null
|
||||||
|
|
||||||
|
// 设置回调监听器
|
||||||
|
fun setOnDownloadListener(listener: OnDownloadListener) {
|
||||||
|
this.downloadListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadAndExtractResources(zipUrl: String): Boolean {
|
||||||
|
var isDownload = false
|
||||||
|
val childString = getFileNameFromUrl(zipUrl)
|
||||||
|
val file = File(destinationFolder, removeFileExtension(childString))
|
||||||
|
var getSubDirectories = ""
|
||||||
|
if (file.exists() && file.isDirectory) {
|
||||||
|
getSubDirectories = getSubDirectories(file)[0]
|
||||||
|
}
|
||||||
|
val sub = removeFileExtension(childString) + "/" + getSubDirectories
|
||||||
|
val subFile = File(destinationFolder, sub)
|
||||||
|
if (!subFile.exists()) {
|
||||||
|
// 创建 DownloadAndExtractTask 时传递回调接口的实例
|
||||||
|
DownloadAndExtractTask(context, zipUrl, object : OnDownloadListener {
|
||||||
|
override fun onDownloadComplete(isDownloaded: Boolean) {
|
||||||
|
// 下载完成后的处理操作
|
||||||
|
// 例如:更新界面、通知用户下载完成等
|
||||||
|
downloadListener?.onDownloadComplete(isDownload)
|
||||||
|
}
|
||||||
|
}).execute()
|
||||||
|
} else {
|
||||||
|
isDownload = true
|
||||||
|
downloadListener?.onDownloadComplete(isDownload)
|
||||||
|
}
|
||||||
|
return isDownload
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class DownloadAndExtractTask(
|
||||||
|
private val context: Context,
|
||||||
|
url: String,
|
||||||
|
private val callback: OnDownloadListener
|
||||||
|
) :
|
||||||
|
AsyncTask<Void, Void, Boolean>() {
|
||||||
|
|
||||||
|
private val zipUrl = url
|
||||||
|
|
||||||
|
override fun doInBackground(vararg params: Void?): Boolean {
|
||||||
|
return try {
|
||||||
|
downloadZipFile()
|
||||||
|
extractZipFile()
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(result: Boolean) {
|
||||||
|
// 下载解压完成后,触发回调
|
||||||
|
callback.onDownloadComplete(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadZipFile() {
|
||||||
|
val url = URL(zipUrl)
|
||||||
|
val connection = url.openConnection()
|
||||||
|
connection.connect()
|
||||||
|
|
||||||
|
val childString = getFileNameFromUrl(zipUrl)
|
||||||
|
val input = BufferedInputStream(url.openStream())
|
||||||
|
val outputFile = File(destinationFolder, childString)
|
||||||
|
val outputStream = FileOutputStream(outputFile)
|
||||||
|
|
||||||
|
val data = ByteArray(1024)
|
||||||
|
var count: Int
|
||||||
|
while (input.read(data, 0, 1024).also { count = it } != -1) {
|
||||||
|
outputStream.write(data, 0, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream.flush()
|
||||||
|
outputStream.close()
|
||||||
|
input.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractZipFile() {
|
||||||
|
val childString = getFileNameFromUrl(zipUrl)
|
||||||
|
val newChildString = removeFileExtension(childString)
|
||||||
|
|
||||||
|
val zipFilePath = "$destinationFolder/$childString"
|
||||||
|
val destDirectory = "$destinationFolder/$newChildString"
|
||||||
|
|
||||||
|
extract7z(zipFilePath, destDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extract7z(archivePath: String, outputPath: String) {
|
||||||
|
|
||||||
|
val archiveFile = File(archivePath)
|
||||||
|
|
||||||
|
if (!archiveFile.exists()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val raf = RandomAccessFile(archiveFile, "r")
|
||||||
|
val inStream = RandomAccessFileInStream(raf)
|
||||||
|
|
||||||
|
val outputDir = File(outputPath)
|
||||||
|
|
||||||
|
if (!outputDir.exists()) {
|
||||||
|
outputDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SevenZip.openInArchive(null, inStream).use { inArchive ->
|
||||||
|
val extractCallback = ArchiveExtractCallback(outputDir, inArchive)
|
||||||
|
inArchive.extract(null, false, extractCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ArchiveExtractCallback(
|
||||||
|
private val outputDir: File,
|
||||||
|
private val inArchive: IInArchive
|
||||||
|
) : IArchiveExtractCallback {
|
||||||
|
override fun getStream(index: Int, extractAskMode: ExtractAskMode?): ISequentialOutStream {
|
||||||
|
val relativeFilePath = getOriginalFileName(index)
|
||||||
|
val outputFile = File(outputDir, relativeFilePath)
|
||||||
|
|
||||||
|
// Create parent directories if they don't exist
|
||||||
|
outputFile.parentFile?.let { parent ->
|
||||||
|
if (!parent.exists()) {
|
||||||
|
parent.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SequentialOutStream(outputFile.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getOriginalFileName(index: Int): String {
|
||||||
|
return inArchive.getStringProperty(index, PropID.PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prepareOperation(extractAskMode: ExtractAskMode?) {
|
||||||
|
// Implement if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOperationResult(extractOperationResult: ExtractOperationResult?) {
|
||||||
|
// Implement if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTotal(total: Long) {
|
||||||
|
// Implement if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCompleted(complete: Long) {
|
||||||
|
// Implement if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SequentialOutStream(private val fileName: String) : ISequentialOutStream {
|
||||||
|
override fun write(data: ByteArray): Int {
|
||||||
|
try {
|
||||||
|
val fileOutputStream = File(fileName).outputStream()
|
||||||
|
fileOutputStream.write(data)
|
||||||
|
fileOutputStream.close()
|
||||||
|
return data.size
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,701 @@
|
|||||||
|
package com.keyboard.journey.view;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.TimeInterpolator;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.LinearGradient;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.keyboard.journey.R;
|
||||||
|
|
||||||
|
import androidx.core.view.animation.PathInterpolatorCompat;
|
||||||
|
|
||||||
|
public class AnimDownloadProgressButton extends androidx.appcompat.widget.AppCompatTextView {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
//背景画笔
|
||||||
|
private Paint mBackgroundPaint;
|
||||||
|
//按钮文字画笔
|
||||||
|
private volatile Paint mTextPaint;
|
||||||
|
//第一个点画笔
|
||||||
|
private Paint mDot1Paint;
|
||||||
|
//第二个点画笔
|
||||||
|
private Paint mDot2Paint;
|
||||||
|
|
||||||
|
|
||||||
|
//背景颜色
|
||||||
|
private int[] mBackgroundColor;
|
||||||
|
private int[] mOriginBackgroundColor;
|
||||||
|
//下载中后半部分后面背景颜色
|
||||||
|
private int mBackgroundSecondColor;
|
||||||
|
//文字颜色
|
||||||
|
private int mTextColor;
|
||||||
|
//覆盖后颜色
|
||||||
|
private int mTextCoverColor;
|
||||||
|
//文字大小
|
||||||
|
private float mAboveTextSize = 50;
|
||||||
|
|
||||||
|
|
||||||
|
private float mProgress = -1;
|
||||||
|
private float mToProgress;
|
||||||
|
private int mMaxProgress;
|
||||||
|
private int mMinProgress;
|
||||||
|
private float mProgressPercent;
|
||||||
|
|
||||||
|
private float mButtonRadius;
|
||||||
|
|
||||||
|
//两个点向右移动距离
|
||||||
|
private float mDot1transX;
|
||||||
|
private float mDot2transX;
|
||||||
|
|
||||||
|
private RectF mBackgroundBounds;
|
||||||
|
private LinearGradient mFillBgGradient;
|
||||||
|
private LinearGradient mProgressBgGradient;
|
||||||
|
private LinearGradient mProgressTextGradient;
|
||||||
|
|
||||||
|
//点运动动画
|
||||||
|
private AnimatorSet mDotAnimationSet;
|
||||||
|
//下载平滑动画
|
||||||
|
private ValueAnimator mProgressAnimation;
|
||||||
|
|
||||||
|
//记录当前文字
|
||||||
|
private CharSequence mCurrentText;
|
||||||
|
|
||||||
|
//普通状态
|
||||||
|
public static final int NORMAL = 0;
|
||||||
|
//下载中
|
||||||
|
public static final int DOWNLOADING = 1;
|
||||||
|
//有点运动状态
|
||||||
|
public static final int INSTALLING = 2;
|
||||||
|
|
||||||
|
private ButtonController mDefaultController;
|
||||||
|
|
||||||
|
private ButtonController mCustomerController;
|
||||||
|
|
||||||
|
|
||||||
|
private int mState;
|
||||||
|
|
||||||
|
public AnimDownloadProgressButton(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnimDownloadProgressButton(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
if (!isInEditMode()) {
|
||||||
|
mContext = context;
|
||||||
|
initController();
|
||||||
|
initAttrs(context, attrs);
|
||||||
|
init();
|
||||||
|
setupAnimations();
|
||||||
|
} else {
|
||||||
|
initController();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initController() {
|
||||||
|
mDefaultController = new DefaultButtonController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawableStateChanged() {
|
||||||
|
super.drawableStateChanged();
|
||||||
|
ButtonController buttonController = switchController();
|
||||||
|
if (buttonController.enablePress()) {
|
||||||
|
if (mOriginBackgroundColor == null) {
|
||||||
|
mOriginBackgroundColor = new int[5];
|
||||||
|
mOriginBackgroundColor[0] = mBackgroundColor[0];
|
||||||
|
mOriginBackgroundColor[1] = mBackgroundColor[1];
|
||||||
|
mOriginBackgroundColor[2] = mBackgroundColor[2];
|
||||||
|
mOriginBackgroundColor[3] = mBackgroundColor[3];
|
||||||
|
mOriginBackgroundColor[4] = mBackgroundColor[4];
|
||||||
|
}
|
||||||
|
if (this.isPressed()) {
|
||||||
|
int pressColorleft = buttonController.getPressedColor(mBackgroundColor[0]);
|
||||||
|
int pressColorright = buttonController.getPressedColor(mBackgroundColor[1]);
|
||||||
|
int pressColor2 = buttonController.getPressedColor(mBackgroundColor[2]);
|
||||||
|
int pressColor3 = buttonController.getPressedColor(mBackgroundColor[3]);
|
||||||
|
int pressColor4 = buttonController.getPressedColor(mBackgroundColor[4]);
|
||||||
|
if (buttonController.enableGradient()) {
|
||||||
|
initGradientColor(pressColorleft, pressColorright, pressColor2, pressColor3, pressColor4);
|
||||||
|
} else {
|
||||||
|
initGradientColor(pressColorleft, pressColorleft, pressColor2, pressColor3, pressColor4);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (buttonController.enableGradient()) {
|
||||||
|
initGradientColor(mOriginBackgroundColor[0], mOriginBackgroundColor[1], mOriginBackgroundColor[2], mOriginBackgroundColor[3], mOriginBackgroundColor[4]);
|
||||||
|
} else {
|
||||||
|
initGradientColor(mOriginBackgroundColor[4], mOriginBackgroundColor[3], mOriginBackgroundColor[2], mOriginBackgroundColor[1], mOriginBackgroundColor[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAttrs(Context context, AttributeSet attrs) {
|
||||||
|
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AnimDownloadProgressButton);
|
||||||
|
int bgColor = a.getColor(R.styleable.AnimDownloadProgressButton_progressbtn_background_color, Color.parseColor("#6699ff"));
|
||||||
|
//初始化背景颜色数组
|
||||||
|
initGradientColor(Color.parseColor("#CDED88"),
|
||||||
|
Color.parseColor("#E8D99F"),
|
||||||
|
Color.parseColor("#83ECCF"),
|
||||||
|
Color.parseColor("#9BBAFC"),
|
||||||
|
Color.parseColor("#92C2FC"));
|
||||||
|
mBackgroundSecondColor = a.getColor(R.styleable.AnimDownloadProgressButton_progressbtn_background_second_color, Color.LTGRAY);
|
||||||
|
mButtonRadius = a.getFloat(R.styleable.AnimDownloadProgressButton_progressbtn_radius, getMeasuredHeight() / 2);
|
||||||
|
mAboveTextSize = a.getFloat(R.styleable.AnimDownloadProgressButton_progressbtn_text_size, 50);
|
||||||
|
mTextColor = a.getColor(R.styleable.AnimDownloadProgressButton_progressbtn_text_color, bgColor);
|
||||||
|
mTextCoverColor = a.getColor(R.styleable.AnimDownloadProgressButton_progressbtn_text_covercolor, Color.WHITE);
|
||||||
|
boolean enableGradient = a.getBoolean(R.styleable.AnimDownloadProgressButton_progressbtn_enable_gradient, false);
|
||||||
|
boolean enablePress = a.getBoolean(R.styleable.AnimDownloadProgressButton_progressbtn_enable_press, false);
|
||||||
|
((DefaultButtonController) mDefaultController).setEnableGradient(enableGradient).setEnablePress(enablePress);
|
||||||
|
if (enableGradient) {
|
||||||
|
initGradientColor(mDefaultController.getLighterColor(mBackgroundColor[0]), mBackgroundColor[1], mBackgroundColor[2], mBackgroundColor[3], mBackgroundColor[4]);
|
||||||
|
}
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
|
||||||
|
mMaxProgress = 100;
|
||||||
|
mMinProgress = 0;
|
||||||
|
mProgress = 0;
|
||||||
|
|
||||||
|
|
||||||
|
//设置背景画笔
|
||||||
|
mBackgroundPaint = new Paint();
|
||||||
|
mBackgroundPaint.setAntiAlias(true);
|
||||||
|
mBackgroundPaint.setStyle(Paint.Style.FILL);
|
||||||
|
|
||||||
|
//设置文字画笔
|
||||||
|
mTextPaint = new Paint();
|
||||||
|
mTextPaint.setAntiAlias(true);
|
||||||
|
mTextPaint.setTextSize(mAboveTextSize);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
//解决文字有时候画不出问题
|
||||||
|
setLayerType(LAYER_TYPE_SOFTWARE, mTextPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置第一个点画笔
|
||||||
|
mDot1Paint = new Paint();
|
||||||
|
mDot1Paint.setAntiAlias(true);
|
||||||
|
mDot1Paint.setTextSize(mAboveTextSize);
|
||||||
|
|
||||||
|
//设置第二个点画笔
|
||||||
|
mDot2Paint = new Paint();
|
||||||
|
mDot2Paint.setAntiAlias(true);
|
||||||
|
mDot2Paint.setTextSize(mAboveTextSize);
|
||||||
|
|
||||||
|
|
||||||
|
//初始化状态设为NORMAL
|
||||||
|
mState = NORMAL;
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//初始化渐变色
|
||||||
|
private int[] initGradientColor(int color1, int color2, int color3, int color4, int color5) {
|
||||||
|
mBackgroundColor = new int[5];
|
||||||
|
mBackgroundColor[0] = color1;
|
||||||
|
mBackgroundColor[1] = color2;
|
||||||
|
mBackgroundColor[2] = color3;
|
||||||
|
mBackgroundColor[3] = color4;
|
||||||
|
mBackgroundColor[4] = color5;
|
||||||
|
return mBackgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setupAnimations() {
|
||||||
|
|
||||||
|
//两个点向右移动动画
|
||||||
|
ValueAnimator dotMoveAnimation = ValueAnimator.ofFloat(0, 20);
|
||||||
|
TimeInterpolator pathInterpolator = PathInterpolatorCompat.create(0.11f, 0f, 0.12f, 1f);
|
||||||
|
dotMoveAnimation.setInterpolator(pathInterpolator);
|
||||||
|
dotMoveAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
float transX = (float) animation.getAnimatedValue();
|
||||||
|
mDot1transX = transX;
|
||||||
|
mDot2transX = transX;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dotMoveAnimation.setDuration(1243);
|
||||||
|
dotMoveAnimation.setRepeatMode(ValueAnimator.RESTART);
|
||||||
|
dotMoveAnimation.setRepeatCount(ValueAnimator.INFINITE);
|
||||||
|
|
||||||
|
|
||||||
|
//两个点渐显渐隐动画
|
||||||
|
final ValueAnimator dotAlphaAnim = ValueAnimator.ofInt(0, 1243).setDuration(1243);
|
||||||
|
dotAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
int time = (int) dotAlphaAnim.getAnimatedValue();
|
||||||
|
int dot1Alpha = calculateDot1AlphaByTime(time);
|
||||||
|
int dot2Alpha = calculateDot2AlphaByTime(time);
|
||||||
|
mDot1Paint.setColor(mTextCoverColor);
|
||||||
|
mDot2Paint.setColor(mTextCoverColor);
|
||||||
|
mDot1Paint.setAlpha(dot1Alpha);
|
||||||
|
mDot2Paint.setAlpha(dot2Alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
dotAlphaAnim.addListener(new Animator.AnimatorListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
mDot1Paint.setAlpha(0);
|
||||||
|
mDot2Paint.setAlpha(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dotAlphaAnim.setRepeatMode(ValueAnimator.RESTART);
|
||||||
|
dotAlphaAnim.setRepeatCount(ValueAnimator.INFINITE);
|
||||||
|
//两个点的动画集合
|
||||||
|
mDotAnimationSet = new AnimatorSet();
|
||||||
|
mDotAnimationSet.playTogether(dotAlphaAnim, dotMoveAnimation);
|
||||||
|
|
||||||
|
//ProgressBar的动画
|
||||||
|
mProgressAnimation = ValueAnimator.ofFloat(0, 1).setDuration(500);
|
||||||
|
mProgressAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
float timepercent = (float) animation.getAnimatedValue();
|
||||||
|
mProgress = ((mToProgress - mProgress) * timepercent + mProgress);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//第一个点透明度计算函数
|
||||||
|
private int calculateDot2AlphaByTime(int time) {
|
||||||
|
int alpha;
|
||||||
|
if (0 <= time && time <= 83) {
|
||||||
|
double DAlpha = 255.0 / 83.0 * time;
|
||||||
|
alpha = (int) DAlpha;
|
||||||
|
} else if (83 < time && time <= 1000) {
|
||||||
|
alpha = 255;
|
||||||
|
} else if (1000 < time && time <= 1083) {
|
||||||
|
double DAlpha = -255.0 / 83.0 * (time - 1083);
|
||||||
|
alpha = (int) DAlpha;
|
||||||
|
} else if (1083 < time && time <= 1243) {
|
||||||
|
alpha = 0;
|
||||||
|
} else {
|
||||||
|
alpha = 255;
|
||||||
|
}
|
||||||
|
return alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
//第二个点透明度计算函数
|
||||||
|
private int calculateDot1AlphaByTime(int time) {
|
||||||
|
int alpha;
|
||||||
|
if (0 <= time && time <= 160) {
|
||||||
|
alpha = 0;
|
||||||
|
} else if (160 < time && time <= 243) {
|
||||||
|
double DAlpha = 255.0 / 83.0 * (time - 160);
|
||||||
|
alpha = (int) DAlpha;
|
||||||
|
} else if (243 < time && time <= 1160) {
|
||||||
|
alpha = 255;
|
||||||
|
} else if (1160 < time && time <= 1243) {
|
||||||
|
double DAlpha = -255.0 / 83.0 * (time - 1243);
|
||||||
|
alpha = (int) DAlpha;
|
||||||
|
} else {
|
||||||
|
alpha = 255;
|
||||||
|
}
|
||||||
|
return alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ValueAnimator createDotAlphaAnimation(int i, Paint mDot1Paint, int i1, int i2, int i3) {
|
||||||
|
|
||||||
|
return new ValueAnimator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
if (!isInEditMode()) {
|
||||||
|
drawing(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawing(Canvas canvas) {
|
||||||
|
drawBackground(canvas);
|
||||||
|
drawTextAbove(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawBackground(Canvas canvas) {
|
||||||
|
mBackgroundBounds = new RectF();
|
||||||
|
if (mButtonRadius == 0) {
|
||||||
|
mButtonRadius = getMeasuredHeight() / 2;
|
||||||
|
}
|
||||||
|
mBackgroundBounds.left = 2;
|
||||||
|
mBackgroundBounds.top = 2;
|
||||||
|
mBackgroundBounds.right = getMeasuredWidth() - 2;
|
||||||
|
mBackgroundBounds.bottom = getMeasuredHeight() - 2;
|
||||||
|
|
||||||
|
ButtonController buttonController = switchController();
|
||||||
|
|
||||||
|
//color
|
||||||
|
switch (mState) {
|
||||||
|
case NORMAL:
|
||||||
|
if (buttonController.enableGradient()) {
|
||||||
|
mFillBgGradient = new LinearGradient(0,
|
||||||
|
getMeasuredHeight() / 2,
|
||||||
|
getMeasuredWidth(),
|
||||||
|
getMeasuredHeight() / 2,
|
||||||
|
mBackgroundColor,
|
||||||
|
null,
|
||||||
|
Shader.TileMode.CLAMP);
|
||||||
|
mBackgroundPaint.setShader(mFillBgGradient);
|
||||||
|
} else {
|
||||||
|
if (mBackgroundPaint.getShader() != null) {
|
||||||
|
mBackgroundPaint.setShader(null);
|
||||||
|
}
|
||||||
|
mBackgroundPaint.setColor(mBackgroundColor[0]);
|
||||||
|
}
|
||||||
|
canvas.drawRoundRect(mBackgroundBounds, mButtonRadius, mButtonRadius, mBackgroundPaint);
|
||||||
|
break;
|
||||||
|
case DOWNLOADING:
|
||||||
|
if (buttonController.enableGradient()) {
|
||||||
|
mProgressPercent = mProgress / (mMaxProgress + 0f);
|
||||||
|
int[] colorList = new int[]{mBackgroundColor[0], mBackgroundColor[1], mBackgroundSecondColor};
|
||||||
|
mProgressBgGradient = new LinearGradient(0, 0, getMeasuredWidth(), 0,
|
||||||
|
colorList,
|
||||||
|
new float[]{0, mProgressPercent, mProgressPercent + 0.001f},
|
||||||
|
Shader.TileMode.CLAMP
|
||||||
|
);
|
||||||
|
mBackgroundPaint.setShader(mProgressBgGradient);
|
||||||
|
} else {
|
||||||
|
mProgressPercent = mProgress / (mMaxProgress + 0f);
|
||||||
|
mProgressBgGradient = new LinearGradient(0, 0, getMeasuredWidth(), 0,
|
||||||
|
new int[]{mBackgroundColor[0], mBackgroundSecondColor},
|
||||||
|
new float[]{mProgressPercent, mProgressPercent + 0.001f},
|
||||||
|
Shader.TileMode.CLAMP
|
||||||
|
);
|
||||||
|
mBackgroundPaint.setColor(mBackgroundColor[0]);
|
||||||
|
mBackgroundPaint.setShader(mProgressBgGradient);
|
||||||
|
}
|
||||||
|
canvas.drawRoundRect(mBackgroundBounds, mButtonRadius, mButtonRadius, mBackgroundPaint);
|
||||||
|
break;
|
||||||
|
case INSTALLING:
|
||||||
|
if (buttonController.enableGradient()) {
|
||||||
|
mFillBgGradient = new LinearGradient(0, getMeasuredHeight() / 2, getMeasuredWidth(), getMeasuredHeight() / 2,
|
||||||
|
mBackgroundColor,
|
||||||
|
null,
|
||||||
|
Shader.TileMode.CLAMP);
|
||||||
|
mBackgroundPaint.setShader(mFillBgGradient);
|
||||||
|
} else {
|
||||||
|
mBackgroundPaint.setShader(null);
|
||||||
|
mBackgroundPaint.setColor(mBackgroundColor[0]);
|
||||||
|
}
|
||||||
|
canvas.drawRoundRect(mBackgroundBounds, mButtonRadius, mButtonRadius, mBackgroundPaint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTextAbove(Canvas canvas) {
|
||||||
|
final float y = canvas.getHeight() / 2 - (mTextPaint.descent() / 2 + mTextPaint.ascent() / 2);
|
||||||
|
if (mCurrentText == null) {
|
||||||
|
mCurrentText = "";
|
||||||
|
}
|
||||||
|
final float textWidth = mTextPaint.measureText(mCurrentText.toString());
|
||||||
|
//color
|
||||||
|
switch (mState) {
|
||||||
|
case NORMAL:
|
||||||
|
mTextPaint.setShader(null);
|
||||||
|
mTextPaint.setColor(mTextCoverColor);
|
||||||
|
canvas.drawText(mCurrentText.toString(), (getMeasuredWidth() - textWidth) / 2, y, mTextPaint);
|
||||||
|
break;
|
||||||
|
case DOWNLOADING:
|
||||||
|
|
||||||
|
//进度条压过距离
|
||||||
|
float coverlength = getMeasuredWidth() * mProgressPercent;
|
||||||
|
//开始渐变指示器
|
||||||
|
float indicator1 = getMeasuredWidth() / 2 - textWidth / 2;
|
||||||
|
//结束渐变指示器
|
||||||
|
float indicator2 = getMeasuredWidth() / 2 + textWidth / 2;
|
||||||
|
//文字变色部分的距离
|
||||||
|
float coverTextLength = textWidth / 2 - getMeasuredWidth() / 2 + coverlength;
|
||||||
|
float textProgress = coverTextLength / textWidth;
|
||||||
|
if (coverlength <= indicator1) {
|
||||||
|
mTextPaint.setShader(null);
|
||||||
|
mTextPaint.setColor(mTextColor);
|
||||||
|
} else if (indicator1 < coverlength && coverlength <= indicator2) {
|
||||||
|
mProgressTextGradient = new LinearGradient((getMeasuredWidth() - textWidth) / 2, 0, (getMeasuredWidth() + textWidth) / 2, 0,
|
||||||
|
new int[]{mTextCoverColor, mTextColor},
|
||||||
|
new float[]{textProgress, textProgress + 0.001f},
|
||||||
|
Shader.TileMode.CLAMP);
|
||||||
|
mTextPaint.setColor(mTextColor);
|
||||||
|
mTextPaint.setShader(mProgressTextGradient);
|
||||||
|
} else {
|
||||||
|
mTextPaint.setShader(null);
|
||||||
|
mTextPaint.setColor(mTextCoverColor);
|
||||||
|
}
|
||||||
|
canvas.drawText(mCurrentText.toString(), (getMeasuredWidth() - textWidth) / 2, y, mTextPaint);
|
||||||
|
break;
|
||||||
|
case INSTALLING:
|
||||||
|
mTextPaint.setColor(mTextCoverColor);
|
||||||
|
canvas.drawText(mCurrentText.toString(), (getMeasuredWidth() - textWidth) / 2, y, mTextPaint);
|
||||||
|
canvas.drawCircle((getMeasuredWidth() + textWidth) / 2 + 4 + mDot1transX, y, 4, mDot1Paint);
|
||||||
|
canvas.drawCircle((getMeasuredWidth() + textWidth) / 2 + 24 + mDot2transX, y, 4, mDot2Paint);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private ButtonController switchController() {
|
||||||
|
if (mCustomerController != null) {
|
||||||
|
return mCustomerController;
|
||||||
|
} else {
|
||||||
|
return mDefaultController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getState() {
|
||||||
|
return mState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(int state) {
|
||||||
|
if (mState != state) {//状态确实有改变
|
||||||
|
this.mState = state;
|
||||||
|
invalidate();
|
||||||
|
if (state == AnimDownloadProgressButton.INSTALLING) {
|
||||||
|
//开启两个点动画
|
||||||
|
mDotAnimationSet.start();
|
||||||
|
} else if (state == NORMAL) {
|
||||||
|
mDotAnimationSet.cancel();
|
||||||
|
} else if (state == DOWNLOADING) {
|
||||||
|
mDotAnimationSet.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置按钮文字
|
||||||
|
*/
|
||||||
|
public void setCurrentText(CharSequence charSequence) {
|
||||||
|
mCurrentText = charSequence;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置带下载进度的文字
|
||||||
|
*/
|
||||||
|
@SuppressLint("StringFormatMatches")
|
||||||
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
|
public void setProgressText(String text, float progress) {
|
||||||
|
if (progress >= mMinProgress && progress < mMaxProgress) {
|
||||||
|
mCurrentText = text + getResources().getString(R.string.downloaded, (int) progress);
|
||||||
|
mToProgress = progress;
|
||||||
|
if (mProgressAnimation.isRunning()) {
|
||||||
|
mProgressAnimation.start();
|
||||||
|
} else {
|
||||||
|
mProgressAnimation.start();
|
||||||
|
}
|
||||||
|
} else if (progress < mMinProgress) {
|
||||||
|
mProgress = 0;
|
||||||
|
} else if (progress >= mMaxProgress) {
|
||||||
|
mProgress = 100;
|
||||||
|
mCurrentText = text + getResources().getString(R.string.downloaded, (int) mProgress);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getProgress() {
|
||||||
|
return mProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(float progress) {
|
||||||
|
this.mProgress = progress;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sometimes you should use the method to avoid memory leak
|
||||||
|
*/
|
||||||
|
public void removeAllAnim() {
|
||||||
|
mDotAnimationSet.cancel();
|
||||||
|
mDotAnimationSet.removeAllListeners();
|
||||||
|
mProgressAnimation.cancel();
|
||||||
|
mProgressAnimation.removeAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public void setProgressBtnBackgroundColor(int color) {
|
||||||
|
// initGradientColor(color, color);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
public void setProgressBtnBackgroundSecondColor(int color) {
|
||||||
|
|
||||||
|
mBackgroundSecondColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getButtonRadius() {
|
||||||
|
return mButtonRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setButtonRadius(float buttonRadius) {
|
||||||
|
mButtonRadius = buttonRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTextColor() {
|
||||||
|
return mTextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTextColor(int textColor) {
|
||||||
|
mTextColor = textColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTextCoverColor() {
|
||||||
|
return mTextCoverColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextCoverColor(int textCoverColor) {
|
||||||
|
mTextCoverColor = textCoverColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinProgress() {
|
||||||
|
return mMinProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinProgress(int minProgress) {
|
||||||
|
mMinProgress = minProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxProgress() {
|
||||||
|
return mMaxProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxProgress(int maxProgress) {
|
||||||
|
mMaxProgress = maxProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enabelDefaultPress(boolean enable) {
|
||||||
|
if (mDefaultController != null) {
|
||||||
|
((DefaultButtonController) mDefaultController).setEnablePress(enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void enabelDefaultGradient(boolean enable) {
|
||||||
|
// if (mDefaultController != null) {
|
||||||
|
// ((DefaultButtonController) mDefaultController).setEnableGradient(enable);
|
||||||
|
// initGradientColor(mDefaultController.getLighterColor(mBackgroundColor[0]), mBackgroundColor[0]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTextSize(float size) {
|
||||||
|
mAboveTextSize = size;
|
||||||
|
mTextPaint.setTextSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getTextSize() {
|
||||||
|
return mAboveTextSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnimDownloadProgressButton setCustomerController(ButtonController customerController) {
|
||||||
|
mCustomerController = customerController;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRestoreInstanceState(Parcelable state) {
|
||||||
|
SavedState ss = (SavedState) state;
|
||||||
|
super.onRestoreInstanceState(ss.getSuperState());
|
||||||
|
mState = ss.state;
|
||||||
|
mProgress = ss.progress;
|
||||||
|
mCurrentText = ss.currentText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Parcelable onSaveInstanceState() {
|
||||||
|
Parcelable superState = super.onSaveInstanceState();
|
||||||
|
return new SavedState(superState, (int) mProgress, mState, mCurrentText.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SavedState extends BaseSavedState {
|
||||||
|
|
||||||
|
private int progress;
|
||||||
|
private int state;
|
||||||
|
private String currentText;
|
||||||
|
|
||||||
|
public SavedState(Parcelable parcel, int progress, int state, String currentText) {
|
||||||
|
super(parcel);
|
||||||
|
this.progress = progress;
|
||||||
|
this.state = state;
|
||||||
|
this.currentText = currentText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SavedState(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
progress = in.readInt();
|
||||||
|
state = in.readInt();
|
||||||
|
currentText = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
|
super.writeToParcel(out, flags);
|
||||||
|
out.writeInt(progress);
|
||||||
|
out.writeInt(state);
|
||||||
|
out.writeString(currentText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SavedState createFromParcel(Parcel in) {
|
||||||
|
return new SavedState(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SavedState[] newArray(int size) {
|
||||||
|
return new SavedState[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
package com.keyboard.journey.view
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
|
||||||
|
interface ButtonController {
|
||||||
|
fun getPressedColor(color: Int): Int
|
||||||
|
|
||||||
|
fun getLighterColor(color: Int): Int
|
||||||
|
|
||||||
|
fun getDarkerColor(color: Int): Int
|
||||||
|
|
||||||
|
fun enablePress(): Boolean
|
||||||
|
|
||||||
|
fun enableGradient(): Boolean
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultButtonController : ButtonController {
|
||||||
|
private var enablePress = false
|
||||||
|
|
||||||
|
private var enableGradient = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得按下的颜色(明度降低10%)
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
override fun getPressedColor(color: Int): Int {
|
||||||
|
val hsv = FloatArray(3)
|
||||||
|
Color.colorToHSV(color, hsv)
|
||||||
|
hsv[2] -= 0.1f
|
||||||
|
return Color.HSVToColor(hsv)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由右边的颜色算出左边的颜色(左边的颜色比右边的颜色降饱和度30%,亮度增加30%)
|
||||||
|
* +
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override fun getLighterColor(color: Int): Int {
|
||||||
|
val hsv = FloatArray(3)
|
||||||
|
Color.colorToHSV(color, hsv)
|
||||||
|
hsv[1] -= 0.3f
|
||||||
|
hsv[2] += 0.3f
|
||||||
|
return Color.HSVToColor(hsv)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由左边的颜色生成右边的颜色
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
override fun getDarkerColor(color: Int): Int {
|
||||||
|
val hsv = FloatArray(3)
|
||||||
|
Color.colorToHSV(color, hsv)
|
||||||
|
hsv[1] += 0.3f
|
||||||
|
hsv[2] -= 0.3f
|
||||||
|
return Color.HSVToColor(hsv)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enablePress(): Boolean {
|
||||||
|
return enablePress
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enableGradient(): Boolean {
|
||||||
|
return enableGradient
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEnablePress(enablePress: Boolean): DefaultButtonController? {
|
||||||
|
this.enablePress = enablePress
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEnableGradient(enableGradient: Boolean): DefaultButtonController? {
|
||||||
|
this.enableGradient = enableGradient
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package com.keyboard.journey.view
|
||||||
|
|
||||||
|
|
||||||
|
open class JourneyKeyboard {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Shift键 -> 一般用来切换键盘大小写字母
|
||||||
|
*/
|
||||||
|
const val KEYCODE_SHIFT = -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模式改变 -> 切换键盘输入法
|
||||||
|
*/
|
||||||
|
const val KEYCODE_MODE_CHANGE = -2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消键 -> 关闭输入法
|
||||||
|
*/
|
||||||
|
const val KEYCODE_CANCEL = -3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成键 -> 长出现在右下角蓝色的完成按钮
|
||||||
|
*/
|
||||||
|
const val KEYCODE_DONE = -4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除键 -> 删除输入框内容
|
||||||
|
*/
|
||||||
|
const val KEYCODE_DELETE = -5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘按键 -> 返回(返回,适用于切换键盘后界面使用,如:NORMAL_MODE_CHANGE或CUSTOM_MODE_CHANGE键盘)
|
||||||
|
*/
|
||||||
|
const val KEYCODE_MODE_BACK = -101
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘按键 ->返回(直接返回到最初,直接返回到NORMAL或CUSTOM键盘)
|
||||||
|
*/
|
||||||
|
const val KEYCODE_BACK = -102
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘按键 ->更多
|
||||||
|
*/
|
||||||
|
const val KEYCODE_MORE = -103
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘类型
|
||||||
|
*/
|
||||||
|
object KeyboardType {
|
||||||
|
/**
|
||||||
|
* 默认键盘 - 字母带符号
|
||||||
|
*/
|
||||||
|
const val NORMAL = 0x00000001
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,497 @@
|
|||||||
|
package com.keyboard.journey.view
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
|
import com.keyboard.journey.R
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_BACK
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_CANCEL
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_DELETE
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_DONE
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_DOT
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_MODE_BACK
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_MODE_CHANGE
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_MORE
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_NONE
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_QUESTION_MARK
|
||||||
|
import com.keyboard.journey.view.JourneyKeyboardView.Config.Companion.KEYCODE_SPACE
|
||||||
|
|
||||||
|
open class JourneyKeyboardView : KeyboardView {
|
||||||
|
|
||||||
|
|
||||||
|
private var isCap = false
|
||||||
|
|
||||||
|
private var isAllCaps = false
|
||||||
|
|
||||||
|
private lateinit var config: Config
|
||||||
|
|
||||||
|
private val paint by lazy { Paint() }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val iconRatio = 0.5f
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
||||||
|
init(context, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||||
|
context,
|
||||||
|
attrs,
|
||||||
|
defStyleAttr
|
||||||
|
) {
|
||||||
|
init(context, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet?,
|
||||||
|
defStyleAttr: Int,
|
||||||
|
defStyleRes: Int
|
||||||
|
) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
init(context, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun init(context: Context, attrs: AttributeSet?) {
|
||||||
|
config = Config(context)
|
||||||
|
|
||||||
|
var a = context.obtainStyledAttributes(attrs, R.styleable.journeyKeyboardView)
|
||||||
|
|
||||||
|
a.indexCount.let {
|
||||||
|
config.run {
|
||||||
|
for (i in 0 until it) {
|
||||||
|
when (val attr = a.getIndex(i)) {
|
||||||
|
R.styleable.journeyKeyboardView_kkbDeleteDrawable -> deleteDrawable =
|
||||||
|
a.getDrawable(attr)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbCapitalDrawable -> capitalDrawable =
|
||||||
|
a.getDrawable(attr)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbCapitalLockDrawable -> capitalLockDrawable =
|
||||||
|
a.getDrawable(attr)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_android_labelTextSize -> labelTextSize =
|
||||||
|
a.getDimensionPixelSize(attr, labelTextSize)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_android_keyTextSize -> keyTextSize =
|
||||||
|
a.getDimensionPixelSize(attr, keyTextSize)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_android_keyTextColor -> keyTextColor =
|
||||||
|
a.getColor(attr, keyTextColor)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbKeyIconColor -> keyIconColor = a.getColor(
|
||||||
|
attr,
|
||||||
|
ContextCompat.getColor(context, R.color.journey_keyboard_key_icon_color)
|
||||||
|
)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbKeySpecialTextColor -> keySpecialTextColor =
|
||||||
|
a.getColor(attr, keySpecialTextColor)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbKeyDoneTextColor -> keyDoneTextColor =
|
||||||
|
a.getColor(attr, keyDoneTextColor)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbKeyNoneTextColor -> keyNoneTextColor =
|
||||||
|
a.getColor(attr, keyNoneTextColor)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_android_keyBackground -> keyBackground =
|
||||||
|
a.getDrawable(attr)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbSpecialKeyBackground -> specialKeyBackground =
|
||||||
|
a.getDrawable(attr)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbDoneKeyBackground -> doneKeyBackground =
|
||||||
|
a.getDrawable(attr)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbNoneKeyBackground -> noneKeyBackground =
|
||||||
|
a.getDrawable(attr)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbKeyDoneTextSize -> keyDoneTextSize =
|
||||||
|
a.getDimensionPixelSize(attr, keyDoneTextSize)
|
||||||
|
|
||||||
|
R.styleable.journeyKeyboardView_kkbKeyDoneText -> keyDoneText =
|
||||||
|
a.getString(attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
paint.textAlign = Paint.Align.CENTER
|
||||||
|
paint.isAntiAlias = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getConfig(): Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setConfig(config: Config) {
|
||||||
|
this.config = config
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
drawKeyboard(canvas, keyboard?.keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制键盘
|
||||||
|
*/
|
||||||
|
private fun drawKeyboard(canvas: Canvas, keys: List<Keyboard.Key>?) {
|
||||||
|
keys?.let {
|
||||||
|
for (key in it) {
|
||||||
|
drawKey(canvas, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制键盘按键
|
||||||
|
*/
|
||||||
|
private fun drawKey(canvas: Canvas, key: Keyboard.Key) {
|
||||||
|
when (key.codes[0]) {
|
||||||
|
Keyboard.KEYCODE_SHIFT -> drawShiftKey(canvas, key)
|
||||||
|
KEYCODE_MODE_CHANGE -> drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.toggleKeyTextColor
|
||||||
|
)
|
||||||
|
|
||||||
|
KEYCODE_CANCEL -> drawCancelKey(canvas, key)
|
||||||
|
KEYCODE_DONE -> drawDoneKey(canvas, key)
|
||||||
|
KEYCODE_DELETE -> drawDeleteKey(canvas, key)
|
||||||
|
KEYCODE_SPACE ->
|
||||||
|
config.keySpecialText.let {
|
||||||
|
key.label = it
|
||||||
|
drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.toggleKeyTextColor,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
KEYCODE_DOT->{
|
||||||
|
drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.toggleKeyTextColor,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KEYCODE_QUESTION_MARK->{
|
||||||
|
drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.toggleKeyTextColor,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KEYCODE_NONE -> drawNoneKey(canvas, key)
|
||||||
|
KEYCODE_MODE_BACK -> drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.toggleKeyTextColor
|
||||||
|
)
|
||||||
|
|
||||||
|
KEYCODE_BACK -> drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.toggleKeyTextColor
|
||||||
|
)
|
||||||
|
|
||||||
|
KEYCODE_MORE -> drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.toggleKeyTextColor
|
||||||
|
)
|
||||||
|
|
||||||
|
in -399..-300 -> drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.keyTextColor
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> drawKey(canvas, key, config.keyBackground, config.keyTextColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawCancelKey(canvas: Canvas, key: Keyboard.Key) {
|
||||||
|
config.keyDoneText?.let {
|
||||||
|
key.label = it
|
||||||
|
}
|
||||||
|
drawKey(canvas, key, config.toggleKeyBackground, config.toggleKeyTextColor, null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawDoneKey(canvas: Canvas, key: Keyboard.Key) {
|
||||||
|
config.keyDoneText?.let {
|
||||||
|
key.label = it
|
||||||
|
}
|
||||||
|
drawKey(canvas, key, config.toggleKeyBackground, config.toggleKeyTextColor, null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawNoneKey(canvas: Canvas, key: Keyboard.Key) {
|
||||||
|
drawKey(canvas, key, config.noneKeyBackground, config.keyNoneTextColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawDeleteKey(canvas: Canvas, key: Keyboard.Key) {
|
||||||
|
drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.keySpecialTextColor,
|
||||||
|
config.deleteDrawable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawShiftKey(canvas: Canvas, key: Keyboard.Key) {
|
||||||
|
when {
|
||||||
|
isAllCaps -> drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.keySpecialTextColor,
|
||||||
|
config.capitalLockDrawable
|
||||||
|
)
|
||||||
|
|
||||||
|
isCap -> drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.keySpecialTextColor,
|
||||||
|
config.capitalDrawable
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> drawKey(
|
||||||
|
canvas,
|
||||||
|
key,
|
||||||
|
config.toggleKeyBackground,
|
||||||
|
config.keySpecialTextColor,
|
||||||
|
config.lowerDrawable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制键盘按键
|
||||||
|
*/
|
||||||
|
private fun drawKey(
|
||||||
|
canvas: Canvas,
|
||||||
|
key: Keyboard.Key,
|
||||||
|
keyBackground: Drawable?,
|
||||||
|
textColor: Int,
|
||||||
|
iconDrawable: Drawable? = key.icon,
|
||||||
|
isDone: Boolean = false
|
||||||
|
) {
|
||||||
|
//绘制按键背景
|
||||||
|
keyBackground?.run {
|
||||||
|
if (key.codes[0] != 0) {
|
||||||
|
state = key.currentDrawableState
|
||||||
|
}
|
||||||
|
|
||||||
|
setBounds(
|
||||||
|
key.x.plus(paddingLeft),
|
||||||
|
key.y.plus(paddingTop),
|
||||||
|
key.x.plus(paddingLeft).plus(key.width),
|
||||||
|
key.y.plus(paddingTop).plus(key.height)
|
||||||
|
)
|
||||||
|
draw(canvas)
|
||||||
|
}
|
||||||
|
|
||||||
|
//绘制键盘图标
|
||||||
|
iconDrawable?.run {
|
||||||
|
|
||||||
|
val drawable = DrawableCompat.wrap(this)
|
||||||
|
config.keyIconColor?.takeIf { it != 0 }?.let {
|
||||||
|
drawable.setTint(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
key.icon = drawable
|
||||||
|
|
||||||
|
var iconWidth = key.icon.intrinsicWidth.toFloat()
|
||||||
|
var iconHeight = key.icon.intrinsicHeight.toFloat()
|
||||||
|
|
||||||
|
val widthRatio = iconWidth.div(key.width.toFloat())
|
||||||
|
val heightRatio = iconHeight.div(key.height.toFloat())
|
||||||
|
|
||||||
|
if (widthRatio <= heightRatio) {//当图标的宽占比小于等于高占比时,以高度比例为基准并控制在iconRatio比例范围内,进行同比例缩放
|
||||||
|
|
||||||
|
val ratio = heightRatio.coerceAtMost(iconRatio)
|
||||||
|
iconWidth = iconWidth.div(heightRatio).times(ratio)
|
||||||
|
iconHeight = iconHeight.div(heightRatio).times(ratio)
|
||||||
|
|
||||||
|
} else {//反之,则以宽度比例为基准并控制在iconRatio比例范围内,进行同比例缩放
|
||||||
|
|
||||||
|
val ratio = widthRatio.coerceAtMost(iconRatio)
|
||||||
|
iconWidth = iconWidth.div(widthRatio).times(ratio)
|
||||||
|
iconHeight = iconHeight.div(widthRatio).times(ratio)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val left = key.x.plus(paddingLeft).plus(key.width.minus(iconWidth).div(2f)).toInt()
|
||||||
|
val top = key.y.plus(paddingTop).plus(key.height.minus(iconHeight).div(2f)).toInt()
|
||||||
|
val right = left.plus(iconWidth).toInt()
|
||||||
|
val bottom = top.plus(iconHeight).toInt()
|
||||||
|
key.icon.setBounds(left, top, right, bottom)
|
||||||
|
key.icon.draw(canvas)
|
||||||
|
|
||||||
|
} ?: key.label?.let {
|
||||||
|
//绘制键盘文字
|
||||||
|
if (isDone) {
|
||||||
|
paint.textSize = config.keyDoneTextSize.toFloat()
|
||||||
|
} else if (it.length > 1 && key.codes.size < 2) {// 键盘key内容多个字符
|
||||||
|
paint.textSize = config.labelTextSize.toFloat()
|
||||||
|
} else {
|
||||||
|
paint.textSize = config.keyTextSize.toFloat()
|
||||||
|
}
|
||||||
|
paint.color = textColor
|
||||||
|
paint.typeface = Typeface.DEFAULT
|
||||||
|
|
||||||
|
canvas.drawText(
|
||||||
|
it.toString(),
|
||||||
|
key.x.plus(paddingLeft).plus(key.width.div(2f)),
|
||||||
|
key.y.plus(paddingTop).plus(key.height.div(2.0f)).plus(
|
||||||
|
paint.textSize.minus(paint.descent()).div(2.0f)
|
||||||
|
),
|
||||||
|
paint
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setCap(isCap: Boolean) {
|
||||||
|
this.isCap = isCap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCap(): Boolean {
|
||||||
|
return isCap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAllCaps(isAllCaps: Boolean) {
|
||||||
|
this.isAllCaps = isAllCaps
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAllCaps(): Boolean {
|
||||||
|
return isAllCaps
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config为journeyKeyboard的配置类,方便统一管理配置信息
|
||||||
|
*/
|
||||||
|
open class Config(context: Context) {
|
||||||
|
|
||||||
|
var deleteDrawable = context.getDrawable(R.drawable.delete_key_background)
|
||||||
|
var lowerDrawable = context.getDrawable(R.mipmap.sym_keyboard_shift)
|
||||||
|
var capitalDrawable = context.getDrawable(R.mipmap.sym_keyboard_shift)
|
||||||
|
var capitalLockDrawable = context.getDrawable(R.mipmap.sym_keyboard_shift_locked)
|
||||||
|
|
||||||
|
var labelTextSize =
|
||||||
|
context.resources.getDimensionPixelSize(R.dimen.journey_keyboard_label_text_size)
|
||||||
|
|
||||||
|
var keyTextSize = context.resources.getDimensionPixelSize(R.dimen.journey_keyboard_text_size)
|
||||||
|
|
||||||
|
var keyTextColor = ContextCompat.getColor(context, R.color.purple_200)
|
||||||
|
|
||||||
|
var keyIconColor: Int? = null
|
||||||
|
|
||||||
|
var keySpecialTextColor =
|
||||||
|
ContextCompat.getColor(context, R.color.purple_500)
|
||||||
|
|
||||||
|
var keyDoneTextColor =
|
||||||
|
ContextCompat.getColor(context, R.color.purple_700)
|
||||||
|
|
||||||
|
var keyNoneTextColor =
|
||||||
|
ContextCompat.getColor(context, R.color.teal_200)
|
||||||
|
|
||||||
|
var keyBackground = context.getDrawable(R.drawable.key_background)
|
||||||
|
|
||||||
|
var specialKeyBackground = context.getDrawable(R.drawable.key_background)
|
||||||
|
|
||||||
|
var toggleKeyBackground = context.getDrawable(R.drawable.key_background)
|
||||||
|
|
||||||
|
var toggleKeyTextColor = ContextCompat.getColor(context, R.color.purple_500)
|
||||||
|
|
||||||
|
var doneKeyBackground = context.getDrawable(R.drawable.key_background)
|
||||||
|
var noneKeyBackground = context.getDrawable(R.drawable.key_background)
|
||||||
|
|
||||||
|
var keyDoneTextSize =
|
||||||
|
context.resources.getDimensionPixelSize(R.dimen.journey_keyboard_done_text_size)
|
||||||
|
|
||||||
|
var keyDoneText: CharSequence? = context.getString(R.string.journey_keyboard_key_done_text)
|
||||||
|
|
||||||
|
|
||||||
|
var keySpecialText: CharSequence? = "English"
|
||||||
|
|
||||||
|
companion object{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift键 -> 一般用来切换键盘大小写字母
|
||||||
|
*/
|
||||||
|
const val KEYCODE_SHIFT = -1
|
||||||
|
/**
|
||||||
|
* 模式改变 -> 切换键盘输入法
|
||||||
|
*/
|
||||||
|
const val KEYCODE_MODE_CHANGE = -2
|
||||||
|
/**
|
||||||
|
* 取消键 -> 关闭输入法
|
||||||
|
*/
|
||||||
|
const val KEYCODE_CANCEL = -3
|
||||||
|
/**
|
||||||
|
* 完成键 -> 长出现在右下角蓝色的完成按钮
|
||||||
|
*/
|
||||||
|
const val KEYCODE_DONE = -4
|
||||||
|
/**
|
||||||
|
* 删除键 -> 删除输入框内容
|
||||||
|
*/
|
||||||
|
const val KEYCODE_DELETE = -5
|
||||||
|
/**
|
||||||
|
* 空格键
|
||||||
|
*/
|
||||||
|
const val KEYCODE_SPACE = 32
|
||||||
|
|
||||||
|
const val KEYCODE_DOT = 46
|
||||||
|
|
||||||
|
const val KEYCODE_QUESTION_MARK = 63
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无作用键 -> 一般用来占位或者禁用按键
|
||||||
|
*/
|
||||||
|
const val KEYCODE_NONE = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘按键 -> 返回(返回,适用于切换键盘后界面使用,如:NORMAL_MODE_CHANGE或CUSTOM_MODE_CHANGE键盘)
|
||||||
|
*/
|
||||||
|
const val KEYCODE_MODE_BACK = -101
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘按键 ->返回(直接返回到最初,直接返回到NORMAL或CUSTOM键盘)
|
||||||
|
*/
|
||||||
|
const val KEYCODE_BACK = -102
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 键盘按键 ->更多
|
||||||
|
*/
|
||||||
|
const val KEYCODE_MORE = -103
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
899
app/src/main/java/com/keyboard/journey/view/Keyboard.java
Normal file
@ -0,0 +1,899 @@
|
|||||||
|
package com.keyboard.journey.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.content.res.XmlResourceParser;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.util.Xml;
|
||||||
|
|
||||||
|
import com.keyboard.journey.R;
|
||||||
|
|
||||||
|
import androidx.annotation.XmlRes;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
|
||||||
|
* consists of rows of keys.
|
||||||
|
* <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
|
||||||
|
* <pre>
|
||||||
|
* <Keyboard
|
||||||
|
* android:keyWidth="%10p"
|
||||||
|
* android:keyHeight="50px"
|
||||||
|
* android:horizontalGap="2px"
|
||||||
|
* android:verticalGap="2px" >
|
||||||
|
* <Row android:keyWidth="32px" >
|
||||||
|
* <Key android:keyLabel="A" />
|
||||||
|
* ...
|
||||||
|
* </Row>
|
||||||
|
* ...
|
||||||
|
* </Keyboard>
|
||||||
|
* </pre>
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_keyWidth
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_keyHeight
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_horizontalGap
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_verticalGap
|
||||||
|
* This class is deprecated because this is just a convenient UI widget class that
|
||||||
|
* application developers can re-implement on top of existing public APIs. If you have
|
||||||
|
* already depended on this class, consider copying the implementation from AOSP into
|
||||||
|
* your project or re-implementing a similar widget by yourselves
|
||||||
|
*/
|
||||||
|
public class Keyboard {
|
||||||
|
|
||||||
|
static final String TAG = "Keyboard";
|
||||||
|
|
||||||
|
// Keyboard XML Tags
|
||||||
|
private static final String TAG_KEYBOARD = "Keyboard";
|
||||||
|
private static final String TAG_ROW = "Row";
|
||||||
|
private static final String TAG_KEY = "Key";
|
||||||
|
|
||||||
|
public static final int EDGE_LEFT = 0x01;
|
||||||
|
public static final int EDGE_RIGHT = 0x02;
|
||||||
|
public static final int EDGE_TOP = 0x04;
|
||||||
|
public static final int EDGE_BOTTOM = 0x08;
|
||||||
|
|
||||||
|
public static final int KEYCODE_SHIFT = -1;
|
||||||
|
public static final int KEYCODE_MODE_CHANGE = -2;
|
||||||
|
public static final int KEYCODE_CANCEL = -3;
|
||||||
|
public static final int KEYCODE_DONE = -4;
|
||||||
|
public static final int KEYCODE_DELETE = -5;
|
||||||
|
public static final int KEYCODE_ALT = -6;
|
||||||
|
|
||||||
|
/** Keyboard label **/
|
||||||
|
private CharSequence mLabel;
|
||||||
|
|
||||||
|
/** Horizontal gap default for all rows */
|
||||||
|
private int mDefaultHorizontalGap;
|
||||||
|
|
||||||
|
/** Default key width */
|
||||||
|
private int mDefaultWidth;
|
||||||
|
|
||||||
|
/** Default key height */
|
||||||
|
private int mDefaultHeight;
|
||||||
|
|
||||||
|
/** Default gap between rows */
|
||||||
|
private int mDefaultVerticalGap;
|
||||||
|
|
||||||
|
/** Is the keyboard in the shifted state */
|
||||||
|
private boolean mShifted;
|
||||||
|
|
||||||
|
/** Key instance for the shift key, if present */
|
||||||
|
private Key[] mShiftKeys = { null, null };
|
||||||
|
|
||||||
|
/** Key index for the shift key, if present */
|
||||||
|
private int[] mShiftKeyIndices = {-1, -1};
|
||||||
|
|
||||||
|
/** Current key width, while loading the keyboard */
|
||||||
|
private int mKeyWidth;
|
||||||
|
|
||||||
|
/** Current key height, while loading the keyboard */
|
||||||
|
private int mKeyHeight;
|
||||||
|
|
||||||
|
/** Total height of the keyboard, including the padding and keys */
|
||||||
|
private int mTotalHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total width of the keyboard, including left side gaps and keys, but not any gaps on the
|
||||||
|
* right side.
|
||||||
|
*/
|
||||||
|
private int mTotalWidth;
|
||||||
|
|
||||||
|
/** List of keys in this keyboard */
|
||||||
|
private List<Key> mKeys;
|
||||||
|
|
||||||
|
/** List of modifier keys such as Shift & Alt, if any */
|
||||||
|
private List<Key> mModifierKeys;
|
||||||
|
|
||||||
|
/** Width of the screen available to fit the keyboard */
|
||||||
|
private int mDisplayWidth;
|
||||||
|
|
||||||
|
/** Height of the screen */
|
||||||
|
private int mDisplayHeight;
|
||||||
|
|
||||||
|
/** Keyboard mode, or zero, if none. */
|
||||||
|
private int mKeyboardMode;
|
||||||
|
|
||||||
|
// Variables for pre-computing nearest keys.
|
||||||
|
|
||||||
|
private static final int GRID_WIDTH = 10;
|
||||||
|
private static final int GRID_HEIGHT = 5;
|
||||||
|
private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
|
||||||
|
private int mCellWidth;
|
||||||
|
private int mCellHeight;
|
||||||
|
private int[][] mGridNeighbors;
|
||||||
|
private int mProximityThreshold;
|
||||||
|
/** Number of key widths from current touch point to search for nearest keys. */
|
||||||
|
private static float SEARCH_DISTANCE = 1.8f;
|
||||||
|
|
||||||
|
private ArrayList<Row> rows = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
|
||||||
|
* Some of the key size defaults can be overridden per row from what the {@link Keyboard}
|
||||||
|
* defines.
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_keyWidth
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_keyHeight
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_horizontalGap
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_verticalGap
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Row_rowEdgeFlags
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Row_keyboardMode
|
||||||
|
*/
|
||||||
|
public static class Row {
|
||||||
|
/** Default width of a key in this row. */
|
||||||
|
public int defaultWidth;
|
||||||
|
/** Default height of a key in this row. */
|
||||||
|
public int defaultHeight;
|
||||||
|
/** Default horizontal gap between keys in this row. */
|
||||||
|
public int defaultHorizontalGap;
|
||||||
|
/** Vertical gap following this row. */
|
||||||
|
public int verticalGap;
|
||||||
|
|
||||||
|
ArrayList<Key> mKeys = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edge flags for this row of keys. Possible values that can be assigned are
|
||||||
|
* {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM}
|
||||||
|
*/
|
||||||
|
public int rowEdgeFlags;
|
||||||
|
|
||||||
|
/** The keyboard mode for this row */
|
||||||
|
public int mode;
|
||||||
|
|
||||||
|
private Keyboard parent;
|
||||||
|
|
||||||
|
public Row(Keyboard parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Row(Resources res, Keyboard parent, XmlResourceParser parser) {
|
||||||
|
this.parent = parent;
|
||||||
|
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
|
R.styleable.journey_Keyboard);
|
||||||
|
defaultWidth = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_keyWidth,
|
||||||
|
parent.mDisplayWidth, parent.mDefaultWidth);
|
||||||
|
defaultHeight = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_keyHeight,
|
||||||
|
parent.mDisplayHeight, parent.mDefaultHeight);
|
||||||
|
defaultHorizontalGap = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_horizontalGap,
|
||||||
|
parent.mDisplayWidth, parent.mDefaultHorizontalGap);
|
||||||
|
verticalGap = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_verticalGap,
|
||||||
|
parent.mDisplayHeight, parent.mDefaultVerticalGap);
|
||||||
|
a.recycle();
|
||||||
|
a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
|
R.styleable.journey_Keyboard_Row);
|
||||||
|
rowEdgeFlags = a.getInt(R.styleable.journey_Keyboard_Row_android_rowEdgeFlags, 0);
|
||||||
|
mode = a.getResourceId(R.styleable.journey_Keyboard_Row_android_keyboardMode,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for describing the position and characteristics of a single key in the keyboard.
|
||||||
|
*
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_keyWidth
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_keyHeight
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_horizontalGap
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_codes
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_keyIcon
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_keyLabel
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_iconPreview
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_isSticky
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_isRepeatable
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_isModifier
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_popupKeyboard
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_popupCharacters
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_keyOutputText
|
||||||
|
* @attr ref android.R.styleable#journey_Keyboard_Key_keyEdgeFlags
|
||||||
|
*/
|
||||||
|
public static class Key {
|
||||||
|
/**
|
||||||
|
* All the key codes (unicode or custom code) that this key could generate, zero'th
|
||||||
|
* being the most important.
|
||||||
|
*/
|
||||||
|
public int[] codes;
|
||||||
|
|
||||||
|
/** Label to display */
|
||||||
|
public CharSequence label;
|
||||||
|
|
||||||
|
/** Icon to display instead of a label. Icon takes precedence over a label */
|
||||||
|
public Drawable icon;
|
||||||
|
/** Preview version of the icon, for the preview popup */
|
||||||
|
public Drawable iconPreview;
|
||||||
|
/** Width of the key, not including the gap */
|
||||||
|
public int width;
|
||||||
|
/** Height of the key, not including the gap */
|
||||||
|
public int height;
|
||||||
|
/** The horizontal gap before this key */
|
||||||
|
public int gap;
|
||||||
|
/** Whether this key is sticky, i.e., a toggle key */
|
||||||
|
public boolean sticky;
|
||||||
|
/** X coordinate of the key in the keyboard layout */
|
||||||
|
public int x;
|
||||||
|
/** Y coordinate of the key in the keyboard layout */
|
||||||
|
public int y;
|
||||||
|
/** The current pressed state of this key */
|
||||||
|
public boolean pressed;
|
||||||
|
/** If this is a sticky key, is it on? */
|
||||||
|
public boolean on;
|
||||||
|
/** Text to output when pressed. This can be multiple characters, like ".com" */
|
||||||
|
public CharSequence text;
|
||||||
|
/** Popup characters */
|
||||||
|
public CharSequence popupCharacters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags that specify the anchoring to edges of the keyboard for detecting touch events
|
||||||
|
* that are just out of the boundary of the key. This is a bit mask of
|
||||||
|
* {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and
|
||||||
|
* {@link Keyboard#EDGE_BOTTOM}.
|
||||||
|
*/
|
||||||
|
public int edgeFlags;
|
||||||
|
/** Whether this is a modifier key, such as Shift or Alt */
|
||||||
|
public boolean modifier;
|
||||||
|
/** The keyboard that this key belongs to */
|
||||||
|
private Keyboard keyboard;
|
||||||
|
/**
|
||||||
|
* If this key pops up a mini keyboard, this is the resource id for the XML layout for that
|
||||||
|
* keyboard.
|
||||||
|
*/
|
||||||
|
public int popupResId;
|
||||||
|
/** Whether this key repeats itself when held down */
|
||||||
|
public boolean repeatable;
|
||||||
|
|
||||||
|
|
||||||
|
private final static int[] KEY_STATE_NORMAL_ON = {
|
||||||
|
android.R.attr.state_checkable,
|
||||||
|
android.R.attr.state_checked
|
||||||
|
};
|
||||||
|
|
||||||
|
private final static int[] KEY_STATE_PRESSED_ON = {
|
||||||
|
android.R.attr.state_pressed,
|
||||||
|
android.R.attr.state_checkable,
|
||||||
|
android.R.attr.state_checked
|
||||||
|
};
|
||||||
|
|
||||||
|
private final static int[] KEY_STATE_NORMAL_OFF = {
|
||||||
|
android.R.attr.state_checkable
|
||||||
|
};
|
||||||
|
|
||||||
|
private final static int[] KEY_STATE_PRESSED_OFF = {
|
||||||
|
android.R.attr.state_pressed,
|
||||||
|
android.R.attr.state_checkable
|
||||||
|
};
|
||||||
|
|
||||||
|
private final static int[] KEY_STATE_NORMAL = {
|
||||||
|
};
|
||||||
|
|
||||||
|
private final static int[] KEY_STATE_PRESSED = {
|
||||||
|
android.R.attr.state_pressed
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Create an empty key with no attributes. */
|
||||||
|
public Key(Row parent) {
|
||||||
|
keyboard = parent.parent;
|
||||||
|
height = parent.defaultHeight;
|
||||||
|
width = parent.defaultWidth;
|
||||||
|
gap = parent.defaultHorizontalGap;
|
||||||
|
edgeFlags = parent.rowEdgeFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a key with the given top-left coordinate and extract its attributes from
|
||||||
|
* the XML parser.
|
||||||
|
* @param res resources associated with the caller's context
|
||||||
|
* @param parent the row that this key belongs to. The row must already be attached to
|
||||||
|
* a {@link Keyboard}.
|
||||||
|
* @param x the x coordinate of the top-left
|
||||||
|
* @param y the y coordinate of the top-left
|
||||||
|
* @param parser the XML parser containing the attributes for this key
|
||||||
|
*/
|
||||||
|
public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) {
|
||||||
|
this(parent);
|
||||||
|
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
|
||||||
|
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
|
R.styleable.journey_Keyboard);
|
||||||
|
|
||||||
|
width = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_keyWidth,
|
||||||
|
keyboard.mDisplayWidth, parent.defaultWidth);
|
||||||
|
height = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_keyHeight,
|
||||||
|
keyboard.mDisplayHeight, parent.defaultHeight);
|
||||||
|
gap = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_horizontalGap,
|
||||||
|
keyboard.mDisplayWidth, parent.defaultHorizontalGap);
|
||||||
|
a.recycle();
|
||||||
|
a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
|
R.styleable.journey_Keyboard_Key);
|
||||||
|
this.x += gap;
|
||||||
|
TypedValue codesValue = new TypedValue();
|
||||||
|
a.getValue(R.styleable.journey_Keyboard_Key_android_codes,
|
||||||
|
codesValue);
|
||||||
|
if (codesValue.type == TypedValue.TYPE_INT_DEC
|
||||||
|
|| codesValue.type == TypedValue.TYPE_INT_HEX) {
|
||||||
|
codes = new int[] { codesValue.data };
|
||||||
|
} else if (codesValue.type == TypedValue.TYPE_STRING) {
|
||||||
|
codes = parseCSV(codesValue.string.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
iconPreview = a.getDrawable(R.styleable.journey_Keyboard_Key_android_iconPreview);
|
||||||
|
if (iconPreview != null) {
|
||||||
|
iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(),
|
||||||
|
iconPreview.getIntrinsicHeight());
|
||||||
|
}
|
||||||
|
popupCharacters = a.getText(
|
||||||
|
R.styleable.journey_Keyboard_Key_android_popupCharacters);
|
||||||
|
popupResId = a.getResourceId(
|
||||||
|
R.styleable.journey_Keyboard_Key_android_popupKeyboard, 0);
|
||||||
|
repeatable = a.getBoolean(
|
||||||
|
R.styleable.journey_Keyboard_Key_android_isRepeatable, false);
|
||||||
|
modifier = a.getBoolean(
|
||||||
|
R.styleable.journey_Keyboard_Key_android_isModifier, false);
|
||||||
|
sticky = a.getBoolean(
|
||||||
|
R.styleable.journey_Keyboard_Key_android_isSticky, false);
|
||||||
|
edgeFlags = a.getInt(R.styleable.journey_Keyboard_Key_android_keyEdgeFlags, 0);
|
||||||
|
edgeFlags |= parent.rowEdgeFlags;
|
||||||
|
|
||||||
|
icon = a.getDrawable(
|
||||||
|
R.styleable.journey_Keyboard_Key_android_keyIcon);
|
||||||
|
if (icon != null) {
|
||||||
|
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
|
||||||
|
}
|
||||||
|
label = a.getText(R.styleable.journey_Keyboard_Key_android_keyLabel);
|
||||||
|
text = a.getText(R.styleable.journey_Keyboard_Key_android_keyOutputText);
|
||||||
|
|
||||||
|
if (codes == null && !TextUtils.isEmpty(label)) {
|
||||||
|
codes = new int[] { label.charAt(0) };
|
||||||
|
}
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the key that it has been pressed, in case it needs to change its appearance or
|
||||||
|
* state.
|
||||||
|
* @see #onReleased(boolean)
|
||||||
|
*/
|
||||||
|
public void onPressed() {
|
||||||
|
pressed = !pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the pressed state of the key.
|
||||||
|
*
|
||||||
|
* <p>Toggled state of the key will be flipped when all the following conditions are
|
||||||
|
* fulfilled:</p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>This is a sticky key, that is, {@link #sticky} is {@code true}.
|
||||||
|
* <li>The parameter {@code inside} is {@code true}.
|
||||||
|
* <li>{@link android.os.Build.VERSION#SDK_INT} is greater than
|
||||||
|
* {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param inside whether the finger was released inside the key. Works only on Android M and
|
||||||
|
* later. See the method document for details.
|
||||||
|
* @see #onPressed()
|
||||||
|
*/
|
||||||
|
public void onReleased(boolean inside) {
|
||||||
|
pressed = !pressed;
|
||||||
|
if (sticky && inside) {
|
||||||
|
on = !on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] parseCSV(String value) {
|
||||||
|
int count = 0;
|
||||||
|
int lastIndex = 0;
|
||||||
|
if (value.length() > 0) {
|
||||||
|
count++;
|
||||||
|
while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int[] values = new int[count];
|
||||||
|
count = 0;
|
||||||
|
StringTokenizer st = new StringTokenizer(value, ",");
|
||||||
|
while (st.hasMoreTokens()) {
|
||||||
|
try {
|
||||||
|
values[count++] = Integer.parseInt(st.nextToken());
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
Log.e(TAG, "Error parsing keycodes " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects if a point falls inside this key.
|
||||||
|
* @param x the x-coordinate of the point
|
||||||
|
* @param y the y-coordinate of the point
|
||||||
|
* @return whether or not the point falls inside the key. If the key is attached to an edge,
|
||||||
|
* it will assume that all points between the key and the edge are considered to be inside
|
||||||
|
* the key.
|
||||||
|
*/
|
||||||
|
public boolean isInside(int x, int y) {
|
||||||
|
boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0;
|
||||||
|
boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0;
|
||||||
|
boolean topEdge = (edgeFlags & EDGE_TOP) > 0;
|
||||||
|
boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0;
|
||||||
|
if ((x >= this.x || (leftEdge && x <= this.x + this.width))
|
||||||
|
&& (x < this.x + this.width || (rightEdge && x >= this.x))
|
||||||
|
&& (y >= this.y || (topEdge && y <= this.y + this.height))
|
||||||
|
&& (y < this.y + this.height || (bottomEdge && y >= this.y))) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the square of the distance between the center of the key and the given point.
|
||||||
|
* @param x the x-coordinate of the point
|
||||||
|
* @param y the y-coordinate of the point
|
||||||
|
* @return the square of the distance of the point from the center of the key
|
||||||
|
*/
|
||||||
|
public int squaredDistanceFrom(int x, int y) {
|
||||||
|
int xDist = this.x + width / 2 - x;
|
||||||
|
int yDist = this.y + height / 2 - y;
|
||||||
|
return xDist * xDist + yDist * yDist;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the drawable state for the key, based on the current state and type of the key.
|
||||||
|
* @return the drawable state of the key.
|
||||||
|
* @see android.graphics.drawable.StateListDrawable#setState(int[])
|
||||||
|
*/
|
||||||
|
public int[] getCurrentDrawableState() {
|
||||||
|
int[] states = KEY_STATE_NORMAL;
|
||||||
|
|
||||||
|
if (on) {
|
||||||
|
if (pressed) {
|
||||||
|
states = KEY_STATE_PRESSED_ON;
|
||||||
|
} else {
|
||||||
|
states = KEY_STATE_NORMAL_ON;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sticky) {
|
||||||
|
if (pressed) {
|
||||||
|
states = KEY_STATE_PRESSED_OFF;
|
||||||
|
} else {
|
||||||
|
states = KEY_STATE_NORMAL_OFF;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pressed) {
|
||||||
|
states = KEY_STATE_PRESSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return states;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a keyboard from the given xml key layout file.
|
||||||
|
* @param context the application or service context
|
||||||
|
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
|
||||||
|
*/
|
||||||
|
public Keyboard(Context context, int xmlLayoutResId) {
|
||||||
|
this(context, xmlLayoutResId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a keyboard from the given xml key layout file. Weeds out rows
|
||||||
|
* that have a keyboard mode defined but don't match the specified mode.
|
||||||
|
* @param context the application or service context
|
||||||
|
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
|
||||||
|
* @param modeId keyboard mode identifier
|
||||||
|
* @param width sets width of keyboard
|
||||||
|
* @param height sets height of keyboard
|
||||||
|
*/
|
||||||
|
public Keyboard(Context context, @XmlRes int xmlLayoutResId, int modeId, int width,
|
||||||
|
int height) {
|
||||||
|
mDisplayWidth = width;
|
||||||
|
mDisplayHeight = height;
|
||||||
|
|
||||||
|
mDefaultHorizontalGap = 0;
|
||||||
|
mDefaultWidth = mDisplayWidth / 10;
|
||||||
|
mDefaultVerticalGap = 0;
|
||||||
|
mDefaultHeight = mDefaultWidth;
|
||||||
|
mKeys = new ArrayList<>();
|
||||||
|
mModifierKeys = new ArrayList<>();
|
||||||
|
mKeyboardMode = modeId;
|
||||||
|
loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a keyboard from the given xml key layout file. Weeds out rows
|
||||||
|
* that have a keyboard mode defined but don't match the specified mode.
|
||||||
|
* @param context the application or service context
|
||||||
|
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
|
||||||
|
* @param modeId keyboard mode identifier
|
||||||
|
*/
|
||||||
|
public Keyboard(Context context, @XmlRes int xmlLayoutResId, int modeId) {
|
||||||
|
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||||
|
mDisplayWidth = dm.widthPixels;
|
||||||
|
mDisplayHeight = dm.heightPixels;
|
||||||
|
//Log.v(TAG, "keyboard's display metrics:" + dm);
|
||||||
|
|
||||||
|
mDefaultHorizontalGap = 0;
|
||||||
|
mDefaultWidth = mDisplayWidth / 10;
|
||||||
|
mDefaultVerticalGap = 0;
|
||||||
|
mDefaultHeight = mDefaultWidth;
|
||||||
|
mKeys = new ArrayList<>();
|
||||||
|
mModifierKeys = new ArrayList<>();
|
||||||
|
mKeyboardMode = modeId;
|
||||||
|
loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Creates a blank keyboard from the given resource file and populates it with the specified
|
||||||
|
* characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
|
||||||
|
* </p>
|
||||||
|
* <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
|
||||||
|
* possible in each row.</p>
|
||||||
|
* @param context the application or service context
|
||||||
|
* @param layoutTemplateResId the layout template file, containing no keys.
|
||||||
|
* @param characters the list of characters to display on the keyboard. One key will be created
|
||||||
|
* for each character.
|
||||||
|
* @param columns the number of columns of keys to display. If this number is greater than the
|
||||||
|
* number of keys that can fit in a row, it will be ignored. If this number is -1, the
|
||||||
|
* keyboard will fit as many keys as possible in each row.
|
||||||
|
*/
|
||||||
|
public Keyboard(Context context, int layoutTemplateResId,
|
||||||
|
CharSequence characters, int columns, int horizontalPadding) {
|
||||||
|
this(context, layoutTemplateResId);
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
int column = 0;
|
||||||
|
mTotalWidth = 0;
|
||||||
|
|
||||||
|
Row row = new Row(this);
|
||||||
|
row.defaultHeight = mDefaultHeight;
|
||||||
|
row.defaultWidth = mDefaultWidth;
|
||||||
|
row.defaultHorizontalGap = mDefaultHorizontalGap;
|
||||||
|
row.verticalGap = mDefaultVerticalGap;
|
||||||
|
row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
|
||||||
|
final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
|
||||||
|
for (int i = 0; i < characters.length(); i++) {
|
||||||
|
char c = characters.charAt(i);
|
||||||
|
if (column >= maxColumns
|
||||||
|
|| x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
|
||||||
|
x = 0;
|
||||||
|
y += mDefaultVerticalGap + mDefaultHeight;
|
||||||
|
column = 0;
|
||||||
|
}
|
||||||
|
final Key key = new Key(row);
|
||||||
|
key.x = x;
|
||||||
|
key.y = y;
|
||||||
|
key.label = String.valueOf(c);
|
||||||
|
key.codes = new int[] { c };
|
||||||
|
column++;
|
||||||
|
x += key.width + key.gap;
|
||||||
|
mKeys.add(key);
|
||||||
|
row.mKeys.add(key);
|
||||||
|
if (x > mTotalWidth) {
|
||||||
|
mTotalWidth = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mTotalHeight = y + mDefaultHeight;
|
||||||
|
rows.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
final void resize(int newWidth, int newHeight) {
|
||||||
|
int numRows = rows.size();
|
||||||
|
for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) {
|
||||||
|
Row row = rows.get(rowIndex);
|
||||||
|
int numKeys = row.mKeys.size();
|
||||||
|
int totalGap = 0;
|
||||||
|
int totalWidth = 0;
|
||||||
|
for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
|
||||||
|
Key key = row.mKeys.get(keyIndex);
|
||||||
|
if (keyIndex > 0) {
|
||||||
|
totalGap += key.gap;
|
||||||
|
}
|
||||||
|
totalWidth += key.width;
|
||||||
|
}
|
||||||
|
if (totalGap + totalWidth > newWidth) {
|
||||||
|
int x = 0;
|
||||||
|
float scaleFactor = (float)(newWidth - totalGap) / totalWidth;
|
||||||
|
for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
|
||||||
|
Key key = row.mKeys.get(keyIndex);
|
||||||
|
key.width *= scaleFactor;
|
||||||
|
key.x = x;
|
||||||
|
x += key.width + key.gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mTotalWidth = newWidth;
|
||||||
|
// TODO: This does not adjust the vertical placement according to the new size.
|
||||||
|
// The main problem in the previous code was horizontal placement/size, but we should
|
||||||
|
// also recalculate the vertical sizes/positions when we get this resize call.
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Key> getKeys() {
|
||||||
|
return mKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Key> getModifierKeys() {
|
||||||
|
return mModifierKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getHorizontalGap() {
|
||||||
|
return mDefaultHorizontalGap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setHorizontalGap(int gap) {
|
||||||
|
mDefaultHorizontalGap = gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getVerticalGap() {
|
||||||
|
return mDefaultVerticalGap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setVerticalGap(int gap) {
|
||||||
|
mDefaultVerticalGap = gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getKeyHeight() {
|
||||||
|
return mDefaultHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setKeyHeight(int height) {
|
||||||
|
mDefaultHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getKeyWidth() {
|
||||||
|
return mDefaultWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setKeyWidth(int width) {
|
||||||
|
mDefaultWidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total height of the keyboard
|
||||||
|
* @return the total height of the keyboard
|
||||||
|
*/
|
||||||
|
public int getHeight() {
|
||||||
|
return mTotalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinWidth() {
|
||||||
|
return mTotalWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setShifted(boolean shiftState) {
|
||||||
|
for (Key shiftKey : mShiftKeys) {
|
||||||
|
if (shiftKey != null) {
|
||||||
|
shiftKey.on = shiftState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mShifted != shiftState) {
|
||||||
|
mShifted = shiftState;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShifted() {
|
||||||
|
return mShifted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public int[] getShiftKeyIndices() {
|
||||||
|
return mShiftKeyIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getShiftKeyIndex() {
|
||||||
|
return mShiftKeyIndices[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeNearestNeighbors() {
|
||||||
|
// Round-up so we don't have any pixels outside the grid
|
||||||
|
mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
|
||||||
|
mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
|
||||||
|
mGridNeighbors = new int[GRID_SIZE][];
|
||||||
|
int[] indices = new int[mKeys.size()];
|
||||||
|
final int gridWidth = GRID_WIDTH * mCellWidth;
|
||||||
|
final int gridHeight = GRID_HEIGHT * mCellHeight;
|
||||||
|
for (int x = 0; x < gridWidth; x += mCellWidth) {
|
||||||
|
for (int y = 0; y < gridHeight; y += mCellHeight) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < mKeys.size(); i++) {
|
||||||
|
final Key key = mKeys.get(i);
|
||||||
|
if (key.squaredDistanceFrom(x, y) < mProximityThreshold ||
|
||||||
|
key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold ||
|
||||||
|
key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1)
|
||||||
|
< mProximityThreshold ||
|
||||||
|
key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) {
|
||||||
|
indices[count++] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int [] cell = new int[count];
|
||||||
|
System.arraycopy(indices, 0, cell, 0, count);
|
||||||
|
mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the indices of the keys that are closest to the given point.
|
||||||
|
* @param x the x-coordinate of the point
|
||||||
|
* @param y the y-coordinate of the point
|
||||||
|
* @return the array of integer indices for the nearest keys to the given point. If the given
|
||||||
|
* point is out of range, then an array of size zero is returned.
|
||||||
|
*/
|
||||||
|
public int[] getNearestKeys(int x, int y) {
|
||||||
|
if (mGridNeighbors == null) computeNearestNeighbors();
|
||||||
|
if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
|
||||||
|
int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
|
||||||
|
if (index < GRID_SIZE) {
|
||||||
|
return mGridNeighbors[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Row createRowFromXml(Resources res, XmlResourceParser parser) {
|
||||||
|
return new Row(res, this, parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
|
||||||
|
XmlResourceParser parser) {
|
||||||
|
return new Key(res, parent, x, y, parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadKeyboard(Context context, XmlResourceParser parser) {
|
||||||
|
boolean inKey = false;
|
||||||
|
boolean inRow = false;
|
||||||
|
boolean leftMostKey = false;
|
||||||
|
int row = 0;
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
Key key = null;
|
||||||
|
Row currentRow = null;
|
||||||
|
Resources res = context.getResources();
|
||||||
|
boolean skipRow = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
int event;
|
||||||
|
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
|
||||||
|
if (event == XmlResourceParser.START_TAG) {
|
||||||
|
String tag = parser.getName();
|
||||||
|
if (TAG_ROW.equals(tag)) {
|
||||||
|
inRow = true;
|
||||||
|
x = 0;
|
||||||
|
currentRow = createRowFromXml(res, parser);
|
||||||
|
rows.add(currentRow);
|
||||||
|
skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
|
||||||
|
if (skipRow) {
|
||||||
|
skipToEndOfRow(parser);
|
||||||
|
inRow = false;
|
||||||
|
}
|
||||||
|
} else if (TAG_KEY.equals(tag)) {
|
||||||
|
inKey = true;
|
||||||
|
key = createKeyFromXml(res, currentRow, x, y, parser);
|
||||||
|
mKeys.add(key);
|
||||||
|
if (key.codes[0] == KEYCODE_SHIFT) {
|
||||||
|
// Find available shift key slot and put this shift key in it
|
||||||
|
for (int i = 0; i < mShiftKeys.length; i++) {
|
||||||
|
if (mShiftKeys[i] == null) {
|
||||||
|
mShiftKeys[i] = key;
|
||||||
|
mShiftKeyIndices[i] = mKeys.size()-1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mModifierKeys.add(key);
|
||||||
|
} else if (key.codes[0] == KEYCODE_ALT) {
|
||||||
|
mModifierKeys.add(key);
|
||||||
|
}
|
||||||
|
currentRow.mKeys.add(key);
|
||||||
|
} else if (TAG_KEYBOARD.equals(tag)) {
|
||||||
|
parseKeyboardAttributes(res, parser);
|
||||||
|
}
|
||||||
|
} else if (event == XmlResourceParser.END_TAG) {
|
||||||
|
if (inKey) {
|
||||||
|
inKey = false;
|
||||||
|
x += key.gap + key.width;
|
||||||
|
if (x > mTotalWidth) {
|
||||||
|
mTotalWidth = x;
|
||||||
|
}
|
||||||
|
} else if (inRow) {
|
||||||
|
inRow = false;
|
||||||
|
y += currentRow.verticalGap;
|
||||||
|
y += currentRow.defaultHeight;
|
||||||
|
row++;
|
||||||
|
} else {
|
||||||
|
// TODO: error or extend?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Parse error:" + e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
mTotalHeight = y - mDefaultVerticalGap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipToEndOfRow(XmlResourceParser parser)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
int event;
|
||||||
|
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
|
||||||
|
if (event == XmlResourceParser.END_TAG
|
||||||
|
&& parser.getName().equals(TAG_ROW)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
|
||||||
|
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
|
R.styleable.journey_Keyboard);
|
||||||
|
|
||||||
|
mDefaultWidth = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_keyWidth,
|
||||||
|
mDisplayWidth, mDisplayWidth / 10);
|
||||||
|
mDefaultHeight = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_keyHeight,
|
||||||
|
mDisplayHeight, 50);
|
||||||
|
mDefaultHorizontalGap = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_horizontalGap,
|
||||||
|
mDisplayWidth, 0);
|
||||||
|
mDefaultVerticalGap = getDimensionOrFraction(a,
|
||||||
|
R.styleable.journey_Keyboard_android_verticalGap,
|
||||||
|
mDisplayHeight, 0);
|
||||||
|
mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
|
||||||
|
mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) {
|
||||||
|
TypedValue value = a.peekValue(index);
|
||||||
|
if (value == null) return defValue;
|
||||||
|
if (value.type == TypedValue.TYPE_DIMENSION) {
|
||||||
|
return a.getDimensionPixelOffset(index, defValue);
|
||||||
|
} else if (value.type == TypedValue.TYPE_FRACTION) {
|
||||||
|
// Round it to avoid values like 47.9999 from getting truncated
|
||||||
|
return Math.round(a.getFraction(index, base, base, defValue));
|
||||||
|
}
|
||||||
|
return defValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1454
app/src/main/java/com/keyboard/journey/view/KeyboardView.java
Normal file
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
20
app/src/main/res/drawable/arrow_right.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="12dp"
|
||||||
|
android:height="12dp"
|
||||||
|
android:viewportWidth="12"
|
||||||
|
android:viewportHeight="12">
|
||||||
|
<path
|
||||||
|
android:pathData="M6.323,1.448L10.875,6L6.323,10.552"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M1.125,6H10.748"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/back_black_icon.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M15.207,5.293C15.598,5.683 15.598,6.317 15.207,6.707L9.914,12L15.207,17.293C15.598,17.683 15.598,18.317 15.207,18.707C14.817,19.098 14.183,19.098 13.793,18.707L7.793,12.707C7.402,12.317 7.402,11.683 7.793,11.293L13.793,5.293C14.183,4.902 14.817,4.902 15.207,5.293Z"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
6
app/src/main/res/drawable/delete_key_background.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 按键正常状态 -->
|
||||||
|
<item android:drawable="@mipmap/sym_keyboard_delete_normal" android:state_pressed="false" />
|
||||||
|
<!-- 按键按下状态 -->
|
||||||
|
<item android:drawable="@mipmap/sym_keyboard_delete_pressed" android:state_pressed="true" />
|
||||||
|
</selector>
|
||||||
41
app/src/main/res/drawable/down_icon.xml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="25dp"
|
||||||
|
android:height="25dp"
|
||||||
|
android:viewportWidth="25"
|
||||||
|
android:viewportHeight="25">
|
||||||
|
<path
|
||||||
|
android:pathData="M6.339,10.233C4.137,10.757 2.5,12.736 2.5,15.098C2.5,17.859 4.738,20.098 7.5,20.098C7.974,20.098 8.432,20.032 8.866,19.909"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M18.527,10.233C20.729,10.757 22.366,12.736 22.366,15.098C22.366,17.859 20.128,20.098 17.366,20.098C16.892,20.098 16.434,20.032 16,19.909"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M18.5,10.098C18.5,6.784 15.814,4.098 12.5,4.098C9.186,4.098 6.5,6.784 6.5,10.098"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M9.033,15.157L12.5,18.636L16.066,15.098"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M12.5,10.098V16.867"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/draw_activate_layout_bg.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
<corners
|
||||||
|
android:topLeftRadius="24dp"
|
||||||
|
android:topRightRadius="24dp" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/draw_round_gray.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#EEEEEE"/>
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/drw_gray_select_bg.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<corners android:radius="80dp"/>
|
||||||
|
<solid android:color="#eeeeee"/>
|
||||||
|
</shape>
|
||||||
8
app/src/main/res/drawable/drw_preview_message_bg.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<solid android:color="#F1F7FF"/>
|
||||||
|
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/drw_round_bg.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
|
||||||
|
android:shape="oval">
|
||||||
|
|
||||||
|
<solid android:color="#F6F4FF" />
|
||||||
|
</shape>
|
||||||
14
app/src/main/res/drawable/gou_icon.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="20dp"
|
||||||
|
android:height="20dp"
|
||||||
|
android:viewportWidth="20"
|
||||||
|
android:viewportHeight="20">
|
||||||
|
<path
|
||||||
|
android:pathData="M9.943,16.834C13.777,16.834 16.833,13.75 16.833,10.057C16.833,6.237 13.763,3.167 9.943,3.167C6.25,3.167 3.167,6.224 3.167,10.057C3.167,13.764 6.237,16.834 9.943,16.834ZM18.333,10.057C18.333,14.592 14.592,18.334 9.943,18.334C5.408,18.334 1.667,14.592 1.667,10.057C1.667,5.408 5.408,1.667 9.943,1.667C14.592,1.667 18.333,5.408 18.333,10.057Z"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M14.576,7.404C14.712,7.539 14.712,7.759 14.576,7.895L8.878,13.593C8.743,13.728 8.523,13.728 8.387,13.593L5.371,10.576C5.235,10.441 5.235,10.221 5.371,10.085L5.862,9.594C5.997,9.458 6.217,9.458 6.353,9.594L8.387,11.629C8.523,11.764 8.743,11.764 8.878,11.629L13.594,6.913C13.73,6.777 13.95,6.777 14.085,6.913L14.576,7.404Z"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
6
app/src/main/res/drawable/key_background.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 按键正常状态 -->
|
||||||
|
<item android:drawable="@drawable/key_normal" android:state_pressed="false" />
|
||||||
|
<!-- 按键按下状态 -->
|
||||||
|
<item android:drawable="@drawable/key_pressed" android:state_pressed="true" />
|
||||||
|
</selector>
|
||||||
6
app/src/main/res/drawable/key_normal.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<!-- 定义按键正常状态的背景颜色和圆角 -->
|
||||||
|
<solid android:color="#CCCCCC" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/key_pressed.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<!-- 定义按键按下状态的背景颜色和圆角 -->
|
||||||
|
<solid android:color="#999999" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
||||||
24
app/src/main/res/drawable/menu.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M6,6H18"
|
||||||
|
android:strokeWidth="2.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6,12H18"
|
||||||
|
android:strokeWidth="2.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6,18H18"
|
||||||
|
android:strokeWidth="2.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/placeholder_icon.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="32"
|
||||||
|
android:viewportHeight="32">
|
||||||
|
<path
|
||||||
|
android:pathData="M15.999,7.244H2.594C4.049,5.018 6.037,3.19 8.378,1.925C10.718,0.66 13.338,-0.002 15.999,0C18.66,-0.002 21.279,0.66 23.62,1.925C25.961,3.19 27.949,5.018 29.404,7.244H15.999ZM15.999,14.468C15.839,14.789 15.664,15.103 15.475,15.409C13.963,17.859 11.367,19.961 7.297,19.961C4.122,19.961 1.812,18.677 0.224,16.899C0.148,16.813 0.073,16.725 0,16.636C0.164,20.766 1.921,24.672 4.903,27.536C7.886,30.4 11.862,32 15.999,32C20.136,32 24.112,30.401 27.095,27.538C30.078,24.674 31.836,20.768 32,16.639C31.927,16.726 31.852,16.812 31.776,16.897C30.188,18.677 27.878,19.961 24.703,19.961C20.631,19.961 18.037,17.859 16.523,15.409C16.334,15.103 16.159,14.789 15.999,14.468ZM21.688,22.381C21.574,22.283 21.441,22.209 21.299,22.162C21.156,22.115 21.005,22.097 20.855,22.108C20.705,22.12 20.559,22.161 20.424,22.229C20.29,22.297 20.171,22.391 20.073,22.505C19.603,23.043 19.008,23.457 18.339,23.709C18.124,23.766 17.904,23.801 17.685,23.816C17.382,23.838 17.1,23.978 16.901,24.207C16.702,24.436 16.602,24.734 16.623,25.037C16.645,25.339 16.785,25.621 17.014,25.82C17.243,26.019 17.542,26.118 17.845,26.097C18.264,26.067 18.678,25.992 19.08,25.873C20.135,25.489 21.074,24.842 21.809,23.995C21.907,23.881 21.982,23.749 22.029,23.606C22.076,23.463 22.095,23.313 22.084,23.163C22.072,23.013 22.032,22.867 21.964,22.733C21.896,22.599 21.802,22.479 21.688,22.381ZM0.35,10.311C0.308,10.351 0.278,10.403 0.262,10.459C0.246,10.515 0.246,10.574 0.261,10.631C0.581,12.096 1.238,13.743 2.358,14.996C3.44,16.207 4.989,17.105 7.297,17.105C10.191,17.105 11.945,15.688 13.043,13.91C13.743,12.739 14.215,11.446 14.436,10.101H0.881C0.783,10.098 0.685,10.115 0.594,10.151C0.503,10.188 0.42,10.242 0.35,10.311ZM18.957,13.91C18.256,12.739 17.783,11.447 17.561,10.101H31.117C31.364,10.101 31.552,10.201 31.65,10.311C31.692,10.351 31.722,10.403 31.738,10.459C31.754,10.515 31.754,10.574 31.739,10.631C31.419,12.096 30.763,13.743 29.642,14.996C28.56,16.207 27.009,17.105 24.701,17.105C21.809,17.105 20.055,15.688 18.957,13.91Z"
|
||||||
|
android:fillColor="#DDDDDD"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
33
app/src/main/res/layout/adapter_category_details_item.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
app:cardBackgroundColor="#eeeeee"
|
||||||
|
app:cardCornerRadius="8dp">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="120dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:src="@drawable/placeholder_icon" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/image_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</LinearLayout>
|
||||||
32
app/src/main/res/layout/custom_keyboard.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<VideoView
|
||||||
|
android:id="@+id/videoView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.keyboard.journey.view.JourneyKeyboardView
|
||||||
|
android:id="@+id/keyboardView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:keyBackground="@drawable/key_background"
|
||||||
|
android:keyTextColor="#ff333333"
|
||||||
|
android:keyTextSize="20sp"
|
||||||
|
android:labelTextSize="16sp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingBottom="6dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
31
app/src/main/res/layout/horizontal_scrolling_rv_item.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_margin="4dp">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
app:cardBackgroundColor="#eeeeee"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="136dp"
|
||||||
|
android:layout_height="100dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/placeholder_icon"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/hs_rv_img"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
417
app/src/main/res/layout/journey_activity_main.xml
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/drawer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/banner_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.youth.banner.Banner
|
||||||
|
android:id="@+id/banner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@mipmap/recommend_top_banner_img" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/banner_btn"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_below="@+id/banner"
|
||||||
|
android:layout_marginTop="-24dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/indicator_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/banner_indicator_one_bg"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/indicator_img"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:src="@mipmap/top_banner_one_img" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/indicator_txt"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/recommend"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/indicator_go_bg"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="26dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:cardBackgroundColor="@color/banner_indicator_one_go_bg_color"
|
||||||
|
app:cardCornerRadius="80dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/indicator_go_txt"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:text="@string/go"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/indicator_line_one_bg"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="#7863F9" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/indicator_line_tow_bg"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="#1A000000" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_below="@+id/view"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/menu_btn"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:cardBackgroundColor="#F6F4FF"
|
||||||
|
app:cardCornerRadius="32dp"
|
||||||
|
app:cardElevation="6dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/menu" />
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="48dp"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:cardBackgroundColor="#F6F4FF"
|
||||||
|
app:cardCornerRadius="32dp"
|
||||||
|
app:cardElevation="6dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_below="@+id/banner_layout"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loading_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<org.libpag.PAGView
|
||||||
|
android:id="@+id/pag_view"
|
||||||
|
android:layout_width="88dp"
|
||||||
|
android:layout_height="100dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="loading"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:listitem="@layout/main_rv_item" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<!--android:layout_gravity="start"-->
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:background="#F6F4FF"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="48dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="6dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@mipmap/journey_app_logo" />
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/apply_keyboard_btn"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:src="@mipmap/keyboard_img" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="Keyboard settings"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/share_btn"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:src="@mipmap/shate_img" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="Share"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/privacy_btn"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:src="@mipmap/privacy_img" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="Privacy"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/dialog_step_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#99000000"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/activate_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:background="@drawable/draw_activate_layout_bg"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Activate Keyboard Journey"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_margin="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/step_1_btn"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/drw_gray_select_bg"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Step 1:Select"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/step_2_btn"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@mipmap/activate_btn_bg"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Step 2:Enabled"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</androidx.drawerlayout.widget.DrawerLayout>
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@mipmap/details_act_bg" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_below="@+id/view"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/back_btn"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/back_black_icon" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_tv"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loading_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<org.libpag.PAGView
|
||||||
|
android:id="@+id/pag_view"
|
||||||
|
android:layout_width="88dp"
|
||||||
|
android:layout_height="100dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:text="loading"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.scwang.smart.refresh.layout.SmartRefreshLayout
|
||||||
|
android:id="@+id/refreshLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:scrollbars="none" />
|
||||||
|
|
||||||
|
<com.scwang.smart.refresh.footer.ClassicsFooter
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
149
app/src/main/res/layout/journey_details_activity.xml
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@mipmap/details_act_bg" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_below="@+id/view"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/back_btn"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/back_black_icon" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_tv"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loading_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<org.libpag.PAGView
|
||||||
|
android:id="@+id/pag_view"
|
||||||
|
android:layout_width="88dp"
|
||||||
|
android:layout_height="100dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:text="loading"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="280dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:src="@drawable/placeholder_icon" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/image_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerInParent="true" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/down_btn"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@mipmap/down_btn_bg"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading_bar"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/down_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/down_icon" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/down_tv"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:text="@string/download"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
230
app/src/main/res/layout/journey_preview_activity.xml
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#F8F8F8">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/title_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/back_btn"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/back_black_icon" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_tv"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/preview"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="invisible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/back_black_icon" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/preview_iv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_below="@+id/title_layout"
|
||||||
|
android:scaleType="centerCrop" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginTop="88dp"
|
||||||
|
android:layout_marginEnd="88dp"
|
||||||
|
android:background="@drawable/drw_preview_message_bg">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:text="Wow! This keyboard background is so cool!"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/edit_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="Activate to enter text..."
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/edit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="Activate to enter text..."
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/unauthorized_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="200dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/keyboard_iv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#99000000"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/activate_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:background="@mipmap/activate_btn_bg"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/gou_icon" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Activate"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/dialog_step_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_below="@+id/title_layout"
|
||||||
|
android:background="#99000000"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/activate_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:background="@drawable/draw_activate_layout_bg"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Activate Keyboard Journey"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_margin="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/step_1_btn"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/drw_gray_select_bg"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Step 1:Select"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/step_2_btn"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@mipmap/activate_btn_bg"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Step 2:Enabled"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</RelativeLayout>
|
||||||
47
app/src/main/res/layout/journey_start_activity.xml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@mipmap/start_act_bg" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="72dp"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:src="@mipmap/journey_app_round_logo" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:src="@mipmap/app_name_icon" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<org.libpag.PAGView
|
||||||
|
android:id="@+id/pag_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="68dp"
|
||||||
|
android:layout_marginEnd="12dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
85
app/src/main/res/layout/main_rv_item.xml
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="@color/black_10" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/moreData"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:background="@drawable/draw_round_gray"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/arrow_right" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loading_tv"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:text="loading"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- <ImageView-->
|
||||||
|
<!-- android:id="@+id/img"-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_margin="12dp"-->
|
||||||
|
<!-- android:layout_height="100dp"-->
|
||||||
|
<!-- android:scaleType="centerCrop" />-->
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/item_rv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:itemCount="4"
|
||||||
|
tools:listitem="@layout/horizontal_scrolling_rv_item" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</LinearLayout>
|
||||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/activate_btn_bg.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/app_name_icon.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/cool_top_banner_img.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/details_act_bg.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/down_btn_bg.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |