فهرست منبع

[refactor]键盘插件,整理代码

hezihao 8 ماه پیش
والد
کامیت
5a897371b8

+ 3 - 3
plugins/keyboard_android/android/src/main/AndroidManifest.xml

@@ -3,12 +3,12 @@
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <application>
-        <activity android:name=".InputMethodPickerActivity"
+        <activity android:name=".keyboard.InputMethodPickerActivity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar"
             android:launchMode="singleTask"/>
 
         <service
-            android:name="com.atmob.keyboard_android.CustomKeyboardService"
+            android:name=".keyboard.CustomKeyboardService"
             android:exported="true"
             android:permission="android.permission.BIND_INPUT_METHOD"
             android:label="追爱小键盘">
@@ -21,7 +21,7 @@
         </service>
 
         <service
-            android:name="com.atmob.keyboard_android.FloatingButtonService"
+            android:name="com.atmob.keyboard_android.floating.FloatingButtonService"
             android:exported="true"
             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
             <intent-filter>

+ 0 - 61
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/InputMethodPickerActivity.kt

@@ -1,61 +0,0 @@
-package  com.atmob.keyboard_android
-
-import android.app.Activity
-import android.content.Context
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import android.view.inputmethod.InputMethodManager
-
-class InputMethodPickerActivity : Activity() {
-    private var first = true
-    private val TAG = "qqq InputMethodPickerActivity"
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        Log.d(TAG, "onCreate")
-
-        // 设置无动画启动
-        overridePendingTransition(0, 0)
-    }
-
-    override fun onResume() {
-        super.onResume()
-        Log.d(TAG, "onResume")
-    }
-
-    override fun onWindowFocusChanged(hasFocus: Boolean) {
-        super.onWindowFocusChanged(hasFocus)
-        Log.d(TAG, "onWindowFocusChanged focus=$hasFocus, first=$first")
-
-        if (!hasFocus) return
-
-        if (first) {
-            first = false
-            //为了确保 Activity 完全进入前台后再弹出输入法选择器
-            Handler(Looper.getMainLooper()).postDelayed({
-                Log.d(TAG, "showInputMethodPicker triggered")
-                showInputMethodPicker()
-            }, 100)
-        } else {
-            // 第二次获得焦点代表输入法弹窗消失
-            Log.d(TAG, "InputMethodPicker dismissed, finishing activity")
-            finish()
-        }
-    }
-
-    private fun showInputMethodPicker() {
-        val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
-        imeManager.showInputMethodPicker()
-    }
-
-
-
-
-    override fun finish() {
-        // 关闭时也不使用动画
-        overridePendingTransition(0, 0)
-        super.finish()
-    }
-}

+ 18 - 75
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/KeyboardAndroidPlugin.kt

@@ -1,59 +1,53 @@
 package com.atmob.keyboard_android
 
 import android.content.Context
-import android.content.Context.INPUT_METHOD_SERVICE
-import android.content.Intent
-import android.net.Uri
 import android.os.Build
-import android.provider.Settings
-import android.util.Log
-import android.view.inputmethod.InputMethodManager
-import android.view.inputmethod.InputConnection
-
-
+import com.atmob.keyboard_android.floating.FloatingButtonService
+import com.atmob.keyboard_android.util.InputMethodUtil
+import com.atmob.keyboard_android.util.LogUtil
 import io.flutter.embedding.engine.plugins.FlutterPlugin
 import io.flutter.plugin.common.MethodCall
 import io.flutter.plugin.common.MethodChannel
 import io.flutter.plugin.common.MethodChannel.MethodCallHandler
 import io.flutter.plugin.common.MethodChannel.Result
 
