first commit

This commit is contained in:
ocean 2026-01-08 16:04:51 +08:00
commit 88ca5e00f6
139 changed files with 32310 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
*.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
.idea/
.safedk/
/app/debug/
/app/release/

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

BIN
app/PersonlisedKeyboard Normal file

Binary file not shown.

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

@ -0,0 +1,84 @@
import java.util.Date
import java.text.SimpleDateFormat
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
id ("kotlin-android")
}
val timestamp = SimpleDateFormat("MM_dd_HH_mm").format(Date())
android {
namespace = "com.app.input.personalised.board"
compileSdk = 36
defaultConfig {
applicationId = "com.app.personalised.board"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
setProperty(
"archivesBaseName",
"PersonalisedKeyboard_V" + versionName + "(${versionCode})_$timestamp"
)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
buildConfig = true
viewBinding = true
}
}
dependencies {
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.13.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.activity:activity:1.12.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
implementation("com.squareup.okhttp3:okhttp:5.3.2")
implementation("com.github.bumptech.glide:glide:5.0.5")
implementation ("jp.wasabeef:glide-transformations:4.3.0")
//Glide支持webp动图的库
implementation("com.github.zjupure:webpdecoder:2.7.4.16.0")
implementation("com.github.omicronapps:7-Zip-JBinding-4Android:Release-16.02-2.03")
val room_version = "2.8.4"
implementation ("androidx.room:room-runtime:$room_version")
kapt("androidx.room:room-compiler:$room_version")
implementation ("androidx.room:room-ktx:$room_version")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("com.google.android.material:material:1.8.0")
}

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

@ -0,0 +1,45 @@
# 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
# 保持 Room 的核心类不被混淆
-keep class androidx.room.** { *; }
-keep class androidx.sqlite.db.** { *; }
# 保持 Room 数据库类的基本结构
-keep class * extends androidx.room.RoomDatabase { *; }
# 保持 Room DAO 接口
-keep @androidx.room.Dao interface * { *; }
-keep @androidx.room.Dao class * { *; }
# 保持 Room 实体类
-keep @androidx.room.Entity class * { *; }
# 保持 Room 的注解类
-keep @androidx.room.Database class * { *; }
-keep class com.omicronapplications.** { *; }
-keep class net.sf.sevenzipjbinding.** { *; }

View File

@ -0,0 +1,25 @@
package com.keyboard.skinning.cool
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.ba.ali.apps.keyboard", appContext.packageName)
}
}

View 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=".BoartApp"
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo"
android:supportsRtl="true"
android:theme="@style/MyKeyBoard"
tools:targetApi="31">
<activity
android:name=".boartactivity.ThemeListActivity"
android:exported="false" />
<activity
android:name=".boartactivity.SplashItemActivity"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".boartactivity.ThemeDetailActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".boartactivity.ThemePreviewActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".boartactivity.MainTabActivity"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name=".boartactivity.FavoriteThemeActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".boartactivity.ThemeSearchActivity"
android:exported="false"
android:screenOrientation="portrait" />
<service
android:name=".keyboardutils.CustomInputMethodService"
android:exported="true"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/keyborad_xml" />
</service>
</application>
</manifest>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
package com.app.input.personalised.board
import android.app.Application
import android.graphics.Typeface
import com.app.input.personalised.board.databean.KeyboardTheme
import com.app.input.personalised.board.databean.ThemeCategory
//import com.pretty.keyboard.theme.keyboard.helper.ObjectBox
import org.json.JSONArray
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.io.StringWriter
class BoartApp : Application() {
companion object {
lateinit var boartAppInstance: BoartApp
lateinit var list: MutableList<ThemeCategory>
const val TAG = "-----------------"
var defaultFont: Typeface? = null
const val DB_VERSION = 1
const val DB_NAME = "db_name"
}
override fun onCreate() {
super.onCreate()
boartAppInstance = this
defaultFont = Typeface.createFromAsset(assets, "my_font.ttf")
dealFile()
}
private fun dealFile() {
val openFile = boartAppInstance.assets.open("new_res.json")
val jsonString = getJsonString(openFile)
if (jsonString != null) {
resolveJsonString(jsonString)
}
}
private fun resolveJsonString(string: String) {
val jsonData = JSONArray(string)
var dataList: MutableList<ThemeCategory> = mutableListOf()
for (i in 0 until jsonData.length()) {
jsonData.getJSONObject(i).run {
val pName = getString("parent_name")
val listArray = getJSONArray("keyboard_list")
var beanDetailsList: MutableList<KeyboardTheme> = mutableListOf()
for (listIndex in 0 until listArray.length()) {
listArray.getJSONObject(listIndex).also {
val title = it.getString("title")
val thUrl = it.getString("thumbUrl")
val thGif = it.getString("thumbUrlGif")
var zipPath = ""
var imgPath = ""
var imgGif = ""
var imgPreviewGif = ""
if (it.has("detail")) {
val contentObject =
it.getJSONObject("detail").getJSONObject("themeContent")
zipPath = contentObject.getString("androidRawZipUrl")
imgPath = contentObject.getString("img")
imgGif = contentObject.getString("imgGif")
imgPreviewGif = contentObject.getString("imgPreviewGif")
} else {
}
beanDetailsList.add(
KeyboardTheme().apply {
setImgPath(imgPath)
setZipPath(zipPath)
setTitleName(title)
setImgGif(imgGif)
thumbUrl = thUrl
thumbGif = thGif
})
}
}
val shuffled = beanDetailsList.shuffled()
val dataBeanWrapper = ThemeCategory()
.apply {
parentName = pName
keyboardList = shuffled
}
dataList.add(dataBeanWrapper)
}
}
updateDataList(dataList)
}
private fun updateDataList(mainList: MutableList<ThemeCategory>) {
list = mainList
}
private fun getJsonString(fileInputStream: InputStream): String? {
return try {
// FileInputStream fileInputStream = new FileInputStream(path);
val charArray = CharArray(fileInputStream.available())
var readCount = 0
val streamReader = InputStreamReader(fileInputStream)
val bufferedReader = BufferedReader(streamReader)
val stringWriter = StringWriter()
while (bufferedReader.read(charArray).also { readCount = it } != -1) {
stringWriter.write(charArray, 0, readCount)
}
stringWriter.toString()
} catch (e: IOException) {
""
}
}
}

View File

@ -0,0 +1,30 @@
package com.app.input.personalised.board.basedata
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.app.input.personalised.board.BoartApp
import com.app.input.personalised.board.databean.KeyboardTheme
@Database(
entities = [KeyboardTheme::class],
version = BoartApp.Companion.DB_VERSION,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun ThemesDao(): ThemeDao
companion object {
val boartAppDatabase: AppDatabase by lazy {
Room.databaseBuilder(
BoartApp.Companion.boartAppInstance, AppDatabase::class.java,
BoartApp.Companion.DB_NAME
).build()
}
}
}

View File

@ -0,0 +1,32 @@
package com.app.input.personalised.board.basedata
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.app.input.personalised.board.databean.KeyboardTheme
@Dao
interface ThemeDao {
@Insert(onConflict = OnConflictStrategy.Companion.IGNORE)
suspend fun insertData(data: KeyboardTheme): Long
@Query("select * from KeyboardTheme ")
fun queryAllLike(): LiveData<List<KeyboardTheme?>?>
@Query("select * from KeyboardTheme where titleName = :title ")
suspend fun queryIsLike(title: String ): KeyboardTheme?
@Delete
suspend fun delete(data: KeyboardTheme)
@Update
suspend fun updateLike(data: KeyboardTheme)
}

View File

@ -0,0 +1,43 @@
package com.app.input.personalised.board.basedata
import com.app.input.personalised.board.databean.KeyboardTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
object ThemeRepository {
suspend fun addLike(data: KeyboardTheme) {
withContext(Dispatchers.IO) {
AppDatabase.boartAppDatabase.ThemesDao().insertData(data)
}
}
suspend fun removeLike(data: KeyboardTheme) {
withContext(Dispatchers.IO) {
val queryIsLike = AppDatabase.boartAppDatabase.ThemesDao().queryIsLike(data.titleName)
if (queryIsLike != null) {
AppDatabase.boartAppDatabase.ThemesDao().delete(queryIsLike)
}
}
}
// Java 友好的方法,用于从 Java 代码调用
fun removeLikeFromJava(data: KeyboardTheme) {
GlobalScope.launch(Dispatchers.IO) {
removeLike(data)
}
}
suspend fun getIsLike(name: String, action: (isLike: Boolean) -> Unit) {
withContext(Dispatchers.IO) {
val query = AppDatabase.boartAppDatabase.ThemesDao().queryIsLike(name)
withContext(Dispatchers.Main) {
action.invoke(query != null)
}
}
}
}

View File

@ -0,0 +1,102 @@
package com.app.input.personalised.board.boartactivity;
import android.os.Bundle;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.itemback.FavoriteRemovedListener;
import com.app.input.personalised.board.basedata.AppDatabase;
import com.app.input.personalised.board.basedata.ThemeRepository;
import com.app.input.personalised.board.databinding.ActivityFavoriteThemeBinding;
import com.app.input.personalised.board.listhelpers.FavoriteListAdapter;
import com.app.input.personalised.board.helpers.GridItemDecoration;
import java.util.List;
public class FavoriteThemeActivity extends AppCompatActivity implements FavoriteRemovedListener {
private ActivityFavoriteThemeBinding vb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityFavoriteThemeBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
initData();
initClick();
}
@Override
protected void onResume() {
super.onResume();
// 确保状态栏始终为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// 窗口获得焦点时确保状态栏为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
}
private void initData() {
vb.className.setText(getString(R.string.likes));
// 观察收藏数据
AppDatabase.Companion.getBoartAppDatabase().ThemesDao().queryAllLike().observe(this, favoriteList -> {
if (favoriteList != null && !favoriteList.isEmpty()) {
List<KeyboardTheme> data = (List<KeyboardTheme>) favoriteList;
GridItemDecoration gridItemDecoration = new GridItemDecoration(2, 2, 0);
FavoriteListAdapter adapterMain = new FavoriteListAdapter(this);
// 设置取消收藏的回调
adapterMain.setRemoveLike(this);
adapterMain.setForYouList(data);
vb.recycler.setLayoutManager(new GridLayoutManager(FavoriteThemeActivity.this, 2));
vb.recycler.setAdapter(adapterMain);
vb.recycler.addItemDecoration(gridItemDecoration);
// 显示列表隐藏空状态
vb.recycler.setVisibility(android.view.View.VISIBLE);
vb.emptyTitle.setVisibility(android.view.View.GONE);
} else {
// 如果没有收藏显示空状态
vb.recycler.setAdapter(null);
vb.recycler.setVisibility(android.view.View.GONE);
vb.emptyTitle.setVisibility(android.view.View.VISIBLE);
}
});
}
@Override
public void OnRemoveLike(KeyboardTheme data) {
// 使用 Java 友好的方法删除收藏
ThemeRepository.INSTANCE.removeLikeFromJava(data);
}
private void initClick() {
vb.back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}

View File

@ -0,0 +1,150 @@
package com.app.input.personalised.board.boartactivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databinding.ActivityMainTabBinding;
import com.app.input.personalised.board.helpers.FontTextView;
import com.app.input.personalised.board.boartfragment.CategoryFragment;
import com.app.input.personalised.board.boartfragment.MyFragment;
import com.app.input.personalised.board.boartfragment.HomeFragment;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class MainTabActivity extends AppCompatActivity {
private ActivityMainTabBinding vb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityMainTabBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// 延迟设置状态栏确保覆盖MaterialComponents主题可能的自动设置
findViewById(R.id.main).post(() -> {
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
});
init();
}
@Override
protected void onResume() {
super.onResume();
// 确保状态栏始终为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// 窗口获得焦点时确保状态栏为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
}
private void init() {
List<Fragment> listFragment = new ArrayList<>();
listFragment.add(HomeFragment.newInstance());
listFragment.add(CategoryFragment.newInstance());
listFragment.add(MyFragment.newInstance());
for (int i = 0; i < listFragment.size(); i++) {
TabLayout.Tab tab = vb.tabLayout.newTab();
View inflate = LayoutInflater.from(this).inflate(R.layout.tab_custom_view, null, false);
ImageView icon = inflate.findViewById(R.id.im_icon);
FontTextView textView = inflate.findViewById(R.id.textView);
if (i == 0) {
textView.setSelected(true);
textView.setText(getString(R.string.tab_home));
icon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.tab_home_selecto));
} else if(i == 1){
textView.setSelected(false);
textView.setText(getString(R.string.tab_category));
icon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.tab_category_selecto));
} else if(i == 2){
textView.setSelected(false);
textView.setText(getString(R.string.tab_favorite));
icon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.tab_favorite_selecto));
}
tab.setCustomView(inflate);
vb.tabLayout.addTab(tab);
}
vb.viewpager.setUserInputEnabled(false);
// 减少预加载页面数量只预加载相邻的1个页面减少初始化开销
vb.viewpager.setOffscreenPageLimit(1);
vb.viewpager.setAdapter(new FragmentStateAdapter(this) {
@NonNull
@Override
public Fragment createFragment(int position) {
return listFragment.get(position);
}
@Override
public int getItemCount() {
return listFragment.size();
}
});
bing();
}
private void bing() {
vb.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
ImageView icon = Objects.requireNonNull(tab.getCustomView()).findViewById(R.id.im_icon);
icon.setSelected(true);
vb.viewpager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
ImageView icon = Objects.requireNonNull(tab.getCustomView()).findViewById(R.id.im_icon);
icon.setSelected(false);
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
vb.viewpager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
Objects.requireNonNull(vb.tabLayout.getTabAt(position)).select();
}
});
}
}

View File

@ -0,0 +1,140 @@
package com.app.input.personalised.board.boartactivity
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.CountDownTimer
import android.widget.ProgressBar
import androidx.core.view.WindowCompat
import com.app.input.personalised.board.R
import com.app.input.personalised.board.helpers.FontTextView
import com.app.input.personalised.board.helpers.StatusBarHelper
import kotlin.math.roundToInt
/**
* 不要修改启动页继承Activity这点
*/
class SplashItemActivity : Activity() {
private lateinit var progressBar: ProgressBar
private var countTime = 1500L
private lateinit var timer: CountDownTimer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
// 设置透明状态栏
StatusBarHelper.setTransparentStatusBar(this)
// 设置导航栏为白色背景,灰色图标
setWhiteNavigationBar()
// 延迟设置,确保覆盖可能的自动设置
findViewById<android.view.View>(R.id.main).post {
StatusBarHelper.setTransparentStatusBar(this)
setWhiteNavigationBar()
}
progressBar = findViewById<ProgressBar>(R.id.nova_progress)
init()
}
private fun setWhiteNavigationBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = this.window
// 设置导航栏背景为白色
window.navigationBarColor = resources.getColor(R.color.future_tech_white, null)
// 设置导航栏图标为深色(灰色),适用于浅色背景
window.decorView.post {
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController?.isAppearanceLightNavigationBars = true
}
}
}
override fun onResume() {
super.onResume()
// 确保状态栏始终为透明
StatusBarHelper.setTransparentStatusBar(this)
// 确保导航栏为白色背景,灰色图标
setWhiteNavigationBar()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
// 窗口获得焦点时,确保状态栏为透明
StatusBarHelper.setTransparentStatusBar(this)
// 确保导航栏为白色背景,灰色图标
setWhiteNavigationBar()
}
}
private fun init() {
// 添加淡入动画
val logo = findViewById<androidx.constraintlayout.utils.widget.ImageFilterView>(R.id.image)
val title = findViewById<FontTextView>(R.id.tv)
logo.alpha = 0f
title.alpha = 0f
progressBar.alpha = 0f
logo.animate()
.alpha(1f)
.setDuration(500)
.start()
title.animate()
.alpha(1f)
.setDuration(500)
.setStartDelay(200)
.start()
progressBar.animate()
.alpha(1f)
.setDuration(500)
.setStartDelay(400)
.start()
timer = object : CountDownTimer(countTime, 50) {
override fun onTick(millisUntilFinished: Long) {
val percentage: Float = 100 - millisUntilFinished.toFloat() / countTime * 100
val round = percentage.roundToInt()
progressBar.progress = round
}
override fun onFinish() {
progressBar.progress = 100
// 添加淡出动画
logo.animate()
.alpha(0f)
.setDuration(300)
.start()
title.animate()
.alpha(0f)
.setDuration(300)
.start()
progressBar.animate()
.alpha(0f)
.setDuration(300)
.withEndAction {
toHome()
}
.start()
}
}
timer.start()
}
private fun toHome() {
startActivity(Intent(this, MainTabActivity::class.java))
finish()
}
override fun onDestroy() {
super.onDestroy()
timer.cancel()
}
}

View File

