mirror of
https://github.com/SimpleMobileTools/Simple-Keyboard.git
synced 2025-06-05 21:49:26 +02:00
578 lines
23 KiB
Kotlin
578 lines
23 KiB
Kotlin
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.util.SparseArray
|
|
import android.util.TypedValue
|
|
import android.util.Xml
|
|
import android.view.inputmethod.EditorInfo
|
|
import android.view.inputmethod.EditorInfo.IME_ACTION_NONE
|
|
import androidx.annotation.XmlRes
|
|
import com.simplemobiletools.keyboard.R
|
|
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"
|
|
* <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
|
|
*/
|
|
class MyKeyboard {
|
|
/** Horizontal gap default for all rows */
|
|
private var mDefaultHorizontalGap = 0
|
|
|
|
/** Default key width */
|
|
private var mDefaultWidth = 0
|
|
|
|
/** Default key height */
|
|
private var mDefaultHeight = 0
|
|
|
|
/** Is the keyboard in the shifted state */
|
|
var mShiftState = SHIFT_OFF
|
|
|
|
/** Key instance for the shift key, if present */
|
|
private val mShiftKeys = arrayOf<Key?>(null, null)
|
|
|
|
/** Total height of the keyboard, including the padding and keys */
|
|
var mHeight = 0
|
|
|
|
/** Total width of the keyboard, including left side gaps and keys, but not any gaps on the right side. */
|
|
var mMinWidth = 0
|
|
|
|
/** List of keys in this keyboard */
|
|
var mKeys: MutableList<Key?>? = null
|
|
|
|
/** Width of the screen available to fit the keyboard */
|
|
private var mDisplayWidth = 0
|
|
|
|
/** What icon should we show at Enter key */
|
|
private var mEnterKeyType = IME_ACTION_NONE
|
|
|
|
/** Keyboard mode, or zero, if none. */
|
|
private var mKeyboardMode = 0
|
|
private var mCellWidth = 0
|
|
private var mCellHeight = 0
|
|
private var mGridNeighbors: SparseArray<IntArray?>? = null
|
|
private var mProximityThreshold = 0
|
|
private val mRows = ArrayList<Row?>()
|
|
|
|
companion object {
|
|
private const val TAG_KEYBOARD = "Keyboard"
|
|
private const val TAG_ROW = "Row"
|
|
private const val TAG_KEY = "Key"
|
|
private const val EDGE_LEFT = 0x01
|
|
private const val EDGE_RIGHT = 0x02
|
|
private const val EDGE_TOP = 0x04
|
|
private const val EDGE_BOTTOM = 0x08
|
|
const val KEYCODE_SHIFT = -1
|
|
const val KEYCODE_MODE_CHANGE = -2
|
|
const val KEYCODE_ENTER = -4
|
|
const val KEYCODE_DELETE = -5
|
|
const val KEYCODE_SPACE = 32
|
|
|
|
// Variables for pre-computing nearest keys.
|
|
private const val GRID_WIDTH = 10
|
|
private const val GRID_HEIGHT = 5
|
|
private const val GRID_SIZE = 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 [MyKeyboard]
|
|
* defines.
|
|
* @attr ref android.R.styleable#Keyboard_keyWidth
|
|
* @attr ref android.R.styleable#Keyboard_horizontalGap
|
|
*/
|
|
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
|
|
|
|
var mKeys = ArrayList<Key>()
|
|
|
|
var parent: MyKeyboard
|
|
|
|
constructor(parent: MyKeyboard) {
|
|
this.parent = parent
|
|
}
|
|
|
|
constructor(res: Resources, parent: MyKeyboard, parser: XmlResourceParser?) {
|
|
this.parent = parent
|
|
val a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.MyKeyboard)
|
|
defaultWidth = getDimensionOrFraction(a, R.styleable.MyKeyboard_keyWidth, parent.mDisplayWidth, parent.mDefaultWidth)
|
|
defaultHeight = res.getDimension(R.dimen.key_height).toInt()
|
|
defaultHorizontalGap = getDimensionOrFraction(a, R.styleable.MyKeyboard_horizontalGap, parent.mDisplayWidth, parent.mDefaultHorizontalGap)
|
|
a.recycle()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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_isRepeatable
|
|
* @attr ref android.R.styleable#Keyboard_Key_popupKeyboard
|
|
* @attr ref android.R.styleable#Keyboard_Key_popupCharacters
|
|
* @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 = ""
|
|
|
|
/** First row of letters can also be used for inserting numbers by long pressing them, show those numbers */
|
|
var topSmallNumber: String = ""
|
|
|
|
/** Icon to display instead of a label. Icon takes precedence over a label */
|
|
var icon: 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
|
|
|
|
/** 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
|
|
|
|
/** Focused state, used after long pressing a key and swiping to alternative keys */
|
|
var focused = false
|
|
|
|
/** 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
|
|
* [MyKeyboard.EDGE_LEFT], [MyKeyboard.EDGE_RIGHT], [MyKeyboard.EDGE_TOP] and [MyKeyboard.EDGE_BOTTOM].
|
|
*/
|
|
var edgeFlags = 0
|
|
|
|
/** The keyboard that this key belongs to */
|
|
private val keyboard: MyKeyboard = parent.parent
|
|
|
|
/** 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 [MyKeyboard].
|
|
* @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.MyKeyboard)
|
|
width = getDimensionOrFraction(a, R.styleable.MyKeyboard_keyWidth, keyboard.mDisplayWidth, parent.defaultWidth)
|
|
height = parent.defaultHeight
|
|
gap = getDimensionOrFraction(a, R.styleable.MyKeyboard_horizontalGap, keyboard.mDisplayWidth, parent.defaultHorizontalGap)
|
|
|
|
a.recycle()
|
|
a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.MyKeyboard_Key)
|
|
this.x += gap
|
|
val codesValue = TypedValue()
|
|
|
|
a.getValue(R.styleable.MyKeyboard_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())
|
|
}
|
|
|
|
popupCharacters = a.getText(R.styleable.MyKeyboard_Key_popupCharacters)
|
|
popupResId = a.getResourceId(R.styleable.MyKeyboard_Key_popupKeyboard, 0)
|
|
repeatable = a.getBoolean(R.styleable.MyKeyboard_Key_isRepeatable, false)
|
|
edgeFlags = a.getInt(R.styleable.MyKeyboard_Key_keyEdgeFlags, 0)
|
|
icon = a.getDrawable(R.styleable.MyKeyboard_Key_keyIcon)
|
|
icon?.setBounds(0, 0, icon!!.intrinsicWidth, icon!!.intrinsicHeight)
|
|
|
|
label = a.getText(R.styleable.MyKeyboard_Key_keyLabel) ?: ""
|
|
topSmallNumber = a.getString(R.styleable.MyKeyboard_Key_topSmallNumber) ?: ""
|
|
|
|
if (label.isNotEmpty() && codes.firstOrNull() != KEYCODE_MODE_CHANGE && codes.firstOrNull() != KEYCODE_SHIFT) {
|
|
codes = arrayListOf(label[0].toInt())
|
|
}
|
|
a.recycle()
|
|
}
|
|
|
|
/** Create an empty key with no attributes. */
|
|
init {
|
|
height = parent.defaultHeight
|
|
width = parent.defaultWidth
|
|
gap = parent.defaultHorizontalGap
|
|
}
|
|
|
|
fun onPressed() {
|
|
pressed = true
|
|
}
|
|
|
|
fun onReleased() {
|
|
pressed = false
|
|
}
|
|
|
|
fun parseCSV(value: String): ArrayList<Int> {
|
|
var count = 0
|
|
var lastIndex = 0
|
|
if (value.isNotEmpty()) {
|
|
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
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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, enterKeyType: Int, modeId: Int = 0) {
|
|
val dm = context.resources.displayMetrics
|
|
mDisplayWidth = dm.widthPixels
|
|
mDefaultHorizontalGap = 0
|
|
mDefaultWidth = mDisplayWidth / 10
|
|
mDefaultHeight = mDefaultWidth
|
|
mKeys = ArrayList()
|
|
mEnterKeyType = enterKeyType
|
|
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.
|
|
*/
|
|
constructor(context: Context, layoutTemplateResId: Int, characters: CharSequence) :
|
|
this(context, layoutTemplateResId, 0) {
|
|
var x = 0
|
|
var y = 0
|
|
var column = 0
|
|
mMinWidth = 0
|
|
val row = Row(this)
|
|
row.defaultHeight = mDefaultHeight
|
|
row.defaultWidth = mDefaultWidth
|
|
row.defaultHorizontalGap = mDefaultHorizontalGap
|
|
|
|
characters.forEachIndexed { index, character ->
|
|
val key = Key(row)
|
|
if (column >= MAX_KEYS_PER_MINI_ROW) {
|
|
column = 0
|
|
x = 0
|
|
y += mDefaultHeight
|
|
mRows.add(row)
|
|
row.mKeys.clear()
|
|
}
|
|
|
|
key.x = x
|
|
key.y = y
|
|
key.label = character.toString()
|
|
key.codes = arrayListOf(character.toInt())
|
|
column++
|
|
x += key.width + key.gap
|
|
mKeys!!.add(key)
|
|
row.mKeys.add(key)
|
|
if (x > mMinWidth) {
|
|
mMinWidth = x
|
|
}
|
|
}
|
|
mHeight = y + mDefaultHeight
|
|
mRows.add(row)
|
|
}
|
|
|
|
fun resize(newWidth: Int, newHeight: Int) {
|
|
val numRows = mRows.size
|
|
for (rowIndex in 0 until numRows) {
|
|
val row = mRows[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
|
|
}
|
|
}
|
|
}
|
|
|
|
mMinWidth = 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.
|
|
}
|
|
|
|
fun setShifted(shiftState: Int): Boolean {
|
|
if (this.mShiftState != shiftState) {
|
|
this.mShiftState = shiftState
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
private fun computeNearestNeighbors() {
|
|
// Round-up so we don't have any pixels outside the grid
|
|
mCellWidth = (mMinWidth + GRID_WIDTH - 1) / GRID_WIDTH
|
|
mCellHeight = (mHeight + GRID_HEIGHT - 1) / GRID_HEIGHT
|
|
mGridNeighbors = SparseArray<IntArray?>(GRID_SIZE)
|
|
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!!.put(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 (mGridNeighbors == null) {
|
|
computeNearestNeighbors()
|
|
}
|
|
|
|
if (x in 0 until mMinWidth && y >= 0 && y < mHeight) {
|
|
val index = y / mCellHeight * GRID_WIDTH + x / mCellWidth
|
|
if (index < GRID_SIZE) {
|
|
return mGridNeighbors!![index]!!
|
|
}
|
|
}
|
|
|
|
return IntArray(0)
|
|
}
|
|
|
|
private fun createRowFromXml(res: Resources, parser: XmlResourceParser?): Row {
|
|
return Row(res, this, parser)
|
|
}
|
|
|
|
private 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
|
|
var row = 0
|
|
var x = 0
|
|
var y = 0
|
|
var key: Key? = null
|
|
var currentRow: Row? = null
|
|
val res = context.resources
|
|
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)
|
|
mRows.add(currentRow)
|
|
} 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
|
|
break
|
|
}
|
|
}
|
|
} else if (key.codes[0] == KEYCODE_ENTER) {
|
|
val enterResourceId = when (mEnterKeyType) {
|
|
EditorInfo.IME_ACTION_SEARCH -> R.drawable.ic_search_vector
|
|
EditorInfo.IME_ACTION_NEXT, EditorInfo.IME_ACTION_GO -> R.drawable.ic_arrow_right_vector
|
|
EditorInfo.IME_ACTION_SEND -> R.drawable.ic_send_vector
|
|
else -> R.drawable.ic_enter_vector
|
|
}
|
|
key.icon = context.resources.getDrawable(enterResourceId, context.theme)
|
|
}
|
|
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 > mMinWidth) {
|
|
mMinWidth = x
|
|
}
|
|
} else if (inRow) {
|
|
inRow = false
|
|
y += currentRow!!.defaultHeight
|
|
row++
|
|
}
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
}
|
|
mHeight = y
|
|
}
|
|
|
|
private fun parseKeyboardAttributes(res: Resources, parser: XmlResourceParser) {
|
|
val a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.MyKeyboard)
|
|
mDefaultWidth = getDimensionOrFraction(a, R.styleable.MyKeyboard_keyWidth, mDisplayWidth, mDisplayWidth / 10)
|
|
mDefaultHeight = res.getDimension(R.dimen.key_height).toInt()
|
|
mDefaultHorizontalGap = getDimensionOrFraction(a, R.styleable.MyKeyboard_horizontalGap, mDisplayWidth, 0)
|
|
mProximityThreshold = (mDefaultWidth * SEARCH_DISTANCE).toInt()
|
|
mProximityThreshold *= mProximityThreshold // Square it for comparison
|
|
a.recycle()
|
|
}
|
|
}
|