-
-/** KeyboardAndroidPlugin */
+/**
+ * 键盘插件
+ */
 class KeyboardAndroidPlugin : FlutterPlugin, MethodCallHandler {
-
     private lateinit var channel: MethodChannel
 
     private lateinit var context: Context
-    private val TAG = "qqq KeyboardAndroidPlugin"
-
 
     override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
         context = flutterPluginBinding.applicationContext
         channel = MethodChannel(flutterPluginBinding.binaryMessenger, "keyboard_android")
         channel.setMethodCallHandler(this)
-        Log.d(TAG, "Plugin attached to engine")
     }
 
     override fun onMethodCall(call: MethodCall, result: Result) {
-        Log.d(TAG, "Method called: ${call.method}")
+        LogUtil.d("Method called: ${call.method}")
         when (call.method) {
             "getPlatformVersion" -> {
                 result.success("Android ${Build.VERSION.RELEASE}")
             }
 
+            // 开启或关闭悬浮窗
             "enableFloatingWindow" -> {
                 val enable = call.argument<Boolean>("enable") ?: false
                 enableFloatingWindow(enable)
                 result.success(null)
             }
 
+            // 打开输入法设置
             "openInputMethodSettings" -> {
-                openInputMethodSettings()
+                InputMethodUtil.openInputMethodSettings(context)
                 result.success(null)
             }
 
+            // 判断指定的输入法是否启用
             "isTargetKeyboardEnabled" -> {
-                result.success(isTargetKeyboardEnabled())
+                result.success(InputMethodUtil.isTargetKeyboardEnabled(context))
             }
 
             else -> {
@@ -62,69 +56,18 @@ class KeyboardAndroidPlugin : FlutterPlugin, MethodCallHandler {
         }
     }
 
-
+    /**
+     * 开启或关闭悬浮窗
+     */
     private fun enableFloatingWindow(enable: Boolean) {
-
         if (enable) {
-            startFloatingWindowService()
+            FloatingButtonService.start(context)
         } else {
-            closeFloatingWindowService()
+            FloatingButtonService.stop(context)
         }
-
-
     }
 
-
     override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
         channel.setMethodCallHandler(null)
     }
-
-    // **新增方法:返回 MethodChannel**
-    fun getMethodChannel(): MethodChannel {
-        return channel
-    }
-
-    //    关闭悬浮窗服务
-    private fun closeFloatingWindowService() {
-        Log.d(TAG, "closeFloatingWindowService: ")
-        val serviceIntent = Intent(context, FloatingButtonService::class.java)
-        context.stopService(serviceIntent)
-    }
-
-    //    打开悬浮窗服务
-    private fun startFloatingWindowService() {
-        Log.d(TAG, "startFloatingWindowService: ")
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {
-            val intent = Intent(
-                Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
-                Uri.parse("package:${context.packageName}")
-            )
-            // **重要:在非 Activity 里启动 Activity 需要添加 FLAG_ACTIVITY_NEW_TASK**
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            context.startActivity(intent)
-
-        } else {
-
-            val serviceIntent = Intent(context, FloatingButtonService::class.java)
-            context.startService(serviceIntent)
-        }
-    }
-
-    //    打开输入法设置
-    private fun openInputMethodSettings() {
-        Log.d(TAG, "openInputMethodSettings: ")
-        val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        context.startActivity(intent)
-    }
-
-
-    //    判断指定的输入法是否启用
-    private fun isTargetKeyboardEnabled(): Boolean {
-        Log.d(TAG, "isTargetKeyboardEnabled: ")
-        val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
-        val enabledInputMethods = imm.enabledInputMethodList
-        return enabledInputMethods.any { it.packageName == context.packageName }
-    }
-
-}
+}

+ 58 - 31
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/FloatingButtonService.kt

@@ -1,39 +1,71 @@
-package com.atmob.keyboard_android
+package com.atmob.keyboard_android.floating
 
 import android.annotation.SuppressLint
 import android.app.Service
 import android.content.Context
 import android.content.Intent
 import android.graphics.PixelFormat
+import android.net.Uri
 import android.os.Build
 import android.os.IBinder
-import android.util.Log
+import android.provider.Settings
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
 import android.view.WindowManager
-import android.view.inputmethod.InputMethodManager
 import android.widget.ImageView