@ -0,0 +1,360 @@
package com.app.input.personalised.board.boartactivity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.app.input.personalised.board.BoartApp
import com.app.input.personalised.board.R
import com.app.input.personalised.board.databean.KeyboardTheme
import com.app.input.personalised.board.itemback.ItemClickListener
import com.app.input.personalised.board.itemback.ThemeApplyListener
import com.app.input.personalised.board.basedata.ThemeRepository
import com.app.input.personalised.board.listhelpers.RelatedThemeAdapter
import com.app.input.personalised.board.boartfragment.EnableKeyboardDialog
import com.app.input.personalised.board.helpers.AppUtils
import com.app.input.personalised.board.helpers.ThemeDownloader
import com.app.input.personalised.board.helpers.ThemePreferences
import com.bumptech.glide.Glide
import com.bumptech.glide.integration.webp.decoder.WebpDrawable
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import kotlinx.coroutines.launch
import java.io.File
class ThemeDetailActivity : AppCompatActivity() {
companion object {
@JvmField
var DISPLAY_URL_KEY: String = "display_url_key"
@JvmField
val ZIP_URL_KEY = "zip_url_key"
@JvmField
val NAME_KEY = "name_key"
@JvmField
val GIF_KEY = "gif_key"
@JvmField
val THUMB_KEY = "thumb_key"
@JvmField
val SOURCE_KEY = "data_key"
}
private var dialog: EnableKeyboardDialog? = null
private lateinit var displayUrl: String
private lateinit var gifUrl: String
private lateinit var zipUrl: String
private lateinit var name: String
private lateinit var applyBtn: LinearLayout
private lateinit var imgData: ImageView
private lateinit var imgBack: ImageView
private lateinit var textName: TextView
private lateinit var recommendedRecycler: RecyclerView
// private lateinit var viewAllLayout: LinearLayout
private lateinit var loadingLayout: FrameLayout
private lateinit var unzipPath: String
private lateinit var tvDownload: TextView
private lateinit var imDownload: ImageView
private lateinit var thumb: String
private lateinit var imgLike: ImageView
private lateinit var data: KeyboardTheme
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_theme_detail)
com.app.input.personalised.board.helpers.StatusBarHelper.setTransparentStatusBar(this)
ViewCompat.setOnApplyWindowInsetsListener(
findViewById<View?>(R.id.main),
OnApplyWindowInsetsListener { v: View?, insets: WindowInsetsCompat? ->
val systemBars = insets!!.getInsets(WindowInsetsCompat.Type.systemBars())
v!!.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
})
// 延迟设置状态栏确保覆盖MaterialComponents主题可能的自动设置
findViewById<View>(R.id.main).post {
com.app.input.personalised.board.helpers.StatusBarHelper.setTransparentStatusBar(this)
}
findViewId()
getExtraData()
displayData()
setApply()
onClick()
}
override fun onResume() {
super.onResume()
// 确保状态栏始终为透明
com.app.input.personalised.board.helpers.StatusBarHelper.setTransparentStatusBar(this)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
// 窗口获得焦点时,确保状态栏为透明
com.app.input.personalised.board.helpers.StatusBarHelper.setTransparentStatusBar(this)
}
}
private fun getExtraData() {
data = intent.getSerializableExtra(SOURCE_KEY) as KeyboardTheme
displayUrl = intent.getStringExtra(DISPLAY_URL_KEY).toString()
zipUrl = intent.getStringExtra(ZIP_URL_KEY).toString()
name = intent.getStringExtra(NAME_KEY).toString()
gifUrl = intent.getStringExtra(GIF_KEY).toString()
thumb = intent.getStringExtra(THUMB_KEY).toString()
val serviceZipName = ThemeDownloader.getServiceZipName(zipUrl)
unzipPath = ThemeDownloader.getUnzipPath(serviceZipName)
Log.d("-------------------", "-------unzipPath=" + unzipPath)
lifecycleScope.launch {
ThemeRepository.getIsLike(name) {
imgLike.isSelected = it
}
}
if (File(unzipPath).exists()) {
imDownload.isVisible = false
tvDownload.text = getString(R.string.apply)
} else {
imDownload.isVisible = true
tvDownload.text = getString(R.string.download_apply)
}
}
private fun findViewId() {
applyBtn = findViewById(R.id.layoutDownloadApply)
imgData = findViewById(R.id.image_data)
imgBack = findViewById(R.id.back)
textName = findViewById(R.id.textview_data_name)
recommendedRecycler = findViewById(R.id.recommended_recycler)
// viewAllLayout = findViewById(R.id.layout_view_all)
imgLike = findViewById(R.id.im_like)
loadingLayout = findViewById(R.id.loading)
imDownload = findViewById(R.id.im_download)
tvDownload = findViewById(R.id.tv_download)
}
private fun displayData() {
textName.text = name
if (gifUrl.isNotEmpty()) {
loadImgGif()
} else {
Glide.with(this)
.load(displayUrl)
.thumbnail(Glide.with(this).load(thumb))
.into(imgData)
}
}
private fun onClick() {
imgBack.setOnClickListener {
finish()
}
imgLike.setOnClickListener {
imgLike.isSelected = !imgLike.isSelected
lifecycleScope.launch {
if (imgLike.isSelected) {
ThemeRepository.addLike(data)
} else {
ThemeRepository.removeLike(data)
}
}
}
val forYouList = BoartApp.Companion.list.filter {
it.parentName == getString(R.string.recommend_name)
}
recommendedRecycler.run {
adapter = RelatedThemeAdapter(
this@ThemeDetailActivity
).apply {
val shuffled = forYouList[0].keyboardList.shuffled()
setForYouList(shuffled)
}.apply {
setClickAction(object : ItemClickListener {
override fun OnItemClickListener() {
finish()
}
})
}
layoutManager = GridLayoutManager(this@ThemeDetailActivity, 2)
}
}
@SuppressLint("CheckResult")
private fun loadImgGif() {
Glide.with(this)
.load(gifUrl)
.thumbnail(Glide.with(this).load(thumb))
.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is WebpDrawable) {
resource.loopCount = WebpDrawable.LOOP_FOREVER
}
return false
}
}).into(imgData)
}
private fun setApply() {
applyBtn.setOnClickListener {
val checkEnable = AppUtils.checkEnable(this)
val checkSetDefault = AppUtils.checkSetDefault(this)
if (!checkEnable || !checkSetDefault) {
showDialog()
return@setOnClickListener
}
startDown()
}
}
private fun showDialog() {
dialog = dialog ?: EnableKeyboardDialog.newInstance() .apply{
}
dialog?.setClickListener {
startDown()
}
dialog?.show(supportFragmentManager, "")
}
private fun startDown() {
loadingLayout.isVisible = true
applyBtn.isEnabled = false
val file = File(unzipPath)
if (file.exists()) {
val findFirstDirectory = ThemeDownloader.findFirstDirectory(file)
apply("${findFirstDirectory}/")
applyBtn.isEnabled = true
loadingLayout.isVisible = false
} else {
ThemeDownloader.startDownloadZip(zipUrl, object :
ThemeApplyListener {
override fun OnApplySkinListener(fileList: List<File?>?) {
runOnUiThread {
applyBtn.isEnabled = true
loadingLayout.isVisible = false
}
if (fileList.isNullOrEmpty()) {
runOnUiThread {
Toast.makeText(
this@ThemeDetailActivity,
getString(R.string.download_fail),
Toast.LENGTH_SHORT
).show()
}
} else {
// lifecycleScope.launch {
// DbFunction.addDownload(data)
// }
if (file.exists()) {
val findFirstDirectory = ThemeDownloader.findFirstDirectory(file)
Log.d(
BoartApp.Companion.TAG,
"----apply------------it=$findFirstDirectory"
)
runOnUiThread {
apply("${findFirstDirectory}/")
}
}
}
}
})
}
}
private fun apply(path: String) {
var skinParentPath = path
if (path.contains("res")) {
skinParentPath = path.substringBeforeLast("res")
}
ThemePreferences.updateSkinPath(skinParentPath)
Toast.makeText(
this@ThemeDetailActivity,
getString(R.string.theme_application_successful),
Toast.LENGTH_SHORT
).show()
startActivity(Intent(this, ThemePreviewActivity::class.java).apply {
putExtra(ThemePreviewActivity.key_name, name)
})
finish()
}
}

View File

@ -0,0 +1,85 @@
package com.app.input.personalised.board.boartactivity;
import android.os.Bundle;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import com.app.input.personalised.board.BoartApp;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.databean.ThemeCategory;
import com.app.input.personalised.board.databinding.ActivityThemeListBinding;
import com.app.input.personalised.board.listhelpers.RecommendedGridAdapter;
import com.app.input.personalised.board.helpers.GridItemDecoration;
import java.util.List;
public class ThemeListActivity extends AppCompatActivity {
private ActivityThemeListBinding vb;
public static final String KEY_NAME ="class_name";
private String name;
private List<KeyboardTheme> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityThemeListBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
name = getIntent().getStringExtra(KEY_NAME);
initData();
initClick();
}
@Override
protected void onResume() {
super.onResume();
// 确保状态栏始终为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// 窗口获得焦点时确保状态栏为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
}
private void initData(){
vb.className.setText(name);
for (ThemeCategory beanWrapper : BoartApp.list) {
if(beanWrapper.getParentName().equals(name)){
data = beanWrapper.getKeyboardList();
}
}
GridItemDecoration gridItemDecoration = new GridItemDecoration(2, 2, 0);
RecommendedGridAdapter adapterMain = new RecommendedGridAdapter(this);
adapterMain.setForYouList(data);
vb.recycler.setLayoutManager(new GridLayoutManager(ThemeListActivity.this, 2));
vb.recycler.setAdapter(adapterMain);
vb.recycler.addItemDecoration(gridItemDecoration);
}
private void initClick(){
vb.back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}

View File

@ -0,0 +1,150 @@
package com.app.input.personalised.board.boartactivity;
import static com.bumptech.glide.request.RequestOptions.bitmapTransform;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databinding.ActivityThemePreviewBinding;
import com.app.input.personalised.board.helpers.KeyboardConstants;
import com.app.input.personalised.board.helpers.AppUtils;
import com.app.input.personalised.board.helpers.ThemePreferences;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import jp.wasabeef.glide.transformations.BlurTransformation;
public class ThemePreviewActivity extends AppCompatActivity {
private ActivityThemePreviewBinding vb;
public static String key_name = "key_name";
private int mPreviousKeyboardHeight = -1;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityThemePreviewBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// 延迟设置状态栏确保覆盖MaterialComponents主题可能的自动设置
findViewById(R.id.main).post(() -> {
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
});
onInit();
}
@Override
protected void onResume() {
super.onResume();
// 确保状态栏始终为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// 窗口获得焦点时确保状态栏为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
}
public void onInit() {
String stringExtra = getIntent().getStringExtra(key_name);
vb.title.setText(stringExtra);
String curPath = ThemePreferences.INSTANCE.getSkinPath();
vb.idBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
if (curPath == null) {
return;
}
String bgPath = curPath+"res/drawable-xxhdpi-v4/"+ KeyboardConstants.previewBg;
Drawable bgDraw = AppUtils.INSTANCE.getBgDrawable(this, bgPath);
if (bgDraw != null) {
Glide.with(this)
.load(bgDraw)
.apply(bitmapTransform(new BlurTransformation(15, 3))) // 设置模糊半径和模糊采样
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
vb.main.setBackground(resource);
return false;
}
})
.preload();
}
keyboardheight();
vb.et.requestFocus();
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
private void keyboardheight() {
final View rootView = getWindow().getDecorView();
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int screenHeight = rootView.getRootView().getHeight();
int keypadHeight = screenHeight - r.bottom;
if (keypadHeight != mPreviousKeyboardHeight) {
if (mPreviousKeyboardHeight < keypadHeight) {
mPreviousKeyboardHeight = keypadHeight;
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) vb.et.getLayoutParams();
params.bottomMargin = mPreviousKeyboardHeight;
vb.et.setLayoutParams(params);
}
}
}
});
}
}

View File

@ -0,0 +1,212 @@
package com.app.input.personalised.board.boartactivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import com.app.input.personalised.board.BoartApp;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.databean.ThemeCategory;
import com.app.input.personalised.board.databinding.ActivityThemeSearchBinding;
import com.app.input.personalised.board.listhelpers.RecommendedGridAdapter;
import com.app.input.personalised.board.helpers.GridItemDecoration;
import java.util.ArrayList;
import java.util.List;
public class ThemeSearchActivity extends AppCompatActivity {
private ActivityThemeSearchBinding vb;
private RecommendedGridAdapter searchAdapter;
private List<KeyboardTheme> allItems = new ArrayList<>();
private List<KeyboardTheme> searchResults = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityThemeSearchBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// 延迟设置状态栏确保覆盖MaterialComponents主题可能的自动设置
findViewById(R.id.main).post(() -> {
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
});
initData();
initSearchRecycler();
initSearchBar();
initClick();
}
@Override
protected void onResume() {
super.onResume();
// 确保状态栏始终为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// 窗口获得焦点时确保状态栏为透明
com.app.input.personalised.board.helpers.StatusBarHelper.INSTANCE.setTransparentStatusBar(this);
}
}
private void initData() {
// 获取所有数据
try {
List<ThemeCategory> viewAllList = BoartApp.Companion.getList();
for (ThemeCategory wrapper : viewAllList) {
if (wrapper.getKeyboardList() != null) {
allItems.addAll(wrapper.getKeyboardList());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void initSearchRecycler() {
searchAdapter = new RecommendedGridAdapter(this);
searchAdapter.setForYouList(searchResults);
vb.recyclerSearchResults.setLayoutManager(new GridLayoutManager(this, 2));
vb.recyclerSearchResults.setAdapter(searchAdapter);
vb.recyclerSearchResults.addItemDecoration(new GridItemDecoration(2, 2, 0));
// 初始状态显示空结果
updateSearchUI();
}
private void initSearchBar() {
// 延迟显示键盘确保界面已加载
new Handler(Looper.getMainLooper()).postDelayed(() -> {
vb.etSearch.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(vb.etSearch, InputMethodManager.SHOW_IMPLICIT);
}
}, 200);
// 监听输入变化实时搜索使用延迟搜索避免频繁搜索
final Handler searchHandler = new Handler(Looper.getMainLooper());
final Runnable[] searchRunnableRef = new Runnable[1];
vb.etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 移除之前的搜索任务
if (searchRunnableRef[0] != null) {
searchHandler.removeCallbacks(searchRunnableRef[0]);
}
// 延迟300ms执行搜索避免输入时频繁搜索
final String searchText = s.toString();
searchRunnableRef[0] = () -> performSearch(searchText);
searchHandler.postDelayed(searchRunnableRef[0], 300);
}
@Override
public void afterTextChanged(Editable s) {
}
});
// 监听软键盘搜索按钮
vb.etSearch.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
performSearch(v.getText().toString());
// 隐藏键盘
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
return true;
}
return false;
});
}
private void initClick() {
// 返回按钮
vb.ivBack.setOnClickListener(v -> finish());
// 清除按钮
vb.ivClear.setOnClickListener(v -> {
vb.etSearch.setText("");
vb.etSearch.requestFocus();
});
}
private void performSearch(String query) {
searchResults.clear();
if (query == null || query.trim().isEmpty()) {
updateSearchUI();
return;
}
String searchQuery = query.trim().toLowerCase();
// 模糊搜索匹配标题
for (KeyboardTheme item : allItems) {
String title = item.getTitleName();
if (title != null && title.toLowerCase().contains(searchQuery)) {
searchResults.add(item);
}
}
updateSearchUI();
}
private void updateSearchUI() {
if (searchResults.isEmpty()) {
if (vb.etSearch.getText().toString().trim().isEmpty()) {
// 搜索框为空显示提示
vb.tvEmptyHint.setText(getString(R.string.search_hint));
vb.tvEmptyHint.setVisibility(View.VISIBLE);
vb.recyclerSearchResults.setVisibility(View.GONE);
} else {
// 有搜索内容但无结果
vb.tvEmptyHint.setText(getString(R.string.no_search_results));
vb.tvEmptyHint.setVisibility(View.VISIBLE);
vb.recyclerSearchResults.setVisibility(View.GONE);
}
} else {
// 有搜索结果
vb.tvEmptyHint.setVisibility(View.GONE);
vb.recyclerSearchResults.setVisibility(View.VISIBLE);
searchAdapter.setForYouList(searchResults);
}
// 根据是否有输入内容显示/隐藏清除按钮
vb.ivClear.setVisibility(
vb.etSearch.getText().toString().trim().isEmpty() ? View.GONE : View.VISIBLE
);
}
}

View File

@ -0,0 +1,90 @@
package com.app.input.personalised.board.boartfragment
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.app.input.personalised.board.BoartApp
import com.app.input.personalised.board.databean.ThemeCategory
import com.app.input.personalised.board.databinding.FragmentCategoryBinding
import com.app.input.personalised.board.listhelpers.CategoryGridAdapter
import com.app.input.personalised.board.boartactivity.ThemeListActivity
class CategoryFragment : Fragment() {
private lateinit var vb: FragmentCategoryBinding
private lateinit var categoryGridAdapter: CategoryGridAdapter
// 懒加载标志,避免重复初始化
private var isDataInitialized = false
companion object {
@JvmStatic
fun newInstance() = CategoryFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
vb = FragmentCategoryBinding.inflate(layoutInflater)
return vb.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 延迟到 onResume 时初始化,此时 Fragment 已经可见
isDataInitialized = true
}
override fun onResume() {
super.onResume()
// 在 onResume 时才加载数据,避免切换时阻塞
if (isDataInitialized && !::categoryGridAdapter.isInitialized) {
// 延迟加载 RecyclerView让切换动画先完成
view?.postDelayed({
if (isAdded && !isDetached) {
initCategoryList()
}
}, 500) // 延迟 500ms确保切换动画完全完成
}
}
private fun initCategoryList() {
val categoryList = try {
val boartAppList = BoartApp.Companion.list
if (boartAppList.isEmpty()) {
mutableListOf()
} else {
boartAppList.toMutableList()
}
} catch (e: UninitializedPropertyAccessException) {
mutableListOf()
}
categoryGridAdapter = CategoryGridAdapter(requireContext(), categoryList).apply {
setOnCategoryCardClickListener(object : CategoryGridAdapter.OnCategoryCardClickListener {
override fun onCategoryCardClick(beanWrapper: ThemeCategory) {
// 跳转到分类二级界面
val intent = Intent(requireContext(), ThemeListActivity::class.java).apply {
putExtra(ThemeListActivity.KEY_NAME, beanWrapper.parentName)
}
startActivity(intent)
}
})
}
vb.categoryRecycler.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = categoryGridAdapter
// 优化性能:减少缓存大小
setItemViewCacheSize(5) // 从10减少到5
// 禁用过度滚动,减少渲染开销
overScrollMode = View.OVER_SCROLL_NEVER
}
}
}

View File

