MoreKeysKeyboard.java revision aeeed758480b0fac848f4556884d978f3004555b
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.keyboard; 18 19import android.graphics.Paint; 20 21import com.android.inputmethod.keyboard.internal.KeySpecParser; 22import com.android.inputmethod.latin.R; 23 24public class MoreKeysKeyboard extends Keyboard { 25 private final int mDefaultKeyCoordX; 26 27 MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) { 28 super(params); 29 mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; 30 } 31 32 public int getDefaultCoordX() { 33 return mDefaultKeyCoordX; 34 } 35 36 public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> { 37 private final String[] mMoreKeys; 38 39 public static class MoreKeysKeyboardParams extends Keyboard.Params { 40 public boolean mIsFixedOrder; 41 /* package */int mTopRowAdjustment; 42 public int mNumRows; 43 public int mNumColumns; 44 public int mTopKeys; 45 public int mLeftKeys; 46 public int mRightKeys; // includes default key. 47 48 public MoreKeysKeyboardParams() { 49 super(); 50 } 51 52 /* package for test */MoreKeysKeyboardParams(int numKeys, int maxColumns, int keyWidth, 53 int rowHeight, int coordXInParent, int parentKeyboardWidth) { 54 super(); 55 setParameters(numKeys, maxColumns, keyWidth, rowHeight, coordXInParent, 56 parentKeyboardWidth); 57 } 58 59 /** 60 * Set keyboard parameters of more keys keyboard. 61 * 62 * @param numKeys number of keys in this more keys keyboard. 63 * @param maxColumnsAndFlags number of maximum columns of this more keys keyboard. 64 * This might have {@link Key#MORE_KEYS_FIXED_COLUMN_ORDER} flag. 65 * @param keyWidth more keys keyboard key width in pixel, including horizontal gap. 66 * @param rowHeight more keys keyboard row height in pixel, including vertical gap. 67 * @param coordXInParent coordinate x of the key preview in parent keyboard. 68 * @param parentKeyboardWidth parent keyboard width in pixel. 69 */ 70 public void setParameters(int numKeys, int maxColumnsAndFlags, int keyWidth, 71 int rowHeight, int coordXInParent, int parentKeyboardWidth) { 72 mIsFixedOrder = (maxColumnsAndFlags & Key.MORE_KEYS_FIXED_COLUMN_ORDER) != 0; 73 final int maxColumns = maxColumnsAndFlags & ~Key.MORE_KEYS_FIXED_COLUMN_ORDER; 74 if (parentKeyboardWidth / keyWidth < maxColumns) { 75 throw new IllegalArgumentException( 76 "Keyboard is too small to hold more keys keyboard: " 77 + parentKeyboardWidth + " " + keyWidth + " " + maxColumns); 78 } 79 mDefaultKeyWidth = keyWidth; 80 mDefaultRowHeight = rowHeight; 81 82 final int numRows = (numKeys + maxColumns - 1) / maxColumns; 83 mNumRows = numRows; 84 final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns) 85 : getOptimizedColumns(numKeys, maxColumns); 86 mNumColumns = numColumns; 87 final int topKeys = numKeys % numColumns; 88 mTopKeys = topKeys == 0 ? numColumns : topKeys; 89 90 final int numLeftKeys = (numColumns - 1) / 2; 91 final int numRightKeys = numColumns - numLeftKeys; // including default key. 92 // Maximum number of keys we can layout both side of the parent key 93 final int maxLeftKeys = coordXInParent / keyWidth; 94 final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; 95 int leftKeys, rightKeys; 96 if (numLeftKeys > maxLeftKeys) { 97 leftKeys = maxLeftKeys; 98 rightKeys = numColumns - leftKeys; 99 } else if (numRightKeys > maxRightKeys + 1) { 100 rightKeys = maxRightKeys + 1; // include default key 101 leftKeys = numColumns - rightKeys; 102 } else { 103 leftKeys = numLeftKeys; 104 rightKeys = numRightKeys; 105 } 106 // If the left keys fill the left side of the parent key, entire more keys keyboard 107 // should be shifted to the right unless the parent key is on the left edge. 108 if (maxLeftKeys == leftKeys && leftKeys > 0) { 109 leftKeys--; 110 rightKeys++; 111 } 112 // If the right keys fill the right side of the parent key, entire more keys 113 // should be shifted to the left unless the parent key is on the right edge. 114 if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { 115 leftKeys++; 116 rightKeys--; 117 } 118 mLeftKeys = leftKeys; 119 mRightKeys = rightKeys; 120 121 // Adjustment of the top row. 122 mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment() 123 : getAutoOrderTopRowAdjustment(); 124 mBaseWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth; 125 // Need to subtract the bottom row's gutter only. 126 mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap 127 + mTopPadding + mBottomPadding; 128 } 129 130 private int getFixedOrderTopRowAdjustment() { 131 if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns 132 || mLeftKeys == 0 || mRightKeys == 1) { 133 return 0; 134 } 135 return -1; 136 } 137 138 private int getAutoOrderTopRowAdjustment() { 139 if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 140 || mLeftKeys == 0 || mRightKeys == 1) { 141 return 0; 142 } 143 return -1; 144 } 145 146 // Return key position according to column count (0 is default). 147 /* package */int getColumnPos(int n) { 148 return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); 149 } 150 151 private int getFixedOrderColumnPos(int n) { 152 final int col = n % mNumColumns; 153 final int row = n / mNumColumns; 154 if (!isTopRow(row)) { 155 return col - mLeftKeys; 156 } 157 final int rightSideKeys = mTopKeys / 2; 158 final int leftSideKeys = mTopKeys - (rightSideKeys + 1); 159 final int pos = col - leftSideKeys; 160 final int numLeftKeys = mLeftKeys + mTopRowAdjustment; 161 final int numRightKeys = mRightKeys - 1; 162 if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { 163 return pos; 164 } else if (numRightKeys < rightSideKeys) { 165 return pos - (rightSideKeys - numRightKeys); 166 } else { // numLeftKeys < leftSideKeys 167 return pos + (leftSideKeys - numLeftKeys); 168 } 169 } 170 171 private int getAutomaticColumnPos(int n) { 172 final int col = n % mNumColumns; 173 final int row = n / mNumColumns; 174 int leftKeys = mLeftKeys; 175 if (isTopRow(row)) { 176 leftKeys += mTopRowAdjustment; 177 } 178 if (col == 0) { 179 // default position. 180 return 0; 181 } 182 183 int pos = 0; 184 int right = 1; // include default position key. 185 int left = 0; 186 int i = 0; 187 while (true) { 188 // Assign right key if available. 189 if (right < mRightKeys) { 190 pos = right; 191 right++; 192 i++; 193 } 194 if (i >= col) 195 break; 196 // Assign left key if available. 197 if (left < leftKeys) { 198 left++; 199 pos = -left; 200 i++; 201 } 202 if (i >= col) 203 break; 204 } 205 return pos; 206 } 207 208 private static int getTopRowEmptySlots(int numKeys, int numColumns) { 209 final int remainings = numKeys % numColumns; 210 return remainings == 0 ? 0 : numColumns - remainings; 211 } 212 213 private int getOptimizedColumns(int numKeys, int maxColumns) { 214 int numColumns = Math.min(numKeys, maxColumns); 215 while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { 216 numColumns--; 217 } 218 return numColumns; 219 } 220 221 public int getDefaultKeyCoordX() { 222 return mLeftKeys * mDefaultKeyWidth; 223 } 224 225 public int getX(int n, int row) { 226 final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX(); 227 if (isTopRow(row)) { 228 return x + mTopRowAdjustment * (mDefaultKeyWidth / 2); 229 } 230 return x; 231 } 232 233 public int getY(int row) { 234 return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; 235 } 236 237 public void markAsEdgeKey(Key key, int row) { 238 if (row == 0) 239 key.markAsTopEdge(this); 240 if (isTopRow(row)) 241 key.markAsBottomEdge(this); 242 } 243 244 private boolean isTopRow(int rowCount) { 245 return mNumRows > 1 && rowCount == mNumRows - 1; 246 } 247 } 248 249 public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) { 250 super(view.getContext(), new MoreKeysKeyboardParams()); 251 load(xmlId, parentKeyboard.mId); 252 253 // TODO: More keys keyboard's vertical gap is currently calculated heuristically. 254 // Should revise the algorithm. 255 mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2; 256 mMoreKeys = parentKey.mMoreKeys; 257 258 final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth; 259 final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight; 260 final int width, height; 261 // Use pre-computed width and height if these values are available and more keys 262 // keyboard has only one key to mitigate visual flicker between key preview and more 263 // keys keyboard. 264 if (view.isKeyPreviewPopupEnabled() && mMoreKeys.length == 1 && previewWidth > 0 265 && previewHeight > 0) { 266 width = previewWidth; 267 height = previewHeight + mParams.mVerticalGap; 268 } else { 269 width = getMaxKeyWidth(view, parentKey.mMoreKeys, mParams.mDefaultKeyWidth); 270 height = parentKeyboard.mMostCommonKeyHeight; 271 } 272 mParams.setParameters(mMoreKeys.length, parentKey.mMaxMoreKeysColumn, width, height, 273 parentKey.mX + parentKey.mWidth / 2, view.getMeasuredWidth()); 274 } 275 276 private static int getMaxKeyWidth(KeyboardView view, String[] moreKeys, int minKeyWidth) { 277 final int padding = (int) view.getContext().getResources() 278 .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding); 279 Paint paint = null; 280 int maxWidth = minKeyWidth; 281 for (String moreKeySpec : moreKeys) { 282 final String label = KeySpecParser.getLabel(moreKeySpec); 283 // If the label is single letter, minKeyWidth is enough to hold the label. 284 if (label != null && label.length() > 1) { 285 if (paint == null) { 286 paint = new Paint(); 287 paint.setAntiAlias(true); 288 } 289 final int width = (int)view.getDefaultLabelWidth(label, paint) + padding; 290 if (maxWidth < width) { 291 maxWidth = width; 292 } 293 } 294 } 295 return maxWidth; 296 } 297 298 @Override 299 public MoreKeysKeyboard build() { 300 final MoreKeysKeyboardParams params = mParams; 301 for (int n = 0; n < mMoreKeys.length; n++) { 302 final String moreKeySpec = mMoreKeys[n]; 303 final int row = n / params.mNumColumns; 304 final Key key = new Key(mResources, params, moreKeySpec, params.getX(n, row), 305 params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight); 306 params.markAsEdgeKey(key, row); 307 params.onAddKey(key); 308 } 309 return new MoreKeysKeyboard(params); 310 } 311 } 312} 313