Home | History | Annotate | Download | only in pinyin
      1 /*
      2  * Copyright (C) 2009 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 
     17 package com.android.inputmethod.pinyin;
     18 
     19 import com.android.inputmethod.pinyin.SoftKeyboard.KeyRow;
     20 
     21 import java.util.List;
     22 
     23 import android.content.Context;
     24 import android.graphics.Canvas;
     25 import android.graphics.Paint;
     26 import android.graphics.Rect;
     27 import android.graphics.Paint.FontMetricsInt;
     28 import android.graphics.drawable.Drawable;
     29 import android.os.Vibrator;
     30 import android.util.AttributeSet;
     31 import android.view.View;
     32 
     33 /**
     34  * Class used to show a soft keyboard.
     35  *
     36  * A soft keyboard view should not handle touch event itself, because we do bias
     37  * correction, need a global strategy to map an event into a proper view to
     38  * achieve better user experience.
     39  */
     40 public class SoftKeyboardView extends View {
     41     /**
     42      * The definition of the soft keyboard for the current this soft keyboard
     43      * view.
     44      */
     45     private SoftKeyboard mSoftKeyboard;
     46 
     47     /**
     48      * The popup balloon hint for key press/release.
     49      */
     50     private BalloonHint mBalloonPopup;
     51 
     52     /**
     53      * The on-key balloon hint for key press/release. If it is null, on-key
     54      * highlight will be drawn on th soft keyboard view directly.
     55      */
     56     private BalloonHint mBalloonOnKey;
     57 
     58     /** Used to play key sounds. */
     59     private SoundManager mSoundManager;
     60 
     61     /** The last key pressed. */
     62     private SoftKey mSoftKeyDown;
     63 
     64     /** Used to indicate whether the user is holding on a key. */
     65     private boolean mKeyPressed = false;
     66 
     67     /**
     68      * The location offset of the view to the keyboard container.
     69      */
     70     private int mOffsetToSkbContainer[] = new int[2];
     71 
     72     /**
     73      * The location of the desired hint view to the keyboard container.
     74      */
     75     private int mHintLocationToSkbContainer[] = new int[2];
     76 
     77     /**
     78      * Text size for normal key.
     79      */
     80     private int mNormalKeyTextSize;
     81 
     82     /**
     83      * Text size for function key.
     84      */
     85     private int mFunctionKeyTextSize;
     86 
     87     /**
     88      * Long press timer used to response long-press.
     89      */
     90     private SkbContainer.LongPressTimer mLongPressTimer;
     91 
     92     /**
     93      * Repeated events for long press
     94      */
     95     private boolean mRepeatForLongPress = false;
     96 
     97     /**
     98      * If this parameter is true, the balloon will never be dismissed even if
     99      * user moves a lot from the pressed point.
    100      */
    101     private boolean mMovingNeverHidePopupBalloon = false;
    102 
    103     /** Vibration for key press. */
    104     private Vibrator mVibrator;
    105 
    106     /** Vibration pattern for key press. */
    107     protected long[] mVibratePattern = new long[] {1, 20};
    108 
    109     /**
    110      * The dirty rectangle used to mark the area to re-draw during key press and
    111      * release. Currently, whenever we can invalidate(Rect), view will call
    112      * onDraw() and we MUST draw the whole view. This dirty information is for
    113      * future use.
    114      */
    115     private Rect mDirtyRect = new Rect();
    116 
    117     private Paint mPaint;
    118     private FontMetricsInt mFmi;
    119     private boolean mDimSkb;
    120 
    121     public SoftKeyboardView(Context context, AttributeSet attrs) {
    122         super(context, attrs);
    123 
    124         mSoundManager = SoundManager.getInstance(mContext);
    125 
    126         mPaint = new Paint();
    127         mPaint.setAntiAlias(true);
    128         mFmi = mPaint.getFontMetricsInt();
    129     }
    130 
    131     public boolean setSoftKeyboard(SoftKeyboard softSkb) {
    132         if (null == softSkb) {
    133             return false;
    134         }
    135         mSoftKeyboard = softSkb;
    136         Drawable bg = softSkb.getSkbBackground();
    137         if (null != bg) setBackgroundDrawable(bg);
    138         return true;
    139     }
    140 
    141     public SoftKeyboard getSoftKeyboard() {
    142         return mSoftKeyboard;
    143     }
    144 
    145     public void resizeKeyboard(int skbWidth, int skbHeight) {
    146         mSoftKeyboard.setSkbCoreSize(skbWidth, skbHeight);
    147     }
    148 
    149     public void setBalloonHint(BalloonHint balloonOnKey,
    150             BalloonHint balloonPopup, boolean movingNeverHidePopup) {
    151         mBalloonOnKey = balloonOnKey;
    152         mBalloonPopup = balloonPopup;
    153         mMovingNeverHidePopupBalloon = movingNeverHidePopup;
    154     }
    155 
    156     public void setOffsetToSkbContainer(int offsetToSkbContainer[]) {
    157         mOffsetToSkbContainer[0] = offsetToSkbContainer[0];
    158         mOffsetToSkbContainer[1] = offsetToSkbContainer[1];
    159     }
    160 
    161     @Override
    162     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    163         int measuredWidth = 0;
    164         int measuredHeight = 0;
    165         if (null != mSoftKeyboard) {
    166             measuredWidth = mSoftKeyboard.getSkbCoreWidth();
    167             measuredHeight = mSoftKeyboard.getSkbCoreHeight();
    168             measuredWidth += mPaddingLeft + mPaddingRight;
    169             measuredHeight += mPaddingTop + mPaddingBottom;
    170         }
    171         setMeasuredDimension(measuredWidth, measuredHeight);
    172     }
    173 
    174     private void showBalloon(BalloonHint balloon, int balloonLocationToSkb[],
    175             boolean movePress) {
    176         long delay = BalloonHint.TIME_DELAY_SHOW;
    177         if (movePress) delay = 0;
    178         if (balloon.needForceDismiss()) {
    179             balloon.delayedDismiss(0);
    180         }
    181         if (!balloon.isShowing()) {
    182             balloon.delayedShow(delay, balloonLocationToSkb);
    183         } else {
    184             balloon.delayedUpdate(delay, balloonLocationToSkb, balloon
    185                     .getWidth(), balloon.getHeight());
    186         }
    187         long b = System.currentTimeMillis();
    188     }
    189 
    190     public void resetKeyPress(long balloonDelay) {
    191         if (!mKeyPressed) return;
    192         mKeyPressed = false;
    193         if (null != mBalloonOnKey) {
    194             mBalloonOnKey.delayedDismiss(balloonDelay);
    195         } else {
    196             if (null != mSoftKeyDown) {
    197                 if (mDirtyRect.isEmpty()) {
    198                     mDirtyRect.set(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
    199                             mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
    200                 }
    201                 invalidate(mDirtyRect);
    202             } else {
    203                 invalidate();
    204             }
    205         }
    206         mBalloonPopup.delayedDismiss(balloonDelay);
    207     }
    208 
    209     // If movePress is true, means that this function is called because user
    210     // moves his finger to this button. If movePress is false, means that this
    211     // function is called when user just presses this key.
    212     public SoftKey onKeyPress(int x, int y,
    213             SkbContainer.LongPressTimer longPressTimer, boolean movePress) {
    214         mKeyPressed = false;
    215         boolean moveWithinPreviousKey = false;
    216         if (movePress) {
    217             SoftKey newKey = mSoftKeyboard.mapToKey(x, y);
    218             if (newKey == mSoftKeyDown) moveWithinPreviousKey = true;
    219             mSoftKeyDown = newKey;
    220         } else {
    221             mSoftKeyDown = mSoftKeyboard.mapToKey(x, y);
    222         }
    223         if (moveWithinPreviousKey || null == mSoftKeyDown) return mSoftKeyDown;
    224         mKeyPressed = true;
    225 
    226         if (!movePress) {
    227             tryPlayKeyDown();
    228             tryVibrate();
    229         }
    230 
    231         mLongPressTimer = longPressTimer;
    232 
    233         if (!movePress) {
    234             if (mSoftKeyDown.getPopupResId() > 0 || mSoftKeyDown.repeatable()) {
    235                 mLongPressTimer.startTimer();
    236             }
    237         } else {
    238             mLongPressTimer.removeTimer();
    239         }
    240 
    241         int desired_width;
    242         int desired_height;
    243         float textSize;
    244         Environment env = Environment.getInstance();
    245 
    246         if (null != mBalloonOnKey) {
    247             Drawable keyHlBg = mSoftKeyDown.getKeyHlBg();
    248             mBalloonOnKey.setBalloonBackground(keyHlBg);
    249 
    250             // Prepare the on-key balloon
    251             int keyXMargin = mSoftKeyboard.getKeyXMargin();
    252             int keyYMargin = mSoftKeyboard.getKeyYMargin();
    253             desired_width = mSoftKeyDown.width() - 2 * keyXMargin;
    254             desired_height = mSoftKeyDown.height() - 2 * keyYMargin;
    255             textSize = env
    256                     .getKeyTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId);
    257             Drawable icon = mSoftKeyDown.getKeyIcon();
    258             if (null != icon) {
    259                 mBalloonOnKey.setBalloonConfig(icon, desired_width,
    260                         desired_height);
    261             } else {
    262                 mBalloonOnKey.setBalloonConfig(mSoftKeyDown.getKeyLabel(),
    263                         textSize, true, mSoftKeyDown.getColorHl(),
    264                         desired_width, desired_height);
    265             }
    266 
    267             mHintLocationToSkbContainer[0] = mPaddingLeft + mSoftKeyDown.mLeft
    268                     - (mBalloonOnKey.getWidth() - mSoftKeyDown.width()) / 2;
    269             mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0];
    270             mHintLocationToSkbContainer[1] = mPaddingTop
    271                     + (mSoftKeyDown.mBottom - keyYMargin)
    272                     - mBalloonOnKey.getHeight();
    273             mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1];
    274             showBalloon(mBalloonOnKey, mHintLocationToSkbContainer, movePress);
    275         } else {
    276             mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
    277                     mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
    278             invalidate(mDirtyRect);
    279         }
    280 
    281         // Prepare the popup balloon
    282         if (mSoftKeyDown.needBalloon()) {
    283             Drawable balloonBg = mSoftKeyboard.getBalloonBackground();
    284             mBalloonPopup.setBalloonBackground(balloonBg);
    285 
    286             desired_width = mSoftKeyDown.width() + env.getKeyBalloonWidthPlus();
    287             desired_height = mSoftKeyDown.height()
    288                     + env.getKeyBalloonHeightPlus();
    289             textSize = env
    290                     .getBalloonTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId);
    291             Drawable iconPopup = mSoftKeyDown.getKeyIconPopup();
    292             if (null != iconPopup) {
    293                 mBalloonPopup.setBalloonConfig(iconPopup, desired_width,
    294                         desired_height);
    295             } else {
    296                 mBalloonPopup.setBalloonConfig(mSoftKeyDown.getKeyLabel(),
    297                         textSize, mSoftKeyDown.needBalloon(), mSoftKeyDown
    298                                 .getColorBalloon(), desired_width,
    299                         desired_height);
    300             }
    301 
    302             // The position to show.
    303             mHintLocationToSkbContainer[0] = mPaddingLeft + mSoftKeyDown.mLeft
    304                     + -(mBalloonPopup.getWidth() - mSoftKeyDown.width()) / 2;
    305             mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0];
    306             mHintLocationToSkbContainer[1] = mPaddingTop + mSoftKeyDown.mTop
    307                     - mBalloonPopup.getHeight();
    308             mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1];
    309             showBalloon(mBalloonPopup, mHintLocationToSkbContainer, movePress);
    310         } else {
    311             mBalloonPopup.delayedDismiss(0);
    312         }
    313 
    314         if (mRepeatForLongPress) longPressTimer.startTimer();
    315         return mSoftKeyDown;
    316     }
    317 
    318     public SoftKey onKeyRelease(int x, int y) {
    319         mKeyPressed = false;
    320         if (null == mSoftKeyDown) return null;
    321 
    322         mLongPressTimer.removeTimer();
    323 
    324         if (null != mBalloonOnKey) {
    325             mBalloonOnKey.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
    326         } else {
    327             mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
    328                     mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
    329             invalidate(mDirtyRect);
    330         }
    331 
    332         if (mSoftKeyDown.needBalloon()) {
    333             mBalloonPopup.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
    334         }
    335 
    336         if (mSoftKeyDown.moveWithinKey(x - mPaddingLeft, y - mPaddingTop)) {
    337             return mSoftKeyDown;
    338         }
    339         return null;
    340     }
    341 
    342     public SoftKey onKeyMove(int x, int y) {
    343         if (null == mSoftKeyDown) return null;
    344 
    345         if (mSoftKeyDown.moveWithinKey(x - mPaddingLeft, y - mPaddingTop)) {
    346             return mSoftKeyDown;
    347         }
    348 
    349         // The current key needs to be updated.
    350         mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
    351                 mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
    352 
    353         if (mRepeatForLongPress) {
    354             if (mMovingNeverHidePopupBalloon) {
    355                 return onKeyPress(x, y, mLongPressTimer, true);
    356             }
    357 
    358             if (null != mBalloonOnKey) {
    359                 mBalloonOnKey.delayedDismiss(0);
    360             } else {
    361                 invalidate(mDirtyRect);
    362             }
    363 
    364             if (mSoftKeyDown.needBalloon()) {
    365                 mBalloonPopup.delayedDismiss(0);
    366             }
    367 
    368             if (null != mLongPressTimer) {
    369                 mLongPressTimer.removeTimer();
    370             }
    371             return onKeyPress(x, y, mLongPressTimer, true);
    372         } else {
    373             // When user moves between keys, repeated response is disabled.
    374             return onKeyPress(x, y, mLongPressTimer, true);
    375         }
    376     }
    377 
    378     private void tryVibrate() {
    379         if (!Settings.getVibrate()) {
    380             return;
    381         }
    382         if (mVibrator == null) {
    383             mVibrator = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
    384         }
    385         mVibrator.vibrate(mVibratePattern, -1);
    386     }
    387 
    388     private void tryPlayKeyDown() {
    389         if (Settings.getKeySound()) {
    390             mSoundManager.playKeyDown();
    391         }
    392     }
    393 
    394     public void dimSoftKeyboard(boolean dimSkb) {
    395         mDimSkb = dimSkb;
    396         invalidate();
    397     }
    398 
    399     @Override
    400     protected void onDraw(Canvas canvas) {
    401         if (null == mSoftKeyboard) return;
    402 
    403         canvas.translate(mPaddingLeft, mPaddingTop);
    404 
    405         Environment env = Environment.getInstance();
    406         mNormalKeyTextSize = env.getKeyTextSize(false);
    407         mFunctionKeyTextSize = env.getKeyTextSize(true);
    408         // Draw the last soft keyboard
    409         int rowNum = mSoftKeyboard.getRowNum();
    410         int keyXMargin = mSoftKeyboard.getKeyXMargin();
    411         int keyYMargin = mSoftKeyboard.getKeyYMargin();
    412         for (int row = 0; row < rowNum; row++) {
    413             KeyRow keyRow = mSoftKeyboard.getKeyRowForDisplay(row);
    414             if (null == keyRow) continue;
    415             List<SoftKey> softKeys = keyRow.mSoftKeys;
    416             int keyNum = softKeys.size();
    417             for (int i = 0; i < keyNum; i++) {
    418                 SoftKey softKey = softKeys.get(i);
    419                 if (SoftKeyType.KEYTYPE_ID_NORMAL_KEY == softKey.mKeyType.mKeyTypeId) {
    420                     mPaint.setTextSize(mNormalKeyTextSize);
    421                 } else {
    422                     mPaint.setTextSize(mFunctionKeyTextSize);
    423                 }
    424                 drawSoftKey(canvas, softKey, keyXMargin, keyYMargin);
    425             }
    426         }
    427 
    428         if (mDimSkb) {
    429             mPaint.setColor(0xa0000000);
    430             canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    431         }
    432 
    433         mDirtyRect.setEmpty();
    434     }
    435 
    436     private void drawSoftKey(Canvas canvas, SoftKey softKey, int keyXMargin,
    437             int keyYMargin) {
    438         Drawable bg;
    439         int textColor;
    440         if (mKeyPressed && softKey == mSoftKeyDown) {
    441             bg = softKey.getKeyHlBg();
    442             textColor = softKey.getColorHl();
    443         } else {
    444             bg = softKey.getKeyBg();
    445             textColor = softKey.getColor();
    446         }
    447 
    448         if (null != bg) {
    449             bg.setBounds(softKey.mLeft + keyXMargin, softKey.mTop + keyYMargin,
    450                     softKey.mRight - keyXMargin, softKey.mBottom - keyYMargin);
    451             bg.draw(canvas);
    452         }
    453 
    454         String keyLabel = softKey.getKeyLabel();
    455         Drawable keyIcon = softKey.getKeyIcon();
    456         if (null != keyIcon) {
    457             Drawable icon = keyIcon;
    458             int marginLeft = (softKey.width() - icon.getIntrinsicWidth()) / 2;
    459             int marginRight = softKey.width() - icon.getIntrinsicWidth()
    460                     - marginLeft;
    461             int marginTop = (softKey.height() - icon.getIntrinsicHeight()) / 2;
    462             int marginBottom = softKey.height() - icon.getIntrinsicHeight()
    463                     - marginTop;
    464             icon.setBounds(softKey.mLeft + marginLeft,
    465                     softKey.mTop + marginTop, softKey.mRight - marginRight,
    466                     softKey.mBottom - marginBottom);
    467             icon.draw(canvas);
    468         } else if (null != keyLabel) {
    469             mPaint.setColor(textColor);
    470             float x = softKey.mLeft
    471                     + (softKey.width() - mPaint.measureText(keyLabel)) / 2.0f;
    472             int fontHeight = mFmi.bottom - mFmi.top;
    473             float marginY = (softKey.height() - fontHeight) / 2.0f;
    474             float y = softKey.mTop + marginY - mFmi.top + mFmi.bottom / 1.5f;
    475             canvas.drawText(keyLabel, x, y + 1, mPaint);
    476         }
    477     }
    478 }
    479