@ -0,0 +1,180 @@
package com.app.input.personalised.board.boartfragment
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.provider.Settings
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import com.app.input.personalised.board.BoartApp
import com.app.input.personalised.board.R
import com.app.input.personalised.board.databinding.DialogEnableKeyboardBinding
import com.app.input.personalised.board.helpers.AppUtils
class EnableKeyboardDialog : DialogFragment() {
private lateinit var vb: DialogEnableKeyboardBinding
private lateinit var layoutStepOne: LinearLayout
private lateinit var layoutStepTwo: LinearLayout
private lateinit var imgStepOkOne: ImageView
private lateinit var imgStepOkTwo: ImageView
private lateinit var intentFilter: IntentFilter
private var myreceiver: BroadcastReceiver? = null
private lateinit var stepOne: TextView
private lateinit var stepTwo: TextView
private lateinit var context: Context
private var clickAction: (() -> Unit )? = null
companion object {
fun newInstance(): EnableKeyboardDialog {
val fragment = EnableKeyboardDialog()
return fragment
}
}
fun setClickListener(action:() -> Unit){
clickAction = action
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
vb = DialogEnableKeyboardBinding.inflate(layoutInflater)
context = BoartApp.Companion.boartAppInstance
findViewId()
onViewStep()
getReceiver()
return vb.root
}
override fun onStart() {
super.onStart()
dialog?.run {
setCanceledOnTouchOutside(true)
window?.run {
setGravity(Gravity.BOTTOM)
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
attributes = attributes.apply {
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.WRAP_CONTENT
}
}
}
}
private fun findViewId() {
layoutStepOne = vb.linearStepOne
layoutStepTwo = vb.linearStepTwo
imgStepOkOne = vb.okOne
imgStepOkTwo = vb.okTwo
stepOne = vb.textStepOne
stepTwo = vb.textStepTwo
}
private fun onViewStep() {
layoutStepOne.setOnClickListener {
startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS))
}
layoutStepTwo.setOnClickListener {
val inputMethodManager =
context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showInputMethodPicker()
}
vb.imClose.setOnClickListener {
dismiss()
}
}
override fun onResume() {
super.onResume()
updateUI()
}
private fun getReceiver() {
myreceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
updateUI()
}
}
intentFilter = IntentFilter(Intent.ACTION_INPUT_METHOD_CHANGED)
context.registerReceiver(myreceiver, intentFilter)
}
private fun updateUI() {
val checkEnable = AppUtils.checkEnable(BoartApp.Companion.boartAppInstance)
val checkSetDefault = AppUtils.checkSetDefault(BoartApp.Companion.boartAppInstance)
if (checkEnable && checkSetDefault) {
clickAction?.invoke()
dismiss()
return
}
if (checkEnable) {
layoutStepOne.isEnabled = false
layoutStepOne.isSelected = true
imgStepOkOne.isVisible = true
stepOne.setTextColor(context.getColor(R.color.step_true))
} else {
layoutStepOne.isEnabled = true
layoutStepOne.isSelected = false
imgStepOkOne.isVisible = false
stepOne.setTextColor(context.getColor(R.color.white))
}
if (checkSetDefault) {
layoutStepTwo.isEnabled = false
layoutStepTwo.isSelected = true
imgStepOkTwo.isVisible = true
stepTwo.setTextColor(context.getColor(R.color.step_true))
} else {
layoutStepTwo.isEnabled = true
layoutStepTwo.isSelected = false
imgStepOkTwo.isVisible = false
stepTwo.setTextColor(context.getColor(R.color.white))
}
}
override fun onDestroy() {
super.onDestroy()
if (myreceiver != null) {
context.unregisterReceiver(myreceiver)
}
}
}

View File

@ -0,0 +1,361 @@
package com.app.input.personalised.board.boartfragment
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.app.input.personalised.board.BoartApp
import com.app.input.personalised.board.R
import com.app.input.personalised.board.databean.KeyboardTheme
import com.app.input.personalised.board.databean.ThemeCategory
import com.app.input.personalised.board.databinding.FragmentHomeBinding
import com.app.input.personalised.board.listhelpers.BannerListAdapter
import com.app.input.personalised.board.listhelpers.RecommendedGridAdapter
import com.app.input.personalised.board.boartactivity.ThemeDetailActivity
import com.app.input.personalised.board.boartactivity.ThemeSearchActivity
class HomeFragment : Fragment() {
private lateinit var vb: FragmentHomeBinding
private lateinit var bannerListAdapter: BannerListAdapter
private lateinit var recommendedGridAdapter: RecommendedGridAdapter
private var viewAllList: MutableList<ThemeCategory> = mutableListOf()
private var bannerList: MutableList<KeyboardTheme> = mutableListOf()
private var currentStaggeredList: MutableList<KeyboardTheme> = mutableListOf()
private var bannerHandler: Handler? = null
private var bannerRunnable: Runnable? = null
private val BANNER_DELAY = 3000L // 3秒
private var isUserScrolling = false // 用户是否正在滑动
// 懒加载标志,避免重复初始化
private var isDataInitialized = false
companion object {
@JvmStatic
fun newInstance() = HomeFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
vb = FragmentHomeBinding.inflate(layoutInflater)
return vb.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 只初始化最轻量级的内容
if (!isDataInitialized) {
initSearchBar() // 先初始化最简单的
isDataInitialized = true
}
}
override fun onResume() {
super.onResume()
// 在 onResume 时才加载数据,此时 Fragment 已经可见
if (isDataInitialized && viewAllList.isEmpty()) {
initData()
// ViewPager2 延迟加载,确保切换动画完全完成
val currentView = view
if (currentView != null) {
currentView.postDelayed({
if (isAdded && !isDetached && currentView.parent != null) {
initBanner()
}
}, 200) // 延迟 200ms
// RecyclerView 延迟更长时间加载,避免阻塞切换
currentView.postDelayed({
if (isAdded && !isDetached && currentView.parent != null) {
initStaggeredRecycler()
}
}, 500) // 延迟 500ms确保切换动画完全完成后再加载
}
}
// 确保 banner 已初始化后再启动自动轮播
if (::bannerListAdapter.isInitialized && !isUserScrolling) {
startBannerAutoScroll()
}
}
private fun initData() {
// 检查数据是否已加载
try {
val boartAppList = BoartApp.Companion.list
if (boartAppList.isEmpty()) {
viewAllList = mutableListOf()
return
}
// 只在需要时创建副本
viewAllList = boartAppList.toMutableList()
} catch (e: UninitializedPropertyAccessException) {
viewAllList = mutableListOf()
return
}
// 获取推荐分类
val recommendList = viewAllList.find { it.parentName == getString(R.string.recommend_name) }
// 获取轮播图数据从推荐分类中取前5个
bannerList = recommendList?.getKeyboardList()?.take(5)?.toMutableList() ?: mutableListOf()
// 瀑布流数据:只使用推荐分类的内容,减少一半数量
if (recommendList?.getKeyboardList() != null) {
val fullList = recommendList.getKeyboardList()
// 只取一半的数据
val halfSize = fullList.size / 2
currentStaggeredList = fullList.take(halfSize).toMutableList()
} else {
currentStaggeredList = mutableListOf()
}
}
private fun initBanner() {
if (bannerList.isEmpty()) {
vb.bannerViewpager.visibility = View.GONE
vb.bannerIndicator.visibility = View.GONE
return
}
bannerListAdapter = BannerListAdapter(requireContext(), bannerList).apply {
setOnBannerClickListener(object : BannerListAdapter.OnBannerClickListener {
override fun onBannerClick(beanDetails: KeyboardTheme) {
openDetailActivity(beanDetails)
}
})
// 可选:显示标题
// setShowTitle(true)
}
vb.bannerViewpager.apply {
// 设置为 1最小预加载减少初始化开销
offscreenPageLimit = 1
adapter = bannerListAdapter
// 设置页面间距
val pageMarginPx = resources.getDimensionPixelOffset(R.dimen.page_margin)
val nextItemVisiblePx = resources.getDimensionPixelOffset(R.dimen.page_visible)
val itemDecoration = HorizontalMarginItemDecoration(pageMarginPx, nextItemVisiblePx)
addItemDecoration(itemDecoration)
// 设置页面切换回调
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
updateBannerIndicator(position)
}
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
when (state) {
ViewPager2.SCROLL_STATE_DRAGGING -> {
isUserScrolling = true
stopBannerAutoScroll()
}
ViewPager2.SCROLL_STATE_IDLE -> {
isUserScrolling = false
startBannerAutoScroll()
}
}
}
})
// 添加触摸监听,暂停自动轮播
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
stopBannerAutoScroll()
isUserScrolling = true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
isUserScrolling = false
startBannerAutoScroll()
}
}
false // 不拦截事件让ViewPager2正常处理滑动
}
}
// 创建指示器
createBannerIndicators()
// 开始自动轮播
startBannerAutoScroll()
}
private fun createBannerIndicators() {
vb.bannerIndicator.removeAllViews()
for (i in bannerList.indices) {
val indicator = View(context).apply {
layoutParams = ViewGroup.MarginLayoutParams(
resources.getDimensionPixelSize(R.dimen.indicator_width),
resources.getDimensionPixelSize(R.dimen.indicator_height)
).apply {
marginEnd = resources.getDimensionPixelSize(R.dimen.indicator_margin)
}
setBackgroundResource(R.drawable.indicator_bg_selector)
isSelected = i == 0
}
vb.bannerIndicator.addView(indicator)
}
}
private fun updateBannerIndicator(position: Int) {
val realPosition = position % bannerList.size
for (i in 0 until vb.bannerIndicator.childCount) {
vb.bannerIndicator.getChildAt(i).isSelected = i == realPosition
}
}
private fun startBannerAutoScroll() {
if (bannerList.isEmpty() || isUserScrolling) return
stopBannerAutoScroll() // 先清除之前的任务
bannerHandler = Handler(Looper.getMainLooper())
bannerRunnable = object : Runnable {
override fun run() {
if (!isUserScrolling && bannerList.isNotEmpty()) {
val currentItem = vb.bannerViewpager.currentItem
val nextItem = if (currentItem < bannerList.size - 1) currentItem + 1 else 0
vb.bannerViewpager.setCurrentItem(nextItem, true)
bannerHandler?.postDelayed(this, BANNER_DELAY)
}
}
}
bannerHandler?.postDelayed(bannerRunnable!!, BANNER_DELAY)
}
private fun stopBannerAutoScroll() {
bannerRunnable?.let { bannerHandler?.removeCallbacks(it) }
}
private fun initStaggeredRecycler() {
// 大幅减少初始加载的数据量,避免一次性渲染太多
val initialLoadCount = 10 // 先只加载前10个大幅减少
val dataToLoad = if (currentStaggeredList.size > initialLoadCount) {
currentStaggeredList.take(initialLoadCount).toMutableList()
} else {
currentStaggeredList
}
// 使用RecommendedAdapter带名称显示
recommendedGridAdapter = RecommendedGridAdapter(requireContext()).apply {
setForYouList(dataToLoad)
}
vb.staggeredRecycler.apply {
// 关键设置在NestedScrollView中使用RecyclerView需要这些配置
setHasFixedSize(false)
isNestedScrollingEnabled = false
// 优化性能:进一步减少缓存大小
setItemViewCacheSize(5) // 从10减少到5
// 禁用过度滚动,减少渲染开销
overScrollMode = View.OVER_SCROLL_NEVER
// 使用GridLayoutManager和详情页一样
layoutManager = GridLayoutManager(requireContext(), 2)
adapter = recommendedGridAdapter
}
// 延迟加载剩余数据,避免阻塞主线程
if (currentStaggeredList.size > initialLoadCount) {
view?.postDelayed({
if (isAdded && !isDetached) {
// 分批加载每次加载10个
val nextBatch = currentStaggeredList.take(initialLoadCount * 2).toMutableList()
recommendedGridAdapter.setForYouList(nextBatch)
// 如果还有更多数据,继续延迟加载
if (currentStaggeredList.size > initialLoadCount * 2) {
view?.postDelayed({
if (isAdded && !isDetached) {
recommendedGridAdapter.setForYouList(currentStaggeredList)
}
}, 800) // 再延迟800ms加载完整数据
}
}
}, 800) // 延迟800ms加载第二批数据
}
}
private fun updateStaggeredData(categoryPosition: Int) {
// 推荐区域始终显示推荐分类的内容,不受分类切换影响
val recommendList = viewAllList.find { it.parentName == getString(R.string.recommend_name) }
if (recommendList != null && recommendList.getKeyboardList() != null) {
currentStaggeredList = recommendList.getKeyboardList().toMutableList()
recommendedGridAdapter.setForYouList(currentStaggeredList)
}
}
private fun initSearchBar() {
vb.tvSearch.setOnClickListener {
val intent = Intent(requireContext(), ThemeSearchActivity::class.java)
startActivity(intent)
}
}
private fun openDetailActivity(beanDetails: KeyboardTheme) {
val intent = Intent(requireContext(), ThemeDetailActivity::class.java).apply {
putExtra(ThemeDetailActivity.SOURCE_KEY, beanDetails)
putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, beanDetails.imgPath)
putExtra(ThemeDetailActivity.ZIP_URL_KEY, beanDetails.zipPath)
putExtra(ThemeDetailActivity.NAME_KEY, beanDetails.titleName)
putExtra(ThemeDetailActivity.GIF_KEY, beanDetails.imgGif)
val thumbGif = beanDetails.thumbGif
val thumb = if (thumbGif != null && thumbGif.isNotEmpty()) {
thumbGif
} else {
beanDetails.thumbUrl
}
putExtra(ThemeDetailActivity.THUMB_KEY, thumb)
}
startActivity(intent)
}
override fun onPause() {
super.onPause()
stopBannerAutoScroll()
}
override fun onDestroyView() {
super.onDestroyView()
stopBannerAutoScroll()
bannerHandler = null
bannerRunnable = null
// 注意:不重置 isDataInitialized因为 Fragment 视图可能被销毁但 Fragment 实例保留
// 如果需要在重新创建视图时重新初始化,可以在这里重置标志
}
// ViewPager2 水平间距装饰器
private class HorizontalMarginItemDecoration(
private val pageMargin: Int,
private val nextItemVisible: Int
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: android.graphics.Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
val itemCount = state.itemCount
val leftMargin = if (position == 0) 0 else pageMargin / 2
val rightMargin = if (position == itemCount - 1) 0 else pageMargin / 2
outRect.set(leftMargin, 0, rightMargin, 0)
}
}
}

View File

@ -0,0 +1,82 @@
package com.app.input.personalised.board.boartfragment
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.app.input.personalised.board.BoartApp
import com.app.input.personalised.board.databinding.FragmentFavoriteBinding
import com.app.input.personalised.board.basedata.AppDatabase
import com.app.input.personalised.board.listhelpers.FavoriteCardListAdapter
class MyFragment : Fragment() {
private lateinit var vb: FragmentFavoriteBinding
private lateinit var favoriteCardListAdapter: FavoriteCardListAdapter
companion object {
@JvmStatic
fun newInstance() = MyFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
vb = FragmentFavoriteBinding.inflate(layoutInflater)
return vb.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initFavoriteCards()
initVersion()
initAbout()
}
private fun initFavoriteCards() {
favoriteCardListAdapter = FavoriteCardListAdapter(requireContext())
vb.favoriteCardRecycler.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = favoriteCardListAdapter
setHasFixedSize(false)
isNestedScrollingEnabled = false
}
// 观察收藏数据变化
AppDatabase.Companion.boartAppDatabase.ThemesDao().queryAllLike().observe(requireActivity()) { favoriteList ->
Log.d(BoartApp.Companion.TAG, "Favorite data changed, count: ${favoriteList?.size}")
val filteredList = favoriteList?.filterNotNull() ?: emptyList()
// 卡片始终显示,将收藏列表传递给适配器(可能为空)
favoriteCardListAdapter.setFavoriteList(filteredList)
// 隐藏外部的空状态提示(因为卡片内部会处理空状态)
vb.emptyTitle.isVisible = false
}
}
private fun initVersion() {
try {
val packageInfo = requireContext().packageManager.getPackageInfo(
requireContext().packageName,
0
)
val versionName = packageInfo.versionName ?: "1.0"
vb.tvVersion.text = versionName
} catch (e: PackageManager.NameNotFoundException) {
vb.tvVersion.text = "1.0"
} catch (e: Exception) {
vb.tvVersion.text = "1.0"
}
}
private fun initAbout() {
vb.layoutAbout.setOnClickListener {
// TODO: 可以打开关于对话框或关于页面
}
}
}

View File

@ -0,0 +1,88 @@
package com.app.input.personalised.board.databean;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.io.Serializable;
@Entity(tableName = "KeyboardTheme")
public class KeyboardTheme implements Serializable {
@PrimaryKey(autoGenerate = true)
private long id;
private String titleName;
private String thumbUrl;
private String thumbGif;
private String zipPath;
private String imgPath;
private String imgGif;
public void setImgGif(String imgGif) {
this.imgGif = imgGif;
}
public String getTitleName() {
return titleName;
}
public String getThumbUrl() {
return thumbUrl;
}
public String getThumbGif() {
return thumbGif;
}
public String getZipPath() {
return zipPath;
}
public String getImgPath() {
return imgPath;
}
public void setTitleName(String name) {
this.titleName = name;
}
public void setThumbUrl(String thumbUrl) {
this.thumbUrl = thumbUrl;
}
public void setThumbGif(String thumbGif) {
this.thumbGif = thumbGif;
}
public void setZipPath(String path) {
this.zipPath = path;
}
public void setImgPath(String imgPath) {
this.imgPath = imgPath;
}
public String getImgGif() {
return imgGif;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}

View File

@ -0,0 +1,28 @@
package com.app.input.personalised.board.databean;
import java.util.List;
public class ThemeCategory {
private String parentName;
private List<KeyboardTheme> keyboardList;
public String getParentName() {
return parentName;
}
public List<KeyboardTheme> getKeyboardList() {
return keyboardList;
}
public void setParentName(String name) {
this.parentName = name;
}
public void setKeyboardList(List<KeyboardTheme> keyboardList) {
this.keyboardList = keyboardList;
}
}

View File

@ -0,0 +1,115 @@
package com.app.input.personalised.board.helpers
import android.app.Activity
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.provider.Settings
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import com.app.input.personalised.board.BoartApp
import com.bumptech.glide.Glide
import com.bumptech.glide.integration.webp.decoder.WebpDrawable
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import java.io.File
object AppUtils {
val transform = RequestOptions().transform(CenterCrop(), RoundedCorners(dpToPx(8f)))
fun initFullScreen(activity: Activity, dark: Boolean? = true) {
val window = activity.window
val decorView = window.decorView
val rootView = decorView.rootView
//
if (dark == null) return
if (dark) {
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
)
} else {
decorView.setSystemUiVisibility(
(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
)
}
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
fun loadWepJif(mContext: Context, webpGifUrl: String, view: ImageView) {
Glide.with(mContext)
.load(webpGifUrl)
// .apply(transform)
.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is WebpDrawable) {
resource.loopCount = WebpDrawable.LOOP_FOREVER
}
return false
}
}).into(view)
}
fun getBgDrawable(con: Context, filePath: String): Drawable? {
if (!File(filePath).exists()) {
return null
}
return BitmapDrawable(con.resources, BitmapFactory.decodeFile(filePath))
}
private val systemService =
BoartApp.Companion.boartAppInstance.getSystemService(Context.INPUT_METHOD_SERVICE)
private val inputMethodManager = systemService as InputMethodManager
fun checkSetDefault(con: Context): Boolean {
val defaultId =
Settings.Secure.getString(con.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD)
return defaultId != null && defaultId.startsWith(con.packageName)
}
fun checkEnable(con: Context): Boolean {
for (methodInfo in inputMethodManager.enabledInputMethodList) {
if (methodInfo.id.startsWith(con.packageName)) {
return true
}
}
return false
}
fun getTextForImeAction(imeOptions: Int): Int {
return imeOptions and EditorInfo.IME_MASK_ACTION
}
fun dpToPx(dpValue: Float): Int {
val scale = BoartApp.Companion.boartAppInstance.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
}

