38 public static final int PAGE_SPECIAL = 0;
39 public static final int PAGE_NUM = 1;
43 void onKey(
int keycode);
45 void onKeyLock(
int keycode);
47 int getModifierState(
int keycode);
49 void onExpandedChanged(
boolean expanded);
53 private static final class ModifierKey
55 private final View key;
56 private final View led;
58 ModifierKey(View key, View led)
64 void setState(
boolean on,
boolean locked)
66 key.setActivated(on || locked);
67 led.setActivated(on || locked);
68 led.setSelected(locked);
72 private Listener listener;
75 private TextView expandKey;
76 private final View[] pages =
new View[2];
77 private final SparseArray<ModifierKey> modifierKeys =
new SparseArray<>();
78 private final Handler repeatHandler =
new Handler(Looper.getMainLooper());
79 private int currentPage = PAGE_SPECIAL;
80 private boolean expanded =
false;
81 private int insetLeft = 0;
82 private int insetRight = 0;
83 private int insetBottom = 0;
85 public ExtendedKeyboardView(Context context)
90 public ExtendedKeyboardView(Context context, AttributeSet attrs)
92 super(context, attrs);
93 setOrientation(VERTICAL);
97 public void setListener(Listener l)
102 private int code(
int resId)
104 return getResources().getInteger(resId);
107 private int px(
int dimenId)
109 return getResources().getDimensionPixelSize(dimenId);
115 addView(bar,
new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
116 LayoutParams.WRAP_CONTENT));
118 panel = buildPanel();
119 panel.setVisibility(GONE);
120 addView(panel,
new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
121 LayoutParams.WRAP_CONTENT));
124 private View buildBar()
126 LinearLayout row =
new LinearLayout(getContext());
127 row.setOrientation(HORIZONTAL);
128 row.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.kbd_bar_bg));
129 int barPad = px(R.dimen.kbd_bar_pad);
130 row.setPadding(barPad, barPad, barPad, barPad);
132 int[] codes = { R.integer.keycode_esc, R.integer.keycode_tab,
133 R.integer.keycode_toggle_shift, R.integer.keycode_toggle_ctrl,
134 R.integer.keycode_toggle_win, R.integer.keycode_toggle_alt,
135 R.integer.keycode_delete };
136 String[] labels = {
"Esc",
"Tab",
"Shift",
"Ctrl",
"Win",
"Alt",
"Del" };
137 boolean[] mods = {
false,
false,
true,
true,
true,
true,
false };
139 for (
int i = 0; i < codes.length; i++)
141 int keycode = code(codes[i]);
144 codes[i] == R.integer.keycode_toggle_win
145 ? makeIconKey(keycode, R.drawable.ic_win_logo,
146 getContext().getString(R.string.kbd_win_key), !mods[i], 1.0f)
147 : makeKey(keycode, labels[i], !mods[i], 1.0f);
148 row.addView(mods[i] ? withIndicator(key, keycode) : key);
151 expandKey = makeKey(0,
"▼",
false, 1.0f);
152 expandKey.setContentDescription(getContext().getString(R.string.menu_ext_keyboard));
153 expandKey.setOnClickListener(v -> setExpanded(!expanded));
154 tintAsChrome(expandKey);
155 row.addView(expandKey);
160 private View buildPanel()
162 LinearLayout p =
new LinearLayout(getContext());
163 p.setOrientation(VERTICAL);
164 p.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.kbd_panel_bg));
166 FrameLayout pageHost =
new FrameLayout(getContext());
167 pages[PAGE_SPECIAL] = buildSpecialPage();
168 pages[PAGE_NUM] = buildNumPage();
169 for (View pg : pages)
170 pageHost.addView(pg, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
171 LayoutParams.WRAP_CONTENT));
172 p.addView(pageHost,
new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
173 LayoutParams.WRAP_CONTENT));
175 selectPage(PAGE_SPECIAL);
179 private View buildSpecialPage()
181 LinearLayout page =
new LinearLayout(getContext());
182 page.setOrientation(VERTICAL);
184 gridRow(
new int[] { R.integer.keycode_F1, R.integer.keycode_F2, R.integer.keycode_F3,
185 R.integer.keycode_F4, R.integer.keycode_F5, R.integer.keycode_F6 },
186 new String[] {
"F1",
"F2",
"F3",
"F4",
"F5",
"F6" }));
187 page.addView(gridRow(
new int[] { R.integer.keycode_F7, R.integer.keycode_F8,
188 R.integer.keycode_F9, R.integer.keycode_F10,
189 R.integer.keycode_F11, R.integer.keycode_F12 },
190 new String[] {
"F7",
"F8",
"F9",
"F10",
"F11",
"F12" }));
191 page.addView(gridRow(
new int[] { R.integer.keycode_insert, R.integer.keycode_home,
192 R.integer.keycode_print, R.integer.keycode_pgup,
193 R.integer.keycode_up, R.integer.keycode_pgdn },
194 new String[] {
"Ins",
"Home",
"PrtSc",
"PgUp",
"↑",
"PgDn" }));
198 gridRow(
new int[] { R.integer.keycode_numpad_keyboard, R.integer.keycode_end,
199 R.integer.keycode_menu, R.integer.keycode_left,
200 R.integer.keycode_down, R.integer.keycode_right },
201 new String[] {
"⇆ 123",
"End",
"Menu",
"←",
"↓",
"→" });
202 tintAsChrome((TextView)row4.getChildAt(0));
207 private View buildNumPage()
209 LinearLayout page =
new LinearLayout(getContext());
210 page.setOrientation(VERTICAL);
211 page.addView(gridRow(
212 new int[] { R.integer.keycode_numpad_left_paren, R.integer.keycode_numpad_right_paren,
213 R.integer.keycode_numpad_7, R.integer.keycode_numpad_8,
214 R.integer.keycode_numpad_9, R.integer.keycode_numpad_subtract },
215 new String[] {
"(",
")",
"7",
"8",
"9",
"−" }));
217 gridRow(
new int[] { R.integer.keycode_numpad_divide, R.integer.keycode_numpad_multiply,
218 R.integer.keycode_numpad_4, R.integer.keycode_numpad_5,
219 R.integer.keycode_numpad_6, R.integer.keycode_numpad_add },
220 new String[] {
"/",
"*",
"4",
"5",
"6",
"+" }));
221 page.addView(gridRow(
new int[] { R.integer.keycode_numpad_equals, R.integer.keycode_comma,
222 R.integer.keycode_numpad_1, R.integer.keycode_numpad_2,
223 R.integer.keycode_numpad_3, R.integer.keycode_backspace },
224 new String[] {
"=",
",",
"1",
"2",
"3",
"⌫" }));
225 int gap = px(R.dimen.kbd_key_gap);
227 gridRow(
new int[] { R.integer.keycode_specialkeys_keyboard, R.integer.keycode_numpad_0,
228 R.integer.keycode_numpad_comma, R.integer.keycode_numpad_enter },
229 new String[] {
"⇆ Fn",
"0",
".",
"↵" },
new float[] { 1.0f, 3.0f, 1.0f, 1.0f },
230 new int[] { 0, 4 * gap, 0, 0 });
231 tintAsChrome((TextView)row4.getChildAt(0));
238 private void tintAsChrome(TextView k)
240 k.setBackgroundTintList(
241 ColorStateList.valueOf(ContextCompat.getColor(getContext(), R.color.kbd_key_chrome)));
242 k.setTextColor(ContextCompat.getColor(getContext(), R.color.kbd_key_chrome_text));
245 private LinearLayout gridRow(
int[] codeRes, String[] labels)
247 float[] weights =
new float[codeRes.length];
248 for (
int i = 0; i < weights.length; i++)
250 return gridRow(codeRes, labels, weights,
new int[codeRes.length]);
254 private LinearLayout gridRow(
int[] codeRes, String[] labels,
float[] weights,
int[] baseWidths)
256 LinearLayout row =
new LinearLayout(getContext());
257 row.setOrientation(HORIZONTAL);
258 for (
int i = 0; i < codeRes.length; i++)
260 TextView k = makeKey(code(codeRes[i]), labels[i],
true, weights[i]);
261 k.getLayoutParams().width = baseWidths[i];
267 private TextView makeKey(
int keycode, String label,
boolean repeatable,
float weight)
269 TextView k =
new TextView(getContext());
271 k.setGravity(Gravity.CENTER);
272 k.setTextColor(ContextCompat.getColor(getContext(), R.color.kbd_text));
273 k.setTextSize(TypedValue.COMPLEX_UNIT_PX,
274 getResources().getDimension(R.dimen.kbd_key_text));
275 styleKey(k, keycode, repeatable, weight);
279 private ImageView makeIconKey(
int keycode,
int iconRes, String description,
boolean repeatable,
282 ImageView k =
new ImageView(getContext());
283 k.setImageResource(iconRes);
284 k.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
285 k.setContentDescription(description);
288 ColorStateList.valueOf(ContextCompat.getColor(getContext(), R.color.kbd_text)));
289 styleKey(k, keycode, repeatable, weight);
293 private void styleKey(View k,
int keycode,
boolean repeatable,
float weight)
295 k.setBackgroundResource(R.drawable.bg_kbd_key);
296 k.setBackgroundTintList(
297 ContextCompat.getColorStateList(getContext(), R.color.kbd_key_tint));
298 k.setClickable(
true);
299 k.setFocusable(
true);
300 LinearLayout.LayoutParams lp =
301 new LinearLayout.LayoutParams(0, px(R.dimen.kbd_key_height), weight);
302 int gap = px(R.dimen.kbd_key_gap);
303 lp.setMargins(gap, gap, gap, gap);
304 k.setLayoutParams(lp);
305 k.setOnClickListener(v -> {
306 if (listener !=
null)
307 listener.onKey(keycode);
310 k.setOnLongClickListener(v -> {
311 startRepeat(v, keycode);
317 private View withIndicator(View key,
int keycode)
319 FrameLayout holder =
new FrameLayout(getContext());
320 holder.setLayoutParams(key.getLayoutParams());
322 new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
323 key.setOnLongClickListener(v -> {
324 v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
325 if (listener !=
null)
326 listener.onKeyLock(keycode);
331 View led =
new View(getContext());
332 led.setBackgroundResource(R.drawable.bg_kbd_led);
333 int size = px(R.dimen.kbd_led_size);
334 int inset = px(R.dimen.kbd_led_inset);
335 FrameLayout.LayoutParams lp =
new FrameLayout.LayoutParams(size, size);
336 lp.gravity = Gravity.TOP | Gravity.START;
337 lp.setMargins(inset, inset, 0, 0);
338 holder.addView(led, lp);
340 modifierKeys.put(keycode,
new ModifierKey(key, led));
345 private void startRepeat(View k,
int keycode)
347 repeatHandler.removeCallbacksAndMessages(
null);
348 repeatHandler.post(
new Runnable() {
349 @Override
public void run()
351 if (!k.isPressed() || !isAttachedToWindow())
353 if (listener !=
null)
354 listener.onKey(keycode);
355 repeatHandler.postDelayed(
this, ViewConfiguration.getKeyRepeatDelay());
360 public void selectPage(
int index)
363 for (
int i = 0; i < pages.length; i++)
364 pages[i].setVisibility(i == index ? VISIBLE : GONE);
367 public void setInsets(
int left,
int right,
int bottom)
369 this.insetLeft = left;
370 this.insetRight = right;
371 this.insetBottom = bottom;
375 private void applyInsets()
377 int barPad = px(R.dimen.kbd_bar_pad);
381 int barBottom = expanded ? barPad : barPad + insetBottom;
382 bar.setPadding(insetLeft + barPad, barPad, insetRight + barPad, barBottom);
385 panel.setPadding(insetLeft, 0, insetRight, insetBottom);
388 public void setExpanded(
boolean expand)
390 setExpanded(expand,
true);
393 public void setExpanded(
boolean expand,
boolean notify)
395 if (expanded == expand)
398 panel.setVisibility(expand ? VISIBLE : GONE);
399 expandKey.setText(expand ?
"▲" :
"▼");
401 if (notify && listener !=
null)
402 listener.onExpandedChanged(expand);
405 public boolean isExpanded()
411 public void refreshModifiers()
413 if (listener ==
null)
415 for (
int i = 0; i < modifierKeys.size(); i++)
417 int state = listener.getModifierState(modifierKeys.keyAt(i));
418 modifierKeys.valueAt(i).setState(state == KeyboardMapper.KEYSTATE_ON,
419 state == KeyboardMapper.KEYSTATE_LOCKED);
423 @Override
protected void onConfigurationChanged(android.content.res.Configuration newConfig)
425 super.onConfigurationChanged(newConfig);
428 int page = currentPage;
429 boolean wasExpanded = expanded;
432 modifierKeys.clear();
439 setExpanded(wasExpanded,
false);
444 @Override
protected void onDetachedFromWindow()
446 repeatHandler.removeCallbacksAndMessages(
null);
447 super.onDetachedFromWindow();