adding an initial implementation of MyKeyboard
This commit is contained in:
parent
4459a5b636
commit
1e4906c48d
|
@ -0,0 +1,849 @@
|
|||
/*
|
||||
* Copyright (C) 2008-2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.simplemobiletools.keyboard.helpers
|
||||
|
||||
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.inputmethodservice.Keyboard
|
||||
import android.text.TextUtils
|
||||
import android.util.TypedValue
|
||||
import android.util.Xml
|
||||
import androidx.annotation.XmlRes
|
||||
import com.simplemobiletools.keyboard.R
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
|
||||
* consists of rows of keys.
|
||||
*
|
||||
* The layout file for a keyboard contains XML that looks like the following snippet:
|
||||
* <pre>
|
||||
* <Keyboard
|
||||
* android:keyWidth="%10p"
|
||||
* android:keyHeight="50px"
|
||||
* android:horizontalGap="2px"
|
||||
* android:verticalGap="2px" >
|
||||
* <Row android:keyWidth="32px" >
|
||||
* <Key android:keyLabel="A" />
|
||||
* ...
|
||||
* </Row>
|
||||
* ...
|
||||
* </Keyboard>
|
||||
</pre> *
|
||||
* @attr ref android.R.styleable#Keyboard_keyWidth
|
||||
* @attr ref android.R.styleable#Keyboard_keyHeight
|
||||
* @attr ref android.R.styleable#Keyboard_horizontalGap
|
||||
* @attr ref android.R.styleable#Keyboard_verticalGap
|
||||
*/
|
||||
class MyKeyboard {
|
||||
/** Keyboard label */
|
||||
private val mLabel: CharSequence? = null
|
||||
|
||||
/** Horizontal gap default for all rows */
|
||||
protected var mDefaultHorizontalGap = 0
|
||||
|
||||
/** Default key width */
|
||||
protected var mDefaultWidth = 0
|
||||
|
||||
/** Default key height */
|
||||
protected var mDefaultHeight = 0
|
||||
|
||||
/** Default gap between rows */
|
||||
protected var mDefaultVerticalGap = 0
|
||||
|
||||
/** Is the keyboard in the shifted state */
|
||||
var isShifted = false
|
||||
private set
|
||||
|
||||
/** Key instance for the shift key, if present */
|
||||
private val mShiftKeys = arrayOf<Key?>(null, null)
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
/** Key index for the shift key, if present */
|
||||
val shiftKeyIndices = intArrayOf(-1, -1)
|
||||
|
||||
/** Current key width, while loading the keyboard */
|
||||
private val mKeyWidth = 0
|
||||
|
||||
/** Current key height, while loading the keyboard */
|
||||
private val mKeyHeight = 0
|
||||
/**
|
||||
* Returns the total height of the keyboard
|
||||
* @return the total height of the keyboard
|
||||
*/
|
||||
/** Total height of the keyboard, including the padding and keys */
|
||||
var height = 0
|
||||
private set
|
||||
|
||||
/**
|
||||
* Total width of the keyboard, including left side gaps and keys, but not any gaps on the
|
||||
* right side.
|
||||
*/
|
||||
var minWidth = 0
|
||||
private set
|
||||
|
||||
/** List of keys in this keyboard */
|
||||
private var mKeys: MutableList<Key?>? = null
|
||||
|
||||
/** List of modifier keys such as Shift & Alt, if any */
|
||||
private var mModifierKeys: MutableList<Key?>? = null
|
||||
|
||||
/** Width of the screen available to fit the keyboard */
|
||||
private var mDisplayWidth = 0
|
||||
|
||||
/** Height of the screen */
|
||||
private var mDisplayHeight = 0
|
||||
|
||||
/** Keyboard mode, or zero, if none. */
|
||||
private var mKeyboardMode = 0
|
||||
private var mCellWidth = 0
|
||||
private var mCellHeight = 0
|
||||
private var mGridNeighbors = ArrayList<IntArray?>()
|
||||
private var mProximityThreshold = 0
|
||||
private val rows = ArrayList<Row?>()
|
||||
|
||||
companion object {
|
||||
const val TAG = "Keyboard"
|
||||
|
||||
// Keyboard XML Tags
|
||||
private const val TAG_KEYBOARD = "Keyboard"
|
||||
private const val TAG_ROW = "Row"
|
||||
private const val TAG_KEY = "Key"
|
||||
const val EDGE_LEFT = 0x01
|
||||
const val EDGE_RIGHT = 0x02
|
||||
const val EDGE_TOP = 0x04
|
||||
const val EDGE_BOTTOM = 0x08
|
||||
const val KEYCODE_SHIFT = -1
|
||||
const val KEYCODE_MODE_CHANGE = -2
|
||||
const val KEYCODE_CANCEL = -3
|
||||
const val KEYCODE_DONE = -4
|
||||
const val KEYCODE_DELETE = -5
|
||||
const val KEYCODE_ALT = -6
|
||||
|
||||
// Variables for pre-computing nearest keys.
|
||||
private const val GRID_WIDTH = 10
|
||||
private const val GRID_HEIGHT = 5
|
||||
private val GRID_SIZE: Int = GRID_WIDTH * GRID_HEIGHT
|
||||
|
||||
/** Number of key widths from current touch point to search for nearest keys. */
|
||||
private const val SEARCH_DISTANCE = 1.8f
|
||||
fun getDimensionOrFraction(a: TypedArray, index: Int, base: Int, defValue: Int): Int {
|
||||
val value = a.peekValue(index) ?: return defValue
|
||||
if (value.type == TypedValue.TYPE_DIMENSION) {
|
||||
return a.getDimensionPixelOffset(index, defValue)
|
||||
} else if (value.type == TypedValue.TYPE_FRACTION) {
|
||||
// Round it to avoid values like 47.9999 from getting truncated
|
||||
return Math.round(a.getFraction(index, base, base, defValue.toFloat()))
|
||||
}
|
||||
return defValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [Keyboard]
|
||||
* defines.
|
||||
* @attr ref android.R.styleable#Keyboard_keyWidth
|
||||
* @attr ref android.R.styleable#Keyboard_keyHeight
|
||||
* @attr ref android.R.styleable#Keyboard_horizontalGap
|
||||
* @attr ref android.R.styleable#Keyboard_verticalGap
|
||||
* @attr ref android.R.styleable#Keyboard_Row_rowEdgeFlags
|
||||
* @attr ref android.R.styleable#Keyboard_Row_keyboardMode
|
||||
*/
|
||||
class Row {
|
||||
/** Default width of a key in this row. */
|
||||
var defaultWidth = 0
|
||||
|
||||
/** Default height of a key in this row. */
|
||||
var defaultHeight = 0
|
||||
|
||||
/** Default horizontal gap between keys in this row. */
|
||||
var defaultHorizontalGap = 0
|
||||
|
||||
/** Vertical gap following this row. */
|
||||
var verticalGap = 0
|
||||
var mKeys = ArrayList<Key>()
|
||||
|
||||
/**
|
||||
* Edge flags for this row of keys. Possible values that can be assigned are
|
||||
* [EDGE_TOP][Keyboard.EDGE_TOP] and [EDGE_BOTTOM][Keyboard.EDGE_BOTTOM]
|
||||
*/
|
||||
var rowEdgeFlags = 0
|
||||
|
||||
/** The keyboard mode for this row */
|
||||
var mode = 0
|
||||
var parent: MyKeyboard
|
||||
|
||||
constructor(parent: MyKeyboard) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
constructor(res: Resources, parent: MyKeyboard, parser: XmlResourceParser?) {
|
||||
this.parent = parent
|
||||
var a = res.obtainAttributes(
|
||||
Xml.asAttributeSet(parser),
|
||||
R.styleable.Keyboard
|
||||
)
|
||||
|
||||
defaultWidth = getDimensionOrFraction(a, R.styleable.Keyboard_keyWidth, parent.mDisplayWidth, parent.mDefaultWidth)
|
||||
defaultHeight = getDimensionOrFraction(a, R.styleable.Keyboard_keyHeight, parent.mDisplayHeight, parent.mDefaultHeight)
|
||||
defaultHorizontalGap = getDimensionOrFraction(a, R.styleable.Keyboard_horizontalGap, parent.mDisplayWidth, parent.mDefaultHorizontalGap)
|
||||
verticalGap = getDimensionOrFraction(a, R.styleable.Keyboard_verticalGap, parent.mDisplayHeight, parent.mDefaultVerticalGap)
|
||||
|
||||
a.recycle()
|
||||
a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Row)
|
||||
rowEdgeFlags = a.getInt(R.styleable.Keyboard_Row_rowEdgeFlags, 0)
|
||||
mode = a.getResourceId(R.styleable.Keyboard_Row_keyboardMode, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for describing the position and characteristics of a single key in the keyboard.
|
||||
*
|
||||
* @attr ref android.R.styleable#Keyboard_keyWidth
|
||||
* @attr ref android.R.styleable#Keyboard_keyHeight
|
||||
* @attr ref android.R.styleable#Keyboard_horizontalGap
|
||||
* @attr ref android.R.styleable#Keyboard_Key_codes
|
||||
* @attr ref android.R.styleable#Keyboard_Key_keyIcon
|
||||
* @attr ref android.R.styleable#Keyboard_Key_keyLabel
|
||||
* @attr ref android.R.styleable#Keyboard_Key_iconPreview
|
||||
* @attr ref android.R.styleable#Keyboard_Key_isSticky
|
||||
* @attr ref android.R.styleable#Keyboard_Key_isRepeatable
|
||||
* @attr ref android.R.styleable#Keyboard_Key_isModifier
|
||||
* @attr ref android.R.styleable#Keyboard_Key_popupKeyboard
|
||||
* @attr ref android.R.styleable#Keyboard_Key_popupCharacters
|
||||
* @attr ref android.R.styleable#Keyboard_Key_keyOutputText
|
||||
* @attr ref android.R.styleable#Keyboard_Key_keyEdgeFlags
|
||||
*/
|
||||
class Key(parent: Row) {
|
||||
/**
|
||||
* All the key codes (unicode or custom code) that this key could generate, zero'th
|
||||
* being the most important.
|
||||
*/
|
||||
var codes = ArrayList<Int>()
|
||||
|
||||
/** Label to display */
|
||||
var label: CharSequence? = null
|
||||
|
||||
/** Icon to display instead of a label. Icon takes precedence over a label */
|
||||
var icon: Drawable? = null
|
||||
|
||||
/** Preview version of the icon, for the preview popup */
|
||||
var iconPreview: Drawable? = null
|
||||
|
||||
/** Width of the key, not including the gap */
|
||||
var width: Int
|
||||
|
||||
/** Height of the key, not including the gap */
|
||||
var height: Int
|
||||
|
||||
/** The horizontal gap before this key */
|
||||
var gap: Int
|
||||
|
||||
/** Whether this key is sticky, i.e., a toggle key */
|
||||
var sticky = false
|
||||
|
||||
/** X coordinate of the key in the keyboard layout */
|
||||
var x = 0
|
||||
|
||||
/** Y coordinate of the key in the keyboard layout */
|
||||
var y = 0
|
||||
|
||||
/** The current pressed state of this key */
|
||||
var pressed = false
|
||||
|
||||
/** If this is a sticky key, is it on? */
|
||||
var on = false
|
||||
|
||||
/** Text to output when pressed. This can be multiple characters, like ".com" */
|
||||
var text: CharSequence? = null
|
||||
|
||||
/** Popup characters */
|
||||
var popupCharacters: CharSequence? = null
|
||||
|
||||
/**
|
||||
* 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
|
||||
* [Keyboard.EDGE_LEFT], [Keyboard.EDGE_RIGHT], [Keyboard.EDGE_TOP] and
|
||||
* [Keyboard.EDGE_BOTTOM].
|
||||
*/
|
||||
var edgeFlags: Int
|
||||
|
||||
/** Whether this is a modifier key, such as Shift or Alt */
|
||||
var modifier = false
|
||||
|
||||
/** The keyboard that this key belongs to */
|
||||
private val keyboard: MyKeyboard
|
||||
|
||||
/**
|
||||
* If this key pops up a mini keyboard, this is the resource id for the XML layout for that
|
||||
* keyboard.
|
||||
*/
|
||||
var popupResId = 0
|
||||
|
||||
/** Whether this key repeats itself when held down */
|
||||
var repeatable = false
|
||||
|
||||
/** 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 [Keyboard].
|
||||
* @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
|
||||
*/
|
||||
constructor(res: Resources, parent: Row, x: Int, y: Int, parser: XmlResourceParser?) : this(parent) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
var a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard)
|
||||
width = getDimensionOrFraction(a, R.styleable.Keyboard_keyWidth, keyboard.mDisplayWidth, parent.defaultWidth)
|
||||
height = getDimensionOrFraction(a, R.styleable.Keyboard_keyHeight, keyboard.mDisplayHeight, parent.defaultHeight)
|
||||
gap = getDimensionOrFraction(a, R.styleable.Keyboard_horizontalGap, keyboard.mDisplayWidth, parent.defaultHorizontalGap)
|
||||
a.recycle()
|
||||
a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Key)
|
||||
this.x += gap
|
||||
val codesValue = TypedValue()
|
||||
|
||||
a.getValue(R.styleable.Keyboard_Key_codes, codesValue)
|
||||
if (codesValue.type == TypedValue.TYPE_INT_DEC || codesValue.type == TypedValue.TYPE_INT_HEX) {
|
||||
codes = arrayListOf(codesValue.data)
|
||||
} else if (codesValue.type == TypedValue.TYPE_STRING) {
|
||||
codes = parseCSV(codesValue.string.toString())
|
||||
}
|
||||
|
||||
iconPreview = a.getDrawable(R.styleable.Keyboard_Key_iconPreview)
|
||||
if (iconPreview != null) {
|
||||
iconPreview!!.setBounds(
|
||||
0, 0, iconPreview!!.intrinsicWidth,
|
||||
iconPreview!!.intrinsicHeight
|
||||
)
|
||||
}
|
||||
popupCharacters = a.getText(
|
||||
R.styleable.Keyboard_Key_popupCharacters
|
||||
)
|
||||
popupResId = a.getResourceId(
|
||||
R.styleable.Keyboard_Key_popupKeyboard, 0
|
||||
)
|
||||
repeatable = a.getBoolean(
|
||||
R.styleable.Keyboard_Key_isRepeatable, false
|
||||
)
|
||||
modifier = a.getBoolean(
|
||||
R.styleable.Keyboard_Key_isModifier, false
|
||||
)
|
||||
sticky = a.getBoolean(
|
||||
R.styleable.Keyboard_Key_isSticky, false
|
||||
)
|
||||
edgeFlags = a.getInt(R.styleable.Keyboard_Key_keyEdgeFlags, 0)
|
||||
edgeFlags = edgeFlags or parent.rowEdgeFlags
|
||||
icon = a.getDrawable(
|
||||
R.styleable.Keyboard_Key_keyIcon
|
||||
)
|
||||
if (icon != null) {
|
||||
icon!!.setBounds(0, 0, icon!!.intrinsicWidth, icon!!.intrinsicHeight)
|
||||
}
|
||||
label = a.getText(R.styleable.Keyboard_Key_keyLabel)
|
||||
text = a.getText(R.styleable.Keyboard_Key_keyOutputText)
|
||||
if (!TextUtils.isEmpty(label)) {
|
||||
codes = arrayListOf(label!![0].toInt())
|
||||
}
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
/** Create an empty key with no attributes. */
|
||||
init {
|
||||
keyboard = parent.parent
|
||||
height = parent.defaultHeight
|
||||
width = parent.defaultWidth
|
||||
gap = parent.defaultHorizontalGap
|
||||
edgeFlags = parent.rowEdgeFlags
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the key that it has been pressed, in case it needs to change its appearance or
|
||||
* state.
|
||||
* @see .onReleased
|
||||
*/
|
||||
fun onPressed() {
|
||||
pressed = !pressed
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the pressed state of the key.
|
||||
*
|
||||
*
|
||||
* Toggled state of the key will be flipped when all the following conditions are
|
||||
* fulfilled:
|
||||
*
|
||||
*
|
||||
* * This is a sticky key, that is, [.sticky] is `true`.
|
||||
* * The parameter `inside` is `true`.
|
||||
* * [android.os.Build.VERSION.SDK_INT] is greater than
|
||||
* [android.os.Build.VERSION_CODES.LOLLIPOP_MR1].
|
||||
*
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
fun onReleased(inside: Boolean) {
|
||||
pressed = !pressed
|
||||
if (sticky && inside) {
|
||||
on = !on
|
||||
}
|
||||
}
|
||||
|
||||
fun parseCSV(value: String): ArrayList<Int> {
|
||||
var count = 0
|
||||
var lastIndex = 0
|
||||
if (value.length > 0) {
|
||||
count++
|
||||
while (value.indexOf(",", lastIndex + 1).also { lastIndex = it } > 0) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
val values = ArrayList<Int>(count)
|
||||
count = 0
|
||||
val st = StringTokenizer(value, ",")
|
||||
while (st.hasMoreTokens()) {
|
||||
try {
|
||||
values[count++] = st.nextToken().toInt()
|
||||
} catch (nfe: NumberFormatException) {
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
fun isInside(x: Int, y: Int): Boolean {
|
||||
val leftEdge = edgeFlags and EDGE_LEFT > 0
|
||||
val rightEdge = edgeFlags and EDGE_RIGHT > 0
|
||||
val topEdge = edgeFlags and EDGE_TOP > 0
|
||||
val bottomEdge = edgeFlags and EDGE_BOTTOM > 0
|
||||
return ((x >= this.x || leftEdge && x <= this.x + width)
|
||||
&& (x < this.x + width || rightEdge && x >= this.x)
|
||||
&& (y >= this.y || topEdge && y <= this.y + height)
|
||||
&& (y < this.y + height || bottomEdge && y >= this.y))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun squaredDistanceFrom(x: Int, y: Int): Int {
|
||||
val xDist = this.x + width / 2 - x
|
||||
val 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
|
||||
*/
|
||||
val currentDrawableState: IntArray
|
||||
get() {
|
||||
var states: IntArray = KEY_STATE_NORMAL
|
||||
if (on) {
|
||||
states = if (pressed) {
|
||||
KEY_STATE_PRESSED_ON
|
||||
} else {
|
||||
KEY_STATE_NORMAL_ON
|
||||
}
|
||||
} else {
|
||||
if (sticky) {
|
||||
states = if (pressed) {
|
||||
KEY_STATE_PRESSED_OFF
|
||||
} else {
|
||||
KEY_STATE_NORMAL_OFF
|
||||
}
|
||||
} else {
|
||||
if (pressed) {
|
||||
states = KEY_STATE_PRESSED
|
||||
}
|
||||
}
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY_STATE_NORMAL_ON = intArrayOf(
|
||||
android.R.attr.state_checkable,
|
||||
android.R.attr.state_checked
|
||||
)
|
||||
private val KEY_STATE_PRESSED_ON = intArrayOf(
|
||||
android.R.attr.state_pressed,
|
||||
android.R.attr.state_checkable,
|
||||
android.R.attr.state_checked
|
||||
)
|
||||
private val KEY_STATE_NORMAL_OFF = intArrayOf(
|
||||
android.R.attr.state_checkable
|
||||
)
|
||||
private val KEY_STATE_PRESSED_OFF = intArrayOf(
|
||||
android.R.attr.state_pressed,
|
||||
android.R.attr.state_checkable
|
||||
)
|
||||
private val KEY_STATE_NORMAL = intArrayOf()
|
||||
private val KEY_STATE_PRESSED = intArrayOf(
|
||||
android.R.attr.state_pressed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
constructor(
|
||||
context: Context, @XmlRes xmlLayoutResId: Int, modeId: Int, width: Int,
|
||||
height: Int
|
||||
) {
|
||||
mDisplayWidth = width
|
||||
mDisplayHeight = height
|
||||
mDefaultHorizontalGap = 0
|
||||
mDefaultWidth = mDisplayWidth / 10
|
||||
mDefaultVerticalGap = 0
|
||||
mDefaultHeight = mDefaultWidth
|
||||
mKeys = ArrayList()
|
||||
mModifierKeys = ArrayList()
|
||||
mKeyboardMode = modeId
|
||||
loadKeyboard(context, context.resources.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
|
||||
*/
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@JvmOverloads
|
||||
constructor(context: Context, @XmlRes xmlLayoutResId: Int, modeId: Int = 0) {
|
||||
val dm = context.resources.displayMetrics
|
||||
mDisplayWidth = dm.widthPixels
|
||||
mDisplayHeight = dm.heightPixels
|
||||
//Log.v(TAG, "keyboard's display metrics:" + dm);
|
||||
mDefaultHorizontalGap = 0
|
||||
mDefaultWidth = mDisplayWidth / 10
|
||||
mDefaultVerticalGap = 0
|
||||
mDefaultHeight = mDefaultWidth
|
||||
mKeys = ArrayList()
|
||||
mModifierKeys = ArrayList()
|
||||
mKeyboardMode = modeId
|
||||
loadKeyboard(context, context.resources.getXml(xmlLayoutResId))
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a blank keyboard from the given resource file and populates it with the specified
|
||||
* characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
|
||||
*
|
||||
*
|
||||
* If the specified number of columns is -1, then the keyboard will fit as many keys as
|
||||
* possible in each row.
|
||||
* @param context the application or service context
|
||||
* @param layoutTemplateResId the layout template file, containing no keys.
|
||||
* @param characters the list of characters to display on the keyboard. One key will be created
|
||||
* for each character.
|
||||
* @param columns the number of columns of keys to display. If this number is greater than the
|
||||
* number of keys that can fit in a row, it will be ignored. If this number is -1, the
|
||||
* keyboard will fit as many keys as possible in each row.
|
||||
*/
|
||||
constructor(
|
||||
context: Context, layoutTemplateResId: Int,
|
||||
characters: CharSequence, columns: Int, horizontalPadding: Int
|
||||
) : this(context, layoutTemplateResId) {
|
||||
var x = 0
|
||||
var y = 0
|
||||
var column = 0
|
||||
minWidth = 0
|
||||
val row = Row(this)
|
||||
row.defaultHeight = mDefaultHeight
|
||||
row.defaultWidth = mDefaultWidth
|
||||
row.defaultHorizontalGap = mDefaultHorizontalGap
|
||||
row.verticalGap = mDefaultVerticalGap
|
||||
row.rowEdgeFlags = EDGE_TOP or EDGE_BOTTOM
|
||||
val maxColumns = if (columns == -1) Int.MAX_VALUE else columns
|
||||
for (i in 0 until characters.length) {
|
||||
val c = characters[i]
|
||||
if (column >= maxColumns
|
||||
|| x + mDefaultWidth + horizontalPadding > mDisplayWidth
|
||||
) {
|
||||
x = 0
|
||||
y += mDefaultVerticalGap + mDefaultHeight
|
||||
column = 0
|
||||
}
|
||||
val key = Key(row)
|
||||
key.x = x
|
||||
key.y = y
|
||||
key.label = c.toString()
|
||||
key.codes = arrayListOf(c.toInt())
|
||||
column++
|
||||
x += key.width + key.gap
|
||||
mKeys!!.add(key)
|
||||
row.mKeys.add(key)
|
||||
if (x > minWidth) {
|
||||
minWidth = x
|
||||
}
|
||||
}
|
||||
height = y + mDefaultHeight
|
||||
rows.add(row)
|
||||
}
|
||||
|
||||
fun resize(newWidth: Int, newHeight: Int) {
|
||||
val numRows = rows.size
|
||||
for (rowIndex in 0 until numRows) {
|
||||
val row = rows[rowIndex] ?: continue
|
||||
val numKeys: Int = row.mKeys.size
|
||||
var totalGap = 0
|
||||
var totalWidth = 0
|
||||
for (keyIndex in 0 until numKeys) {
|
||||
val key: Key = row.mKeys.get(keyIndex)
|
||||
if (keyIndex > 0) {
|
||||
totalGap += key.gap
|
||||
}
|
||||
totalWidth += key.width
|
||||
}
|
||||
|
||||
if (totalGap + totalWidth > newWidth) {
|
||||
var x = 0
|
||||
val scaleFactor = (newWidth - totalGap).toFloat() / totalWidth
|
||||
for (keyIndex in 0 until numKeys) {
|
||||
val key = row.mKeys[keyIndex]
|
||||
key.width *= scaleFactor.toInt()
|
||||
key.x = x
|
||||
x += key.width + key.gap
|
||||
}
|
||||
}
|
||||
}
|
||||
minWidth = newWidth
|
||||
// TODO: This does not adjust the vertical placement according to the new size.
|
||||
// The main problem in the previous code was horizontal placement/size, but we should
|
||||
// also recalculate the vertical sizes/positions when we get this resize call.
|
||||
}
|
||||
|
||||
val keys: List<Key?>?
|
||||
get() = mKeys
|
||||
val modifierKeys: List<Key?>?
|
||||
get() = mModifierKeys
|
||||
|
||||
fun setShifted(shiftState: Boolean): Boolean {
|
||||
for (shiftKey in mShiftKeys) {
|
||||
if (shiftKey != null) {
|
||||
shiftKey.on = shiftState
|
||||
}
|
||||
}
|
||||
if (isShifted != shiftState) {
|
||||
isShifted = shiftState
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
val shiftKeyIndex: Int
|
||||
get() = shiftKeyIndices[0]
|
||||
|
||||
private fun computeNearestNeighbors() {
|
||||
// Round-up so we don't have any pixels outside the grid
|
||||
mCellWidth = (minWidth + GRID_WIDTH - 1) / GRID_WIDTH
|
||||
mCellHeight = (height + GRID_HEIGHT - 1) / GRID_HEIGHT
|
||||
mGridNeighbors = ArrayList<IntArray?>()
|
||||
val indices = IntArray(mKeys!!.size)
|
||||
val gridWidth: Int = GRID_WIDTH * mCellWidth
|
||||
val gridHeight: Int = GRID_HEIGHT * mCellHeight
|
||||
var x = 0
|
||||
while (x < gridWidth) {
|
||||
var y = 0
|
||||
while (y < gridHeight) {
|
||||
var count = 0
|
||||
for (i in mKeys!!.indices) {
|
||||
val key = mKeys!![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
|
||||
}
|
||||
}
|
||||
val cell = IntArray(count)
|
||||
System.arraycopy(indices, 0, cell, 0, count)
|
||||
mGridNeighbors[y / mCellHeight * GRID_WIDTH + x / mCellWidth] = cell
|
||||
y += mCellHeight
|
||||
}
|
||||
x += mCellWidth
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun getNearestKeys(x: Int, y: Int): IntArray? {
|
||||
if (x >= 0 && x < minWidth && y >= 0 && y < height) {
|
||||
val index: Int = y / mCellHeight * GRID_WIDTH + x / mCellWidth
|
||||
if (index < GRID_SIZE) {
|
||||
return mGridNeighbors[index]
|
||||
}
|
||||
}
|
||||
return IntArray(0)
|
||||
}
|
||||
|
||||
protected fun createRowFromXml(res: Resources, parser: XmlResourceParser?): Row {
|
||||
return Row(res, this, parser)
|
||||
}
|
||||
|
||||
protected fun createKeyFromXml(res: Resources, parent: Row, x: Int, y: Int, parser: XmlResourceParser?): Key {
|
||||
return Key(res, parent, x, y, parser)
|
||||
}
|
||||
|
||||
private fun loadKeyboard(context: Context, parser: XmlResourceParser) {
|
||||
var inKey = false
|
||||
var inRow = false
|
||||
val leftMostKey = false
|
||||
var row = 0
|
||||
var x = 0
|
||||
var y = 0
|
||||
var key: Key? = null
|
||||
var currentRow: Row? = null
|
||||
val res = context.resources
|
||||
var skipRow = false
|
||||
try {
|
||||
var event: Int
|
||||
while (parser.next().also { event = it } != XmlResourceParser.END_DOCUMENT) {
|
||||
if (event == XmlResourceParser.START_TAG) {
|
||||
val tag = parser.name
|
||||
if (TAG_ROW == 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 == tag) {
|
||||
inKey = true
|
||||
key = createKeyFromXml(res, currentRow!!, x, y, parser)
|
||||
mKeys!!.add(key)
|
||||
if (key.codes[0] == KEYCODE_SHIFT) {
|
||||
// Find available shift key slot and put this shift key in it
|
||||
for (i in mShiftKeys.indices) {
|
||||
if (mShiftKeys[i] == null) {
|
||||
mShiftKeys[i] = key
|
||||
shiftKeyIndices[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 == tag) {
|
||||
parseKeyboardAttributes(res, parser)
|
||||
}
|
||||
} else if (event == XmlResourceParser.END_TAG) {
|
||||
if (inKey) {
|
||||
inKey = false
|
||||
x += key!!.gap + key.width
|
||||
if (x > minWidth) {
|
||||
minWidth = x
|
||||
}
|
||||
} else if (inRow) {
|
||||
inRow = false
|
||||
y += currentRow!!.verticalGap
|
||||
y += currentRow.defaultHeight
|
||||
row++
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
height = y - mDefaultVerticalGap
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun skipToEndOfRow(parser: XmlResourceParser) {
|
||||
var event: Int
|
||||
while (parser.next().also { event = it } != XmlResourceParser.END_DOCUMENT) {
|
||||
if (event == XmlResourceParser.END_TAG
|
||||
&& parser.name == TAG_ROW
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseKeyboardAttributes(res: Resources, parser: XmlResourceParser) {
|
||||
val a = res.obtainAttributes(
|
||||
Xml.asAttributeSet(parser),
|
||||
R.styleable.Keyboard
|
||||
)
|
||||
mDefaultWidth = getDimensionOrFraction(
|
||||
a,
|
||||
R.styleable.Keyboard_keyWidth,
|
||||
mDisplayWidth, mDisplayWidth / 10
|
||||
)
|
||||
mDefaultHeight = getDimensionOrFraction(
|
||||
a,
|
||||
R.styleable.Keyboard_keyHeight,
|
||||
mDisplayHeight, 50
|
||||
)
|
||||
mDefaultHorizontalGap = getDimensionOrFraction(
|
||||
a,
|
||||
R.styleable.Keyboard_horizontalGap,
|
||||
mDisplayWidth, 0
|
||||
)
|
||||
mDefaultVerticalGap = getDimensionOrFraction(
|
||||
a,
|
||||
R.styleable.Keyboard_verticalGap,
|
||||
mDisplayHeight, 0
|
||||
)
|
||||
mProximityThreshold = (mDefaultWidth * SEARCH_DISTANCE) as Int
|
||||
mProximityThreshold = mProximityThreshold * mProximityThreshold // Square it for comparison
|
||||
a.recycle()
|
||||
}
|
||||
}
|
|
@ -763,6 +763,7 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
|
|||
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode
|
||||
)
|
||||
}
|
||||
|
||||
if (mCurrentKeyIndex != NOT_A_KEY && keys.size > mCurrentKeyIndex) {
|
||||
val newKey = keys[mCurrentKeyIndex]
|
||||
newKey.onPressed()
|
||||
|
|
Loading…
Reference in New Issue