View File

@ -0,0 +1,106 @@
package com.app.input.personalised.board.helpers;
import com.app.input.personalised.board.keyboardutils.KeyboardKey;
import com.app.input.personalised.board.keyboardutils.KeyboardConfig;
import com.app.input.personalised.board.keyboardutils.KeyboardLayout;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class ConfigFileParser {
public static KeyboardConfig initConfig(String path) {
String filePath = "keyboard.conf"; // 文件路径
KeyboardConfig config = parseConfig(path);
return config;
}
public static KeyboardConfig parseConfig(String filePath) {
// InputStream open = App.appInstance.getAssets().open(filePath);
KeyboardConfig config = new KeyboardConfig();
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
KeyboardLayout currentLayout = null;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue; // 跳过空行
}
if (line.startsWith("Version:")) {
config.setVersion(line.split(":")[1].trim());
} else if (line.startsWith("SupportLayouts:")) {
config.setSupportLayouts(line.split(":")[1].trim());
} else if (line.startsWith("HideHint:")) {
config.setHideHint(Integer.parseInt(line.split(":")[1].trim()));
} else if (line.startsWith("LayoutStyle:")) {
config.setLayoutStyle(line.split(":")[1].trim());
} else if (line.equals("KeyDefault") || line.equals("KeyMarkDefault") || line.equals("KeyFuncDefault")) {
LinkedHashMap<String, String> maps = config.getMaps();
maps.put(line, "");
} else if (line.contains(":") && currentLayout == null) {
String[] parts = line.split(":");
String keyName = parts[0].trim();
String keyValue = parts[1].trim();
String latestKey = null;
LinkedHashMap<String, String> maps = config.getMaps();
for (Map.Entry<String, String> entry : maps.entrySet()) {
latestKey = entry.getKey();
}
if (latestKey != null) {
maps.put(latestKey, keyValue);
}
} else if (line.startsWith("Row")) {
currentLayout = new KeyboardLayout(line.split(":")[0].trim());
config.addLayout(currentLayout);
} else if (currentLayout != null) {
if (line.equals("Key")) {
String[] parts = line.split(":");
String keyName = parts[0].trim();
KeyboardKey keyboardKey = new KeyboardKey(keyName);
currentLayout.addKey(keyboardKey);
} else if (line.contains(":") && currentLayout.getLastKey().getBackground() == null) {
// 解析按键的其他属性 Label
String[] parts = line.split(":");
String keyName = parts[0].trim();
String keyValue = parts[1].trim();
KeyboardKey keyboardKey = currentLayout.getLastKey();
if (keyName.equals("Label")) {
keyboardKey.setLabel(keyValue);
}
if (keyName.equals("Background")) {
keyboardKey.setBackground(keyValue);
}
} else {
if (line.equals("KeyShift") || line.equals("KeyDelete") || line.equals("KeyAlphaSymbol") || line.equals("KeyEmoji")
|| line.equals("KeyMark")
|| line.equals("KeySpace")
|| line.equals("KeyEnter")) {
KeyboardKey funcationKeyboardKey = new KeyboardKey(line);
config.addKey(funcationKeyboardKey);
} else if (line.contains(":")) {
String[] parts = line.split(":");
String keyName = parts[0].trim();
String keyValue = parts[1].trim();
KeyboardKey lastKeyboardKey = config.getLastKeyList();
if (keyName.equals("Label")) {
lastKeyboardKey.setLabel(keyValue);
}
if (keyName.equals("Background")) {
lastKeyboardKey.setBackground(keyValue);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return config;
}
}

View File

@ -0,0 +1,37 @@
package com.app.input.personalised.board.helpers;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.app.input.personalised.board.BoartApp;
import com.app.input.personalised.board.R;
public class FontTextView extends androidx.appcompat.widget.AppCompatTextView {
public FontTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(context,attrs);
}
private void initAttrs(Context context, AttributeSet attrs){
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTV);
boolean aBoolean = typedArray.getBoolean(R.styleable.MyTV_apply_font,false);
if(aBoolean){
initFont(this);
}
typedArray.recycle();
}
public static void initFont(TextView tv) {
tv.setTypeface(BoartApp.Companion.getDefaultFont());
}
}

View File

@ -0,0 +1,76 @@
package com.app.input.personalised.board.helpers;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.app.input.personalised.board.BoartApp;
public class GridItemDecoration extends RecyclerView.ItemDecoration {
private int v, h, ex;
public GridItemDecoration(int v, int h, int ex) {
this.v = Math.round(dpToPx(v));
this.h = Math.round(dpToPx(h));
this.ex = Math.round(dpToPx(ex));
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int spanCount = 1;
int spanSize = 1;
int spanIndex = 0;
int childAdapterPosition = parent.getChildAdapterPosition(view);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
spanCount = staggeredGridLayoutManager.getSpanCount();
if (layoutParams.isFullSpan()) {
spanSize = spanCount;
}
spanIndex = layoutParams.getSpanIndex();
} else if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
GridLayoutManager.LayoutParams layoutParams = (GridLayoutManager.LayoutParams) view.getLayoutParams();
spanCount = gridLayoutManager.getSpanCount();
spanSize = gridLayoutManager.getSpanSizeLookup().getSpanSize(childAdapterPosition);
spanIndex = layoutParams.getSpanIndex();
} else if (layoutManager instanceof LinearLayoutManager) {
outRect.left = v;
outRect.right = v;
outRect.bottom = h;
}
if (spanSize == spanCount) {
outRect.left = v + ex;
outRect.right = v + ex;
outRect.bottom = h;
} else {
int itemAllSpacing = (v * (spanCount + 1) + ex * 2) / spanCount;
int left = v * (spanIndex + 1) - itemAllSpacing * spanIndex + ex;
int right = itemAllSpacing - left;
outRect.left = left;
outRect.right = right;
outRect.bottom = h;
}
}
public static float dpToPx(float dpValue) {
float density = BoartApp.boartAppInstance.getResources().getDisplayMetrics().density;
return density * dpValue + 0.5f;
}
}

View File

@ -0,0 +1,60 @@
package com.app.input.personalised.board.helpers
object KeyboardConstants {
const val KEY_CODE_DELETE = -5
//同一个按键
const val KEY_CODE_SHIFT = -1
const val KEY_CODE_NUMBER_SHIFT = -103
const val KEY_CODE_SYMBOL_SHIFT = -101
//同一个按键
const val KEY_CODE_CHANGE_NUMBER = -2
const val KEY_CODE_BACK = -102
const val KEY_CODE_COMPLETE = -4
const val KEY_CODE_CANCEL = -3
const val KEY_CODE_SPACE = 32
const val functionNormalName = "btn_keyboard_key_functional_normal.9.png"
const val functionPressName = "btn_keyboard_key_functional_pressed.9.png"
const val normalName = "btn_keyboard_key_normal_normal.9.png"
const val pressName = "btn_keyboard_key_normal_pressed.9.png"
const val toNormalName="btn_keyboard_key_toggle_normal_on.9.png"
const val toPressName="btn_keyboard_key_toggle_pressed_on.9.png"
const val spaceNormalName = "btn_keyboard_spacekey_normal_normal.9.png"
const val spacePressName = "btn_keyboard_spacekey_normal_pressed.9.png"
const val imeSwitchName ="ic_ime_switcher.png"
const val deleteNormalName = "sym_keyboard_delete_normal.png"
const val deletePressName = "sym_keyboard_delete_pressed.png"
const val backName ="sym_keyboard_return_normal.png"
const val searchName ="sym_keyboard_search.png"
const val shiftNormalName ="sym_keyboard_shift.png"
const val shiftLockName ="sym_keyboard_shift_locked.png"
const val keyTextColorName ="key_text_color_normal"
const val keyTextColorFunctionName ="key_text_color_functional"
const val videoName ="keyboard_background_video.mp4"
const val bgName ="keyboard_background.jpg"
const val bgName_png ="keyboard_background.png"
const val previewBg="keyboard_preview_screenshot.jpg"
const val video ="keyboard_background_video.gif"
}

View File

@ -0,0 +1,55 @@
package com.app.input.personalised.board.helpers
import android.app.Activity
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.WindowManager
import androidx.core.view.WindowCompat
object StatusBarHelper {
/**
* 设置状态栏为透明与CategoryListActivity保持一致
* 状态栏图标为深色适用于浅色背景
* 强制设置为透明覆盖任何其他设置包括MaterialComponents主题的自动设置
*/
fun setTransparentStatusBar(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = activity.window
// 清除所有可能影响状态栏的标志
window.clearFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
)
// 添加绘制系统栏背景的标志
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
// 强制设置为透明 - 立即设置
window.statusBarColor = android.graphics.Color.TRANSPARENT
// 延迟再次设置确保覆盖MaterialComponents主题可能的自动设置
Handler(Looper.getMainLooper()).postDelayed({
window.statusBarColor = android.graphics.Color.TRANSPARENT
}, 100)
// 设置状态栏图标为深色(适用于浅色背景)
// 使用多种方式确保设置生效
val decorView = window.decorView
if (decorView.isAttachedToWindow) {
setLightStatusBar(window, decorView)
} else {
decorView.post {
setLightStatusBar(window, decorView)
// 再次确保状态栏颜色为透明
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
}
}
private fun setLightStatusBar(window: android.view.Window, decorView: View) {
val windowInsetsController = WindowCompat.getInsetsController(window, decorView)
windowInsetsController?.isAppearanceLightStatusBars = true
}
}

View File

@ -0,0 +1,238 @@
package com.app.input.personalised.board.helpers;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import com.app.input.personalised.board.BoartApp;
import com.app.input.personalised.board.itemback.ThemeApplyListener;
import net.sf.sevenzipjbinding.ArchiveFormat;
import net.sf.sevenzipjbinding.IArchiveOpenCallback;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class ThemeDownloader {
public static void startDownloadZip(String zipPath, ThemeApplyListener callback) {
OkHttpClient clientZip = new OkHttpClient().newBuilder().
connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS).build();
Request.Builder builder = new Request.Builder();
Request request = builder.get().url(zipPath).build();
clientZip.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.OnApplySkinListener(null);
}
@Override
public void onResponse(Call call, Response response) {
InputStream inputStream = response.body().byteStream();
long l = response.body().contentLength();
MediaType mediaType = response.body().contentType();
saveZipFile(inputStream, getServiceZipName(zipPath), callback);
}
});
}
public static String getServiceZipName(String zipPath) {
String pointStr = "/";
int lastIndexOf = zipPath.lastIndexOf(pointStr);
String zipName = zipPath.substring(lastIndexOf + pointStr.length());
return zipName;
}
private static String getunZipFolderName(String zipPath) {
String pointStr = ".";
int lastIndexOf = zipPath.lastIndexOf(pointStr);
String zipName = zipPath.substring(0, lastIndexOf);
return zipName;
}
private static void saveZipFile(InputStream inputStream, String zipFileName, ThemeApplyListener callback) {
File zipfFile = new File(BoartApp.boartAppInstance.getFilesDir(), zipFileName);
Log.d("-------------------","-------zipFileName="+zipFileName);
byte[] bytes = new byte[4096];
int readLength = 0;
InputStream is = inputStream;
FileOutputStream fileOs = null;
try {
fileOs = new FileOutputStream(zipfFile);
while ((readLength = is.read(bytes)) != -1) {
fileOs.write(bytes, 0, readLength);
}
fileOs.flush();
} catch (Exception exception) {
} finally {
try {
if (is != null) {
is.close();
}
if (fileOs != null) {
fileOs.close();
}
} catch (IOException ioException) {
}
un7ZZipFile(zipfFile, callback);
}
}
public static String getUnzipPath(String zipName){
String folderName = getunZipFolderName(zipName);
String replace = folderName.replace(".", "");
return BoartApp.boartAppInstance.getFilesDir().getPath() + "/" + replace;
}
private static void un7ZZipFile(File saveZipFile, ThemeApplyListener callback) {
List<File> fileList = new ArrayList<>();
String unzipFolderPath = getUnzipPath(saveZipFile.getName());
try {
RandomAccessFileInStream inStream = new RandomAccessFileInStream(new RandomAccessFile(saveZipFile, "r"));
IInArchive open = SevenZip.openInArchive(ArchiveFormat.SEVEN_ZIP, inStream, new IArchiveOpenCallback() {
@Override
public void setTotal(Long files, Long bytes) {
}
@Override
public void setCompleted(Long files, Long bytes) {
}
});
ISimpleInArchive simple = open.getSimpleInterface();
for (ISimpleInArchiveItem archiveItem : simple.getArchiveItems()) {
RandomAccessFileOutStream outStream = null;
try {
File itemFile;
if (archiveItem.isFolder()) {
File itemFolder = new File(unzipFolderPath, archiveItem.getPath());
boolean mkdirs = itemFolder.mkdirs();
continue;
} else {
itemFile = new File(unzipFolderPath, archiveItem.getPath());
if (!itemFile.getParentFile().exists()) {
boolean mkdirs = itemFile.getParentFile().mkdirs();
}
}
outStream = new RandomAccessFileOutStream(new RandomAccessFile(itemFile, "rw"));
archiveItem.extractSlow(outStream);
fileList.add(itemFile);
} finally {
if (outStream != null) {
outStream.close();
}
}
}
inStream.close();
open.close();
} catch (FileNotFoundException | SevenZipException exception) {
} catch (IOException ioException) {
} finally {
if (saveZipFile.exists()) {
saveZipFile.delete();
}
callback.OnApplySkinListener(fileList);
}
}
public static File findFirstDirectory(File dir) {
if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
return file; // 返回第一个文件目录
}
}
}
}
return null; // 如果没有找到文件目录则返回null
}
private static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
// 如果不是 BitmapDrawable则创建一个空的 Bitmap 对象
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
// Bitmap 保存到文件
private static void saveBitmapToFile(Bitmap bitmap, File file) throws IOException {
if(!file.exists()){
file.createNewFile();
}
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); // 保存为 PNG 格式
out.flush();
out.close();
}
// 示例 Drawable 写入文件
public static void saveDrawableToFile(Drawable drawable, File file) throws IOException {
Bitmap bitmap = drawableToBitmap(drawable);
saveBitmapToFile(bitmap, file);
}
}

View File

@ -0,0 +1,19 @@
package com.app.input.personalised.board.helpers
import android.content.Context
import com.app.input.personalised.board.BoartApp
object ThemePreferences {
val SP_NAME = "keyboard_skin"
val SKIN_PATH = "skin_path"
val spSkin = BoartApp.Companion.boartAppInstance.getSharedPreferences(SP_NAME,Context.MODE_PRIVATE)
fun updateSkinPath(skinPath:String){
spSkin.edit().putString(SKIN_PATH,skinPath).apply()
}
fun getSkinPath( ):String?{
return spSkin.getString(SKIN_PATH,null)
}
}

View File

@ -0,0 +1,181 @@
package com.app.input.personalised.board.helpers
import android.content.Context
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.util.Xml
import androidx.core.content.ContextCompat
import com.app.input.personalised.board.BoartApp
import com.app.input.personalised.board.keyboardutils.KeyboardConfig
import com.app.input.personalised.board.R
import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.io.StringReader
import kotlin.collections.iterator
class ThemeResourceManager(var context: Context) {
private var textSize = 13f
var functionDraw: Drawable =
getDefaultDrawList(R.drawable.key_normal_default, R.drawable.key_pressed_default)
var generalDraw: Drawable =
getDefaultDrawList(R.drawable.key_normal_default, R.drawable.key_pressed_default)
var toDraw: Drawable = getDefaultDrawList(R.drawable.key_normal_default, R.drawable.key_pressed_default)
var spaceDraw: Drawable = getDefaultDrawList(R.drawable.key_normal_default, R.drawable.key_pressed_default)
var switchDraw: Drawable? = null
var deleteDraw: Drawable? = null
var backDraw: Drawable? = null
var searchDraw: Drawable? = null
var shiftDraw: Drawable? = null
var shiftLockDraw: Drawable? = null
var keyTextColor: Int = ContextCompat.getColor(context, R.color.black)
var keyTextColorFunction: Int = ContextCompat.getColor(context, R.color.black)
fun getConfig(): KeyboardConfig? {
val skinPath = ThemePreferences.getSkinPath()
val configFilePath = skinPath + "assets/keyboard.conf"
val file = File(configFilePath)
return if (file.exists()) {
ConfigFileParser.initConfig(configFilePath)
} else {
null
}
}
fun getConfigBg(name: String): Drawable? {
ThemePreferences.getSkinPath()?.let { resPath ->
val pPath = "${resPath}res/drawable-xhdpi-v4/"
return getDrawList(
pPath + name,
pPath + name
)
}
return null
}
fun updateSkinConfig() {
ThemePreferences.getSkinPath()?.let { resPath ->
val pPath = "${resPath}res/drawable-xhdpi-v4/"
pPath.let {
readColors(resPath) {
for ((name, value) in it) {
if (name == KeyboardConstants.keyTextColorName) {
keyTextColor = value
}
if (name == KeyboardConstants.keyTextColorFunctionName) {
keyTextColorFunction = value
}
}
}
functionDraw = getDrawList(
it + KeyboardConstants.functionNormalName,
it + KeyboardConstants.functionPressName
)
generalDraw = getDrawList(it + KeyboardConstants.normalName, it + KeyboardConstants.pressName)
toDraw = getDrawList(it + KeyboardConstants.toNormalName, it + KeyboardConstants.toPressName)
spaceDraw =
getDrawList(it + KeyboardConstants.spaceNormalName, it + KeyboardConstants.spacePressName)
switchDraw =
getDrawList(it + KeyboardConstants.imeSwitchName, it + KeyboardConstants.imeSwitchName)
deleteDraw = getDrawList(
it + KeyboardConstants.deleteNormalName,
it + KeyboardConstants.deletePressName
)
backDraw = getDrawList(it + KeyboardConstants.backName, it + KeyboardConstants.backName)
searchDraw = getDrawList(it + KeyboardConstants.searchName, it + KeyboardConstants.searchName)
shiftDraw = getDrawList(
it + KeyboardConstants.shiftNormalName,
it + KeyboardConstants.shiftNormalName
)
shiftLockDraw =
getDrawList(it + KeyboardConstants.shiftLockName, it + KeyboardConstants.shiftLockName)
}
}
}
private fun getDefaultDrawList(normalDrawId: Int, pressDrawId: Int): StateListDrawable {
val normalDraw = ContextCompat.getDrawable(BoartApp.Companion.boartAppInstance, normalDrawId)
val pressDraw = ContextCompat.getDrawable(BoartApp.Companion.boartAppInstance, pressDrawId)
val stateListDrawable = StateListDrawable().apply {
addState(
intArrayOf(android.R.attr.state_pressed),
pressDraw
)
addState(intArrayOf(), normalDraw)
}
return stateListDrawable
}
private fun getDrawList(normalPath: String, pressPath: String): StateListDrawable {
val pressDraw = BitmapFactory.decodeFile(pressPath)
val normalDraw = BitmapFactory.decodeFile(normalPath)
val stateListDrawable = StateListDrawable().apply {
addState(
intArrayOf(android.R.attr.state_pressed),
BitmapDrawable(context.resources, pressDraw)
)
addState(intArrayOf(), BitmapDrawable(context.resources, normalDraw))
}
return stateListDrawable
}
private fun readColors(resPath: String, callBack: (Map<String, Int>) -> Unit) {
val resMaps = mutableMapOf<String, Int>()
val pPath = "${resPath}res/colors.xml"
val file = File(pPath)
if (file.exists()) {
val xmlPullParser = Xml.newPullParser().apply {
setInput(StringReader(file.readText()))
setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
}
var curType = xmlPullParser.eventType
while (curType != XmlPullParser.END_DOCUMENT) {
val b = curType == XmlPullParser.START_TAG
val b1 = xmlPullParser.name == "color"
val b2 = xmlPullParser.name == "item"
if (b && (b1 || b2)) {
val attributeName = xmlPullParser.getAttributeValue(null, "name")
val nextTextValue = xmlPullParser.nextText()
val b3 = attributeName == KeyboardConstants.keyTextColorName
val b4 = attributeName == KeyboardConstants.keyTextColorFunctionName
if (b3 || b4) {
resMaps[attributeName] = Color.parseColor(nextTextValue)
}
}
curType = xmlPullParser.next()
}
}
callBack.invoke(resMaps)
}
}

