Home | History | Annotate | Download | only in softkeyboard
      1 /*
      2  * Copyright (C) 2008-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.example.android.softkeyboard;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Canvas;
     22 import android.graphics.Paint;
     23 import android.graphics.Rect;
     24 import android.graphics.drawable.Drawable;
     25 import android.view.GestureDetector;
     26 import android.view.MotionEvent;
     27 import android.view.View;
     28 
     29 import java.util.ArrayList;
     30 import java.util.List;
     31 
     32 public class CandidateView extends View {
     33 
     34     private static final int OUT_OF_BOUNDS = -1;
     35 
     36     private SoftKeyboard mService;
     37     private List<String> mSuggestions;
     38     private int mSelectedIndex;
     39     private int mTouchX = OUT_OF_BOUNDS;
     40     private Drawable mSelectionHighlight;
     41     private boolean mTypedWordValid;
     42 
     43     private Rect mBgPadding;
     44 
     45     private static final int MAX_SUGGESTIONS = 32;
     46     private static final int SCROLL_PIXELS = 20;
     47 
     48     private int[] mWordWidth = new int[MAX_SUGGESTIONS];
     49     private int[] mWordX = new int[MAX_SUGGESTIONS];
     50 
     51     private static final int X_GAP = 10;
     52 
     53     private static final List<String> EMPTY_LIST = new ArrayList<String>();
     54 
     55     private int mColorNormal;
     56     private int mColorRecommended;
     57     private int mColorOther;
     58     private int mVerticalPadding;
     59     private Paint mPaint;
     60     private boolean mScrolled;
     61     private int mTargetScrollX;
     62 
     63     private int mTotalWidth;
     64 
     65     private GestureDetector mGestureDetector;
     66 
     67     /**
     68      * Construct a CandidateView for showing suggested words for completion.
     69      * @param context
     70      * @param attrs
     71      */
     72     public CandidateView(Context context) {
     73         super(context);
     74         mSelectionHighlight = context.getResources().getDrawable(
     75                 android.R.drawable.list_selector_background);
     76         mSelectionHighlight.setState(new int[] {
     77                 android.R.attr.state_enabled,
     78                 android.R.attr.state_focused,
     79                 android.R.attr.state_window_focused,
     80                 android.R.attr.state_pressed
     81         });
     82 
     83         Resources r = context.getResources();
     84 
     85         setBackgroundColor(r.getColor(R.color.candidate_background));
     86 
     87         mColorNormal = r.getColor(R.color.candidate_normal);
     88         mColorRecommended = r.getColor(R.color.candidate_recommended);
     89         mColorOther = r.getColor(R.color.candidate_other);
     90         mVerticalPadding = r.getDimensionPixelSize(R.dimen.candidate_vertical_padding);
     91 
     92         mPaint = new Paint();
     93         mPaint.setColor(mColorNormal);
     94         mPaint.setAntiAlias(true);
     95         mPaint.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_font_height));
     96         mPaint.setStrokeWidth(0);
     97 
     98         mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
     99             @Override
    100             public boolean onScroll(MotionEvent e1, MotionEvent e2,
    101                     float distanceX, float distanceY) {
    102                 mScrolled = true;
    103                 int sx = getScrollX();
    104                 sx += distanceX;
    105                 if (sx < 0) {
    106                     sx = 0;
    107                 }
    108                 if (sx + getWidth() > mTotalWidth) {
    109                     sx -= distanceX;
    110                 }
    111                 mTargetScrollX = sx;
    112                 scrollTo(sx, getScrollY());
    113                 invalidate();
    114                 return true;
    115             }
    116         });
    117         setHorizontalFadingEdgeEnabled(true);
    118         setWillNotDraw(false);
    119         setHorizontalScrollBarEnabled(false);
    120         setVerticalScrollBarEnabled(false);
    121     }
    122 
    123     /**
    124      * A connection back to the service to communicate with the text field
    125      * @param listener
    126      */
    127     public void setService(SoftKeyboard listener) {
    128         mService = listener;
    129     }
    130 
    131     @Override
    132     public int computeHorizontalScrollRange() {
    133         return mTotalWidth;
    134     }
    135 
    136     @Override
    137     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    138         int measuredWidth = resolveSize(50, widthMeasureSpec);
    139 
    140         // Get the desired height of the icon menu view (last row of items does
    141         // not have a divider below)
    142         Rect padding = new Rect();
    143         mSelectionHighlight.getPadding(padding);
    144         final int desiredHeight = ((int)mPaint.getTextSize()) + mVerticalPadding
    145                 + padding.top + padding.bottom;
    146 
    147         // Maximum possible width and desired height
    148         setMeasuredDimension(measuredWidth,
    149                 resolveSize(desiredHeight, heightMeasureSpec));
    150     }
    151 
    152     /**
    153      * If the canvas is null, then only touch calculations are performed to pick the target
    154      * candidate.
    155      */
    156     @Override
    157     protected void onDraw(Canvas canvas) {
    158         if (canvas != null) {
    159             super.onDraw(canvas);
    160         }
    161         mTotalWidth = 0;
    162         if (mSuggestions == null) return;
    163 
    164         if (mBgPadding == null) {
    165             mBgPadding = new Rect(0, 0, 0, 0);
    166             if (getBackground() != null) {
    167                 getBackground().getPadding(mBgPadding);
    168             }
    169         }
    170         int x = 0;
    171         final int count = mSuggestions.size();
    172         final int height = getHeight();
    173         final Rect bgPadding = mBgPadding;
    174         final Paint paint = mPaint;
    175         final int touchX = mTouchX;
    176         final int scrollX = getScrollX();
    177         final boolean scrolled = mScrolled;
    178         final boolean typedWordValid = mTypedWordValid;
    179         final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent());
    180 
    181         for (int i = 0; i < count; i++) {
    182             String suggestion = mSuggestions.get(i);
    183             float textWidth = paint.measureText(suggestion);
    184             final int wordWidth = (int) textWidth + X_GAP * 2;
    185 
    186             mWordX[i] = x;
    187             mWordWidth[i] = wordWidth;
    188             paint.setColor(mColorNormal);
    189             if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) {
    190                 if (canvas != null) {
    191                     canvas.translate(x, 0);
    192                     mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height);
    193                     mSelectionHighlight.draw(canvas);
    194                     canvas.translate(-x, 0);
    195                 }
    196                 mSelectedIndex = i;
    197             }
    198 
    199             if (canvas != null) {
    200                 if ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid)) {
    201                     paint.setFakeBoldText(true);
    202                     paint.setColor(mColorRecommended);
    203                 } else if (i != 0) {
    204                     paint.setColor(mColorOther);
    205                 }
    206                 canvas.drawText(suggestion, x + X_GAP, y, paint);
    207                 paint.setColor(mColorOther);
    208                 canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top,
    209                         x + wordWidth + 0.5f, height + 1, paint);
    210                 paint.setFakeBoldText(false);
    211             }
    212             x += wordWidth;
    213         }
    214         mTotalWidth = x;
    215         if (mTargetScrollX != getScrollX()) {
    216             scrollToTarget();
    217         }
    218     }
    219 
    220     private void scrollToTarget() {
    221         int sx = getScrollX();
    222         if (mTargetScrollX > sx) {
    223             sx += SCROLL_PIXELS;
    224             if (sx >= mTargetScrollX) {
    225                 sx = mTargetScrollX;
    226                 requestLayout();
    227             }
    228         } else {
    229             sx -= SCROLL_PIXELS;
    230             if (sx <= mTargetScrollX) {
    231                 sx = mTargetScrollX;
    232                 requestLayout();
    233             }
    234         }
    235         scrollTo(sx, getScrollY());
    236         invalidate();
    237     }
    238 
    239     public void setSuggestions(List<String> suggestions, boolean completions,
    240             boolean typedWordValid) {
    241         clear();
    242         if (suggestions != null) {
    243             mSuggestions = new ArrayList<String>(suggestions);
    244         }
    245         mTypedWordValid = typedWordValid;
    246         scrollTo(0, 0);
    247         mTargetScrollX = 0;
    248         // Compute the total width
    249         onDraw(null);
    250         invalidate();
    251         requestLayout();
    252     }
    253 
    254     public void clear() {
    255         mSuggestions = EMPTY_LIST;
    256         mTouchX = OUT_OF_BOUNDS;
    257         mSelectedIndex = -1;
    258         invalidate();
    259     }
    260 
    261     @Override
    262     public boolean onTouchEvent(MotionEvent me) {
    263 
    264         if (mGestureDetector.onTouchEvent(me)) {
    265             return true;
    266         }
    267 
    268         int action = me.getAction();
    269         int x = (int) me.getX();
    270         int y = (int) me.getY();
    271         mTouchX = x;
    272 
    273         switch (action) {
    274         case MotionEvent.ACTION_DOWN:
    275             mScrolled = false;
    276             invalidate();
    277             break;
    278         case MotionEvent.ACTION_MOVE:
    279             if (y <= 0) {
    280                 // Fling up!?
    281                 if (mSelectedIndex >= 0) {
    282                     mService.pickSuggestionManually(mSelectedIndex);
    283                     mSelectedIndex = -1;
    284                 }
    285             }
    286             invalidate();
    287             break;
    288         case MotionEvent.ACTION_UP:
    289             if (!mScrolled) {
    290                 if (mSelectedIndex >= 0) {
    291                     mService.pickSuggestionManually(mSelectedIndex);
    292                 }
    293             }
    294             mSelectedIndex = -1;
    295             removeHighlight();
    296             requestLayout();
    297             break;
    298         }
    299         return true;
    300     }
    301 
    302     /**
    303      * For flick through from keyboard, call this method with the x coordinate of the flick
    304      * gesture.
    305      * @param x
    306      */
    307     public void takeSuggestionAt(float x) {
    308         mTouchX = (int) x;
    309         // To detect candidate
    310         onDraw(null);
    311         if (mSelectedIndex >= 0) {
    312             mService.pickSuggestionManually(mSelectedIndex);
    313         }
    314         invalidate();
    315     }
    316 
    317     private void removeHighlight() {
    318         mTouchX = OUT_OF_BOUNDS;
    319         invalidate();
    320     }
    321 }
    322