MoreKeysKeyboard.java revision 5ef4fccbb90491e1f6c2e87b47ebf9f3659949fb
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; 20import android.graphics.drawable.Drawable; 21 22import com.android.inputmethod.keyboard.internal.KeySpecParser; 23import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 24import com.android.inputmethod.latin.R; 25import com.android.inputmethod.latin.Utils; 26 27public class MoreKeysKeyboard extends Keyboard { 28 private final int mDefaultKeyCoordX; 29 30 MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) { 31 super(params); 32 mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; 33 } 34 35 public int getDefaultCoordX() { 36 return mDefaultKeyCoordX; 37 } 38 39 public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> { 40 private final Key mParentKey; 41 private final Drawable mDivider; 42 43 private static final float LABEL_PADDING_RATIO = 0.2f; 44 private static final float DIVIDER_RATIO = 0.2f; 45 46 public static class MoreKeysKeyboardParams extends Keyboard.Params { 47 public boolean mIsFixedOrder; 48 /* package */int mTopRowAdjustment; 49 public int mNumRows; 50 public int mNumColumns; 51 public int mTopKeys; 52 public int mLeftKeys; 53 public int mRightKeys; // includes default key. 54 public int mDividerWidth; 55 public int mColumnWidth; 56 57 public MoreKeysKeyboardParams() { 58 super(); 59 } 60 61 /** 62 * Set keyboard parameters of more keys keyboard. 63 * 64 * @param numKeys number of keys in this more keys keyboard. 65 * @param maxColumns number of maximum columns of this more keys keyboard. 66 * @param keyWidth more keys keyboard key width in pixel, including horizontal gap. 67 * @param rowHeight more keys keyboard row height in pixel, including vertical gap. 68 * @param coordXInParent coordinate x of the key preview in parent keyboard. 69 * @param parentKeyboardWidth parent keyboard width in pixel. 70 * @param isFixedColumnOrder if true, more keys should be laid out in fixed order. 71 * @param dividerWidth width of divider, zero for no dividers. 72 */ 73 public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight, 74 int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder, 75 int dividerWidth) { 76 mIsFixedOrder = isFixedColumnOrder; 77 if (parentKeyboardWidth / keyWidth < maxColumns) { 78 throw new IllegalArgumentException( 79 "Keyboard is too small to hold more keys keyboard: " 80 + parentKeyboardWidth + " " + keyWidth + " " + maxColumns); 81 } 82 mDefaultKeyWidth = keyWidth; 83 mDefaultRowHeight = rowHeight; 84 85 final int numRows = (numKeys + maxColumns - 1) / maxColumns; 86 mNumRows = numRows; 87 final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns) 88 : getOptimizedColumns(numKeys, maxColumns); 89 mNumColumns = numColumns; 90 final int topKeys = numKeys % numColumns; 91 mTopKeys = topKeys == 0 ? numColumns : topKeys; 92 93 final int numLeftKeys = (numColumns - 1) / 2; 94 final int numRightKeys = numColumns - numLeftKeys; // including default key. 95 // Maximum number of keys we can layout both side of the parent key 96 final int maxLeftKeys = coordXInParent / keyWidth; 97 final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; 98 int leftKeys, rightKeys; 99 if (numLeftKeys > maxLeftKeys) { 100 leftKeys = maxLeftKeys; 101 rightKeys = numColumns - leftKeys; 102 } else if (numRightKeys > maxRightKeys + 1) { 103 rightKeys = maxRightKeys + 1; // include default key 104 leftKeys = numColumns - rightKeys; 105 } else { 106 leftKeys = numLeftKeys; 107 rightKeys = numRightKeys; 108 } 109 // If the left keys fill the left side of the parent key, entire more keys keyboard 110 // should be shifted to the right unless the parent key is on the left edge. 111 if (maxLeftKeys == leftKeys && leftKeys > 0) { 112 leftKeys--; 113 rightKeys++; 114 } 115 // If the right keys fill the right side of the parent key, entire more keys 116 // should be shifted to the left unless the parent key is on the right edge. 117 if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { 118 leftKeys++; 119 rightKeys--; 120 } 121 mLeftKeys = leftKeys; 122 mRightKeys = rightKeys; 123 124 // Adjustment of the top row. 125 mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment() 126 : getAutoOrderTopRowAdjustment(); 127 mDividerWidth = dividerWidth; 128 mColumnWidth = mDefaultKeyWidth + mDividerWidth; 129 mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth; 130 // Need to subtract the bottom row's gutter only. 131 mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap 132 + mTopPadding + mBottomPadding; 133 } 134 135 private int getFixedOrderTopRowAdjustment() { 136 if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns 137 || mLeftKeys == 0 || mRightKeys == 1) { 138 return 0; 139 } 140 return -1; 141 } 142 143 private int getAutoOrderTopRowAdjustment() { 144 if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 145 || mLeftKeys == 0 || mRightKeys == 1) { 146 return 0; 147 } 148 return -1; 149 } 150 151 // Return key position according to column count (0 is default). 152 /* package */int getColumnPos(int n) { 153 return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); 154 } 155 156 private int getFixedOrderColumnPos(int n) { 157 final int col = n % mNumColumns; 158 final int row = n / mNumColumns; 159 if (!isTopRow(row)) { 160 return col - mLeftKeys; 161 } 162 final int rightSideKeys = mTopKeys / 2; 163 final int leftSideKeys = mTopKeys - (rightSideKeys + 1); 164 final int pos = col - leftSideKeys; 165 final int numLeftKeys = mLeftKeys + mTopRowAdjustment; 166 final int numRightKeys = mRightKeys - 1; 167 if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { 168 return pos; 169 } else if (numRightKeys < rightSideKeys) { 170 return pos - (rightSideKeys - numRightKeys); 171 } else { // numLeftKeys < leftSideKeys 172 return pos + (leftSideKeys - numLeftKeys); 173 } 174 } 175 176 private int getAutomaticColumnPos(int n) { 177 final int col = n % mNumColumns; 178 final int row = n / mNumColumns; 179 int leftKeys = mLeftKeys; 180 if (isTopRow(row)) { 181 leftKeys += mTopRowAdjustment; 182 } 183 if (col == 0) { 184 // default position. 185 return 0; 186 } 187 188 int pos = 0; 189 int right = 1; // include default position key. 190 int left = 0; 191 int i = 0; 192 while (true) { 193 // Assign right key if available. 194 if (right < mRightKeys) { 195 pos = right; 196 right++; 197 i++; 198 } 199 if (i >= col) 200 break; 201 // Assign left key if available. 202 if (left < leftKeys) { 203 left++; 204 pos = -left; 205 i++; 206 } 207 if (i >= col) 208 break; 209 } 210 return pos; 211 } 212 213 private static int getTopRowEmptySlots(int numKeys, int numColumns) { 214 final int remainings = numKeys % numColumns; 215 return remainings == 0 ? 0 : numColumns - remainings; 216 } 217 218 private int getOptimizedColumns(int numKeys, int maxColumns) { 219 int numColumns = Math.min(numKeys, maxColumns); 220 while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { 221 numColumns--; 222 } 223 return numColumns; 224 } 225 226 public int getDefaultKeyCoordX() { 227 return mLeftKeys * mColumnWidth; 228 } 229 230 public int getX(int n, int row) { 231 final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); 232 if (isTopRow(row)) { 233 return x + mTopRowAdjustment * (mColumnWidth / 2); 234 } 235 return x; 236 } 237 238 public int getY(int row) { 239 return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; 240 } 241 242 public void markAsEdgeKey(Key key, int row) { 243 if (row == 0) 244 key.markAsTopEdge(this); 245 if (isTopRow(row)) 246 key.markAsBottomEdge(this); 247 } 248 249 private boolean isTopRow(int rowCount) { 250 return mNumRows > 1 && rowCount == mNumRows - 1; 251 } 252 } 253 254 public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) { 255 super(view.getContext(), new MoreKeysKeyboardParams()); 256 load(xmlId, parentKeyboard.mId); 257 258 // TODO: More keys keyboard's vertical gap is currently calculated heuristically. 259 // Should revise the algorithm. 260 mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2; 261 mParentKey = parentKey; 262 263 final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth; 264 final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight; 265 final int width, height; 266 // Use pre-computed width and height if these values are available and more keys 267 // keyboard has only one key to mitigate visual flicker between key preview and more 268 // keys keyboard. 269 final boolean validKeyPreview = view.isKeyPreviewPopupEnabled() 270 && !parentKey.noKeyPreview() && (previewWidth > 0) && (previewHeight > 0); 271 final boolean singleMoreKeyWithPreview = validKeyPreview 272 && parentKey.mMoreKeys.length == 1; 273 if (singleMoreKeyWithPreview) { 274 width = previewWidth; 275 height = previewHeight + mParams.mVerticalGap; 276 } else { 277 width = getMaxKeyWidth(view, parentKey, mParams.mDefaultKeyWidth); 278 height = parentKeyboard.mMostCommonKeyHeight; 279 } 280 final int dividerWidth; 281 if (parentKey.needsDividersInMoreKeys()) { 282 mDivider = mResources.getDrawable(R.drawable.more_keys_divider); 283 // TODO: Drawable itself should have an alpha value. 284 mDivider.setAlpha(128); 285 dividerWidth = (int)(width * DIVIDER_RATIO); 286 } else { 287 mDivider = null; 288 dividerWidth = 0; 289 } 290 mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(), 291 width, height, parentKey.mX + parentKey.mWidth / 2, view.getMeasuredWidth(), 292 parentKey.isFixedColumnOrderMoreKeys(), dividerWidth); 293 } 294 295 private static int getMaxKeyWidth(KeyboardView view, Key parentKey, int minKeyWidth) { 296 final int padding = (int)(view.getResources() 297 .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding) 298 + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0)); 299 Paint paint = null; 300 int maxWidth = minKeyWidth; 301 for (String moreKeySpec : parentKey.mMoreKeys) { 302 final String label = KeySpecParser.getLabel(moreKeySpec); 303 // If the label is single letter, minKeyWidth is enough to hold the label. 304 if (label != null && Utils.codePointCount(label) > 1) { 305 if (paint == null) { 306 paint = new Paint(); 307 paint.setAntiAlias(true); 308 } 309 final int width = (int)view.getDefaultLabelWidth(label, paint) + padding; 310 if (maxWidth < width) { 311 maxWidth = width; 312 } 313 } 314 } 315 return maxWidth; 316 } 317 318 private static class MoreKeyDivider extends Key.Spacer { 319 private final Drawable mIcon; 320 321 public MoreKeyDivider(MoreKeysKeyboardParams params, Drawable icon, int x, int y) { 322 super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight); 323 mIcon = icon; 324 } 325 326 @Override 327 public Drawable getIcon(KeyboardIconsSet iconSet) { 328 // KeyboardIconsSet is unused. Use the icon that has been passed to the constructor. 329 return mIcon; 330 } 331 } 332 333 @Override 334 public MoreKeysKeyboard build() { 335 final MoreKeysKeyboardParams params = mParams; 336 // moreKeyFlags == 0 means that the rendered text size will be determined by its 337 // label's code point count. 338 final int moreKeyFlags = mParentKey.hasLabelsInMoreKeys() ? 0 339 : Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO; 340 final String[] moreKeys = mParentKey.mMoreKeys; 341 for (int n = 0; n < moreKeys.length; n++) { 342 final String moreKeySpec = moreKeys[n]; 343 final int row = n / params.mNumColumns; 344 final int x = params.getX(n, row); 345 final int y = params.getY(row); 346 final Key key = new Key(mResources, params, moreKeySpec, x, y, 347 params.mDefaultKeyWidth, params.mDefaultRowHeight, moreKeyFlags); 348 params.markAsEdgeKey(key, row); 349 params.onAddKey(key); 350 351 final int pos = params.getColumnPos(n); 352 // The "pos" value represents the offset from the default position. Negative means 353 // left of the default position. 354 if (params.mDividerWidth > 0 && pos != 0) { 355 final int dividerX = (pos > 0) ? x - params.mDividerWidth 356 : x + params.mDefaultKeyWidth; 357 final Key divider = new MoreKeyDivider(params, mDivider, dividerX, y); 358 params.onAddKey(divider); 359 } 360 } 361 return new MoreKeysKeyboard(params); 362 } 363 } 364} 365