View File

@ -0,0 +1,9 @@
package com.app.input.personalised.board.itemback
import com.app.input.personalised.board.databean.KeyboardTheme
interface FavoriteRemovedListener {
fun OnRemoveLike(data: KeyboardTheme)
}

View File

@ -0,0 +1,7 @@
package com.app.input.personalised.board.itemback
interface ItemClickListener {
fun OnItemClickListener( )
}

View File

@ -0,0 +1,9 @@
package com.app.input.personalised.board.itemback
import java.io.File
interface ThemeApplyListener {
fun OnApplySkinListener(fileList: List<File?>?)
}

View File

@ -0,0 +1,7 @@
package com.app.input.personalised.board.itemback
interface ViewAllListener {
fun OnClickSeeAll(name: String)
}

View File

@ -0,0 +1,285 @@
package com.app.input.personalised.board.keyboardutils;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.media.MediaPlayer;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.ImageView;
import android.widget.VideoView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.integration.webp.decoder.WebpDrawable;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.app.input.personalised.board.BoartApp;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.helpers.KeyboardConstants;
import com.app.input.personalised.board.helpers.AppUtils;
import com.app.input.personalised.board.helpers.ThemePreferences;
import com.app.input.personalised.board.sourcekey.KeyboardLayout;
import com.app.input.personalised.board.sourcekey.KeyboardView;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
public class CustomInputMethodService extends InputMethodService implements KeyboardView.OnKeyboardActionListener {
private CustomKeyboardView customKeyboardView;
private KeyboardLayout mKeyBoard;
private View parentView;
private ImageView imBG;
private VideoView videoView;
private int a = R.xml.xml_one;
private int b= R.xml.xml_two;
private int c = R.xml.xml_three;
private int curImeAction = EditorInfo.IME_ACTION_UNSPECIFIED;
@SuppressLint("InflateParams")
@Override
public View onCreateInputView() {
parentView = getLayoutInflater().inflate(R.layout.keyboard_input_view, null);
findView();
return parentView;
}
private void findView() {
imBG = parentView.findViewById(R.id.gif_bg);
videoView = parentView.findViewById(R.id.video_view);
mKeyBoard = new KeyboardLayout(this, a);
customKeyboardView = parentView.findViewById(R.id.custom_input_view);
customKeyboardView.setEnabled(true);
customKeyboardView.setPreviewEnabled(false);
customKeyboardView.setKeyboard(mKeyBoard);
customKeyboardView.setOnKeyboardActionListener(this);
}
@Override
public void onWindowHidden() {
super.onWindowHidden();
if(videoView.isPlaying()){
videoView.pause();
}
}
@Override
public void onWindowShown() {
super.onWindowShown();
EditorInfo currentInputEditorInfo = getCurrentInputEditorInfo();
curImeAction = AppUtils.INSTANCE.getTextForImeAction(currentInputEditorInfo.imeOptions);
String skinPath = ThemePreferences.INSTANCE.getSkinPath();
if(skinPath == null || skinPath.isEmpty()){
Log.d(BoartApp.TAG, "---------skinPath= bull");
customKeyboardView.updateUi(curImeAction);
}else {
Log.d(BoartApp.TAG, "---------skinPath= 1111");
KeyboardHelper.INSTANCE.readBgOrVideo(this, new Function2<String, Drawable, Unit>() {
@Override
public Unit invoke(String s, Drawable drawable) {
Log.d(BoartApp.TAG, "---------s= "+s+"---------drawable="+drawable);
if (s != null) {
customKeyboardView.setBackground(null);
if(s.endsWith(".gif")){
imBG.setVisibility(View.VISIBLE);
videoView.setVisibility(View.GONE);
Glide.with(CustomInputMethodService.this)
.load(s)
.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
if (resource instanceof WebpDrawable) {
((WebpDrawable) resource).setLoopCount(WebpDrawable.LOOP_FOREVER);
}
return false;
}
}).into(imBG) ;
}else if(s.endsWith(".mp4")){
imBG.setVisibility(View.GONE);
videoView.setVisibility(View.VISIBLE);
videoView.setVideoPath(s);
videoView.start();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
}
});
}
} else {
customKeyboardView.setBackground(drawable);
}
customKeyboardView.updateUi(curImeAction);
return null;
}
});
}
}
@Override
public void onDestroy() {
super.onDestroy();
videoView.stopPlayback();
}
@Override
public void onPress(int primaryCode) {
}
@Override
public void onRelease(int primaryCode) {
}
@Override
public void onKey(int primaryCode, int[] keyCodes) {
InputConnection curInputConnect = getCurrentInputConnection();
switch (primaryCode) {
case KeyboardConstants.KEY_CODE_DELETE:
curInputConnect.deleteSurroundingText(1, 0);
break;
case KeyboardConstants.KEY_CODE_SHIFT:
switchShift();
break;
case KeyboardConstants.KEY_CODE_NUMBER_SHIFT:
case KeyboardConstants.KEY_CODE_SYMBOL_SHIFT:
switchMoreOrNumber();
break;
case KeyboardConstants.KEY_CODE_CHANGE_NUMBER:
case KeyboardConstants.KEY_CODE_BACK:
switchNormalOrNumber();
break;
case KeyboardConstants.KEY_CODE_COMPLETE:
case KeyboardConstants.KEY_CODE_CANCEL:
curInputConnect.performEditorAction(curImeAction);
// curInputConnect.performEditorAction(EditorInfo.IME_ACTION_DONE);
break;
default:
String codeToChar = KeyboardHelper.INSTANCE.primaryCodeToChar(primaryCode);
curInputConnect.commitText(codeToChar, 1);
if (customKeyboardView.isLowerCase() == 1) {
//自动转小写
customKeyboardView.setLowerCase(0);
KeyboardHelper.INSTANCE.keyToLowerCase(mKeyBoard);
customKeyboardView.setKeyboard(mKeyBoard);
}
break;
}
}
private void switchMoreOrNumber() {
int mode = customKeyboardView.getMode();
switch (mode) {
case 1:
mKeyBoard = new KeyboardLayout(this, c);
customKeyboardView.setMode(2);
customKeyboardView.setKeyboard(mKeyBoard);
break;
case 2:
mKeyBoard = new KeyboardLayout(this, b);
customKeyboardView.setMode(1);
customKeyboardView.setKeyboard(mKeyBoard);
break;
}
}
private void switchNormalOrNumber() {
int mode = customKeyboardView.getMode();
switch (mode) {
case 0:
mKeyBoard = new KeyboardLayout(this, b);
customKeyboardView.setMode(1);
customKeyboardView.setKeyboard(mKeyBoard);
break;
case 1:
case 2:
mKeyBoard = new KeyboardLayout(this, a);
customKeyboardView.setMode(0);
customKeyboardView.setKeyboard(mKeyBoard);
break;
}
}
private void switchShift() {
int lowerCase = customKeyboardView.isLowerCase();
switch (lowerCase) {
case 0:
//当前小写转大写
customKeyboardView.setLowerCase(1);
KeyboardHelper.INSTANCE.keyToUpper(mKeyBoard);
customKeyboardView.setKeyboard(mKeyBoard);
break;
case 1:
//当前大写转锁定大写
customKeyboardView.setLowerCase(2);
break;
case 2:
//当前锁定大写转小写
customKeyboardView.setLowerCase(0);
KeyboardHelper.INSTANCE.keyToLowerCase(mKeyBoard);
customKeyboardView.setKeyboard(mKeyBoard);
break;
}
}
@Override
public void onText(CharSequence text) {
}
@Override
public void swipeLeft() {
}
@Override
public void swipeRight() {
}
@Override
public void swipeDown() {
}
@Override
public void swipeUp() {
}
}

View File

@ -0,0 +1,324 @@
package com.app.input.personalised.board.keyboardutils;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import androidx.core.graphics.drawable.DrawableCompat;
import com.app.input.personalised.board.BoartApp;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.sourcekey.KeyboardLayout;
import com.app.input.personalised.board.sourcekey.KeyboardView;
import com.app.input.personalised.board.helpers.KeyboardConstants;
import com.app.input.personalised.board.helpers.ThemeResourceManager;
import java.util.ArrayList;
import java.util.List;
public class CustomKeyboardView extends KeyboardView {
private Paint mPaint;
private Context mContext;
private float mRation = 0.5f;
//0 小写 1 大写 2 大写锁定
private int isLowerCase = 0;
//0 默认键盘 1 字母键盘 2 符号键盘
private int mMode = 0;
private ThemeResourceManager themeResourceManager;
private int curImeAction = EditorInfo.IME_ACTION_UNSPECIFIED;
public CustomKeyboardView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setAttribute(attrs, context);
}
public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
setAttribute(attrs, context);
}
public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
setAttribute(attrs, context);
}
public void setMode(int mType) {
this.mMode = mType;
}
public int getMode() {
return mMode;
}
public int isLowerCase() {
return isLowerCase;
}
public void setLowerCase(int lowerCase) {
isLowerCase = lowerCase;
}
public void updateUi(int ime) {
Log.d(BoartApp.TAG, "----------ime=" + ime);
curImeAction = ime;
themeResourceManager.updateSkinConfig();
invalidate();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
}
private void setAttribute(AttributeSet attrs, Context con) {
themeResourceManager = new ThemeResourceManager(con);
initPaint();
TypedArray mTypedArray = con.obtainStyledAttributes(attrs, R.styleable.CustomInputView);
// int color = mTypedArray.getColor(R.styleable.CustomInputView_text_color_done, 1);
//
// Drawable drawable = mTypedArray.getDrawable(R.styleable.CustomInputView_drawable_cancel);
//
// int textSize = mTypedArray.getInt(R.styleable.CustomInputView_text_size_key, 12);
mTypedArray.recycle();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
KeyboardConfig config = themeResourceManager.getConfig();
List<KeyboardKey> keyboardKeys = new ArrayList<>();
int i = 0;
for (KeyboardLayout.Key curKey : getKeyboard().getKeys()) {
int code = curKey.codes[0];
if (config != null&& !config.getLayouts().isEmpty()) {
if (code == 113 ||code == 81 || code == 49||code == 91) {
i = 0;
com.app.input.personalised.board.keyboardutils.KeyboardLayout keyboardLayout = config.getLayouts().get(0);
keyboardKeys = keyboardLayout.getKeys();
} else if (code == 97||code == 65 || code == 33||code == 126) {
i = 0;
com.app.input.personalised.board.keyboardutils.KeyboardLayout keyboardLayout = config.getLayouts().get(1);
keyboardKeys = keyboardLayout.getKeys();
}else if (code == -1 || code == -103||code==-101) {
i = 0;
com.app.input.personalised.board.keyboardutils.KeyboardLayout keyboardLayout = config.getLayouts().get(2);
keyboardKeys = keyboardLayout.getKeys();
}else if (code == -2 || code == -102) {
i = 0;
com.app.input.personalised.board.keyboardutils.KeyboardLayout keyboardLayout = config.getLayouts().get(3);
keyboardKeys = keyboardLayout.getKeys();
}
String background = keyboardKeys.get(i).getBackground()+".9.png";
i++;
Drawable configBg = themeResourceManager.getConfigBg(background);
realNewDraw(configBg, curKey, canvas, code);
} else {
realDraw(curKey, canvas, code);
}
}
}
private void realNewDraw(Drawable configBg, KeyboardLayout.Key curKey, Canvas canvas, int code) {
switch (code) {
case KeyboardConstants.KEY_CODE_SHIFT:
// drawAllShift(curKey, canvas);
onDrawCurKey(curKey, canvas, "Shift", configBg, null);
break;
case KeyboardConstants.KEY_CODE_NUMBER_SHIFT:
onDrawCurKey(curKey, canvas, "More", configBg, null);
break;
case KeyboardConstants.KEY_CODE_DELETE:
onDrawCurKey(curKey, canvas, "Delete", configBg, null);
break;
case KeyboardConstants.KEY_CODE_SYMBOL_SHIFT:
onDrawCurKey(curKey, canvas, "123", configBg, null);
break;
case KeyboardConstants.KEY_CODE_CHANGE_NUMBER:
onDrawCurKey(curKey, canvas, null, configBg, null);
break;
case KeyboardConstants.KEY_CODE_BACK:
onDrawCurKey(curKey, canvas, "Back", configBg, null);
break;
case KeyboardConstants.KEY_CODE_SPACE:
onDrawCurKey(curKey, canvas, null, configBg, null);
break;
case KeyboardConstants.KEY_CODE_COMPLETE, KeyboardConstants.KEY_CODE_CANCEL:
Log.d(BoartApp.TAG, "-11111111111---------curImeAction=" + curImeAction);
if (curImeAction == EditorInfo.IME_ACTION_SEARCH) {
onDrawCurKey(curKey, canvas, "Search", configBg, null);
} else {
onDrawCurKey(curKey, canvas, "Done", configBg, null);
}
break;
default:
onDrawCurKey(curKey, canvas, null, configBg, null);
break;
}
}
private void realDraw(KeyboardLayout.Key curKey, Canvas canvas, int code) {
switch (code) {
case KeyboardConstants.KEY_CODE_SHIFT:
drawAllShift(curKey, canvas);
break;
case KeyboardConstants.KEY_CODE_NUMBER_SHIFT:
onDrawCurKey(curKey, canvas, "More", themeResourceManager.getFunctionDraw(), null);
break;
case KeyboardConstants.KEY_CODE_DELETE:
onDrawCurKey(curKey, canvas, "Delete", themeResourceManager.getFunctionDraw(), null);
break;
case KeyboardConstants.KEY_CODE_SYMBOL_SHIFT:
onDrawCurKey(curKey, canvas, "123", themeResourceManager.getFunctionDraw(), null);
break;
case KeyboardConstants.KEY_CODE_CHANGE_NUMBER:
onDrawCurKey(curKey, canvas, null, themeResourceManager.getToDraw(), null);
break;
case KeyboardConstants.KEY_CODE_BACK:
onDrawCurKey(curKey, canvas, "Back", themeResourceManager.getToDraw(), null);
break;
case KeyboardConstants.KEY_CODE_SPACE:
onDrawCurKey(curKey, canvas, null, themeResourceManager.getSpaceDraw(), null);
break;
case KeyboardConstants.KEY_CODE_COMPLETE, KeyboardConstants.KEY_CODE_CANCEL:
Log.d(BoartApp.TAG, "-11111111111---------curImeAction=" + curImeAction);
if (curImeAction == EditorInfo.IME_ACTION_SEARCH) {
onDrawCurKey(curKey, canvas, "Search", themeResourceManager.getFunctionDraw(), null);
} else {
onDrawCurKey(curKey, canvas, "Done", themeResourceManager.getFunctionDraw(), null);
}
break;
default:
onDrawCurKey(curKey, canvas, null, themeResourceManager.getGeneralDraw(), null);
break;
}
}
private void drawAllShift(KeyboardLayout.Key curKey, Canvas canvas) {
if (isLowerCase == 0) {
onDrawCurKey(curKey, canvas, "Shift", themeResourceManager.getFunctionDraw(), null);
} else if (isLowerCase == 1) {
onDrawCurKey(curKey, canvas, "Shift", themeResourceManager.getFunctionDraw(), null);
} else if (isLowerCase == 2) {
onDrawCurKey(curKey, canvas, "Shift", themeResourceManager.getFunctionDraw(), null);
}
}
private void onDrawCurKey(KeyboardLayout.Key curKey, Canvas curCanvas, String label, Drawable bgDrawable, Drawable iconDraw) {
if (bgDrawable != null) {
onDrawKeyBackground(curKey, curCanvas, bgDrawable);
}
if (iconDraw != null) {
onDrawKeyIcon(curKey, curCanvas, iconDraw);
}
onDrawKeyText(curKey, curCanvas, label);
}
private void onDrawKeyText(KeyboardLayout.Key curKey, Canvas curCanvas, String label) {
mPaint.setColor(themeResourceManager.getKeyTextColor());
mPaint.setTextSize(mContext.getResources().getDimension(R.dimen.text_size));
float v = curKey.width / 2f;
float v1 = curKey.height / 2f;
float v2 = (mPaint.getTextSize() - mPaint.descent()) / 2f;
if (curKey.label != null) {
curCanvas.drawText((String) curKey.label, curKey.x + getPaddingLeft() + v, curKey.y + getPaddingRight() + v1 + v2, mPaint);
} else if (label != null) {
curCanvas.drawText(label, curKey.x + getPaddingLeft() + v, curKey.y + getPaddingRight() + v1 + v2, mPaint);
}
}
private void onDrawKeyBackground(KeyboardLayout.Key curKey, Canvas curCanvas, Drawable curDrawable) {
if (curKey.codes[0] != 0) {
curDrawable.setState(curKey.getCurrentDrawableState());
}
Rect rect = new Rect((curKey.x + this.getPaddingLeft()), (curKey.y + this.getPaddingTop()), (curKey.x + this.getPaddingLeft() + curKey.width), (curKey.y + this.getPaddingTop() + curKey.height));
curDrawable.setBounds(rect);
curDrawable.draw(curCanvas);
}
private void onDrawKeyIcon(KeyboardLayout.Key curKey, Canvas curCanvas, Drawable curDrawable) {
Drawable wrap = DrawableCompat.wrap(curDrawable);
curKey.icon = curDrawable;
float iconW = (float) curKey.icon.getIntrinsicWidth();
float iconH = (float) curKey.icon.getIntrinsicHeight();
float wDivRation = iconW / curKey.width;
float hDivRation = iconH / curKey.height;
curKey.icon.draw(curCanvas);
if (wDivRation > hDivRation) {
float minRatio = 0;
if (wDivRation <= mRation) {
minRatio = wDivRation;
} else {
minRatio = mRation;
}
iconH = (iconH / wDivRation) * minRatio;
iconW = (iconW / wDivRation) * minRatio;
} else {
float minRatio = 0;
if (hDivRation <= mRation) {
minRatio = hDivRation;
} else {
minRatio = mRation;
}
iconH = (iconH / hDivRation) * minRatio;
iconW = (iconW / hDivRation) * minRatio;
}
float subW = (curKey.width - iconW) / 2f;
float subH = (curKey.height - iconH) / 2f;
int xLeft = (int) (curKey.x + getPaddingLeft() + subW);
int yTop = (int) (curKey.y + getPaddingTop() + subH);
int xRight = (int) (xLeft + iconW);
int yBottom = (int) (yTop + iconH);
curKey.icon.setBounds(xLeft, yTop, xRight, yBottom);
curKey.icon.draw(curCanvas);
}
}

