MoreKeysKeyboard.java revision 03288ef47fd93758b5665e19fe9b892ece6e586f
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.keyboard; 18 19import android.content.Context; 20import android.graphics.Paint; 21import android.graphics.drawable.Drawable; 22 23import com.android.inputmethod.annotations.UsedForTesting; 24import com.android.inputmethod.keyboard.internal.KeyboardBuilder; 25import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 26import com.android.inputmethod.keyboard.internal.KeyboardParams; 27import com.android.inputmethod.keyboard.internal.MoreKeySpec; 28import com.android.inputmethod.latin.R; 29import com.android.inputmethod.latin.utils.StringUtils; 30import com.android.inputmethod.latin.utils.TypefaceUtils; 31 32public final class MoreKeysKeyboard extends Keyboard { 33 private final int mDefaultKeyCoordX; 34 35 MoreKeysKeyboard(final MoreKeysKeyboardParams params) { 36 super(params); 37 mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; 38 } 39 40 public int getDefaultCoordX() { 41 return mDefaultKeyCoordX; 42 } 43 44 @UsedForTesting 45 static class MoreKeysKeyboardParams extends KeyboardParams { 46 public boolean mIsFixedOrder; 47 /* package */int mTopRowAdjustment; 48 public int mNumRows; 49 public int mNumColumns; 50 public int mTopKeys; 51 public int mLeftKeys; 52 public int mRightKeys; // includes default key. 53 public int mDividerWidth; 54 public int mColumnWidth; 55 56 public MoreKeysKeyboardParams() { 57 super(); 58 } 59 60 /** 61 * Set keyboard parameters of more keys keyboard. 62 * 63 * @param numKeys number of keys in this more keys keyboard. 64 * @param maxColumns number of maximum columns of this more keys keyboard. 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 * @param isFixedColumnOrder if true, more keys should be laid out in fixed order. 70 * @param dividerWidth width of divider, zero for no dividers. 71 */ 72 public void setParameters(final int numKeys, final int maxColumns, final int keyWidth, 73 final int rowHeight, final int coordXInParent, final int parentKeyboardWidth, 74 final boolean isFixedColumnOrder, final int dividerWidth) { 75 mIsFixedOrder = isFixedColumnOrder; 76 if (parentKeyboardWidth / keyWidth < Math.min(numKeys, maxColumns)) { 77 throw new IllegalArgumentException("Keyboard is too small to hold more keys: " 78 + parentKeyboardWidth + " " + keyWidth + " " + numKeys + " " + maxColumns); 79 } 80 mDefaultKeyWidth = keyWidth; 81 mDefaultRowHeight = rowHeight; 82 83 final int numRows = (numKeys + maxColumns - 1) / maxColumns; 84 mNumRows = numRows; 85 final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns) 86 : getOptimizedColumns(numKeys, maxColumns); 87 mNumColumns = numColumns; 88 final int topKeys = numKeys % numColumns; 89 mTopKeys = topKeys == 0 ? numColumns : topKeys; 90 91 final int numLeftKeys = (numColumns - 1) / 2; 92 final int numRightKeys = numColumns - numLeftKeys; // including default key. 93 // Maximum number of keys we can layout both side of the parent key 94 final int maxLeftKeys = coordXInParent / keyWidth; 95 final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; 96 int leftKeys, rightKeys; 97 if (numLeftKeys > maxLeftKeys) { 98 leftKeys = maxLeftKeys; 99 rightKeys = numColumns - leftKeys; 100 } else if (numRightKeys > maxRightKeys + 1) { 101 rightKeys = maxRightKeys + 1; // include default key 102 leftKeys = numColumns - rightKeys; 103 } else { 104 leftKeys = numLeftKeys; 105 rightKeys = numRightKeys; 106 } 107 // If the left keys fill the left side of the parent key, entire more keys keyboard 108 // should be shifted to the right unless the parent key is on the left edge. 109 if (maxLeftKeys == leftKeys && leftKeys > 0) { 110 leftKeys--; 111 rightKeys++; 112 } 113 // If the right keys fill the right side of the parent key, entire more keys 114 // should be shifted to the left unless the parent key is on the right edge. 115 if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { 116 leftKeys++; 117 rightKeys--; 118 } 119 mLeftKeys = leftKeys; 120 mRightKeys = rightKeys; 121 122 // Adjustment of the top row. 123 mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment() 124 : getAutoOrderTopRowAdjustment(); 125 mDividerWidth = dividerWidth; 126 mColumnWidth = mDefaultKeyWidth + mDividerWidth; 127 mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth; 128 // Need to subtract the bottom row's gutter only. 129 mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap 130 + mTopPadding + mBottomPadding; 131 } 132 133 private int getFixedOrderTopRowAdjustment() { 134 if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns 135 || mLeftKeys == 0 || mRightKeys == 1) { 136 return 0; 137 } 138 return -1; 139 } 140 141 private int getAutoOrderTopRowAdjustment() { 142 if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 143 || mLeftKeys == 0 || mRightKeys == 1) { 144 return 0; 145 } 146 return -1; 147 } 148 149 // Return key position according to column count (0 is default). 150 /* package */int getColumnPos(final int n) { 151 return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); 152 } 153 154 private int getFixedOrderColumnPos(final int n) { 155 final int col = n % mNumColumns; 156 final int row = n / mNumColumns; 157 if (!isTopRow(row)) { 158 return col - mLeftKeys; 159 } 160 final int rightSideKeys = mTopKeys / 2; 161 final int leftSideKeys = mTopKeys - (rightSideKeys + 1); 162 final int pos = col - leftSideKeys; 163 final int numLeftKeys = mLeftKeys + mTopRowAdjustment; 164 final int numRightKeys = mRightKeys - 1; 165 if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { 166 return pos; 167 } else if (numRightKeys < rightSideKeys) { 168 return pos - (rightSideKeys - numRightKeys); 169 } else { // numLeftKeys < leftSideKeys 170 return pos + (leftSideKeys - numLeftKeys); 171 } 172 } 173 174 private int getAutomaticColumnPos(final int n) { 175 final int col = n % mNumColumns; 176 final int row = n / mNumColumns; 177 int leftKeys = mLeftKeys; 178 if (isTopRow(row)) { 179 leftKeys += mTopRowAdjustment; 180 } 181 if (col == 0) { 182 // default position. 183 return 0; 184 } 185 186 int pos = 0; 187 int right = 1; // include default position key. 188 int left = 0; 189 int i = 0; 190 while (true) { 191 // Assign right key if available. 192 if (right < mRightKeys) { 193 pos = right; 194 right++; 195 i++; 196 } 197 if (i >= col) 198 break; 199 // Assign left key if available. 200 if (left < leftKeys) { 201 left++; 202 pos = -left; 203 i++; 204 } 205 if (i >= col) 206 break; 207 } 208 return pos; 209 } 210 211 private static int getTopRowEmptySlots(final int numKeys, final int numColumns) { 212 final int remainings = numKeys % numColumns; 213 return remainings == 0 ? 0 : numColumns - remainings; 214 } 215 216 private int getOptimizedColumns(final int numKeys, final int maxColumns) { 217 int numColumns = Math.min(numKeys, maxColumns); 218 while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { 219 numColumns--; 220 } 221 return numColumns; 222 } 223 224 public int getDefaultKeyCoordX() { 225 return mLeftKeys * mColumnWidth + mLeftPadding; 226 } 227 228 public int getX(final int n, final int row) { 229 final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); 230 if (isTopRow(row)) { 231 return x + mTopRowAdjustment * (mColumnWidth / 2); 232 } 233 return x; 234 } 235 236 public int getY(final int row) { 237 return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; 238 } 239 240 public void markAsEdgeKey(final Key key, final int row) { 241 if (row == 0) 242 key.markAsTopEdge(this); 243 if (isTopRow(row)) 244 key.markAsBottomEdge(this); 245 } 246 247 private boolean isTopRow(final int rowCount) { 248 return mNumRows > 1 && rowCount == mNumRows - 1; 249 } 250 } 251 252 public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> { 253 private final Key mParentKey; 254 private final Drawable mDivider; 255 256 private static final float LABEL_PADDING_RATIO = 0.2f; 257 private static final float DIVIDER_RATIO = 0.2f; 258 259 /** 260 * The builder of MoreKeysKeyboard. 261 * @param context the context of {@link MoreKeysKeyboardView}. 262 * @param key the {@link Key} that invokes more keys keyboard. 263 * @param keyboard the {@link Keyboard} that contains the parentKey. 264 * @param singleMoreKeyWithPreview true if the <code>key</code> has only one more key 265 * and key popup preview is enabled. 266 * @param keyPreviewDrawParams the parameter to place key preview. 267 * @param paintToMeasure the {@link Paint} object to measure a more key width 268 */ 269 public Builder(final Context context, final Key key, final Keyboard keyboard, 270 final boolean singleMoreKeyWithPreview, final int keyPreviewVisibleWidth, 271 final int keyPreviewVisibleHeight, final Paint paintToMeasure) { 272 super(context, new MoreKeysKeyboardParams()); 273 load(keyboard.mMoreKeysTemplate, keyboard.mId); 274 275 // TODO: More keys keyboard's vertical gap is currently calculated heuristically. 276 // Should revise the algorithm. 277 mParams.mVerticalGap = keyboard.mVerticalGap / 2; 278 mParentKey = key; 279 280 final int keyWidth, rowHeight; 281 if (singleMoreKeyWithPreview) { 282 // Use pre-computed width and height if this more keys keyboard has only one key to 283 // mitigate visual flicker between key preview and more keys keyboard. 284 // Caveats for the visual assets: To achieve this effect, both the key preview 285 // backgrounds and the more keys keyboard panel background have the exact same 286 // left/right/top paddings. The bottom paddings of both backgrounds don't need to 287 // be considered because the vertical positions of both backgrounds were already 288 // adjusted with their bottom paddings deducted. 289 keyWidth = keyPreviewVisibleWidth; 290 rowHeight = keyPreviewVisibleHeight + mParams.mVerticalGap; 291 } else { 292 final float padding = context.getResources().getDimension( 293 R.dimen.config_more_keys_keyboard_key_horizontal_padding) 294 + (key.hasLabelsInMoreKeys() 295 ? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f); 296 keyWidth = getMaxKeyWidth(key, mParams.mDefaultKeyWidth, padding, paintToMeasure); 297 rowHeight = keyboard.mMostCommonKeyHeight; 298 } 299 final int dividerWidth; 300 if (key.needsDividersInMoreKeys()) { 301 mDivider = mResources.getDrawable(R.drawable.more_keys_divider); 302 dividerWidth = (int)(keyWidth * DIVIDER_RATIO); 303 } else { 304 mDivider = null; 305 dividerWidth = 0; 306 } 307 final MoreKeySpec[] moreKeys = key.getMoreKeys(); 308 mParams.setParameters(moreKeys.length, key.getMoreKeysColumn(), keyWidth, rowHeight, 309 key.getX() + key.getWidth() / 2, keyboard.mId.mWidth, 310 key.isFixedColumnOrderMoreKeys(), dividerWidth); 311 } 312 313 private static int getMaxKeyWidth(final Key parentKey, final int minKeyWidth, 314 final float padding, final Paint paint) { 315 int maxWidth = minKeyWidth; 316 for (final MoreKeySpec spec : parentKey.getMoreKeys()) { 317 final String label = spec.mLabel; 318 // If the label is single letter, minKeyWidth is enough to hold the label. 319 if (label != null && StringUtils.codePointCount(label) > 1) { 320 maxWidth = Math.max(maxWidth, 321 (int)(TypefaceUtils.getStringWidth(label, paint) + padding)); 322 } 323 } 324 return maxWidth; 325 } 326 327 @Override 328 public MoreKeysKeyboard build() { 329 final MoreKeysKeyboardParams params = mParams; 330 final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags(); 331 final MoreKeySpec[] moreKeys = mParentKey.getMoreKeys(); 332 for (int n = 0; n < moreKeys.length; n++) { 333 final MoreKeySpec moreKeySpec = moreKeys[n]; 334 final int row = n / params.mNumColumns; 335 final int x = params.getX(n, row); 336 final int y = params.getY(row); 337 final Key key = moreKeySpec.buildKey(x, y, moreKeyFlags, params); 338 params.markAsEdgeKey(key, row); 339 params.onAddKey(key); 340 341 final int pos = params.getColumnPos(n); 342 // The "pos" value represents the offset from the default position. Negative means 343 // left of the default position. 344 if (params.mDividerWidth > 0 && pos != 0) { 345 final int dividerX = (pos > 0) ? x - params.mDividerWidth 346 : x + params.mDefaultKeyWidth; 347 final Key divider = new MoreKeyDivider(params, mDivider, dividerX, y); 348 params.onAddKey(divider); 349 } 350 } 351 return new MoreKeysKeyboard(params); 352 } 353 } 354 355 private static class MoreKeyDivider extends Key.Spacer { 356 private final Drawable mIcon; 357 358 public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon, 359 final int x, final int y) { 360 super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight); 361 mIcon = icon; 362 } 363 364 @Override 365 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 366 // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the 367 // constructor. 368 // TODO: Drawable itself should have an alpha value. 369 mIcon.setAlpha(128); 370 return mIcon; 371 } 372 } 373} 374