上传Android项目

This commit is contained in:
pengxiaolong
2025-11-26 16:47:15 +08:00
commit be276ae65d
209 changed files with 59142 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
Key of Love

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

10
.idea/deploymentTargetSelector.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

13
.idea/deviceManager.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

19
.idea/gradle.xml generated Normal file
View 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="testRunner" value="CHOOSE_PER_TEST" />
<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>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -0,0 +1,61 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

10
.idea/migrations.xml generated Normal file
View 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
View File

@@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

70
app/build.gradle.kts Normal file
View File

@@ -0,0 +1,70 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
}
android {
namespace = "com.example.myapplication"
compileSdk = 34
defaultConfig {
applicationId = "com.boshan.key.of.love"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
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 {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.core:core:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.navigation:navigation-fragment-ktx:2.8.0")
implementation("androidx.navigation:navigation-ui-ktx:2.8.0")
implementation("com.google.android.material:material:1.12.0")
implementation("de.hdodenhof:circleimageview:3.1.0")
implementation("com.google.android.flexbox:flexbox:3.0.0")
// Jetpack Compose
implementation("androidx.activity:activity-compose:1.8.0")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.navigation:navigation-compose:2.7.5")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# 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

View File

@@ -0,0 +1,24 @@
package com.example.myapplication
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.example.myapplication", appContext.packageName)
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<!-- 启动页 Activity -->
<activity
android:name=".SplashActivity"
android:exported="true"
android:theme="@style/Theme.MyApp.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 引导页 -->
<activity
android:name=".OnboardingActivity"
android:exported="false"/>
<!-- 主界面 -->
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
<!-- 输入法服务 -->
<service
android:name=".MyInputMethodService"
android:label="@string/app_name"
android:exported="false"
android:permission="android.permission.BIND_INPUT_METHOD"
android:foregroundServiceType="connectedDevice">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/method" />
</service>
</application>
</manifest>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

50000
app/src/main/assets/vocab.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,158 @@
package com.example.myapplication.data
import android.content.Context
import com.example.myapplication.Trie
import java.util.concurrent.atomic.AtomicBoolean
import java.util.PriorityQueue
import kotlin.math.max
class BigramPredictor(
private val context: Context,
private val trie: Trie
) {
@Volatile private var model: BigramModel? = null
private val loading = AtomicBoolean(false)
// 词 ↔ id 映射
@Volatile private var word2id: Map<String, Int> = emptyMap()
@Volatile private var id2word: List<String> = emptyList()
//预先加载语言模型并构建词到ID和ID到词的双向映射。
fun preload() {
if (!loading.compareAndSet(false, true)) return
Thread {
try {
val m = LanguageModelLoader.load(context)
model = m
// 建索引vocab 与 bigram 索引对齐,注意不丢前三个符号)
val map = HashMap<String, Int>(m.vocab.size * 2)
m.vocab.forEachIndexed { idx, w -> map[w] = idx }
word2id = map
id2word = m.vocab
} catch (_: Throwable) {
// 保持静默,允许无模型运行(仅 Trie 起作用)
} finally {
loading.set(false)
}
}.start()
}
// 模型是否已准备好
fun isReady(): Boolean = model != null
//基于上文 lastWord可空与前缀 prefix 联想,优先bigram 条件概率 → Trie 过滤 → Top-K,兜底unigram Top-K同样做 Trie 过滤)
fun suggest(prefix: String, lastWord: String?, topK: Int = 10): List<String> {
val m = model
val pfx = prefix.trim()
if (m == null) {
// 模型未载入时,纯 Trie 前缀联想(你的 Trie 应提供类似 startsWith
return safeTriePrefix(pfx, topK)
}
val candidates = mutableListOf<Pair<String, Float>>()
val lastId = lastWord?.let { word2id[it] }
if (lastId != null) {
// 1) bigram 邻域
val start = m.biRowptr[lastId]
val end = m.biRowptr[lastId + 1]
if (start in 0..end && end <= m.biCols.size) {
// 先把 bigram 候选过一遍前缀过滤
for (i in start until end) {
val nextId = m.biCols[i]
val w = m.vocab[nextId]
if (pfx.isEmpty() || w.startsWith(pfx, ignoreCase = true)) {
val score = m.biLogp[i] // logP(next|last)
candidates += w to score
}
}
}
}
// 2) 如果有 bigram 过滤后的候选,直接取 topK
if (candidates.isNotEmpty()) {
return topKByScore(candidates, topK)
}
// 3) 兜底:用 unigram + 前缀过滤
val heap = topKHeap(topK)
for (i in m.vocab.indices) {
val w = m.vocab[i]
if (pfx.isEmpty() || w.startsWith(pfx, ignoreCase = true)) {
heap.offer(w to m.uniLogp[i])
if (heap.size > topK) heap.poll()
}
}
return heap.toSortedListDescending()
}
//供上层在用户选中词时更新“上文”状态
fun normalizeWordForContext(word: String): String? {
// 你可以在这里做大小写/符号处理,或将 OOV 映射为 <unk>
return if (word2id.containsKey(word)) word else "<unk>"
}
//在Trie数据结构中查找与给定前缀匹配的字符串并返回其中评分最高的topK个结果。
private fun safeTriePrefix(prefix: String, topK: Int): List<String> {
if (prefix.isEmpty()) return emptyList()
return try {
trie.startsWith(prefix).take(topK)
} catch (_: Throwable) {
emptyList()
}
}
//从给定的候选词对列表中通过一个小顶堆来过滤出评分最高的前k个词
private fun topKByScore(pairs: List<Pair<String, Float>>, k: Int): List<String> {
val heap = topKHeap(k)
for (p in pairs) {
heap.offer(p)
if (heap.size > k) heap.poll()
}
return heap.toSortedListDescending()
}
//创建一个优先队列,用于在一组候选词对中保持评分最高的 k 个词。
private fun topKHeap(k: Int): PriorityQueue<Pair<String, Float>> {
// 小顶堆,比较 Float 分数
return PriorityQueue(k.coerceAtLeast(1)) { a, b ->
a.second.compareTo(b.second) // 分数小的优先被弹出
}
}
// 排序后的候选词列表
private fun PriorityQueue<Pair<String, Float>>.toSortedListDescending(): List<String> {
val list = ArrayList<Pair<String, Float>>(this.size)
while (this.isNotEmpty()) {
val p = this.poll() ?: continue // 防御性判断,避免 null
list.add(p)
}
list.reverse() // 从高分到低分
return list.map { it.first }
}
}

View File

@@ -0,0 +1,47 @@
package com.example.myapplication
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.navigation.NavController
import androidx.navigation.NavDestination
class MainActivity : AppCompatActivity() {
private lateinit var bottomNav: BottomNavigationView
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. 找到 NavHostFragment
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
// 2. 找到 BottomNavigationView
bottomNav = findViewById(R.id.bottom_nav)
// 3. 绑定导航控制器(负责切换 Fragment、保持选中状态
bottomNav.setupWithNavController(navController)
// 4. 取消图标颜色 tint —— 使用原图标颜色
bottomNav.itemIconTintList = null
// 5. 添加导航监听(用于某些 Fragment 隐藏底部导航栏)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.rechargeFragment, R.id.goldCoinRechargeFragment,R.id.PersonalSettings,R.id.MySkin,R.id.searchFragment,R.id.MyKeyboard,R.id.searchResultFragment,R.id.keyboardDetailFragment,R.id.feedbackFragment,R.id.notificationFragment-> {
bottomNav.visibility = View.GONE
}
else -> {
bottomNav.visibility = View.VISIBLE
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
package com.example.myapplication
import android.app.Application
import android.util.Log
class MyApplication : Application() {
companion object {
const val TAG = "MyKeyboard"
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "Application created")
// 初始化日志系统等全局配置
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
package com.example.myapplication
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class OnboardingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_onboarding)
val btnStart = findViewById<TextView>(R.id.tv_skip)
btnStart.setOnClickListener {
// 标记已经不是第一次启动了
val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
prefs.edit().putBoolean("is_first_launch", false).apply()
// 跳转到主界面
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
}

View File

@@ -0,0 +1,24 @@
package com.example.myapplication
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
val isFirstLaunch = prefs.getBoolean("is_first_launch", true)
if (isFirstLaunch) {
// 第一次启动 → 进入引导页
startActivity(Intent(this, OnboardingActivity::class.java))
} else {
// 不是第一次 → 直接进入主界面
startActivity(Intent(this, MainActivity::class.java))
}
finish()
}
}

View File

@@ -0,0 +1,51 @@
package com.example.myapplication
import android.content.Context
class SuggestionStats(ctx: Context) {
// 永远持有 applicationContext避免泄漏/早期异常
private val app = ctx.applicationContext
private val prefs = app.getSharedPreferences("suggestion_stats", Context.MODE_PRIVATE)
// 点击次数统计
fun incClick(word: String?) {
val w = word?.trim().orEmpty()
if (w.isEmpty()) return
val key = "cnt_$w"
val old = prefs.getInt(key, 0)
prefs.edit().putInt(key, old + 1).apply()
}
// 从应用程序的 SharedPreferences 中获取指定单词的点击次数。
fun getCount(word: String?): Int {
val w = word?.trim().orEmpty()
if (w.isEmpty()) return 0
return prefs.getInt("cnt_$w", 0)
}
// 按点击次数从大到小排序候选词。
fun sortByCount(candidates: List<String?>): List<String> {
val clean = candidates.mapNotNull { it?.trim() }.filter { it.isNotEmpty() }
if (clean.isEmpty()) return emptyList()
// 本轮候选的计数缓存(避免在 Comparator 里频繁读 SP
val counts = clean.asSequence().distinct().associateWith { w ->
prefs.getInt("cnt_$w", 0)
}
// 稳定排序:是否有记录(1/0) -> 次数降序
return clean.sortedWith(
compareByDescending<String> { w -> if ((counts[w] ?: 0) > 0) 1 else 0 }
.thenByDescending { w -> counts[w] ?: 0 }
)
}
}

View File

@@ -0,0 +1,60 @@
package com.example.myapplication
class Trie {
//表示Trie数据结构中的一个节点该节点可以存储其子节点并且可以标记是否是一个完整单词的结尾
private data class TrieNode(
val children: MutableMap<Char, TrieNode> = mutableMapOf(),
var isEndOfWord: Boolean = false
)
private val root = TrieNode()//根节点
//将一个单词插入到Trie数据结构中。通过遍历单词的每个字符创建并连接相应的节点最终在最后一个字符的节点上标记该路径代表一个完整单词。
fun insert(word: String) {
var current = root
for (char in word.lowercase()) {
current = current.children.getOrPut(char) { TrieNode() }
}
current.isEndOfWord = true
}
//在Trie数据结构中查找指定的单词是否存在。
fun search(word: String): Boolean {
var current = root
for (char in word.lowercase()) {
current = current.children[char] ?: return false
}
return current.isEndOfWord
}
//查找以prefix为前缀的所有单词。通过遍历prefix的每个字符找到相应的节点然后从该节点开始递归查找所有以该节点为起点的单词。
fun startsWith(prefix: String): List<String> {
var current = root
for (char in prefix.lowercase()) {
current = current.children[char] ?: return emptyList()
}
return getAllWordsFromNode(current, prefix)
}
//从给定节点开始递归查找所有以该节点为起点的单词。
private fun getAllWordsFromNode(node: TrieNode, prefix: String): List<String> {
val words = mutableListOf<String>()
if (node.isEndOfWord) {
words.add(prefix)
}
for ((char, child) in node.children) {
words.addAll(getAllWordsFromNode(child, prefix + char))
}
return words
}
}

View File

@@ -0,0 +1,68 @@
package com.example.myapplication.data
import android.content.Context
import java.io.BufferedReader
import java.io.InputStreamReader
data class BigramModel(
val vocab: List<String>, // 保留全部词(含 <unk>, <s>, </s>),与二元矩阵索引对齐
val uniLogp: FloatArray, // 长度 = vocab.size
val biRowptr: IntArray, // 长度 = vocab.size + 1 (CSR)
val biCols: IntArray, // 长度 = nnz
val biLogp: FloatArray // 长度 = nnz
)
object LanguageModelLoader {
fun load(context: Context): BigramModel {
val vocab = context.assets.open("vocab.txt").bufferedReader()
.use(BufferedReader::readLines)
val uniLogp = readFloat32(context, "uni_logp.bin")
val biRowptr = readInt32(context, "bi_rowptr.bin")
val biCols = readInt32(context, "bi_cols.bin")
val biLogp = readFloat32(context, "bi_logp.bin")
require(uniLogp.size == vocab.size) { "uni_logp length != vocab size" }
require(biRowptr.size == vocab.size + 1) { "bi_rowptr length invalid" }
require(biCols.size == biLogp.size) { "bi cols/logp nnz mismatch" }
return BigramModel(vocab, uniLogp, biRowptr, biCols, biLogp)
}
private fun readInt32(context: Context, name: String): IntArray {
context.assets.open(name).use { input ->
val bytes = input.readBytes()
val n = bytes.size / 4
val out = IntArray(n)
var i = 0; var j = 0
while (i < n) {
// 小端序
val v = (bytes[j].toInt() and 0xFF) or
((bytes[j+1].toInt() and 0xFF) shl 8) or
((bytes[j+2].toInt() and 0xFF) shl 16) or
((bytes[j+3].toInt() and 0xFF) shl 24)
out[i++] = v
j += 4
}
return out
}
}
private fun readFloat32(context: Context, name: String): FloatArray {
context.assets.open(name).use { input ->
val bytes = input.readBytes()
val n = bytes.size / 4
val out = FloatArray(n)
var i = 0; var j = 0
while (i < n) {
val bits = (bytes[j].toInt() and 0xFF) or
((bytes[j+1].toInt() and 0xFF) shl 8) or
((bytes[j+2].toInt() and 0xFF) shl 16) or
((bytes[j+3].toInt() and 0xFF) shl 24)
out[i++] = Float.fromBits(bits)
j += 4
}
return out
}
}
}

View File

@@ -0,0 +1,38 @@
// 输入法读取词库文件并构建 Trie 树
package com.example.myapplication.data
import android.content.Context
import com.example.myapplication.Trie
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.concurrent.atomic.AtomicBoolean
class WordDictionary(private val context: Context) {
// 初始化给空 Trie避免构造期读取导致崩溃
val wordTrie: Trie = Trie()
private val loaded = AtomicBoolean(false)
/** 背景安全加载,多次调用只会生效一次 */
fun loadIfNeeded() {
if (!loaded.compareAndSet(false, true)) return
try {
context.assets.open("vocab.txt").use { input ->
BufferedReader(InputStreamReader(input)).useLines { lines ->
var idx = 0
lines.forEach { line ->
idx++
if (idx <= 3) return@forEach // 跳过 <unk>, <s>, </s>
val w = line.trim()
if (w.isNotEmpty()) {
wordTrie.insert(w)
}
}
}
}
} catch (_: Throwable) {
// 失败也不抛,让输入法继续运行
}
}
}

View File

@@ -0,0 +1,19 @@
package com.example.myapplication.ui.circle
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.myapplication.R
class CircleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_circle, container, false)
}
}

