fix(输入法): 消除RTOS和Android文字键盘间距
这个提交包含在:
1 父节点
87aa966a51
当前提交
bce6f8083b
27 个文件已更改
+406
-253
没有与搜索条件相匹配的文件
+5
-3
@@ -1,17 +1,19 @@
|
||||
# 蟹蟹输入 Android应用内键盘组件
|
||||
|
||||
基于CrabKeyboard MIT词库,由herry重写的Android手表原生键盘组件,1.2.0 / 10200。提供可集成的 **AAR**、演示APK和独立接入示例;适用Android 8.1 / API 27及以上。
|
||||
基于CrabKeyboard MIT词库,由herry重写的Android手表原生键盘组件,1.2.2 / 10202。提供可集成的 **AAR**、演示APK和独立接入示例;适用Android 8.1 / API 27及以上。
|
||||
|
||||
应用接入后,点击自己的输入框自动展开内置键盘。**不注册系统输入法、不要求用户切换输入法、不改变系统默认输入法。** 圆屏按每行所在高度计算安全弦宽;4×4完整候选,单字16个一页,词组扩宽或增加行高,候选不显示拼音。九宫、标准QWERTY全拼/英文、14键、数字、三页符号、Unicode删除、按住连删及光标编辑。
|
||||
|
||||
完全离线,无网络或外部存储权限;组件不持久化输入内容,不启用学习。密码输入默认英文并遮蔽预览。页面隐藏/销毁时清除组件会话与候选,宿主输入框中的内容仍由宿主决定如何处理。异步查询按会话序号隔离,旧结果不能回填新的输入框。
|
||||
|
||||
1.2.2 去掉英文、数字、符号模式未使用的候选栏空位,文字与键盘紧密相接。本版真机间距检查通过;完整测试的限制见[验收记录](./VALIDATION_1.2.2.md)。
|
||||
|
||||
## 一分钟接入
|
||||
|
||||
将发行版`crab-keyboard-1.2.0.aar`放入宿主`app/libs/`:
|
||||
将发行版`crab-keyboard-1.2.2.aar`放入宿主`app/libs/`:
|
||||
|
||||
```groovy
|
||||
dependencies { implementation files('libs/crab-keyboard-1.2.0.aar') }
|
||||
dependencies { implementation files('libs/crab-keyboard-1.2.2.aar') }
|
||||
```
|
||||
|
||||
```java
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Android 输入组件 1.2.2 间距修复
|
||||
|
||||
2026-11-14,演示包 `com.sydxky.crabinput`,版本 `1.2.2 / 10202`;最低API27。
|
||||
|
||||
英文、数字、符号模式现在由编码标题直接定位首排按键,不再先分配候选区域再局部抵消高度,去掉12逻辑像素的空位。中文候选栏与九宫、全拼、14键行为保持原逻辑。
|
||||
|
||||
4项Java单元测试通过;组件及演示Release构建和lint通过(组件无问题,演示0错误/1警告)。全量词库104,837组索引、163,238候选无损回读与SQLite完整性检查通过。正式演示APK为原生产证书、单签名者、APK v2有效,AAR不需要应用签名。
|
||||
|
||||
实体OWW261,Android11/API30、466×466。UiAutomator新增的 `directModesHaveNoUnusedCandidateGap` 已通过:实际打开内置键盘,分别点击英文、数字、符号模式,测得编码标题到首排按键间距在0–5物理像素内,预览到编码标题在0–4像素内;同场检查全部可见按钮的圆屏安全边界,并保留三张真机截图。
|
||||
|
||||
整组9项测试为6项通过、3项未通过。通过项包括新间距检查、全量词库、安全输入模式、14键词组、全拼连续输入、完整候选分页。另三项(Unicode/长按删除、独立宿主接入、九宫词组)执行期间,同一手表被另一任务切换到弹球界面,造成找不到目标输入框;未将它们记为通过。按用户明确选择“仅完成现有验证并发布”,停止争用设备且未重跑。测试前后系统默认输入法一致。
|
||||
|
||||
本次没有领取账号奖励、执行支付或修改其他应用数据。RTOS真机、另一款方表、长期性能/功耗和商店审核未验收。
|
||||
+2
-2
@@ -12,8 +12,8 @@ android {
|
||||
applicationId 'com.sydxky.crabinput'
|
||||
minSdk 27
|
||||
targetSdk 35
|
||||
versionCode 10200
|
||||
versionName '1.2.0'
|
||||
versionCode 10202
|
||||
versionName '1.2.2'
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
signingConfigs {
|
||||
|
||||
@@ -113,4 +113,24 @@ public class ImeDeviceTest {
|
||||
UiObject2 delete=device.wait(Until.findObject(By.desc("删除")),5000);Rect bounds=delete.getVisibleBounds();device.swipe(bounds.centerX(),bounds.centerY(),bounds.centerX(),bounds.centerY(),160);SystemClock.sleep(150);action("收起键盘");
|
||||
int count=device.findObject(By.desc("demo_text")).getText().length();assertTrue("Hold repeats deletion",count<14);SystemClock.sleep(300);assertEquals("Release stops repeat",count,device.findObject(By.desc("demo_text")).getText().length());
|
||||
}
|
||||
@Test public void directModesHaveNoUnusedCandidateGap() throws Exception {
|
||||
editor("demo_text");
|
||||
Context context=InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
String[][] modes={{"abc","英文","q","english"},{"123","数字","1","number"},{"符号","符号 · 1/3 · 再点符号翻页",",","symbols"}};
|
||||
for(String[] mode:modes){
|
||||
tap(mode[0]);
|
||||
UiObject2 caption=device.findObject(By.pkg(PKG).text(mode[1]));
|
||||
UiObject2 first=device.findObject(By.pkg(PKG).text(mode[2]));
|
||||
UiObject2 preview=device.findObject(By.pkg(PKG).desc("编辑光标"));
|
||||
assertNotNull(caption);assertNotNull(first);assertNotNull(preview);
|
||||
Rect captionBounds=caption.getVisibleBounds(),keyBounds=first.getVisibleBounds();
|
||||
int gap=keyBounds.top-captionBounds.bottom;
|
||||
assertTrue("No blank candidate band: "+mode[3]+" gap="+gap,gap>=0&&gap<=Math.ceil(device.getDisplayHeight()*4/410f));
|
||||
int previewGap=captionBounds.top-preview.getVisibleBounds().bottom;
|
||||
assertTrue("Preview joins composition",previewGap>=0&&previewGap<=Math.ceil(device.getDisplayHeight()*3/410f));
|
||||
for(UiObject2 button:device.findObjects(By.pkg(PKG).clazz("android.widget.Button")))assertRoundSafe(button.getVisibleBounds());
|
||||
assertTrue(device.takeScreenshot(new java.io.File(context.getExternalFilesDir(null),"gap-"+mode[3]+".png")));
|
||||
}
|
||||
action("收起键盘");
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ plugins { id 'com.android.application' }
|
||||
android {
|
||||
namespace 'com.sydxky.crabinput.host'
|
||||
compileSdk 37
|
||||
defaultConfig { applicationId 'com.sydxky.crabinput.host'; minSdk 27; targetSdk 35; versionCode 10200; versionName '1.2.0' }
|
||||
defaultConfig { applicationId 'com.sydxky.crabinput.host'; minSdk 27; targetSdk 35; versionCode 10202; versionName '1.2.2' }
|
||||
compileOptions { sourceCompatibility JavaVersion.VERSION_11; targetCompatibility JavaVersion.VERSION_11 }
|
||||
}
|
||||
dependencies { implementation project(':keyboard') }
|
||||
@@ -110,7 +110,9 @@ final class KeyboardSurface extends ViewGroup {
|
||||
if(count>0)action("⌄","expand",410-left-35,candidateY,33,32,24,false);
|
||||
else place(label(state.busy?"正在查词…":state.message,12,MUTED),left,candidateY,available,32);
|
||||
}
|
||||
float keyY=candidateY+38;
|
||||
// A direct-entry mode has no candidate strip; attach its keys to the
|
||||
// composition caption instead of reserving an invisible suggestion row.
|
||||
float keyY=isChinese()?candidateY+38:compositionY+25;
|
||||
String[][] captions,values;
|
||||
switch(state.mode) {
|
||||
case "t9":captions=new String[][]{{"标点","ABC","DEF"},{"GHI","JKL","MNO"},{"PQRS","TUV","WXYZ"}};values=new String[][]{{"mode:sym","key:2","key:3"},{"key:4","key:5","key:6"},{"key:7","key:8","key:9"}};break;
|
||||
@@ -121,7 +123,7 @@ final class KeyboardSurface extends ViewGroup {
|
||||
}
|
||||
float keyHeight=captions.length==4?36:49;
|
||||
// Hide an unused suggestion strip in direct entry modes to enlarge the keys.
|
||||
if(!isChinese()){keyY-=27;keyHeight=captions.length==4?43:58;}
|
||||
if(!isChinese()){keyHeight=captions.length==4?43:58;}
|
||||
for(int i=0;i<captions.length;i++)row(captions[i],values[i],keyY+i*(keyHeight+4),keyHeight,captions[i].length>=7?21:24);
|
||||
float modeY=keyY+captions.length*(keyHeight+4)+3;
|
||||
row(new String[]{"九宫","26键","14键",state.upper?"ABC":"abc","123","符号"},new String[]{"mode:t9","mode:py","mode:14","mode:en","mode:num","mode:sym"},modeY,27,13);
|
||||
|
||||
@@ -28,8 +28,8 @@ def main():
|
||||
command=['adb','-s',args.serial,'shell','am','instrument','-w','-r','com.sydxky.crabinput.test/androidx.test.runner.AndroidJUnitRunner']
|
||||
result=subprocess.run(command,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,timeout=240)
|
||||
output=result.stdout.decode('utf-8',errors='replace');(args.output/'instrumentation.txt').write_text(output,encoding='utf-8');print(output,flush=True)
|
||||
passed=result.returncode==0 and 'OK (8 tests)'in output and 'FAILURES!!!'not in output
|
||||
report={'model':model,'sdk':sdk,'version':'1.2.0','apk_sha256':hashlib.sha256(apk.read_bytes()).hexdigest(),'instrumentation_tests':8,'embedded_component':True,'system_ime_unchanged':adb('shell','settings','get','secure','default_input_method')==before,'passed':passed}
|
||||
passed=result.returncode==0 and 'OK (9 tests)'in output and 'FAILURES!!!'not in output
|
||||
report={'model':model,'sdk':sdk,'version':'1.2.2','apk_sha256':hashlib.sha256(apk.read_bytes()).hexdigest(),'instrumentation_tests':9,'embedded_component':True,'system_ime_unchanged':adb('shell','settings','get','secure','default_input_method')==before,'passed':passed}
|
||||
(args.output/'device-validation.json').write_text(json.dumps(report,indent=2)+'\n',encoding='utf-8')
|
||||
assert passed and report['system_ime_unchanged'],'See instrumentation.txt; failed/unfinished cases are not accepted'
|
||||
adb('shell','am','start','-n','com.sydxky.crabinput/.MainActivity');time.sleep(.5)
|
||||
|
||||
在新工单中引用
屏蔽一个用户