View File

@ -0,0 +1,168 @@
package com.app.input.personalised.board.keyboardutils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class KeyboardConfig {
private String version;
private String supportLayouts;
private int hideHint;
private String layoutStyle;
private List<KeyboardLayout> layouts = new ArrayList<>();
private List<KeyboardKey> keyboardKeyList = new ArrayList<>();
private LinkedHashMap<String, String> maps = new LinkedHashMap<>();
private String KeyDefault;
private String KeyMarkDefault;
private String KeyFuncDefault;
private String KeyShift;
private String KeyDelete;
private String KeyAlphaSymbol;
private String KeyEmoji;
private String KeyMark;
private String KeySpace;
private String KeyEnter;
// Getters and Setters
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getSupportLayouts() {
return supportLayouts;
}
public void setSupportLayouts(String supportLayouts) {
this.supportLayouts = supportLayouts;
}
public int getHideHint() {
return hideHint;
}
public void setHideHint(int hideHint) {
this.hideHint = hideHint;
}
public String getLayoutStyle() {
return layoutStyle;
}
public void setLayoutStyle(String layoutStyle) {
this.layoutStyle = layoutStyle;
}
public List<KeyboardLayout> getLayouts() {
return layouts;
}
public void addLayout(KeyboardLayout layout) {
this.layouts.add(layout);
}
public List<KeyboardKey> getKeyList() {
return keyboardKeyList;
}
public KeyboardKey getLastKeyList() {
return keyboardKeyList.isEmpty() ? null : keyboardKeyList.get(keyboardKeyList.size() - 1);
}
public void addKey(KeyboardKey keyboardKey) {
this.keyboardKeyList.add(keyboardKey);
}
public LinkedHashMap<String, String> getMaps() {
return maps;
}
public void setMaps(LinkedHashMap<String, String> maps) {
this.maps = maps;
}
public String getKeyDefault() {
return KeyDefault;
}
public void setKeyDefault(String keyDefault) {
KeyDefault = keyDefault;
}
public String getKeyMarkDefault() {
return KeyMarkDefault;
}
public void setKeyMarkDefault(String keyMarkDefault) {
KeyMarkDefault = keyMarkDefault;
}
public String getKeyFuncDefault() {
return KeyFuncDefault;
}
public void setKeyFuncDefault(String keyFuncDefault) {
KeyFuncDefault = keyFuncDefault;
}
public String getKeyShift() {
return KeyShift;
}
public void setKeyShift(String keyShift) {
KeyShift = keyShift;
}
public String getKeyDelete() {
return KeyDelete;
}
public void setKeyDelete(String keyDelete) {
KeyDelete = keyDelete;
}
public String getKeyAlphaSymbol() {
return KeyAlphaSymbol;
}
public void setKeyAlphaSymbol(String keyAlphaSymbol) {
KeyAlphaSymbol = keyAlphaSymbol;
}
public String getKeyEmoji() {
return KeyEmoji;
}
public void setKeyEmoji(String keyEmoji) {
KeyEmoji = keyEmoji;
}
public String getKeyMark() {
return KeyMark;
}
public void setKeyMark(String keyMark) {
KeyMark = keyMark;
}
public String getKeySpace() {
return KeySpace;
}
public void setKeySpace(String keySpace) {
KeySpace = keySpace;
}
public String getKeyEnter() {
return KeyEnter;
}
public void setKeyEnter(String keyEnter) {
KeyEnter = keyEnter;
}
}

View File

@ -0,0 +1,100 @@
package com.app.input.personalised.board.keyboardutils
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import com.app.input.personalised.board.BoartApp
import com.app.input.personalised.board.sourcekey.KeyboardLayout
import com.app.input.personalised.board.helpers.KeyboardConstants
import com.app.input.personalised.board.helpers.ThemePreferences
import java.io.File
object KeyboardHelper {
fun keyToUpper(mKeyBoard: KeyboardLayout) {
for (key in mKeyBoard.keys) {
if (key.label != null) {
if (key.label.length == 1) {
val charLabel = key.label.toString()[0]
val toUpperCase = charLabel.uppercaseChar()
key.codes[0] = toUpperCase.toInt()
key.label = toUpperCase.toString()
}
}
}
}
fun keyToLowerCase(mKeyBoard: KeyboardLayout) {
for (key in mKeyBoard.keys) {
if (key.label != null) {
if (key.label.length == 1) {
val charLabel = key.label.toString()[0]
val toLowerCase = charLabel.lowercaseChar()
key.codes[0] = toLowerCase.toInt()
key.label = toLowerCase.toString()
}
}
}
}
fun primaryCodeToChar(primCode: Int): String {
val toString = primCode.toChar().toString()
return toString
}
@SuppressLint("SuspiciousIndentation")
fun readBgOrVideo(
context: Context,
playAction: (gif: String?, bgDraw: Drawable?) -> Unit
) {
ThemePreferences.getSkinPath()?.let { resPath ->
val videoPath = "${resPath}res/raw/${KeyboardConstants.videoName}"
val videoPath2 = "${resPath}res/raw/${KeyboardConstants.video}"
val backgroundPath = "${resPath}res/drawable-xxhdpi-v4/${KeyboardConstants.bgName}"
val backgroundPath_png = "${resPath}res/drawable-xxhdpi-v4/${KeyboardConstants.bgName_png}"
val file = File(videoPath)
val file2 = File(videoPath2)
val file3 = File(backgroundPath)
val file4 = File(backgroundPath_png)
if (file.exists() || file2.exists()) {
Log.d(BoartApp.Companion.TAG, "--------11111111= resPath=${resPath}")
val path: String = if (file.exists()) {
videoPath
} else {
videoPath2
}
val bitmapDrawable =
BitmapDrawable(context.resources, BitmapFactory.decodeFile(path))
playAction.invoke(path, null)
// playAction.invoke(mediaPlayer,null);
} else {
Log.d(
BoartApp.Companion.TAG,
"--------11111111= file3.exists()" + file3.exists() + "---resPath=" + resPath
)
if (file3.exists()) {
val bitmapDrawable =
BitmapDrawable(context.resources, BitmapFactory.decodeFile(backgroundPath))
playAction.invoke(null, bitmapDrawable)
} else if (file4.exists()) {
val bitmapDrawable =
BitmapDrawable(context.resources, BitmapFactory.decodeFile(backgroundPath_png))
playAction.invoke(null, bitmapDrawable)
} else {
playAction.invoke(null, null)
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package com.app.input.personalised.board.keyboardutils;
// 按键对象模型
public class KeyboardKey {
private String name;
private String background;
private String label;
public KeyboardKey(String name) {
this.name = name;
}
// Getters and Setters
public String getName() {
return name;
}
public String getBackground() {
return background;
}
public void setBackground(String background) {
this.background = background;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}

View File

@ -0,0 +1,35 @@
package com.app.input.personalised.board.keyboardutils;
import java.util.ArrayList;
import java.util.List;
public class KeyboardLayout {
private String name;
private List<KeyboardKey> keyboardKeys = new ArrayList<>();
public KeyboardLayout(String name) {
this.name = name;
}
// Getters and Setters
public String getName() {
return name;
}
public List<KeyboardKey> getKeys() {
return keyboardKeys;
}
public void addKey(KeyboardKey keyboardKey) {
this.keyboardKeys.add(keyboardKey);
}
public KeyboardKey getLastKey() {
return keyboardKeys.isEmpty() ? null : keyboardKeys.get(keyboardKeys.size() - 1);
}
}

View File

@ -0,0 +1,114 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.helpers.AppUtils;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList;
import java.util.List;
public class BannerListAdapter extends RecyclerView.Adapter<BannerListAdapter.BannerViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
private OnBannerClickListener mListener;
private boolean showTitle = false; // 是否显示标题
public interface OnBannerClickListener {
void onBannerClick(KeyboardTheme beanDetails);
}
public BannerListAdapter(Context context, List<KeyboardTheme> list) {
mContext = context;
this.mList = list != null ? list : new ArrayList<>();
}
public void setOnBannerClickListener(OnBannerClickListener listener) {
mListener = listener;
}
public void setShowTitle(boolean show) {
showTitle = show;
}
@NonNull
@Override
public BannerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_banner, parent, false);
return new BannerViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull BannerViewHolder holder, int position) {
KeyboardTheme beanDetails = mList.get(position);
String thumbGif = beanDetails.getThumbGif();
String thumb = beanDetails.getThumbUrl();
String title = beanDetails.getTitleName();
// 设置标题
if (showTitle && title != null && !title.isEmpty()) {
holder.tvTitle.setText(title);
holder.tvTitle.setVisibility(View.VISIBLE);
holder.viewGradient.setVisibility(View.VISIBLE);
} else {
holder.tvTitle.setVisibility(View.GONE);
holder.viewGradient.setVisibility(View.GONE);
}
// 加载图片使用圆角
int cornerRadius = (int) (16 * mContext.getResources().getDisplayMetrics().density);
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.transform(new RoundedCorners(cornerRadius));
if (thumbGif != null && !thumbGif.isEmpty()) {
// WebP GIF 使用Common加载但需要应用圆角
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.imageView);
} else if (thumb != null && !thumb.isEmpty()) {
Glide.with(mContext)
.load(thumb)
.apply(options)
.into(holder.imageView);
} else {
holder.imageView.setImageResource(R.drawable.placeholder);
}
holder.itemView.setOnClickListener(v -> {
if (mListener != null) {
mListener.onBannerClick(beanDetails);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class BannerViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView tvTitle;
View viewGradient;
public BannerViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.iv_banner);
tvTitle = itemView.findViewById(R.id.tv_banner_title);
viewGradient = itemView.findViewById(R.id.view_gradient);
}
}
}

View File

@ -0,0 +1,104 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.databean.ThemeCategory;
import com.app.input.personalised.board.helpers.AppUtils;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
public class CategoryGridAdapter extends RecyclerView.Adapter<CategoryGridAdapter.CategoryCardViewHolder> {
private Context mContext;
private List<ThemeCategory> mList = new ArrayList<>();
private OnCategoryCardClickListener mListener;
public interface OnCategoryCardClickListener {
void onCategoryCardClick(ThemeCategory beanWrapper);
}
public CategoryGridAdapter(Context context, List<ThemeCategory> list) {
mContext = context;
this.mList = list;
}
public void setOnCategoryCardClickListener(OnCategoryCardClickListener listener) {
mListener = listener;
}
@NonNull
@Override
public CategoryCardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_category_card, parent, false);
return new CategoryCardViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CategoryCardViewHolder holder, int position) {
ThemeCategory beanWrapper = mList.get(position);
String categoryName = beanWrapper.getParentName();
holder.tvCategoryName.setText(categoryName != null ? categoryName : "");
// 显示该分类的第一张图
List<KeyboardTheme> keyboardList = beanWrapper.getKeyboardList();
if (keyboardList != null && !keyboardList.isEmpty()) {
KeyboardTheme firstItem = keyboardList.get(0);
String thumbGif = firstItem.getThumbGif();
String thumb = firstItem.getThumbUrl();
if (thumbGif != null && !thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.ivCategoryImage);
} else if (thumb != null && !thumb.isEmpty()) {
Glide.with(mContext)
.load(thumb)
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.centerCrop()
.into(holder.ivCategoryImage);
} else {
holder.ivCategoryImage.setImageResource(R.drawable.placeholder);
}
} else {
holder.ivCategoryImage.setImageResource(R.drawable.placeholder);
}
holder.cardView.setOnClickListener(v -> {
if (mListener != null) {
mListener.onCategoryCardClick(beanWrapper);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class CategoryCardViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
ImageView ivCategoryImage;
TextView tvCategoryName;
public CategoryCardViewHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_view);
ivCategoryImage = itemView.findViewById(R.id.iv_category_image);
tvCategoryName = itemView.findViewById(R.id.tv_category_name);
}
}
}

View File

@ -0,0 +1,88 @@
package com.app.input.personalised.board.listhelpers;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.ThemeCategory;
import com.app.input.personalised.board.helpers.FontTextView;
import java.util.ArrayList;
import java.util.List;
public class CategoryTabAdapter extends RecyclerView.Adapter<CategoryTabAdapter.CategoryViewHolder> {
private Context mContext;
private List<ThemeCategory> mList = new ArrayList<>();
private int selectedPosition = 0;
private OnCategoryClickListener mListener;
public interface OnCategoryClickListener {
void onCategoryClick(int position, ThemeCategory beanWrapper);
}
public CategoryTabAdapter(Context context, List<ThemeCategory> list) {
mContext = context;
this.mList = list;
}
public void setOnCategoryClickListener(OnCategoryClickListener listener) {
mListener = listener;
}
@NonNull
@Override
public CategoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_category, parent, false);
return new CategoryViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CategoryViewHolder holder, @SuppressLint("RecyclerView") int position) {
ThemeCategory beanWrapper = mList.get(position);
holder.tvCategory.setText(beanWrapper.getParentName());
boolean isSelected = position == selectedPosition;
holder.tvCategory.setSelected(isSelected);
holder.itemView.setSelected(isSelected);
holder.itemView.setOnClickListener(v -> {
int oldPosition = selectedPosition;
selectedPosition = position;
notifyItemChanged(oldPosition);
notifyItemChanged(selectedPosition);
if (mListener != null) {
mListener.onCategoryClick(position, beanWrapper);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public void setSelectedPosition(int position) {
int oldPosition = selectedPosition;
selectedPosition = position;
notifyItemChanged(oldPosition);
notifyItemChanged(selectedPosition);
}
public static class CategoryViewHolder extends RecyclerView.ViewHolder {
FontTextView tvCategory;
public CategoryViewHolder(@NonNull View itemView) {
super(itemView);
tvCategory = itemView.findViewById(R.id.tv_category);
}
}
}

View File

@ -0,0 +1,181 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.boartactivity.FavoriteThemeActivity;
import com.app.input.personalised.board.boartactivity.ThemeDetailActivity;
import com.app.input.personalised.board.helpers.AppUtils;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
public class FavoriteCardListAdapter extends RecyclerView.Adapter<FavoriteCardListAdapter.FavoriteCardViewHolder> {
private Context mContext;
private List<KeyboardTheme> mFavoriteList = new ArrayList<>();
public FavoriteCardListAdapter(Context context) {
mContext = context;
}
public void setFavoriteList(List<KeyboardTheme> favoriteList) {
this.mFavoriteList = favoriteList != null ? favoriteList : new ArrayList<>();
notifyDataSetChanged();
}
@NonNull
@Override
public FavoriteCardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_favorite_card, parent, false);
return new FavoriteCardViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull FavoriteCardViewHolder holder, int position) {
// 每个卡片显示所有收藏的前2-4张预览图
int totalCount = mFavoriteList.size();
int imageCount = Math.min(totalCount, 4);
// 显示收藏数量
holder.tvFavoriteCount.setText(totalCount + " " + mContext.getString(R.string.favorite).toLowerCase() + "s");
// 隐藏所有图片容器
holder.frameImage1.setVisibility(View.GONE);
holder.frameImage2.setVisibility(View.GONE);
holder.frameImage3.setVisibility(View.GONE);
holder.frameImage4.setVisibility(View.GONE);
// 如果有收藏显示预览图
if (imageCount > 0) {
// 显示图片区域隐藏空状态
holder.layoutImages.setVisibility(View.VISIBLE);
holder.layoutEmptyHint.setVisibility(View.GONE);
// 根据数量显示图片
FrameLayout[] frames = {holder.frameImage1, holder.frameImage2, holder.frameImage3, holder.frameImage4};
ImageView[] imageViews = {holder.ivImage1, holder.ivImage2, holder.ivImage3, holder.ivImage4};
for (int i = 0; i < imageCount; i++) {
KeyboardTheme item = mFavoriteList.get(i);
FrameLayout frame = frames[i];
ImageView imageView = imageViews[i];
frame.setVisibility(View.VISIBLE);
// 设置权重确保图片平均分配空间
ViewGroup.LayoutParams params = frame.getLayoutParams();
if (params instanceof LinearLayout.LayoutParams) {
((LinearLayout.LayoutParams) params).weight = 1.0f;
}
String thumbGif = item.getThumbGif();
String thumb = item.getThumbUrl();
if (thumbGif != null && !thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, imageView);
} else if (thumb != null && !thumb.isEmpty()) {
Glide.with(mContext)
.load(thumb)
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.centerCrop()
.into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder);
}
// 设置预览图点击事件跳转到对应的详情页
final KeyboardTheme clickItem = item;
frame.setOnClickListener(v -> {
openDetailActivity(clickItem);
});
}
// 动态调整父布局的weightSum确保图片平均分配
holder.layoutImages.setWeightSum(imageCount);
} else {
// 没有收藏时隐藏图片区域显示空状态提示
holder.layoutImages.setVisibility(View.GONE);
holder.layoutEmptyHint.setVisibility(View.VISIBLE);
}
// 点击卡片跳转到收藏列表界面
holder.cardView.setOnClickListener(v -> {
Intent intent = new Intent(mContext, FavoriteThemeActivity.class);
mContext.startActivity(intent);
});
// 点击"查看全部"也跳转到收藏列表界面
holder.layoutSeeAll.setOnClickListener(v -> {
Intent intent = new Intent(mContext, FavoriteThemeActivity.class);
mContext.startActivity(intent);
});
}
private void openDetailActivity(KeyboardTheme item) {
Intent intent = new Intent(mContext, ThemeDetailActivity.class);
intent.putExtra(ThemeDetailActivity.SOURCE_KEY, item);
intent.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, item.getImgPath());
intent.putExtra(ThemeDetailActivity.ZIP_URL_KEY, item.getZipPath());
intent.putExtra(ThemeDetailActivity.NAME_KEY, item.getTitleName());
intent.putExtra(ThemeDetailActivity.GIF_KEY, item.getImgGif());
String intent_thumb;
String thumbGif = item.getThumbGif();
if (thumbGif != null && !thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = item.getThumbUrl();
}
intent.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intent);
}
@Override
public int getItemCount() {
// 始终显示一个卡片即使没有收藏
return 1;
}
public static class FavoriteCardViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
LinearLayout layoutImages;
LinearLayout layoutSeeAll;
FrameLayout frameImage1, frameImage2, frameImage3, frameImage4;
ImageView ivImage1, ivImage2, ivImage3, ivImage4;
TextView tvCardTitle, tvSeeAll, tvFavoriteCount;
LinearLayout layoutEmptyHint;
public FavoriteCardViewHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_view);
layoutImages = itemView.findViewById(R.id.layout_images);
layoutSeeAll = itemView.findViewById(R.id.layout_see_all);
frameImage1 = itemView.findViewById(R.id.frame_image1);
frameImage2 = itemView.findViewById(R.id.frame_image2);
frameImage3 = itemView.findViewById(R.id.frame_image3);
frameImage4 = itemView.findViewById(R.id.frame_image4);
ivImage1 = itemView.findViewById(R.id.iv_image1);
ivImage2 = itemView.findViewById(R.id.iv_image2);
ivImage3 = itemView.findViewById(R.id.iv_image3);
ivImage4 = itemView.findViewById(R.id.iv_image4);
tvCardTitle = itemView.findViewById(R.id.tv_card_title);
tvSeeAll = itemView.findViewById(R.id.tv_see_all);
tvFavoriteCount = itemView.findViewById(R.id.tv_favorite_count);
layoutEmptyHint = itemView.findViewById(R.id.layout_empty_hint);
}
}
}