View File

@@ -0,0 +1,498 @@
package com.example.myapplication.ui.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.example.myapplication.R
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.card.MaterialCardView
import android.graphics.drawable.TransitionDrawable
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.widget.HorizontalScrollView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import kotlin.math.abs
import android.content.Intent
class HomeFragment : Fragment() {
private lateinit var bottomSheet: MaterialCardView
private lateinit var bottomSheetBehavior: BottomSheetBehavior<MaterialCardView>
private lateinit var scrim: View
private lateinit var viewPager: ViewPager2
private lateinit var tagContainer: LinearLayout
private lateinit var tagScroll: HorizontalScrollView
private lateinit var header: View
private lateinit var tabList1: TextView
private lateinit var tabList2: TextView
private lateinit var backgroundImage: ImageView
private var parentWidth = 0
private var parentHeight = 0
private val dragToCloseThreshold by lazy {
val dp = 40f
(dp * resources.displayMetrics.density)
}
// 第二个列表的“标签页”,数量不固定,可以从服务端/本地配置来
private val tags = listOf("标签一", "标签二", "标签三", "标签四", "标签五", "标签六", "标签七", "标签八", "标签九", "标签十")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 充值按钮点击
view.findViewById<View>(R.id.rechargeButton).setOnClickListener {
findNavController().navigate(R.id.action_global_rechargeFragment)
}
scrim = view.findViewById(R.id.view_scrim)
bottomSheet = view.findViewById(R.id.bottomSheet)
tagContainer = view.findViewById(R.id.tagContainer)
tagScroll = view.findViewById(R.id.tagScroll)
header = view.findViewById(R.id.bottomSheetHeader)
tabList1 = view.findViewById(R.id.tab_list1)
tabList2 = view.findViewById(R.id.tab_list2)
viewPager = view.findViewById(R.id.viewPager)
backgroundImage = bottomSheet.findViewById(R.id.backgroundImage)
val root = view.findViewById<CoordinatorLayout>(R.id.rootCoordinator)
val floatingImage = view.findViewById<ImageView>(R.id.floatingImage)
// 拿到父布局的宽高(需要等布局完成)
root.post {
parentWidth = root.width
parentHeight = root.height
}
initDrag(floatingImage, root)
setupBottomSheet(view)
setupViewPager()
setupTopTabs()
setupTags()
}
// ---------------- 拖拽效果 ----------------
private fun initDrag(target: View, parent: ViewGroup) {
var dX = 0f
var dY = 0f
var lastRawX = 0f
var lastRawY = 0f
var isDragging = false
val touchSlop = ViewConfiguration.get(requireContext()).scaledTouchSlop
target.setOnTouchListener { v, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// 告诉 CoordinatorLayout别拦截这次事件
parent.requestDisallowInterceptTouchEvent(true)
// 暂时禁止 BottomSheet 拖动
if (::bottomSheetBehavior.isInitialized) {
bottomSheetBehavior.isDraggable = false
}
dX = v.x - event.rawX
dY = v.y - event.rawY
lastRawX = event.rawX
lastRawY = event.rawY
isDragging = false
true
}
MotionEvent.ACTION_MOVE -> {
val dxMove = event.rawX - lastRawX
val dyMove = event.rawY - lastRawY
if (!isDragging && (abs(dxMove) > touchSlop || abs(dyMove) > touchSlop)) {
isDragging = true
}
if (isDragging) {
var newX = event.rawX + dX
var newY = event.rawY + dY
// 限制在父布局范围内
val maxX = parentWidth - v.width
val maxY = parentHeight - v.height
newX = newX.coerceIn(0f, maxX.toFloat())
newY = newY.coerceIn(0f, maxY.toFloat())
v.x = newX
v.y = newY
}
lastRawX = event.rawX
lastRawY = event.rawY
true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
// 允许父布局继续拦截之后的事件
parent.requestDisallowInterceptTouchEvent(false)
// 恢复 BottomSheet 可拖动
if (::bottomSheetBehavior.isInitialized) {
bottomSheetBehavior.isDraggable = true
}
if (!isDragging) {
v.performClick()
}
// 手指抬起:吸边
snapToEdge(v)
true
}
else -> false
}
}
}
/**
* 吸边逻辑:左右贴边(需要上下也吸边可以再扩展)
*/
private fun snapToEdge(v: View) {
if (parentWidth == 0 || parentHeight == 0) return
val centerX = v.x + v.width / 2f
val toLeft = centerX < parentWidth / 2f
val targetX = if (toLeft) 0f else (parentWidth - v.width).toFloat()
// 如果你还想限制上下边距,比如离底部留 80dp 不遮挡 BottomSheet可以再处理 y
val minTop = 0f
val maxBottom = (parentHeight - v.height).toFloat()
val targetY = v.y.coerceIn(minTop, maxBottom)
v.animate()
.x(targetX)
.y(targetY)
.setDuration(200)
.start()
}
// ---------------- BottomSheet 行为 ----------------
private fun setupBottomSheet(root: View) {
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
// 允许拖拽,允许嵌套滚动控制
bottomSheetBehavior.isDraggable = true
bottomSheetBehavior.isHideable = false
bottomSheetBehavior.isFitToContents = false
// 展开时高度占屏幕 70%
bottomSheetBehavior.halfExpandedRatio = 0.7f
// 先等布局完成之后,计算“按钮下面剩余空间”作为 peekHeight
root.post {
val coordinatorHeight = root.height-40
val button = root.findViewById<View>(R.id.rechargeButton)
val buttonBottom = button.bottom
val peek = (coordinatorHeight - buttonBottom).coerceAtLeast(200)
bottomSheetBehavior.peekHeight = peek
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
// 监听状态变化,用来控制遮罩显示/隐藏
bottomSheetBehavior.addBottomSheetCallback(object :
BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_COLLAPSED -> {
scrim.isVisible = false
}
BottomSheetBehavior.STATE_DRAGGING,
BottomSheetBehavior.STATE_EXPANDED,
BottomSheetBehavior.STATE_HALF_EXPANDED -> {
scrim.isVisible = true
}
else -> {}
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
// 跟随滑动渐变遮罩透明度
if (slideOffset >= 0f) {
scrim.alpha = slideOffset.coerceIn(0f, 1f)
}
}
})
// 点击遮罩,关闭回原位
scrim.setOnClickListener {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
// 简单的“空白区域下滑”关闭:在遮罩上响应手势(简单版,只要 move 就关)
scrim.setOnTouchListener { _, event ->
// 这里可以更精细地判断手势方向,这里简单处理为:有滑动就关闭
// 如果你想更准,可以根据 down / move 的 dy 判断
// 为了示例就写得简单一点
// MotionEvent.ACTION_MOVE = 2
if (event.action == 2) {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
true
} else {
false
}
}
// 点击底部盒子的“头部”,在折叠 / 半展开之间切换
header.setOnClickListener {
when (bottomSheetBehavior.state) {
BottomSheetBehavior.STATE_COLLAPSED -> {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}
BottomSheetBehavior.STATE_HALF_EXPANDED,
BottomSheetBehavior.STATE_EXPANDED -> {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
else -> {}
}
}
}
// ---------------- ViewPager2 + 列表 ----------------
private fun setupViewPager() {
val pageCount = 1 + tags.size // 1 = 第一个列表,剩下的是第二个列表的标签页
viewPager.adapter = SheetPagerAdapter(pageCount)
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
updateTabsAndTags(position) // 里面会调用 highlightTag把标签高亮并滚动
}
})
}
// 顶部“列表一 / 列表二”选项栏点击
private fun setupTopTabs() {
tabList1.setOnClickListener {
viewPager.currentItem = 0 // 列表一
}
tabList2.setOnClickListener {
viewPager.currentItem = 1 // 列表二的第一个标签页
}
}
// 顶部标签行(只在第二个列表时可见)
private fun setupTags() {
tagContainer.removeAllViews()
tags.forEachIndexed { index, tag ->
val tv = layoutInflater.inflate(
R.layout.item_tag,
tagContainer,
false
) as TextView
tv.text = tag
tv.setOnClickListener {
// 当前位置 = 1 + 标签下标
viewPager.currentItem = 1 + index
}
tagContainer.addView(tv)
}
// 默认选中列表一,所以标签行默认隐藏
tagScroll.isVisible = false
}
// 根据当前 page 更新上方两个选项 & 标签高亮/显隐
private fun updateTabsAndTags(position: Int) {
if (position == 0) {
tabList1.setTextColor(requireContext().getColor(R.color.black))
tabList2.setTextColor(requireContext().getColor(R.color.light_black))
tagScroll.isVisible = false
fadeImage(backgroundImage, R.drawable.option_background)
} else {
tabList1.setTextColor(requireContext().getColor(R.color.light_black))
tabList2.setTextColor(requireContext().getColor(R.color.black))
tagScroll.isVisible = true
fadeImage(backgroundImage, R.drawable.option_background_two)
val tagIndex = position - 1
highlightTag(tagIndex)
}
}
//背景淡入淡出
private fun fadeImage(imageView: ImageView, newImageRes: Int) {
val oldDrawable = imageView.drawable
val newDrawable = ContextCompat.getDrawable(requireContext(), newImageRes)
if (newDrawable == null) {
return
}
// 第一次还没有旧图,直接设置就好
if (oldDrawable == null) {
imageView.setImageDrawable(newDrawable)
return
}
val transitionDrawable = TransitionDrawable(arrayOf(oldDrawable, newDrawable)).apply {
// 关键:启用交叉淡入淡出,旧图才会一起淡出
isCrossFadeEnabled = true
}
imageView.setImageDrawable(transitionDrawable)
transitionDrawable.startTransition(300) // 300ms 淡入淡出
}
private fun highlightTag(index: Int) {
for (i in 0 until tagContainer.childCount) {
val child = tagContainer.getChildAt(i) as TextView
if (i == index) {
child.setBackgroundResource(R.drawable.tag_selected_bg)
child.setTextColor(requireContext().getColor(android.R.color.white))
// 关键:把选中的标签滚动到可见(这里我用“居中”效果)
tagScroll.post {
val scrollViewWidth = tagScroll.width
val childCenter = child.left + child.width / 2
val targetScrollX = childCenter - scrollViewWidth / 2
tagScroll.smoothScrollTo(targetScrollX.coerceAtLeast(0), 0)
}
} else {
child.setBackgroundResource(R.drawable.tag_unselected_bg)
child.setTextColor(requireContext().getColor(R.color.light_black))
}
}
}
// ---------------- 共享的 ViewHolder 类 ----------------
inner class PageViewHolder(val recyclerView: RecyclerView) :
RecyclerView.ViewHolder(recyclerView)
// ---------------- ViewPager2 的 Adapter ----------------
/**
* 每一页都是一个 RecyclerView 卡片列表:
* - position = 0列表一数据 A
* - position >= 1列表二的第 index 个标签页(数据 B[index]
*/
inner class SheetPagerAdapter(
private val pageCount: Int
) : RecyclerView.Adapter<SheetPagerAdapter.PageViewHolder>() {
inner class PageViewHolder(val root: View) : RecyclerView.ViewHolder(root)
override fun getItemViewType(position: Int): Int {
// 0第一个列表页>0第二个列表的各标签页
return if (position == 0) 0 else 1
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
val layoutId = when (viewType) {
0 -> R.layout.bottom_page_list1 // 第一个列表的自定义内容
else -> R.layout.bottom_page_list2 // 第二个列表各标签页的自定义内容
}
val root = LayoutInflater.from(parent.context)
.inflate(layoutId, parent, false)
// 如果需要,禁用嵌套滚动(对 NestedScrollView 一般问题不大,可以不写)
// root.findViewById<NestedScrollView>(R.id.scrollContent)?.isNestedScrollingEnabled = false
return PageViewHolder(root)
}
override fun onBindViewHolder(holder: PageViewHolder, position: Int) {
val root = holder.root
if (position == 0) {
// 这里可以拿到 bottom_page_list1 中的控件,做一些初始化
// val someView = root.findViewById<TextView>(R.id.xxx)
// someView.text = "xxx"
} else {
// // 第二个列表对应的标签页
// val tagIndex = position - 1
// val tagName = tags[tagIndex]
// // 示例:把标题改成“标签一的内容 / 标签二的内容 ……”
// val titleView = root.findViewById<TextView>(R.id.pageTitle)
// titleView?.text = "$tagName 的自定义内容"
// // 你也可以根据 tagIndex显示/隐藏不同区域
}
//让当前页里的滚动容器具备“下拉关闭 BottomSheet”的能力
val scrollContent = root.findViewById<View>(R.id.scrollContent)
if (scrollContent != null) {
setupPullToClose(scrollContent)
}
}
override fun getItemCount(): Int = pageCount
}
private fun setupPullToClose(scrollable: View) {
var downY = 0f
var isDraggingToClose = false
scrollable.setOnTouchListener { _, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
downY = event.rawY
isDraggingToClose = false
}
MotionEvent.ACTION_MOVE -> {
// 已经是折叠状态,不拦截,交给内容自己滚(其实也滚不动多少)
if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
return@setOnTouchListener false
}
val dy = event.rawY - downY
if (!scrollable.canScrollVertically(-1) && // 已在顶部
dy > dragToCloseThreshold && // 向下拉超过阈值
(bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
bottomSheetBehavior.state == BottomSheetBehavior.STATE_HALF_EXPANDED)
) {
isDraggingToClose = true
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
return@setOnTouchListener true
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
isDraggingToClose = false
}
}
isDraggingToClose
}
}
}

View File

@@ -0,0 +1,28 @@
package com.example.myapplication.ui.keyboard
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import android.widget.FrameLayout
import com.example.myapplication.R
class KeyboardDetailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.keyboard_detail, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
}
}

View File

@@ -0,0 +1,28 @@
package com.example.myapplication.ui.keyboard
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import android.widget.FrameLayout
import com.example.myapplication.R
class MyKeyboard : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.my_keyboard, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
}
}

View File

@@ -0,0 +1,59 @@
package com.example.myapplication.ui.mine
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.example.myapplication.R
import android.widget.LinearLayout
import de.hdodenhof.circleimageview.CircleImageView
class MineFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_mine, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 会员充值按钮点击
view.findViewById<ImageView>(R.id.imgLeft).setOnClickListener {
findNavController().navigate(R.id.action_global_rechargeFragment)
}
// 金币充值按钮点击
view.findViewById<ImageView>(R.id.imgRight).setOnClickListener {
findNavController().navigate(R.id.action_global_goldCoinRechargeFragment)
}
// 头像点击
view.findViewById<CircleImageView>(R.id.avatar).setOnClickListener {
findNavController().navigate(R.id.action_mineFragment_to_personalSettings)
}
//我的键盘
view.findViewById<LinearLayout>(R.id.keyboard_settings).setOnClickListener {
findNavController().navigate(R.id.action_mineFragment_to_mykeyboard)
}
// 反馈按钮点击
view.findViewById<LinearLayout>(R.id.click_Feedback).setOnClickListener {
findNavController().navigate(R.id.action_mineFragment_to_feedbackFragment)
}
// 反馈按钮点击
view.findViewById<LinearLayout>(R.id.click_Notice).setOnClickListener {
findNavController().navigate(R.id.action_mineFragment_to_notificationFragment)
}
}
}

View File

@@ -0,0 +1,34 @@
package com.example.myapplication.ui.mine.myotherpages
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.example.myapplication.R
import android.widget.FrameLayout
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout
import java.util.*
class FeedbackFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.feedback_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 设置关闭按钮点击事件
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
}
}

View File

@@ -0,0 +1,34 @@
package com.example.myapplication.ui.mine.myotherpages
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.example.myapplication.R
import android.widget.FrameLayout
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout
import java.util.*
class NotificationFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.notification_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 设置关闭按钮点击事件
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
}
}

View File

@@ -0,0 +1,34 @@
package com.example.myapplication.ui.mine.myotherpages
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.example.myapplication.R
import android.widget.FrameLayout
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout
import java.util.*
class PersonalSettings : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.personal_settings, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 设置关闭按钮点击事件
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
}
}

View File

@@ -0,0 +1,28 @@
package com.example.myapplication.ui.recharge
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import com.example.myapplication.R
class GoldCoinRechargeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.gold_coin_recharge, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 设置关闭按钮点击事件
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
}
}

View File

@@ -0,0 +1,35 @@
package com.example.myapplication.ui.recharge
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.ImageView
import androidx.fragment.app.Fragment
import com.example.myapplication.R
class RechargeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.activity_recharge, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 找到旧价格 TextView
val tvOldPrice = view.findViewById<TextView>(R.id.tvOldPrice)
// 旧价格加删除线
tvOldPrice.paintFlags = tvOldPrice.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
// 设置关闭按钮点击事件
view.findViewById<ImageView>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
}
}

View File

@@ -0,0 +1,345 @@
package com.example.myapplication.ui.shop
import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.viewpager2.widget.ViewPager2
import com.example.myapplication.R
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.animation.ValueAnimator
import android.animation.ArgbEvaluator
class ShopFragment : Fragment() {
private lateinit var viewPager: ViewPager2
private lateinit var tagScroll: HorizontalScrollView
private lateinit var tagContainer: LinearLayout
// 标签标题,可以根据需要修改
private val tabTitles = listOf("全部", "数码", "服饰", "家居", "美食","数码", "服饰", "家居", "美食")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_shop, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 金币充值按钮点击
view.findViewById<View>(R.id.rechargeButton).setOnClickListener {
findNavController().navigate(R.id.action_global_goldCoinRechargeFragment)
}
// 我的皮肤按钮点击
view.findViewById<View>(R.id.skinButton).setOnClickListener {
findNavController().navigate(R.id.action_shopfragment_to_myskin)
}
// 搜索按钮点击
view.findViewById<View>(R.id.searchButton).setOnClickListener {
findNavController().navigate(R.id.action_shopfragment_to_searchfragment)
}
tagScroll = view.findViewById(R.id.tagScroll)
tagContainer = view.findViewById(R.id.tagContainer)
viewPager = view.findViewById(R.id.viewPager)
val rechargeButton = view.findViewById<LinearLayout>(R.id.rechargeButton)
rechargeButton.setOnClickListener {
findNavController().navigate(R.id.action_global_goldCoinRechargeFragment)
}
// 1. 设置 ViewPager2 的 Adapter
viewPager.adapter = ShopPagerAdapter(this, tabTitles.size)
// 2. 创建顶部标签
setupTags()
// 3. 绑定 ViewPager2 滑动 & 标签联动
setupViewPager()
}
/** 动态创建标签 TextView */
private fun setupTags() {
tagContainer.removeAllViews()
val context = requireContext()
val density = context.resources.displayMetrics.density
// ⬇⬇⬇ 你要求的 padding 值(已适配 dp
val paddingHorizontal = (16 * density).toInt() // 左右 16dp
val paddingVertical = (6 * density).toInt() // 上下 6dp
val marginEnd = (8 * density).toInt() // 标签之间 8dp 间距
tabTitles.forEachIndexed { index, title ->
val tv = TextView(context).apply {
text = title
textSize = 12f // 字体大小 12sp
// ✅ 设置内边距左右16dp上下6dp
setPadding(
paddingHorizontal,
paddingVertical,
paddingHorizontal,
paddingVertical
)
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.MATCH_PARENT
).apply {
setMargins(0, 0, marginEnd, 0) // 右侧 8dp 间距
}
gravity = android.view.Gravity.CENTER
// 胶囊大圆角背景
background = createCapsuleBackground()
// 初始化选中状态
isSelected = index == 0
updateTagStyleNoAnim(this, isSelected) // 初始化不用动画,避免闪烁
// 点击切换页面
setOnClickListener {
if (viewPager.currentItem != index) {
viewPager.currentItem = index
}
}
}
tagContainer.addView(tv)
}
}
private fun createCapsuleBackground(): GradientDrawable {
val density = resources.displayMetrics.density
return GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 50f * density // 大圆角
setColor(Color.parseColor("#F1F1F1")) // 默认未选中背景
setStroke((2 * density).toInt(), Color.parseColor("#F1F1F1"))
}
}
/** 设置 ViewPager2 的监听,实现滑动联动标签 */
private fun setupViewPager() {
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
updateTagState(position)
}
})
}
/** 根据当前位置更新所有标签的选中状态 */
private fun updateTagState(position: Int) {
for (i in 0 until tagContainer.childCount) {
val child = tagContainer.getChildAt(i) as TextView
val newSelected = i == position
// ✅ 如果这个标签的选中状态没有变化,就不要动它,避免“闪一下”
if (child.isSelected == newSelected) continue
child.isSelected = newSelected
updateTagStyleWithAnim(child, newSelected)
if (newSelected) {
// 让选中项尽量居中显示
child.post {
val scrollX = child.left - (tagScroll.width - child.width) / 2
tagScroll.smoothScrollTo(scrollX, 0)
}
}
}
}
/** 统一控制标签样式,可根据自己项目主题改颜色/大小 **/
private fun updateTagStyle(textView: TextView, selected: Boolean) {
val context = textView.context
val density = context.resources.displayMetrics.density
// 确保背景是 GradientDrawable方便改边框和背景色
val bg = (textView.background as? GradientDrawable)
?: createCapsuleBackground().also { textView.background = it }
// 颜色配置(按你要求)
val selectedTextColor = Color.parseColor("#1B1F1A")
val unselectedTextColor = Color.parseColor("#9F9F9F")
val selectedStrokeColor = Color.parseColor("#02BEAC")
val unselectedStrokeColor = Color.parseColor("#F1F1F1")
val selectedBgColor = Color.parseColor("#FFFFFF")
val unselectedBgColor = Color.parseColor("#F1F1F1")
// 当前颜色作为起点
val startTextColor = textView.currentTextColor
val startStrokeColor = try {
// 没有方便的 getter这里通过 isSelected 反推一个“起点”
if (selected) unselectedStrokeColor else selectedStrokeColor
} catch (e: Exception) {
if (selected) unselectedStrokeColor else selectedStrokeColor
}
val startBgColor = if (selected) unselectedBgColor else selectedBgColor
// 目标颜色
val endTextColor = if (selected) selectedTextColor else unselectedTextColor
val endStrokeColor = if (selected) selectedStrokeColor else unselectedStrokeColor
val endBgColor = if (selected) selectedBgColor else unselectedBgColor
val strokeWidth = (2 * density).toInt()
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 200L // 动画时长可以自己调
addUpdateListener { va ->
val fraction = va.animatedFraction
val evaluator = ArgbEvaluator()
val currentTextColor =
evaluator.evaluate(fraction, startTextColor, endTextColor) as Int
val currentStrokeColor =
evaluator.evaluate(fraction, startStrokeColor, endStrokeColor) as Int
val currentBgColor =
evaluator.evaluate(fraction, startBgColor, endBgColor) as Int
textView.setTextColor(currentTextColor)
bg.setStroke(strokeWidth, currentStrokeColor)
bg.setColor(currentBgColor)
}
}
animator.start()
// 字重变化
textView.setTypeface(null, if (selected) Typeface.BOLD else Typeface.NORMAL)
}
/** ViewPager2 的 Adapter可以替换成你的真实 Fragment */
private class ShopPagerAdapter(
fragment: Fragment,
private val pageCount: Int
) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = pageCount
override fun createFragment(position: Int): Fragment {
// 根据 position 返回不同的页面 Fragment
// 这里先用一个简单的占位示例
return SimplePageFragment.newInstance("当前页:${position + 1}")
}
}
private fun updateTagStyleNoAnim(textView: TextView, selected: Boolean) {
val density = resources.displayMetrics.density
val bg = (textView.background as? GradientDrawable)
?: createCapsuleBackground().also { textView.background = it }
val strokeWidth = (2 * density).toInt()
if (selected) {
bg.setColor(Color.parseColor("#FFFFFF")) // 背景白色
bg.setStroke(strokeWidth, Color.parseColor("#02BEAC")) // 边框 #02BEAC
textView.setTextColor(Color.parseColor("#1B1F1A")) // 字体 #1B1F1A
textView.setTypeface(null, Typeface.BOLD)
} else {
bg.setColor(Color.parseColor("#F1F1F1")) // 背景 #F1F1F1
bg.setStroke(strokeWidth, Color.parseColor("#F1F1F1")) // 边框 #F1F1F1
textView.setTextColor(Color.parseColor("#9F9F9F")) // 字体 #9F9F9F
textView.setTypeface(null, Typeface.NORMAL)
}
}
private fun updateTagStyleWithAnim(textView: TextView, selected: Boolean) {
val density = resources.displayMetrics.density
val bg = (textView.background as? GradientDrawable)
?: createCapsuleBackground().also { textView.background = it }
val strokeWidth = (2 * density).toInt()
// 颜色配置
val selectedTextColor = Color.parseColor("#1B1F1A")
val unselectedTextColor = Color.parseColor("#9F9F9F")
val selectedStrokeColor = Color.parseColor("#02BEAC")
val unselectedStrokeColor = Color.parseColor("#F1F1F1")
val selectedBgColor = Color.parseColor("#FFFFFF")
val unselectedBgColor = Color.parseColor("#F1F1F1")
// 起点、终点颜色我们自己定义,而不是乱读当前值,避免抖动
val startTextColor: Int
val endTextColor: Int
val startStrokeColor: Int
val endStrokeColor: Int
val startBgColor: Int
val endBgColor: Int
if (selected) {
// 未选中 -> 选中
startTextColor = unselectedTextColor
endTextColor = selectedTextColor
startStrokeColor = unselectedStrokeColor
endStrokeColor = selectedStrokeColor
startBgColor = unselectedBgColor
endBgColor = selectedBgColor
} else {
// 选中 -> 未选中
startTextColor = selectedTextColor
endTextColor = unselectedTextColor
startStrokeColor = selectedStrokeColor
endStrokeColor = unselectedStrokeColor
startBgColor = selectedBgColor
endBgColor = unselectedBgColor
}
val evaluator = ArgbEvaluator()
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 200L
addUpdateListener { va ->
val fraction = va.animatedFraction
val currentTextColor =
evaluator.evaluate(fraction, startTextColor, endTextColor) as Int
val currentStrokeColor =
evaluator.evaluate(fraction, startStrokeColor, endStrokeColor) as Int
val currentBgColor =
evaluator.evaluate(fraction, startBgColor, endBgColor) as Int
textView.setTextColor(currentTextColor)
bg.setStroke(strokeWidth, currentStrokeColor)
bg.setColor(currentBgColor)
}
}
animator.start()
textView.setTypeface(null, if (selected) Typeface.BOLD else Typeface.NORMAL)
}
}

