All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.solovyev.android.keyboard.CandidateView Maven / Gradle / Ivy

There is a newer version: 1.1.18
Show newest version
/*
 * Copyright 2013 serso aka se.solovyev
 *
 * 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.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Contact details
 *
 * Email: [email protected]
 * Site:  http://se.solovyev.org
 */

package org.solovyev.android.keyboard;

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 javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;

public class CandidateView extends View {

	private static final int OUT_OF_BOUNDS = -1;

	@Nonnull
	private AKeyboardController keyboardController;
	private List 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 EMPTY_LIST = new ArrayList();

	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
	 */
	public CandidateView(@Nonnull 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);
	}

	public void setKeyboardController(@Nonnull AKeyboardController keyboardController) {
		this.keyboardController = keyboardController;
	}

	@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 suggestions, boolean completions,
							   boolean typedWordValid) {
		clear();
		if (suggestions != null) {
			mSuggestions = new ArrayList(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) {
						keyboardController.pickSuggestionManually(mSelectedIndex);
						mSelectedIndex = -1;
					}
				}
				invalidate();
				break;
			case MotionEvent.ACTION_UP:
				if (!mScrolled) {
					if (mSelectedIndex >= 0) {
						keyboardController.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) {
			keyboardController.pickSuggestionManually(mSelectedIndex);
		}
		invalidate();
	}

	private void removeHighlight() {
		mTouchX = OUT_OF_BOUNDS;
		invalidate();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy