/CircleIME/src/com/marvin/circleime/CandidateView.java

http://eyes-free.googlecode.com/ · Java · 333 lines · 239 code · 52 blank · 42 comment · 42 complexity · 00aac28f3dacaceb7ca823f69898fc92 MD5 · raw file

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