View File

@ -0,0 +1,126 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.helpers.AppUtils;
import com.app.input.personalised.board.itemback.FavoriteRemovedListener;
import com.app.input.personalised.board.boartactivity.ThemeDetailActivity;
import java.util.ArrayList;
import java.util.List;
public class FavoriteListAdapter extends RecyclerView.Adapter<FavoriteListAdapter.ForYouViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
private FavoriteRemovedListener mCallBack;
public FavoriteListAdapter(Context context) {
mContext = context;
}
public void setForYouList(List<KeyboardTheme> list) {
this.mList = list;
notifyDataSetChanged();
}
public void setRemoveLike(FavoriteRemovedListener callback) {
mCallBack = callback;
}
@NonNull
@Override
public ForYouViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_favorite, parent, false);
return new ForYouViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ForYouViewHolder holder, int position) {
KeyboardTheme beanDetails = mList.get(position);
String thumbGif = beanDetails.getThumbGif();
String thumb = beanDetails.getThumbUrl();
if (!thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.itemImg);
} else {
Glide.with(mContext)
.load(thumb).error(R.drawable.placeholder)
.placeholder(R.drawable.placeholder).into(holder.itemImg);
}
holder.itemFavorite.setSelected(true);
holder.layoutFavorite.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 先调用回调删除数据库中的收藏
if (mCallBack != null) {
mCallBack.OnRemoveLike(beanDetails);
}
// 注意不需要手动调用 notifyItemRemoved
// 因为 LiveData 会自动更新列表适配器会通过 setForYouList 重新设置数据
}
});
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentApply = new Intent(mContext, ThemeDetailActivity.class);
intentApply.putExtra(ThemeDetailActivity.SOURCE_KEY, beanDetails);
intentApply.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, beanDetails.getImgPath());
intentApply.putExtra(ThemeDetailActivity.ZIP_URL_KEY, beanDetails.getZipPath());
intentApply.putExtra(ThemeDetailActivity.NAME_KEY, beanDetails.getTitleName());
intentApply.putExtra(ThemeDetailActivity.GIF_KEY, beanDetails.getImgGif());
String intent_thumb;
if (!thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class ForYouViewHolder extends RecyclerView.ViewHolder {
private CardView cardView;
private FrameLayout layoutFavorite;
private ImageView itemImg, itemFavorite;
public ForYouViewHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_view);
layoutFavorite = itemView.findViewById(R.id.layout_favorite);
itemImg = itemView.findViewById(R.id.im);
itemFavorite = itemView.findViewById(R.id.im_favorite);
}
}
}

View File

@ -0,0 +1,74 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.databean.ThemeCategory;
import com.app.input.personalised.board.itemback.ViewAllListener;
import com.app.input.personalised.board.databinding.ItemHomeSectionHeaderBinding;
import com.app.input.personalised.board.helpers.GridItemDecoration;
import java.util.ArrayList;
import java.util.List;
public class HomeSectionAdapter extends RecyclerView.Adapter<HomeSectionAdapter.MainViewHolder> {
private Context mContext;
private List<ThemeCategory> mList = new ArrayList<>();
private ViewAllListener mCallBack;
public HomeSectionAdapter(Context context, List<ThemeCategory> list) {
mContext = context;
this.mList = list;
}
public void setClickAction(ViewAllListener callback) {
mCallBack = callback;
}
@NonNull
@Override
public MainViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomeSectionHeaderBinding inflate = ItemHomeSectionHeaderBinding.inflate(LayoutInflater.from(parent.getContext()));
return new MainViewHolder(inflate);
}
@Override
public void onBindViewHolder(@NonNull MainViewHolder holder, int position) {
ThemeCategory beanWrapper = mList.get(position);
String parentName = beanWrapper.getParentName();
holder.binding.className.setText(parentName);
GridItemDecoration gridItemDecoration = new GridItemDecoration(3, 3, 0);
ThemePreviewAdapter themePreviewAdapter = new ThemePreviewAdapter(mContext, beanWrapper.getKeyboardList().subList(0, 6));
holder.binding.childRecycler.setLayoutManager(new GridLayoutManager(mContext, 3));
holder.binding.childRecycler.setAdapter(themePreviewAdapter);
if (holder.binding.childRecycler.getItemDecorationCount() <= 0) {
holder.binding.childRecycler.addItemDecoration(gridItemDecoration);
}
holder.binding.tvAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCallBack.OnClickSeeAll(parentName);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class MainViewHolder extends RecyclerView.ViewHolder {
private ItemHomeSectionHeaderBinding binding;
public MainViewHolder(@NonNull ItemHomeSectionHeaderBinding itemView) {
super(itemView.getRoot());
binding = itemView;
}
}
}

View File

@ -0,0 +1,107 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.boartactivity.ThemeDetailActivity;
import com.app.input.personalised.board.helpers.AppUtils;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
public class RecommendedGridAdapter extends RecyclerView.Adapter<RecommendedGridAdapter.RecommendedViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
public RecommendedGridAdapter(Context context) {
mContext = context;
}
public void setForYouList(List<KeyboardTheme> list) {
this.mList = list;
notifyDataSetChanged();
}
@NonNull
@Override
public RecommendedViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_theme_with_name, parent, false);
return new RecommendedViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecommendedViewHolder holder, int position) {
KeyboardTheme beanDetails = mList.get(position);
String thumbGif = beanDetails.getThumbGif();
String thumb = beanDetails.getThumbUrl();
String title = beanDetails.getTitleName();
holder.tvName.setText(title != null ? title : "");
if (thumbGif != null && !thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.itemImg);
} else if (thumb != null && !thumb.isEmpty()) {
Glide.with(mContext)
.load(thumb)
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.centerCrop()
.into(holder.itemImg);
} else {
holder.itemImg.setImageResource(R.drawable.placeholder);
}
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentApply = new Intent(mContext, ThemeDetailActivity.class);
intentApply.putExtra(ThemeDetailActivity.SOURCE_KEY, beanDetails);
intentApply.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, beanDetails.getImgPath());
intentApply.putExtra(ThemeDetailActivity.ZIP_URL_KEY, beanDetails.getZipPath());
intentApply.putExtra(ThemeDetailActivity.NAME_KEY, beanDetails.getTitleName());
intentApply.putExtra(ThemeDetailActivity.GIF_KEY, beanDetails.getImgGif());
String intent_thumb;
if (thumbGif != null && !thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class RecommendedViewHolder extends RecyclerView.ViewHolder {
private CardView cardView;
private ImageView itemImg;
private TextView tvName;
public RecommendedViewHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_view);
itemImg = itemView.findViewById(R.id.imPreview);
tvName = itemView.findViewById(R.id.tv_name);
}
}
}

View File

@ -0,0 +1,127 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.itemback.ItemClickListener;
import com.app.input.personalised.board.boartactivity.ThemeDetailActivity;
import com.bumptech.glide.Glide;
import com.app.input.personalised.board.helpers.AppUtils;
import java.util.ArrayList;
import java.util.List;
public class RelatedThemeAdapter extends RecyclerView.Adapter<RelatedThemeAdapter.ForYouViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
private ItemClickListener mCallBack;
public RelatedThemeAdapter(Context context) {
mContext = context;
}
public void setForYouList(List<KeyboardTheme> list) {
this.mList = list;
notifyDataSetChanged();
}
public void setClickAction(ItemClickListener callback) {
mCallBack = callback;
}
@NonNull
@Override
public ForYouViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_theme, parent, false);
return new ForYouViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ForYouViewHolder holder, int position) {
KeyboardTheme beanDetails = mList.get(position);
String thumbGif = beanDetails.getThumbGif();
String thumb = beanDetails.getThumbUrl();
if (!thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.itemImg);
} else {
Glide.with(mContext).load(thumb).into(holder.itemImg);
}
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentApply = new Intent(mContext, ThemeDetailActivity.class);
intentApply.putExtra(ThemeDetailActivity.SOURCE_KEY, beanDetails);
intentApply.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, beanDetails.getImgPath());
intentApply.putExtra(ThemeDetailActivity.ZIP_URL_KEY, beanDetails.getZipPath());
intentApply.putExtra(ThemeDetailActivity.NAME_KEY, beanDetails.getTitleName());
intentApply.putExtra(ThemeDetailActivity.GIF_KEY, beanDetails.getImgGif());
String intent_thumb;
if (!thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
if (mCallBack != null) {
mCallBack.OnItemClickListener();
}
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class ForYouViewHolder extends RecyclerView.ViewHolder {
private CardView cardView;
private ImageView itemImg;
public ForYouViewHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_view);
itemImg = itemView.findViewById(R.id.imPreview);
}
}
// private void loadWepJif(String webpGifUrl,ImageView view){
// Glide.with(mContext).load(webpGifUrl).addListener(new RequestListener<Drawable>() {
// @Override
// public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
// return false;
// }
//
// @Override
// public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
// if(resource instanceof WebpDrawable){
// WebpDrawable webpDrawable = (WebpDrawable) resource;
// webpDrawable.setLoopCount(LOOP_FOREVER);
// }
//
// return false;
// }
// }).into(view);
// }
}

View File

@ -0,0 +1,122 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.databinding.ItemHomeSectionBinding;
import com.app.input.personalised.board.boartactivity.ThemeDetailActivity;
import com.app.input.personalised.board.helpers.AppUtils;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
public class ThemePreviewAdapter extends RecyclerView.Adapter<ThemePreviewAdapter.ChildViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
public ThemePreviewAdapter(Context context, List<KeyboardTheme> list) {
mContext = context;
this.mList = list;
}
@NonNull
@Override
public ChildViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomeSectionBinding inflate = ItemHomeSectionBinding.inflate(LayoutInflater.from(parent.getContext()));
return new ChildViewHolder(inflate);
}
@Override
public void onBindViewHolder(@NonNull ChildViewHolder holder, int position) {
KeyboardTheme beanDetails = mList.get(position);
String thumbGif = beanDetails.getThumbGif();
String thumb = beanDetails.getThumbUrl();
if (!thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.binding.imageView);
} else {
Glide.with(mContext).load(thumb)
.error(R.drawable.placeholder)
.placeholder(R.drawable.placeholder)
.into(holder.binding.imageView);
}
holder.binding.fragme.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentApply = new Intent(mContext, ThemeDetailActivity.class);
intentApply.putExtra(ThemeDetailActivity.SOURCE_KEY, beanDetails);
intentApply.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, beanDetails.getImgPath());
intentApply.putExtra(ThemeDetailActivity.ZIP_URL_KEY, beanDetails.getZipPath());
intentApply.putExtra(ThemeDetailActivity.NAME_KEY, beanDetails.getTitleName());
intentApply.putExtra(ThemeDetailActivity.GIF_KEY, beanDetails.getImgGif());
String intent_thumb;
if (!thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class ChildViewHolder extends RecyclerView.ViewHolder {
private ItemHomeSectionBinding binding;
public ChildViewHolder(@NonNull ItemHomeSectionBinding itemView) {
super(itemView.getRoot());
binding = itemView;
}
}
// private void loadWepJif(String webpGifUrl,ImageView view){
// Glide.with(mContext).load(webpGifUrl).addListener(new RequestListener<Drawable>() {
// @Override
// public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
// return false;
// }
//
// @Override
// public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
// if(resource instanceof WebpDrawable){
// WebpDrawable webpDrawable = (WebpDrawable) resource;
// webpDrawable.setLoopCount(LOOP_FOREVER);
// }
//
// return false;
// }
// }).into(view);
// }
}

View File

@ -0,0 +1,106 @@
package com.app.input.personalised.board.listhelpers;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.personalised.board.R;
import com.app.input.personalised.board.databean.KeyboardTheme;
import com.app.input.personalised.board.boartactivity.ThemeDetailActivity;
import com.app.input.personalised.board.helpers.AppUtils;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
public class ThemeStaggeredAdapter extends RecyclerView.Adapter<ThemeStaggeredAdapter.StaggeredViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
public ThemeStaggeredAdapter(Context context, List<KeyboardTheme> list) {
mContext = context;
this.mList = list;
}
public void setData(List<KeyboardTheme> list) {
this.mList = list;
notifyDataSetChanged();
}
@NonNull
@Override
public StaggeredViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_staggered, parent, false);
return new StaggeredViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull StaggeredViewHolder holder, int position) {
KeyboardTheme beanDetails = mList.get(position);
String thumbGif = beanDetails.getThumbGif();
String thumb = beanDetails.getThumbUrl();
String title = beanDetails.getTitleName();
holder.tvName.setText(title != null ? title : "");
// 加载图片参考HomeChildAdapter的实现
if (thumbGif != null && !thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.imageView);
} else if (thumb != null && !thumb.isEmpty()) {
Glide.with(mContext)
.load(thumb)
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.centerCrop()
.into(holder.imageView);
} else {
// 如果没有图片URL显示占位符
holder.imageView.setImageResource(R.drawable.placeholder);
}
holder.cardView.setOnClickListener(v -> {
Intent intentApply = new Intent(mContext, ThemeDetailActivity.class);
intentApply.putExtra(ThemeDetailActivity.SOURCE_KEY, beanDetails);
intentApply.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, beanDetails.getImgPath());
intentApply.putExtra(ThemeDetailActivity.ZIP_URL_KEY, beanDetails.getZipPath());
intentApply.putExtra(ThemeDetailActivity.NAME_KEY, beanDetails.getTitleName());
intentApply.putExtra(ThemeDetailActivity.GIF_KEY, beanDetails.getImgGif());
String intent_thumb;
if (thumbGif != null && !thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class StaggeredViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
ImageView imageView;
TextView tvName;
public StaggeredViewHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_view);
imageView = itemView.findViewById(R.id.iv_image);
tvName = itemView.findViewById(R.id.tv_name);
}
}
}

View File

