| /* |
| * Copyright (C) 2008-2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.softkeyboard; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.view.GestureDetector; |
| import android.view.MotionEvent; |
| import android.view.View; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class CandidateView extends View { |
| |
| private static final int OUT_OF_BOUNDS = -1; |
| |
| private SoftKeyboard mService; |
| private List<String> mSuggestions; |
| private int mSelectedIndex; |
| private int mTouchX = OUT_OF_BOUNDS; |
| private Drawable mSelectionHighlight; |
| private boolean mTypedWordValid; |
| |
| private Rect mBgPadding; |
| |
| private static final int MAX_SUGGESTIONS = 32; |
| private static final int SCROLL_PIXELS = 20; |
| |
| private int[] mWordWidth = new int[MAX_SUGGESTIONS]; |
| private int[] mWordX = new int[MAX_SUGGESTIONS]; |
| |
| private static final int X_GAP = 10; |
| |
| private static final List<String> EMPTY_LIST = new ArrayList<String>(); |
| |
| private int mColorNormal; |
| private int mColorRecommended; |
| private int mColorOther; |
| private int mVerticalPadding; |
| private Paint mPaint; |
| private boolean mScrolled; |
| private int mTargetScrollX; |
| |
| private int mTotalWidth; |
| |
| private GestureDetector mGestureDetector; |
| |
| /** |
| * Construct a CandidateView for showing suggested words for completion. |
| * @param context |
| * @param attrs |
| */ |
| public CandidateView(Context context) { |
| super(context); |
| mSelectionHighlight = context.getResources().getDrawable( |
| android.R.drawable.list_selector_background); |
| mSelectionHighlight.setState(new int[] { |
| android.R.attr.state_enabled, |
| android.R.attr.state_focused, |
| android.R.attr.state_window_focused, |
| android.R.attr.state_pressed |
| }); |
| |
| Resources r = context.getResources(); |
| |
| setBackgroundColor(r.getColor(R.color.candidate_background)); |
| |
| mColorNormal = r.getColor(R.color.candidate_normal); |
| mColorRecommended = r.getColor(R.color.candidate_recommended); |
| mColorOther = r.getColor(R.color.candidate_other); |
| mVerticalPadding = r.getDimensionPixelSize(R.dimen.candidate_vertical_padding); |
| |
| mPaint = new Paint(); |
| mPaint.setColor(mColorNormal); |
| mPaint.setAntiAlias(true); |
| mPaint.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_font_height)); |
| mPaint.setStrokeWidth(0); |
| |
| mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { |
| @Override |
| public boolean onScroll(MotionEvent e1, MotionEvent e2, |
| float distanceX, float distanceY) { |
| mScrolled = true; |
| int sx = getScrollX(); |
| sx += distanceX; |
| if (sx < 0) { |
| sx = 0; |
| } |
| if (sx + getWidth() > mTotalWidth) { |
| sx -= distanceX; |
| } |
| mTargetScrollX = sx; |
| scrollTo(sx, getScrollY()); |
| invalidate(); |
| return true; |
| } |
| }); |
| setHorizontalFadingEdgeEnabled(true); |
| setWillNotDraw(false); |
| setHorizontalScrollBarEnabled(false); |
| setVerticalScrollBarEnabled(false); |
| } |
| |
| /** |
| * A connection back to the service to communicate with the text field |
| * @param listener |
| */ |
| public void setService(SoftKeyboard listener) { |
| mService = listener; |
| } |
| |
| @Override |
| public int computeHorizontalScrollRange() { |
| return mTotalWidth; |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| int measuredWidth = resolveSize(50, widthMeasureSpec); |
| |
| // Get the desired height of the icon menu view (last row of items does |
| // not have a divider below) |
| Rect padding = new Rect(); |
| mSelectionHighlight.getPadding(padding); |
| final int desiredHeight = ((int)mPaint.getTextSize()) + mVerticalPadding |
| + padding.top + padding.bottom; |
| |
| // Maximum possible width and desired height |
| setMeasuredDimension(measuredWidth, |
| resolveSize(desiredHeight, heightMeasureSpec)); |
| } |
| |
| /** |
| * If the canvas is null, then only touch calculations are performed to pick the target |
| * candidate. |
| */ |
| @Override |
| protected void onDraw(Canvas canvas) { |
| if (canvas != null) { |
| super.onDraw(canvas); |
| } |
| mTotalWidth = 0; |
| if (mSuggestions == null) return; |
| |
| if (mBgPadding == null) { |
| mBgPadding = new Rect(0, 0, 0, 0); |
| if (getBackground() != null) { |
| getBackground().getPadding(mBgPadding); |
| } |
| } |
| int x = 0; |
| final int count = mSuggestions.size(); |
| final int height = getHeight(); |
| final Rect bgPadding = mBgPadding; |
| final Paint paint = mPaint; |
| final int touchX = mTouchX; |
| final int scrollX = getScrollX(); |
| final boolean scrolled = mScrolled; |
| final boolean typedWordValid = mTypedWordValid; |
| final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent()); |
| |
| for (int i = 0; i < count; i++) { |
| String suggestion = mSuggestions.get(i); |
| float textWidth = paint.measureText(suggestion); |
| final int wordWidth = (int) textWidth + X_GAP * 2; |
| |
| mWordX[i] = x; |
| mWordWidth[i] = wordWidth; |
| paint.setColor(mColorNormal); |
| if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) { |
| if (canvas != null) { |
| canvas.translate(x, 0); |
| mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); |
| mSelectionHighlight.draw(canvas); |
| canvas.translate(-x, 0); |
| } |
| mSelectedIndex = i; |
| } |
| |
| if (canvas != null) { |
| if ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid)) { |
| paint.setFakeBoldText(true); |
| paint.setColor(mColorRecommended); |
| } else if (i != 0) { |
| paint.setColor(mColorOther); |
| } |
| canvas.drawText(suggestion, x + X_GAP, y, paint); |
| paint.setColor(mColorOther); |
| canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top, |
| x + wordWidth + 0.5f, height + 1, paint); |
| paint.setFakeBoldText(false); |
| } |
| x += wordWidth; |
| } |
| mTotalWidth = x; |
| if (mTargetScrollX != getScrollX()) { |
| scrollToTarget(); |
| } |
| } |
| |
| private void scrollToTarget() { |
| int sx = getScrollX(); |
| if (mTargetScrollX > sx) { |
| sx += SCROLL_PIXELS; |
| if (sx >= mTargetScrollX) { |
| sx = mTargetScrollX; |
| requestLayout(); |
| } |
| } else { |
| sx -= SCROLL_PIXELS; |
| if (sx <= mTargetScrollX) { |
| sx = mTargetScrollX; |
| requestLayout(); |
| } |
| } |
| scrollTo(sx, getScrollY()); |
| invalidate(); |
| } |
| |
| public void setSuggestions(List<String> suggestions, boolean completions, |
| boolean typedWordValid) { |
| clear(); |
| if (suggestions != null) { |
| mSuggestions = new ArrayList<String>(suggestions); |
| } |
| mTypedWordValid = typedWordValid; |
| scrollTo(0, 0); |
| mTargetScrollX = 0; |
| // Compute the total width |
| onDraw(null); |
| invalidate(); |
| requestLayout(); |
| } |
| |
| public void clear() { |
| mSuggestions = EMPTY_LIST; |
| mTouchX = OUT_OF_BOUNDS; |
| mSelectedIndex = -1; |
| invalidate(); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent me) { |
| |
| if (mGestureDetector.onTouchEvent(me)) { |
| return true; |
| } |
| |
| int action = me.getAction(); |
| int x = (int) me.getX(); |
| int y = (int) me.getY(); |
| mTouchX = x; |
| |
| switch (action) { |
| case MotionEvent.ACTION_DOWN: |
| mScrolled = false; |
| invalidate(); |
| break; |
| case MotionEvent.ACTION_MOVE: |
| if (y <= 0) { |
| // Fling up!? |
| if (mSelectedIndex >= 0) { |
| mService.pickSuggestionManually(mSelectedIndex); |
| mSelectedIndex = -1; |
| } |
| } |
| invalidate(); |
| break; |
| case MotionEvent.ACTION_UP: |
| if (!mScrolled) { |
| if (mSelectedIndex >= 0) { |
| mService.pickSuggestionManually(mSelectedIndex); |
| } |
| } |
| mSelectedIndex = -1; |
| removeHighlight(); |
| requestLayout(); |
| break; |
| } |
| return true; |
| } |
| |
| /** |
| * For flick through from keyboard, call this method with the x coordinate of the flick |
| * gesture. |
| * @param x |
| */ |
| public void takeSuggestionAt(float x) { |
| mTouchX = (int) x; |
| // To detect candidate |
| onDraw(null); |
| if (mSelectedIndex >= 0) { |
| mService.pickSuggestionManually(mSelectedIndex); |
| } |
| invalidate(); |
| } |
| |
| private void removeHighlight() { |
| mTouchX = OUT_OF_BOUNDS; |
| invalidate(); |
| } |
| } |