-
-import android.app.ActivityOptions
-import android.app.Activity
-
+import com.atmob.keyboard_android.R
+import com.atmob.keyboard_android.keyboard.InputMethodPickerActivity
+import com.atmob.keyboard_android.util.LogUtil
+import kotlin.math.abs
+
+/**
+ * 悬浮窗按钮服务
+ */
 class FloatingButtonService : Service() {
-    private val TAG = "qqq FloatingButtonService"
     private lateinit var windowManager: WindowManager
     private lateinit var floatingView: View
     private lateinit var layoutParams: WindowManager.LayoutParams
 
+    companion object {
+        /**
+         * 启动服务
+         */
+        fun start(context: Context) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {
+                val intent = Intent(
+                    Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+                    Uri.parse("package:${context.packageName}")
+                )
+                // 重要:在非 Activity 里启动 Activity 需要添加 FLAG_ACTIVITY_NEW_TASK
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                context.startActivity(intent)
+            } else {
+                val serviceIntent = Intent(context, FloatingButtonService::class.java)
+                context.startService(serviceIntent)
+            }
+        }
+
+        /**
+         * 停止服务
+         */
+        fun stop(context: Context) {
+            val serviceIntent = Intent(context, FloatingButtonService::class.java)
+            context.stopService(serviceIntent)
+        }
+    }
+
     @SuppressLint("ClickableViewAccessibility")
     override fun onCreate() {
         super.onCreate()
-        Log.d(TAG, "onCreate: ")
+        LogUtil.d("onCreate: ")
         // 初始化 WindowManager
         windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
 
         // 加载悬浮窗布局
-        floatingView = LayoutInflater.from(this).inflate(R.layout.floating_button_layout, null)
+        floatingView =
+            LayoutInflater.from(this).inflate(R.layout.floating_button_layout, null, false)
 
         // 悬浮窗参数
         layoutParams = WindowManager.LayoutParams(
@@ -46,17 +78,16 @@ class FloatingButtonService : Service() {
             WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                     WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                     WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
-
-
-                    PixelFormat.TRANSLUCENT
+            PixelFormat.TRANSLUCENT
         )
         layoutParams.gravity = Gravity.TOP or Gravity.START
 
         val screenWidth = resources.displayMetrics.widthPixels
         val screenHeight = resources.displayMetrics.heightPixels
-        layoutParams.x = screenWidth - 200 // 初始位置在屏幕右侧
-        layoutParams.y = screenHeight / 2  // 初始位置在屏幕中间
-
+        // 初始位置在屏幕右侧
+        layoutParams.x = screenWidth - 200
+        // 初始位置在屏幕中间
+        layoutParams.y = screenHeight / 2
 
         // 拖动逻辑(带吸附效果)
         val floatingButton = floatingView.findViewById<ImageView>(R.id.floating_button)
@@ -74,15 +105,17 @@ class FloatingButtonService : Service() {
                         initialY = layoutParams.y
                         touchX = event.rawX
                         touchY = event.rawY
-                        isClick = true // 认为是点击事件,后续会判断是否有拖动
+                        // 认为是点击事件,后续会判断是否有拖动
+                        isClick = true
                         return false
                     }
 
                     MotionEvent.ACTION_MOVE -> {
                         val deltaX = (event.rawX - touchX).toInt()
                         val deltaY = (event.rawY - touchY).toInt()
-                        if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
-                            isClick = false // 发生拖动,不是点击
+                        if (abs(deltaX) > 10 || abs(deltaY) > 10) {
+                            // 发生拖动,不是点击
+                            isClick = false
                         }
                         layoutParams.x = initialX + deltaX
                         layoutParams.y = initialY + deltaY
@@ -92,7 +125,8 @@ class FloatingButtonService : Service() {
 
                     MotionEvent.ACTION_UP -> {
                         if (isClick) {
-                            v.performClick() // 触发点击事件
+                            // 触发点击事件
+                            v.performClick()
                         } else {
                             snapToEdge()
                         }
@@ -103,10 +137,11 @@ class FloatingButtonService : Service() {
             }
         })
 
-        // **点击按钮 -> 弹出输入法选择框**
+        // 点击按钮,弹出输入法选择框
         floatingButton.setOnClickListener {
             showInputMethodPicker()
         }
+
         // 添加悬浮窗到屏幕
         windowManager.addView(floatingView, layoutParams)
     }
@@ -115,12 +150,7 @@ class FloatingButtonService : Service() {
      * 显示输入法选择框
      */
     private fun showInputMethodPicker() {
-        Log.d(TAG, "showInputMethodPicker: ")
-        val intent = Intent(this, InputMethodPickerActivity::class.java)
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
-
-        val options = ActivityOptions.makeCustomAnimation(this, 0, 0)
-        startActivity(intent, options.toBundle())
+        InputMethodPickerActivity.start(this)
     }
 
     /**
@@ -128,12 +158,11 @@ class FloatingButtonService : Service() {
      */
     private fun snapToEdge() {
         val screenWidth = resources.displayMetrics.widthPixels
-        val middle = screenWidth / 2
+        val middle = screenWidth / 2f
         layoutParams.x = if (layoutParams.x < middle) 0 else screenWidth - floatingView.width
         windowManager.updateViewLayout(floatingView, layoutParams)
     }
 
-
     override fun onDestroy() {
         super.onDestroy()
         windowManager.removeView(floatingView)
@@ -142,6 +171,4 @@ class FloatingButtonService : Service() {
     override fun onBind(intent: Intent?): IBinder? {
         return null
     }
-
-
 }

+ 67 - 54
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/CustomKeyboardService.kt

@@ -1,45 +1,51 @@
-package com.atmob.keyboard_android
+package com.atmob.keyboard_android.keyboard
 
+import android.annotation.SuppressLint
 import android.content.ClipboardManager
 import android.inputmethodservice.InputMethodService
-import android.util.Log
 import android.view.View
+import android.view.inputmethod.EditorInfo
 import android.widget.Button
 import android.widget.EditText
 import android.widget.GridLayout
 import android.widget.Toast
-import android.view.inputmethod.EditorInfo
-import io.flutter.embedding.engine.FlutterEngine
+import com.atmob.keyboard_android.R
+import com.atmob.keyboard_android.util.LogUtil
 import io.flutter.embedding.engine.FlutterEngineCache
 import io.flutter.plugin.common.MethodCall
 import io.flutter.plugin.common.MethodChannel
-import io.flutter.plugin.common.EventChannel
-
-import com.atmob.keyboard_android.KeyboardAndroidPlugin
 
+/**
+ * 自定义键盘的输入法服务
+ */
 class CustomKeyboardService : InputMethodService() {
+    /**
+     * 用于与 Flutter 端通信的 MethodChannel
+     */
+    private lateinit var mMethodChannel: MethodChannel
 
-    private val TAG = "qqq CustomKeyboardService"
-
-    private lateinit var methodChannel: MethodChannel // 用于与 Flutter 端通信的 MethodChannel
-
-    private var keyboardView2: View? = null // 保存键盘视图
-
-    private var keyMappings: List<Pair<String, String>> = listOf()  // 存储按键映射
+    /**
+     * 保存键盘视图
+     */
+    private var vKeyboardView: View? = null
 
+    /**
+     * 存储按键映射
+     */
+    private var mKeyMappings: List<Pair<String, String>> = listOf()
 
     override fun onCreate() {
         super.onCreate()
-        Log.d(TAG, "输入法服务已启动!")
+        LogUtil.d("输入法服务已启动!")
 
         val flutterEngine = FlutterEngineCache.getInstance().get("my_engine_id")
         if (flutterEngine != null) {
-            methodChannel =
+            mMethodChannel =
                 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "keyboard_android")
-            Log.d(TAG, "MethodChannel 初始化成功")
+            LogUtil.d("MethodChannel 初始化成功")
 
             // 设置 MethodChannel 的回调
-            methodChannel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
+            mMethodChannel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
                 when (call.method) {
                     "inputText" -> {
                         val text = call.argument<String>("text")
@@ -53,18 +59,15 @@ class CustomKeyboardService : InputMethodService() {
                     }
                 }
             }
-
         } else {
-            Log.e(TAG, "FlutterEngine 未找到,MethodChannel 无法初始化")
+            LogUtil.e("FlutterEngine 未找到,MethodChannel 无法初始化")
         }
-
-
     }
 
     override fun onCreateInputView(): View {
-        Log.d(TAG, "onCreateInputView!")
+        LogUtil.d("onCreateInputView!")
         val keyboardView = layoutInflater.inflate(R.layout.keyboard_layout, null)
-        keyboardView2 = keyboardView
+        vKeyboardView = keyboardView
 
         // 获取按键映射
         fetchKeyMappings()
@@ -74,31 +77,32 @@ class CustomKeyboardService : InputMethodService() {
 
     override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
         super.onStartInputView(info, restarting)
-        Log.d(TAG, "onStartInputView: 重新加载键盘数据")
-        fetchKeyMappings() // 重新获取按键映射
+        LogUtil.d("onStartInputView: 重新加载键盘数据")
+        // 重新获取按键映射
+        fetchKeyMappings()
     }
 
     /**通过 KeyboardAndroidPlugin 获取按键映射**/
     private fun fetchKeyMappings() {
         // 通过 methodChannel 获取按键映射
-        methodChannel.invokeMethod("getKeyMappings", null, object : MethodChannel.Result {
+        mMethodChannel.invokeMethod("getKeyMappings", null, object : MethodChannel.Result {
             override fun success(result: Any?) {
                 // 如果获取成功,处理键映射
                 if (result is List<*>) {
-                    keyMappings = result.filterIsInstance<Map<String, String>>().map {
+                    mKeyMappings = result.filterIsInstance<Map<String, String>>().map {
                         it["label"]!! to it["method"]!!
                     }
-                    Log.d(TAG, "按键映射获取成功: $keyMappings")
-                    setupKeyboardButtons(keyboardView2!!)
+                    LogUtil.d("按键映射获取成功: $mKeyMappings")
+                    setupKeyboardButtons(vKeyboardView!!)
                 }
             }
 
             override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
-                Log.e(TAG, "获取按键映射失败: $errorMessage")
+                LogUtil.d("获取按键映射失败: $errorMessage")
             }
 
             override fun notImplemented() {
-                Log.e(TAG, "Flutter 端未实现 getKeyMappings 方法")
+                LogUtil.d("Flutter 端未实现 getKeyMappings 方法")
             }
         })
     }
@@ -106,12 +110,12 @@ class CustomKeyboardService : InputMethodService() {
 
     /**
      * 设置键盘按钮
-     * @param keyboardView 键盘视图
-     * @param keyMappings 按键映射
      *
+     * @param keyboardView 键盘视图
      */
+    @SuppressLint("ClickableViewAccessibility")
     private fun setupKeyboardButtons(keyboardView: View) {
-        Log.d(TAG, "当前键映射: $keyMappings") // 打印映射
+        LogUtil.d("当前键映射: $mKeyMappings") // 打印映射
         val editText = keyboardView.findViewById<EditText>(R.id.keyboard_input)
         val btnPaste = keyboardView.findViewById<Button>(R.id.btn_paste)
         val btnDelete = keyboardView.findViewById<Button>(R.id.btn_delete)
@@ -119,23 +123,32 @@ class CustomKeyboardService : InputMethodService() {
         val btnSend = keyboardView.findViewById<Button>(R.id.btn_send)
         val numberPad = keyboardView.findViewById<GridLayout>(R.id.number_pad)
 
-        editText.setOnTouchListener { _, _ -> true } // 禁止触摸输入
-        numberPad.removeAllViews() // 清空,避免重复创建
-        Log.d(TAG, "setupKeyboardButtons: 添加 ${keyMappings.size} 个按钮")
-        keyMappings.forEach { (label, methodName) ->
+        // 禁止触摸输入
+        editText.setOnTouchListener { _, _ -> true }
+        // 清空,避免重复创建
+        numberPad.removeAllViews()
+
+        LogUtil.d("setupKeyboardButtons: 添加 ${mKeyMappings.size} 个按钮")
+
+        mKeyMappings.forEach { (label, methodName) ->
             val button = Button(this).apply {
                 text = label
                 layoutParams = GridLayout.LayoutParams()
                 setOnClickListener {
-
-                    sendDynamicTextRequest(methodName, editText.text.toString()) // **每次点击时动态获取数据**
+                    // 每次点击时动态获取数据
+                    sendDynamicTextRequest(methodName, editText.text.toString())
                 }
             }
             numberPad.addView(button)
         }
 
-        btnPaste.setOnClickListener { pasteText(editText) }
-        btnDelete.setOnClickListener { currentInputConnection.deleteSurroundingText(1, 0) }
+        btnPaste.setOnClickListener {
+            pasteText(editText)
+        }
+
+        btnDelete.setOnClickListener {
+            currentInputConnection.deleteSurroundingText(1, 0)
+        }
         btnClear.setOnClickListener {
             currentInputConnection.deleteSurroundingText(
                 Int.MAX_VALUE,
@@ -147,31 +160,33 @@ class CustomKeyboardService : InputMethodService() {
 
 
     /**
-     * **动态调用 Flutter 获取文本**
+     * 动态调用 Flutter 获取文本
+     *
      * @param methodName 方法名
      * @param currentContent 当前输入连接
      */
     private fun sendDynamicTextRequest(methodName: String, currentContent: String) {
-        methodChannel.invokeMethod(
+        mMethodChannel.invokeMethod(
             "sendDynamicTextRequest",
             mapOf("method" to methodName, "currentContent" to currentContent),
             object : MethodChannel.Result {
                 override fun success(result: Any?) {
-                    Log.d(TAG, "sendDynamicTextRequest 请求已发送")
+                    LogUtil.d("sendDynamicTextRequest 请求已发送")
                 }
 
                 override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
-                    Log.e(TAG, "获取动态文本失败: $errorMessage")
+                    LogUtil.d("获取动态文本失败: $errorMessage")
                 }
 
                 override fun notImplemented() {
-                    Log.e(TAG, "Flutter 端未实现 sendDynamicTextRequest 方法")
+                    LogUtil.d("Flutter 端未实现 sendDynamicTextRequest 方法")
                 }
             })
     }
 
-
-    //    剪贴板粘贴
+    /**
+     * 剪贴板粘贴
+     */
     private fun pasteText(editText: EditText) {
         val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
         val clipData = clipboard.primaryClip
@@ -182,10 +197,8 @@ class CustomKeyboardService : InputMethodService() {
         }
     }
 
-    public fun inputText(text: String) {
-
-        Log.d(TAG, "inputText: $text")
+    private fun inputText(text: String) {
+        LogUtil.d("inputText: $text")
         currentInputConnection.commitText(text, 1)
     }
-
 }

+ 80 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/keyboard/InputMethodPickerActivity.kt

@@ -0,0 +1,80 @@
+package  com.atmob.keyboard_android.keyboard
+
+import android.app.Activity
+import android.app.ActivityOptions
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.inputmethod.InputMethodManager
+import com.atmob.keyboard_android.util.LogUtil
+
+/**
+ * 选择输入法的页面
+ */
+class InputMethodPickerActivity : Activity() {
+    private var isFirst = true
+
+    private val mMainHandler = Handler(Looper.getMainLooper())
+
+    companion object {
+        /**
+         * 启动
+         */
+        fun start(context: Context) {
+            val intent = Intent(context, InputMethodPickerActivity::class.java)
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+            val options = ActivityOptions.makeCustomAnimation(context, 0, 0)
+            context.startActivity(intent, options.toBundle())
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        // 设置无动画启动
+        overridePendingTransition(0, 0)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mMainHandler.removeCallbacksAndMessages(null)
+    }
+
+    override fun onWindowFocusChanged(hasFocus: Boolean) {
+        super.onWindowFocusChanged(hasFocus)
+        LogUtil.d("onWindowFocusChanged focus=$hasFocus, first=$isFirst")
+
+        if (!hasFocus) {
+            return
+        }
+
+        if (isFirst) {
+            isFirst = false
+            //为了确保 Activity 完全进入前台后再弹出输入法选择器
+            mMainHandler.postDelayed({
+                LogUtil.d("showInputMethodPicker triggered")
+                showInputMethodPicker()
+            }, 100)
+        } else {
+            // 第二次获得焦点代表输入法弹窗消失
+            LogUtil.d("InputMethodPicker dismissed, finishing activity")
+            finish()
+        }
+    }
+
+    /**
+     * 显示输入法选择器
+     */
+    private fun showInputMethodPicker() {
+        val inputMethodManager =
+            getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        inputMethodManager.showInputMethodPicker()
+    }
+
+    override fun finish() {
+        // 关闭时也不使用动画
+        overridePendingTransition(0, 0)
+        super.finish()
+    }
+}

+ 31 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/InputMethodUtil.kt

@@ -0,0 +1,31 @@
+package com.atmob.keyboard_android.util
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.inputmethod.InputMethodManager
+
+/**
+ * 输入法工具类
+ */
+class InputMethodUtil private constructor() {
+    companion object {
+        /**
+         * 打开输入法设置
+         */
+        fun openInputMethodSettings(context: Context) {
+            val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            context.startActivity(intent)
+        }
+
+        /**
+         * 判断指定的输入法是否启用
+         */
+        fun isTargetKeyboardEnabled(context: Context): Boolean {
+            val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+            val enabledInputMethods = imm.enabledInputMethodList
+            return enabledInputMethods.any { it.packageName == context.packageName }
+        }
+    }
+}

+ 26 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/LogUtil.kt

@@ -0,0 +1,26 @@
+package com.atmob.keyboard_android.util
+
+import android.util.Log
+
+/**
+ * Log工具类
+ */
+class LogUtil private constructor() {
+    companion object {
+        private const val TAG = "KeyboardAndroid"
+
+        /**
+         * 输出Debug日志
+         */
+        fun d(msg: String) {
+            Log.d(TAG, msg)
+        }
+
+        /**
+         * 输出Error日志
+         */
+        fun e(msg: String) {
+            Log.e(TAG, msg)
+        }
+    }
+}

+ 3 - 3
plugins/keyboard_android/android/src/main/res/layout/floating_button_layout.xml

@@ -7,8 +7,8 @@
         android:id="@+id/floating_button"
         android:layout_width="60dp"
         android:layout_height="60dp"
-        android:src="@android:drawable/ic_menu_manage"
         android:background="@android:color/holo_blue_light"
+        android:padding="10dp"
         android:scaleType="centerInside"
-        android:padding="10dp"/>
-</FrameLayout>
+        android:src="@android:drawable/ic_menu_manage" />
+</FrameLayout>

+ 25 - 10
plugins/keyboard_android/android/src/main/res/layout/keyboard_layout.xml

@@ -31,11 +31,10 @@
             android:layout_height="wrap_content"
             android:layout_weight="3"
             android:columnCount="3"
-            android:rowCount="4"
-            android:padding="5dp">
+            android:padding="5dp"
+            android:rowCount="4">
 
             <!-- 这里原有的静态按钮定义已全部删除 -->
-
         </GridLayout>
 
         <!-- 操作按钮 -->
@@ -45,13 +44,29 @@
             android:layout_weight="1"
             android:orientation="vertical">
 
-            <Button android:id="@+id/btn_paste" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="粘贴"/>
-            <Button android:id="@+id/btn_delete" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="删除"/>
-            <Button android:id="@+id/btn_clear" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="清空"/>
-            <Button android:id="@+id/btn_send" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="发送"/>
+            <Button
+                android:id="@+id/btn_paste"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="粘贴" />
 
-        </LinearLayout>
+            <Button
+                android:id="@+id/btn_delete"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="删除" />
 
-    </LinearLayout>
+            <Button
+                android:id="@+id/btn_clear"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="清空" />
 
-</LinearLayout>
+            <Button
+                android:id="@+id/btn_send"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="发送" />
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>

+ 1 - 2
plugins/keyboard_android/android/src/main/res/xml/keyboard_method.xml

@@ -1,4 +1,3 @@
 <input-method xmlns:android="http://schemas.android.com/apk/res/android"
-    android:settingsActivity=".KeyboardSettingsActivity"
     android:isDefault="true"
-    />
+    android:settingsActivity=".KeyboardSettingsActivity" />