@ -0,0 +1,858 @@
package com.app.input.personalised.board.sourcekey;
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.TypedValue;
import android.util.Xml;
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;
import com.app.input.personalised.board.R;
public class KeyboardLayout {
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 KeyboardLayout.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<KeyboardLayout.Key> mKeys;
/** List of modifier keys such as Shift & Alt, if any */
private List<KeyboardLayout.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<KeyboardLayout.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 KeyboardLayout}
* defines.
* @attr ref android.R.styleable#King_Keyboard_keyWidth
* @attr ref android.R.styleable#King_Keyboard_keyHeight
* @attr ref android.R.styleable#King_Keyboard_horizontalGap
* @attr ref android.R.styleable#King_Keyboard_verticalGap
* @attr ref android.R.styleable#King_Keyboard_Row_rowEdgeFlags
* @attr ref android.R.styleable#King_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<KeyboardLayout.Key> mKeys = new ArrayList<>();
/**
* Edge flags for this row of keys. Possible values that can be assigned are
* {@link KeyboardLayout#EDGE_TOP EDGE_TOP} and {@link KeyboardLayout#EDGE_BOTTOM EDGE_BOTTOM}
*/
public int rowEdgeFlags;
/** The keyboard mode for this row */
public int mode;
private KeyboardLayout parent;
public Row(KeyboardLayout parent) {
this.parent = parent;
}
public Row(Resources res, KeyboardLayout parent, XmlResourceParser parser) {
this.parent = parent;
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.My_Keyboard_view);
defaultWidth = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
parent.mDisplayWidth, parent.mDefaultWidth);
defaultHeight = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
parent.mDisplayHeight, parent.mDefaultHeight);
defaultHorizontalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
parent.mDisplayWidth, parent.mDefaultHorizontalGap);
verticalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_verticalGap,
parent.mDisplayHeight, parent.mDefaultVerticalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Kil_Keyboard_Row);
rowEdgeFlags = a.getInt(R.styleable.Kil_Keyboard_Row_android_rowEdgeFlags, 0);
mode = a.getResourceId(R.styleable.Kil_Keyboard_Row_android_keyboardMode,
0);
}
}
/**
* Class for describing the position and characteristics of a single key in the keyboard.
*
* @attr ref android.R.styleable#King_Keyboard_keyWidth
* @attr ref android.R.styleable#King_Keyboard_keyHeight
* @attr ref android.R.styleable#King_Keyboard_horizontalGap
* @attr ref android.R.styleable#King_Keyboard_Key_codes
* @attr ref android.R.styleable#King_Keyboard_Key_keyIcon
* @attr ref android.R.styleable#King_Keyboard_Key_keyLabel
* @attr ref android.R.styleable#King_Keyboard_Key_iconPreview
* @attr ref android.R.styleable#King_Keyboard_Key_isSticky
* @attr ref android.R.styleable#King_Keyboard_Key_isRepeatable
* @attr ref android.R.styleable#King_Keyboard_Key_isModifier
* @attr ref android.R.styleable#King_Keyboard_Key_popupKeyboard
* @attr ref android.R.styleable#King_Keyboard_Key_popupCharacters
* @attr ref android.R.styleable#King_Keyboard_Key_keyOutputText
* @attr ref android.R.styleable#King_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 KeyboardLayout#EDGE_LEFT}, {@link KeyboardLayout#EDGE_RIGHT}, {@link KeyboardLayout#EDGE_TOP} and
* {@link KeyboardLayout#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 KeyboardLayout 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(KeyboardLayout.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 KeyboardLayout}.
* @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, KeyboardLayout.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.My_Keyboard_view);
width = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
keyboard.mDisplayWidth, parent.defaultWidth);
height = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
keyboard.mDisplayHeight, parent.defaultHeight);
gap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
keyboard.mDisplayWidth, parent.defaultHorizontalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.K_Keyboard_Key);
this.x += gap;
TypedValue codesValue = new TypedValue();
a.getValue(R.styleable.K_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.K_Keyboard_Key_android_iconPreview);
if (iconPreview != null) {
iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(),
iconPreview.getIntrinsicHeight());
}
popupCharacters = a.getText(
R.styleable.K_Keyboard_Key_android_popupCharacters);
popupResId = a.getResourceId(
R.styleable.K_Keyboard_Key_android_popupKeyboard, 0);
repeatable = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isRepeatable, false);
modifier = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isModifier, false);
sticky = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isSticky, false);
edgeFlags = a.getInt(R.styleable.K_Keyboard_Key_android_keyEdgeFlags, 0);
edgeFlags |= parent.rowEdgeFlags;
icon = a.getDrawable(
R.styleable.K_Keyboard_Key_android_keyIcon);
if (icon != null) {
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
}
label = a.getText(R.styleable.K_Keyboard_Key_android_keyLabel);
text = a.getText(R.styleable.K_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) {
}
}
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 KeyboardLayout(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 KeyboardLayout(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 KeyboardLayout(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));
}
public KeyboardLayout(Context context, int layoutTemplateResId,
CharSequence characters, int columns, int horizontalPadding) {
this(context, layoutTemplateResId);
int x = 0;
int y = 0;
int column = 0;
mTotalWidth = 0;
KeyboardLayout.Row row = new KeyboardLayout.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 KeyboardLayout.Key key = new KeyboardLayout.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) {
KeyboardLayout.Row row = rows.get(rowIndex);
int numKeys = row.mKeys.size();
int totalGap = 0;
int totalWidth = 0;
for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
KeyboardLayout.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) {
KeyboardLayout.Key key = row.mKeys.get(keyIndex);
key.width *= scaleFactor;
key.x = x;
x += key.width + key.gap;
}
}
}
mTotalWidth = newWidth;
// TODO: 这不会根据新大小调整垂直位置
// 之前代码的主要问题是水平位置/大小但我们应该
// 在收到此调整大小调用时也重新计算垂直大小/位置
}
public List<KeyboardLayout.Key> getKeys() {
return mKeys;
}
public List<KeyboardLayout.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 (KeyboardLayout.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() {
// 向上取整这样网格外就不会有任何像素
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 KeyboardLayout.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 KeyboardLayout.Row createRowFromXml(Resources res, XmlResourceParser parser) {
return new KeyboardLayout.Row(res, this, parser);
}
protected KeyboardLayout.Key createKeyFromXml(Resources res, KeyboardLayout.Row parent, int x, int y,
XmlResourceParser parser) {
return new KeyboardLayout.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;
KeyboardLayout.Key key = null;
KeyboardLayout.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) {
// 查找可用的 shift 按键槽并将此 shift 按键放入其中
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: 错误还是扩展
}
}
}
} catch (Exception 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.My_Keyboard_view);
mDefaultWidth = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
mDisplayWidth, mDisplayWidth / 10);
mDefaultHeight = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
mDisplayHeight, 50);
mDefaultHorizontalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
mDisplayWidth, 0);
mDefaultVerticalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_verticalGap,
mDisplayHeight, 0);
mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
mProximityThreshold = mProximityThreshold * mProximityThreshold; // 平方以便比较
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) {
// 四舍五入以避免像 47.9999 这样的值被截断
return Math.round(a.getFraction(index, base, base, defValue));
}
return defValue;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="@color/future_tech_accent" />
<item android:color="@color/future_tech_text_secondary" />
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/future_tech_accent" android:state_selected="true"/>
<item android:color="@color/color_gray" android:state_selected="false"/>
</selector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/future_tech_accent"
android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="270"
android:startColor="#00000000"
android:centerColor="#40000000"
android:endColor="#80000000" />
<corners android:radius="16dp" />
</shape>

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="@color/future_tech_light_gray" />
<corners android:radius="16dp" />
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M608,736c-6.4,0 -19.2,0 -25.6,-6.4l-192,-192C384,524.8 384,499.2 390.4,486.4l192,-192c12.8,-12.8 32,-12.8 44.8,0s12.8,32 0,44.8L460.8,512l166.4,166.4c12.8,12.8 12.8,32 0,44.8C627.2,736 614.4,736 608,736z"
android:fillColor="@color/main_text_color"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="@color/future_tech_accent_light" />
<corners android:radius="20dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@color/future_tech_light_gray" />
<corners android:radius="20dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M128,128h256v256H128V128zM384,128h256v256H384V128zM640,128h256v256H640V128zM128,384h256v256H128V384zM384,384h256v256H384V384zM640,384h256v256H640V384zM128,640h256v256H128V640zM384,640h256v256H384V640zM640,640h256v256H640V640z"
android:fillColor="#999999"
android:strokeWidth="2"
android:strokeColor="#999999"/>
</vector>

View File

@ -0,0 +1,10 @@
<?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="40dp"
android:topRightRadius="40dp" />
</shape>

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="@color/apply_step_false" />
<corners android:radius="12dp" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/set_button_bg_shape" android:state_selected="false" />
<item android:drawable="@drawable/dialog_enable_bg" />
</selector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M716.8,60.2c-81.3,0 -153.6,36.1 -204.8,96.4 -51.2,-60.2 -123.5,-96.4 -204.8,-96.4C153.6,60.2 30.1,192.8 30.1,358.4c0,45.2 6,87.3 21.1,126.5 0,0 12,36.1 21.1,57.2C189.7,798.1 512,963.8 512,963.8s322.3,-165.6 439.7,-424.7c0,0 15.1,-33.1 21.1,-57.2 12,-39.2 21.1,-81.3 21.1,-126.5C993.9,192.8 870.4,60.2 716.8,60.2zM915.6,466.8c-6,15.1 -15.1,42.2 -18.1,48.2 -87.3,192.8 -307.2,334.3 -385.5,379.5 -78.3,-45.2 -298.2,-186.7 -385.5,-379.5 -6,-15.1 -15.1,-36.1 -18.1,-51.2 -12,-33.1 -18.1,-72.3 -18.1,-105.4C90.4,225.9 189.7,120.5 307.2,120.5c60.2,0 117.5,27.1 159.6,75.3L512,247l45.2,-51.2C599.3,147.6 656.6,120.5 716.8,120.5c120.5,0 216.8,105.4 216.8,234.9 0,36.1 -6,72.3 -18.1,111.4z"
android:fillColor="#757575"
android:strokeWidth="2"
android:strokeColor="#757575"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M667.8,117.3C832.9,117.3 938.7,249.7 938.7,427.9c0,138.3 -125.1,290.5 -371.6,461.6a96.8,96.8 0,0 1,-110.2 0C210.4,718.4 85.3,566.1 85.3,427.9 85.3,249.7 191.1,117.3 356.2,117.3c59.6,0 100.1,20.8 155.8,68.1C567.7,138.2 608.2,117.3 667.8,117.3z"
android:fillColor="@color/future_tech_accent"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/favorite_normal_shape" android:state_selected="false"/>
<item android:drawable="@drawable/favorite_selected_shape" android:state_selected="true" />
</selector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M8.72,8L14.21,2.51C14.412,2.307 14.412,1.992 14.21,1.79C14.007,1.587 13.692,1.587 13.49,1.79L8,7.28L2.51,1.79C2.307,1.587 1.992,1.587 1.79,1.79C1.587,1.992 1.587,2.307 1.79,2.51L7.28,8L1.79,13.49C1.587,13.692 1.587,14.007 1.79,14.21C1.88,14.3 2.015,14.367 2.15,14.367C2.285,14.367 2.42,14.322 2.51,14.21L8,8.72L13.49,14.21C13.58,14.3 13.715,14.367 13.85,14.367C13.985,14.367 14.12,14.322 14.21,14.21C14.412,14.007 14.412,13.692 14.21,13.49L8.72,8Z"
android:fillColor="#000000"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 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="@color/future_tech_light_gray" />
<corners android:radius="8dp" />
</shape>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="@color/future_tech_accent" />
<corners android:radius="4dp" />
<size android:width="24dp" android:height="6dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@color/future_tech_border" />
<corners android:radius="3dp" />
<size android:width="6dp" android:height="6dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape android:shape="rectangle">
<corners android:radius="4dp"/>
<solid android:color="@color/white"/>
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:radius="4dp"/>
<solid android:color="@color/key_bg_press"/>
</shape>
</item>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/white"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 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">
<corners android:radius="10dp"/>
<solid android:color="@color/white"/>
</shape>

View File

@ -0,0 +1,15 @@
<?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="@color/future_tech_accent_light" />
<corners android:radius="16dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
</shape>
</item>
</selector>

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="@color/future_tech_accent" />
<corners android:radius="12dp" />
</shape>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="3dp" />
<solid android:color="@color/future_tech_light_gray" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="3dp" />
<solid android:color="@color/future_tech_accent"/>
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M128,128h256v256H128V128zM384,128h256v256H384V128zM640,128h256v256H640V128zM128,384h256v256H128V384zM384,384h256v256H384V384zM640,384h256v256H640V384zM128,640h256v256H128V640zM384,640h256v256H384V640zM640,640h256v256H640V640z"
android:fillColor="@color/future_tech_accent"
android:strokeWidth="2"
android:strokeColor="@color/future_tech_accent"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/category_tab_normal" android:state_selected="false"/>
<item android:drawable="@drawable/tab_category_selected" android:state_selected="true" />
</selector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<!-- 用户图标 -->
<path
android:pathData="M512,128c-141.4,0 -256,114.6 -256,256 0,70.7 28.7,134.7 75.2,181.2 46.5,46.5 110.5,75.2 180.8,75.2s134.3,-28.7 180.8,-75.2c46.5,-46.5 75.2,-110.5 75.2,-181.2 0,-141.4 -114.6,-256 -256,-256zM512,192c106,0 192,86 192,192 0,53 -21.5,101.1 -56.3,136.1 -34.8,35 -82.8,55.9 -135.7,55.9s-100.9,-20.9 -135.7,-55.9c-34.8,-35 -56.3,-83.1 -56.3,-136.1 0,-106 86,-192 192,-192z"
android:fillColor="#999999"/>
<path
android:pathData="M512,640c-141.4,0 -256,114.6 -256,256v128h512v-128c0,-141.4 -114.6,-256 -256,-256zM512,704c106,0 192,86 192,192v64h-384v-64c0,-106 86,-192 192,-192z"
android:fillColor="#999999"/>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<!-- 用户图标 -->
<path
android:pathData="M512,128c-141.4,0 -256,114.6 -256,256 0,70.7 28.7,134.7 75.2,181.2 46.5,46.5 110.5,75.2 180.8,75.2s134.3,-28.7 180.8,-75.2c46.5,-46.5 75.2,-110.5 75.2,-181.2 0,-141.4 -114.6,-256 -256,-256zM512,192c106,0 192,86 192,192 0,53 -21.5,101.1 -56.3,136.1 -34.8,35 -82.8,55.9 -135.7,55.9s-100.9,-20.9 -135.7,-55.9c-34.8,-35 -56.3,-83.1 -56.3,-136.1 0,-106 86,-192 192,-192z"
android:fillColor="@color/future_tech_accent"/>
<path
android:pathData="M512,640c-141.4,0 -256,114.6 -256,256v128h512v-128c0,-141.4 -114.6,-256 -256,-256zM512,704c106,0 192,86 192,192v64h-384v-64c0,-106 86,-192 192,-192z"
android:fillColor="@color/future_tech_accent"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/tab_favorite_normal" android:state_selected="false"/>
<item android:drawable="@drawable/tab_favorite_selected" android:state_selected="true" />
</selector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512,96L192,352v512h192V704h256v160h192V352L512,96zM512,160l256,192v448h-128V640H384v160H256V352L512,160z"
android:fillColor="#999999"
android:strokeWidth="1"
android:strokeColor="#999999"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512,96L192,352v512h192V704h256v160h192V352L512,96zM512,160l256,192v448h-128V640H384v160H256V352L512,160z"
android:fillColor="@color/future_tech_accent"
android:strokeWidth="1"
android:strokeColor="@color/future_tech_accent"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/tab_home_normal" android:state_selected="false"/>
<item android:drawable="@drawable/tab_home_selected" android:state_selected="true" />
</selector>

View File

@ -0,0 +1,60 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
android:background="@color/future_tech_white"
tools:context="com.app.input.personalised.board.boartactivity.FavoriteThemeActivity">
<ImageView
android:id="@+id/back"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginTop="5dp"
android:paddingVertical="7dp"
android:paddingStart="5dp"
android:src="@drawable/bg_back"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.app.input.personalised.board.helpers.FontTextView
android:id="@+id/class_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/future_tech_text_primary"
android:textSize="18sp"
app:apply_font="true"
app:layout_constraintBottom_toBottomOf="@id/back"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/back" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/back" />
<!-- 空状态提示 -->
<com.app.input.personalised.board.helpers.FontTextView
android:id="@+id/empty_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_like"
android:textColor="@color/future_tech_text_secondary"
android:textSize="16sp"
android:visibility="gone"
app:apply_font="true"
app:layout_constraintTop_toTopOf="@id/recycler"
app:layout_constraintBottom_toBottomOf="@id/recycler"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="match_parent"
android:id="@+id/main"
android:background="@color/future_tech_white"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/tab_layout" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_alignParentBottom="true"
app:tabIndicatorHeight="0dp" />
</RelativeLayout>

View File

@ -0,0 +1,50 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
android:background="@color/future_tech_white">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/image"
android:layout_width="120dp"
android:layout_height="120dp"
app:roundPercent="0.2"
android:src="@mipmap/logo"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="0.35" />
<com.app.input.personalised.board.helpers.FontTextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/future_tech_text_primary"
android:textSize="24sp"
android:textStyle="bold"
app:apply_font="true"
app:layout_constraintTop_toBottomOf="@id/image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp" />
<ProgressBar
android:id="@+id/nova_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="280dp"
android:layout_height="6dp"
android:progress="0"
android:max="100"
android:progressDrawable="@drawable/splash_progress_drawable"
android:indeterminate="false"
app:layout_constraintTop_toBottomOf="@id/tv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,144 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
android:background="@color/future_tech_white">
<ImageView
android:id="@+id/back"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginTop="5dp"
android:paddingStart="5dp"
android:paddingVertical="7dp"
android:src="@drawable/bg_back"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.app.input.personalised.board.helpers.FontTextView
android:id="@+id/textview_data_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/future_tech_text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:apply_font="true"
app:layout_constraintBottom_toBottomOf="@id/back"
app:layout_constraintLeft_toRightOf="@id/back"
app:layout_constraintRight_toLeftOf="@id/im_like"
app:layout_constraintTop_toTopOf="@id/back" />
<ImageView
android:id="@+id/im_like"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginEnd="16dp"
android:padding="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/favorite_selector"
android:contentDescription="@string/favorite"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/back" />
<androidx.cardview.widget.CardView
android:id="@+id/card_viewData"
android:layout_width="match_parent"
android:layout_height="204dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:cardBackgroundColor="@color/transparent"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:layout_constraintTop_toBottomOf="@id/back">
<ImageView
android:id="@+id/image_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:id="@+id/layoutDownloadApply"
android:layout_width="260dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="28dp"
android:layout_marginEnd="16dp"
android:background="@drawable/set_button_bg_shape"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_viewData">
<ImageView
android:id="@+id/im_download"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/icon_setting_white" />
<com.app.input.personalised.board.helpers.FontTextView
android:id="@+id/tv_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="@string/download_apply"
app:apply_font="true"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
<com.app.input.personalised.board.helpers.FontTextView
android:id="@+id/text_for_you"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="25dp"
android:text="@string/recommended"
android:textColor="@color/future_tech_text_primary"
android:textSize="16sp"
app:apply_font="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/layoutDownloadApply" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recommended_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="17dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_for_you" />
<FrameLayout
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_loading"
android:clickable="true"
android:focusable="true"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateTint="@color/white" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,45 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
android:background="@color/future_tech_white"
tools:context="com.app.input.personalised.board.boartactivity.ThemeListActivity">
<ImageView
android:id="@+id/back"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginTop="5dp"
android:paddingVertical="7dp"
android:paddingStart="5dp"
android:src="@drawable/bg_back"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.app.input.personalised.board.helpers.FontTextView
android:id="@+id/class_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/future_tech_text_primary"
android:textSize="18sp"
app:apply_font="true"
app:layout_constraintBottom_toBottomOf="@id/back"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/back" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/back" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/main"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/relayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_loading" />
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_marginStart="5dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="5dp"
android:background="@drawable/preview_edittext_bg"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="@string/et_hint"
android:paddingStart="15dp"
android:textColor="@color/black"
android:textColorHint="@color/apply_step_false"
android:textCursorDrawable="@color/color_74CBFF" />
</RelativeLayout>
<ImageView
android:id="@+id/id_back"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginTop="5dp"
android:paddingStart="5dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:src="@drawable/bg_back_white" />
<com.app.input.personalised.board.helpers.FontTextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/id_back"
android:layout_alignBottom="@id/id_back"
android:layout_centerHorizontal="true"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="18sp"
app:apply_font="true" />
</RelativeLayout>

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