View File

@@ -0,0 +1,36 @@
package com.example.myapplication.ui.shop
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.example.myapplication.R
class SimplePageFragment : Fragment() {
companion object {
fun newInstance(text: String): SimplePageFragment {
val fragment = SimplePageFragment()
val args = Bundle()
args.putString("text", text)
fragment.arguments = args
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.simple_page_layout, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// val text = arguments?.getString("text") ?: ""
// view.findViewById<TextView>(R.id.textView).text = text
}
}

View File

@@ -0,0 +1,28 @@
package com.example.myapplication.ui.shop.myskin
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import android.widget.FrameLayout
import com.example.myapplication.R
class MySkin : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.my_skin, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
}
}

View File

@@ -0,0 +1,149 @@
package com.example.myapplication.ui.shop.search
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.cardview.widget.CardView
import androidx.fragment.app.Fragment
import com.example.myapplication.R
import com.google.android.flexbox.FlexboxLayout
import com.google.android.flexbox.FlexboxLayout.LayoutParams
import androidx.navigation.fragment.findNavController
import androidx.core.os.bundleOf
import androidx.navigation.fragment.findNavController
class SearchFragment : Fragment() {
private lateinit var historyLayout: FlexboxLayout
private lateinit var etInput: EditText
private val prefsName = "search_history"
private lateinit var historySection: LinearLayout
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_search, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 返回
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
// 详情跳转
view.findViewById<CardView>(R.id.card_view).setOnClickListener {
findNavController().navigate(R.id.action_global_keyboardDetailFragment)
}
historySection = view.findViewById(R.id.layout_history_section)
historyLayout = view.findViewById(R.id.layout_history_list)
etInput = view.findViewById(R.id.et_input)
// 加载历史记录
loadHistory()
// 点击 Search 按钮
val tvSearch = view.findViewById<TextView>(R.id.tv_search)
tvSearch.setOnClickListener {
val keyword = etInput.text.toString().trim()
if (keyword.isNotEmpty()) {
saveHistory(keyword)
// 把搜索词放进 Bundle
val bundle = bundleOf("search_keyword" to keyword)
// 跳转时带上 bundle
findNavController().navigate(
R.id.action_searchFragment_to_searchResultFragment,
bundle
)
etInput.setText("")
} else {
Toast.makeText(requireContext(), "请输入搜索内容", Toast.LENGTH_SHORT).show()
}
}
// 清空历史记录
view.findViewById<FrameLayout>(R.id.iv_delete_history).setOnClickListener {
clearHistory()
}
}
/** 保存历史记录到 SharedPreferences */
private fun saveHistory(keyword: String) {
val prefs = requireContext().getSharedPreferences(prefsName, Context.MODE_PRIVATE)
val oldList = prefs.getStringSet("list", mutableSetOf())!!.toMutableSet()
oldList.add(keyword)
prefs.edit().putStringSet("list", oldList).apply()
}
/** 加载历史记录 */
private fun loadHistory() {
historyLayout.removeAllViews()
val prefs = requireContext().getSharedPreferences(prefsName, Context.MODE_PRIVATE)
val list = prefs.getStringSet("list", emptySet())?.toList() ?: emptyList()
if (list.isEmpty()) {
historySection.visibility = View.GONE // 没有历史 → 隐藏区域
return
} else {
historySection.visibility = View.VISIBLE // 有历史 → 显示区域
}
for (item in list.reversed()) {
historyLayout.addView(createHistoryItem(item))
}
}
/** 创建历史记录 item */
private fun createHistoryItem(keyword: String): View {
val tv = TextView(requireContext())
tv.text = keyword
tv.textSize = 11f // 字体大小 11sp
tv.setTextColor(Color.parseColor("#1B1F1A"))
tv.setPadding(30, 16, 30, 16)
tv.setBackgroundResource(R.drawable.history_item_bg)
val params = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
FlexboxLayout.LayoutParams.WRAP_CONTENT
)
params.setMargins(10, 10, 10, 10)
tv.layoutParams = params
tv.setOnClickListener {
etInput.setText(keyword)
etInput.setSelection(keyword.length)
}
return tv
}
/** 清空历史记录 */
private fun clearHistory() {
val prefs = requireContext().getSharedPreferences(prefsName, Context.MODE_PRIVATE)
prefs.edit().clear().apply()
historyLayout.removeAllViews()
historySection.visibility = View.GONE
}
}

