MoreKeysKeyboard.java revision a729377395967f7652d93992cbcf50cd2ff522d1
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.KeyPreviewDrawParams; 25import com.android.inputmethod.keyboard.internal.KeyboardBuilder; 26import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 27import com.android.inputmethod.keyboard.internal.KeyboardParams; 28import com.android.inputmethod.keyboard.internal.MoreKeySpec; 29import com.android.inputmethod.latin.R; 30import com.android.inputmethod.latin.StringUtils; 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 < maxColumns) { 77 throw new IllegalArgumentException( 78 "Keyboard is too small to hold more keys keyboard: " 79 + parentKeyboardWidth + " " + keyWidth + " " + maxColumns); 80 } 81 mDefaultKeyWidth = keyWidth; 82 mDefaultRowHeight = rowHeight; 83 84 final int numRows = (numKeys + maxColumns - 1) / maxColumns; 85 mNumRows = numRows; 86 final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns) 87 : getOptimizedColumns(numKeys, maxColumns); 88 mNumColumns = numColumns; 89 final int topKeys = numKeys % numColumns; 90 mTopKeys = topKeys == 0 ? numColumns : topKeys; 91 92 final int numLeftKeys = (numColumns - 1) / 2; 93 final int numRightKeys = numColumns - numLeftKeys; // including default key. 94 // Maximum number of keys we can layout both side of the parent key 95 final int maxLeftKeys = coordXInParent / keyWidth; 96 final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; 97 int leftKeys, rightKeys; 98 if (numLeftKeys > maxLeftKeys) { 99 leftKeys = maxLeftKeys; 100 rightKeys = numColumns - leftKeys; 101 } else if (numRightKeys > maxRightKeys + 1) { 102 rightKeys = maxRightKeys + 1; // include default key 103 leftKeys = numColumns - rightKeys; 104 } else { 105 leftKeys = numLeftKeys; 106 rightKeys = numRightKeys; 107 } 108 // If the left keys fill the left side of the parent key, entire more keys keyboard 109 // should be shifted to the right unless the parent key is on the left edge. 110 if (maxLeftKeys == leftKeys && leftKeys > 0) { 111 leftKeys--; 112 rightKeys++; 113 } 114 // If the right keys fill the right side of the parent key, entire more keys 115 // should be shifted to the left unless the parent key is on the right edge. 116 if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { 117 leftKeys++; 118 rightKeys--; 119 } 120 mLeftKeys = leftKeys; 121 mRightKeys = rightKeys; 122 123 // Adjustment of the top row. 124 mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment() 125 : getAutoOrderTopRowAdjustment(); 126 mDividerWidth = dividerWidth; 127 mColumnWidth = mDefaultKeyWidth + mDividerWidth; 128 mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth; 129 // Need to subtract the bottom row's gutter only. 130 mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap 131 + mTopPadding + mBottomPadding; 132 } 133 134 private int getFixedOrderTopRowAdjustment() { 135 if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns 136 || mLeftKeys == 0 || mRightKeys == 1) { 137 return 0; 138 } 139 return -1; 140 } 141 142 private int getAutoOrderTopRowAdjustment() { 143 if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 144 || mLeftKeys == 0 || mRightKeys == 1) { 145 return 0; 146 } 147 return -1; 148 } 149 150 // Return key position according to column count (0 is default). 151 /* package */int getColumnPos(final int n) { 152 return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); 153 } 154 155 private int getFixedOrderColumnPos(final int n) { 156 final int col = n % mNumColumns; 157 final int row = n / mNumColumns; 158 if (!isTopRow(row)) { 159 return col - mLeftKeys; 160 } 161 final int rightSideKeys = mTopKeys / 2; 162 final int leftSideKeys = mTopKeys - (rightSideKeys + 1); 163 final int pos = col - leftSideKeys; 164 final int numLeftKeys = mLeftKeys + mTopRowAdjustment; 165 final int numRightKeys = mRightKeys - 1; 166 if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { 167 return pos; 168 } else if (numRightKeys < rightSideKeys) { 169 return pos - (rightSideKeys - numRightKeys); 170 } else { // numLeftKeys < leftSideKeys 171 return pos + (leftSideKeys - numLeftKeys); 172 } 173 } 174 175 private int getAutomaticColumnPos(final int n) { 176 final int col = n % mNumColumns; 177 final int row = n / mNumColumns; 178 int leftKeys = mLeftKeys; 179 if (isTopRow(row)) { 180 leftKeys += mTopRowAdjustment; 181 } 182 if (col == 0) { 183 // default position. 184 return 0; 185 } 186 187 int pos = 0; 188 int right = 1; // include default position key. 189 int left = 0; 190 int i = 0; 191 while (true) { 192 // Assign right key if available. 193 if (right < mRightKeys) { 194 pos = right; 195 right++; 196 i++; 197 } 198 if (i >= col) 199 break; 200 // Assign left key if available. 201 if (left < leftKeys) { 202 left++; 203 pos = -left; 204 i++; 205 } 206 if (i >= col) 207 break; 208 } 209 return pos; 210 } 211 212 private static int getTopRowEmptySlots(final int numKeys, final int numColumns) { 213 final int remainings = numKeys % numColumns; 214 return remainings == 0 ? 0 : numColumns - remainings; 215 } 216 217 private int getOptimizedColumns(final int numKeys, final int maxColumns) { 218 int numColumns = Math.min(numKeys, maxColumns); 219 while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { 220 numColumns--; 221 } 222 return numColumns; 223 } 224 225 public int getDefaultKeyCoordX() { 226 return mLeftKeys * mColumnWidth; 227 } 228 229 public int getX(final int n, final int row) { 230 final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); 231 if (isTopRow(row)) { 232 return x + mTopRowAdjustment * (mColumnWidth / 2); 233 } 234 return x; 235 } 236 237 public int getY(final int row) { 238 return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; 239 } 240 241 public void markAsEdgeKey(final Key key, final int row) { 242 if (row == 0) 243 key.markAsTopEdge(this); 244 if (isTopRow(row)) 245 key.markAsBottomEdge(this); 246 } 247 248 private boolean isTopRow(final int rowCount) { 249 return mNumRows > 1 && rowCount == mNumRows - 1; 250 } 251 } 252 253 public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> { 254 private final Key mParentKey; 255 private final Drawable mDivider; 256 257 private static final float LABEL_PADDING_RATIO = 0.2f; 258 private static final float DIVIDER_RATIO = 0.2f; 259 260 261 /** 262 * The builder of MoreKeysKeyboard. 263 * @param context the context of {@link MoreKeysKeyboardView}. 264 * @param parentKey the {@link Key} that invokes more keys keyboard. 265 * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey. 266 * @param keyPreviewDrawParams the parameter to place key preview. 267 */ 268 public Builder(final Context context, final Key parentKey, 269 final MainKeyboardView parentKeyboardView, 270 final KeyPreviewDrawParams keyPreviewDrawParams) { 271 super(context, new MoreKeysKeyboardParams()); 272 final Keyboard parentKeyboard = parentKeyboardView.getKeyboard(); 273 load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId); 274 275 // TODO: More keys keyboard's vertical gap is currently calculated heuristically. 276 // Should revise the algorithm. 277 mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2; 278 mParentKey = parentKey; 279 280 final int width, height; 281 final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled() 282 && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1; 283 if (singleMoreKeyWithPreview) { 284 // Use pre-computed width and height if this more keys keyboard has only one key to 285 // mitigate visual flicker between key preview and more keys keyboard. 286 // Caveats for the visual assets: To achieve this effect, both the key preview 287 // backgrounds and the more keys keyboard panel background have the exact same 288 // left/right/top paddings. The bottom paddings of both backgrounds don't need to 289 // be considered because the vertical positions of both backgrounds were already 290 // adjusted with their bottom paddings deducted. 291 width = keyPreviewDrawParams.mPreviewVisibleWidth; 292 height = keyPreviewDrawParams.mPreviewVisibleHeight 293 + mParams.mVerticalGap; 294 } else { 295 width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth); 296 height = parentKeyboard.mMostCommonKeyHeight; 297 } 298 final int dividerWidth; 299 if (parentKey.needsDividersInMoreKeys()) { 300 mDivider = mResources.getDrawable(R.drawable.more_keys_divider); 301 dividerWidth = (int)(width * DIVIDER_RATIO); 302 } else { 303 mDivider = null; 304 dividerWidth = 0; 305 } 306 mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(), 307 width, height, parentKey.mX + parentKey.mWidth / 2, 308 parentKeyboardView.getMeasuredWidth(), parentKey.isFixedColumnOrderMoreKeys(), 309 dividerWidth); 310 } 311 312 private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey, 313 final int minKeyWidth) { 314 final int padding = (int)(view.getResources() 315 .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding) 316 + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0)); 317 final Paint paint = view.newDefaultLabelPaint(); 318 paint.setTypeface(parentKey.selectTypeface(view.mKeyDrawParams)); 319 paint.setTextSize(parentKey.selectMoreKeyTextSize(view.mKeyDrawParams)); 320 int maxWidth = minKeyWidth; 321 for (final MoreKeySpec spec : parentKey.mMoreKeys) { 322 final String label = spec.mLabel; 323 // If the label is single letter, minKeyWidth is enough to hold the label. 324 if (label != null && StringUtils.codePointCount(label) > 1) { 325 final int width = (int)view.getLabelWidth(label, paint) + padding; 326 if (maxWidth < width) { 327 maxWidth = width; 328 } 329 } 330 } 331 return maxWidth; 332 } 333 334 @Override 335 public MoreKeysKeyboard build() { 336 final MoreKeysKeyboardParams params = mParams; 337 final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags(); 338 final MoreKeySpec[] moreKeys = mParentKey.mMoreKeys; 339 for (int n = 0; n < moreKeys.length; n++) { 340 final MoreKeySpec moreKeySpec = moreKeys[n]; 341 final int row = n / params.mNumColumns; 342 final int x = params.getX(n, row); 343 final int y = params.getY(row); 344 final Key key = new Key(params, moreKeySpec, x, y, 345 params.mDefaultKeyWidth, params.mDefaultRowHeight, moreKeyFlags); 346 params.markAsEdgeKey(key, row); 347 params.onAddKey(key); 348 349 final int pos = params.getColumnPos(n); 350 // The "pos" value represents the offset from the default position. Negative means 351 // left of the default position. 352 if (params.mDividerWidth > 0 && pos != 0) { 353 final int dividerX = (pos > 0) ? x - params.mDividerWidth 354 : x + params.mDefaultKeyWidth; 355 final Key divider = new MoreKeyDivider(params, mDivider, dividerX, y); 356 params.onAddKey(divider); 357 } 358 } 359 return new MoreKeysKeyboard(params); 360 } 361 } 362 363 private static class MoreKeyDivider extends Key.Spacer { 364 private final Drawable mIcon; 365 366 public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon, 367 final int x, final int y) { 368 super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight); 369 mIcon = icon; 370 } 371 372 @Override 373 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 374 // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the 375 // constructor. 376 // TODO: Drawable itself should have an alpha value. 377 mIcon.setAlpha(128); 378 return mIcon; 379 } 380 } 381} 382