View File

@@ -0,0 +1,39 @@
package com.example.myapplication.ui.shop.search
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import com.example.myapplication.R
class SearchResultFragment : Fragment() {
private lateinit var etInput: EditText
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_search_result, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 返回按钮
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
parentFragmentManager.popBackStack()
}
etInput = view.findViewById(R.id.et_input)
// ⭐ 接收从上一个页面传来的搜索词
val keyword = arguments?.getString("search_keyword") ?: ""
etInput.setText(keyword)
// etInput.setSelection(keyword.length) // 光标移动到最后
}
}

View File

@@ -0,0 +1,11 @@
package com.example.myapplication.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@@ -0,0 +1,58 @@
package com.example.myapplication.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun MyApplicationTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -0,0 +1,34 @@
package com.example.myapplication.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromAlpha="0.0"
android:toAlpha="1.0" />

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromAlpha="0.0"
android:toAlpha="1.0" />

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromAlpha="1.0"
android:toAlpha="0.0" />

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromAlpha="1.0"
android:toAlpha="0.0" />

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 选中时文字颜色 #4EDBC5 -->
<item android:color="#4EDBC5" android:state_checked="true" />
<!-- 未选中时的文字颜色,自定义一个你喜欢的 -->
<item android:color="#8A8A8A" />
</selector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#30CC65" android:state_checked="true"/> <!-- ON 绿色 -->
<item android:color="#D6D6D6"/> <!-- OFF 灰色 -->
</selector>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#F3FFF8"
android:centerColor="#E1FDFF"
android:endColor="#FFFFFF"
android:angle="-90"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,11 @@
<!-- 启动页 -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 背景图 -->
<item>
<bitmap
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/startup_page"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="#33000000"/>
</shape>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按下状态 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#CCCCCC"/>
<corners android:radius="4dp"/>
<stroke android:width="1dp" android:color="#999999"/>
</shape>
</item>
<!-- 默认状态 -->
<item>
<shape android:shape="rectangle">
<solid android:color="#FFFFFF"/>
<corners android:radius="4dp"/>
<stroke android:width="1dp" android:color="#DDDDDD"/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Shift键激活状态 -->
<item android:state_activated="true">
<shape android:shape="rectangle">
<solid android:color="#4CAF50"/>
<corners android:radius="4dp"/>
<stroke android:width="1dp" android:color="#388E3C"/>
</shape>
</item>
<!-- 按下状态 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#9E9E9E"/>
<corners android:radius="4dp"/>
<stroke android:width="1dp" android:color="#757575"/>
</shape>
</item>
<!-- 默认状态 -->
<item>
<shape android:shape="rectangle">
<solid android:color="#E0E0E0"/>
<corners android:radius="4dp"/>
<stroke android:width="1dp" android:color="#BDBDBD"/>
</shape>
</item>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/selected_circle" android:state_checked="true"/>
<item android:drawable="@drawable/circle"/>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#FAFAFA"/>
<corners android:radius="11dp"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View 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">
<solid android:color="#EDFFFD"/>
<corners android:radius="50dp"/>
<!-- 白色边框 -->
<stroke android:width="1dp" android:color="#FFFFFF"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,14 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<stroke
android:width="1dp"
android:color="#FFFFFF" />
<corners
android:topLeftRadius="36dp"
android:topRightRadius="36dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,11 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#F1F1F1"
android:endColor="#FFFFFF"
android:angle="-90" /> <!-- 90 = 从上到下渐变 -->
<corners android:radius="10dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#F1F1F1"/>
<corners android:radius="50dp"/>
<padding android:left="15dp" android:right="15dp" android:top="8dp" android:bottom="8dp"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 选中状态图标 -->
<item android:drawable="@drawable/selected_home" android:state_checked="true"/>
<!-- 默认图标 -->
<item android:drawable="@drawable/home"/>
</selector>

View 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>

View 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>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/white" />
</selector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="7dp"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#02BEAC"/>
<corners android:radius="50dp"/>
</shape>

View File

@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#111111" /> <!-- 背景色 -->
<corners android:radius="5dp" /> <!-- 圆角半径,越大越圆 -->
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Some files were not shown because too many files have changed in this diff Show More