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.PinyinIME.DecodingInfo; 20 21 import android.content.Context; 22 import android.util.AttributeSet; 23 import android.view.GestureDetector; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.View.OnTouchListener; 27 import android.view.animation.AlphaAnimation; 28 import android.view.animation.Animation; 29 import android.view.animation.AnimationSet; 30 import android.view.animation.TranslateAnimation; 31 import android.view.animation.Animation.AnimationListener; 32 import android.widget.ImageButton; 33 import android.widget.RelativeLayout; 34 import android.widget.ViewFlipper; 35 36 interface ArrowUpdater { updateArrowStatus()37 void updateArrowStatus(); 38 } 39 40 41 /** 42 * Container used to host the two candidate views. When user drags on candidate 43 * view, animation is used to dismiss the current candidate view and show a new 44 * one. These two candidate views and their parent are hosted by this container. 45 * <p> 46 * Besides the candidate views, there are two arrow views to show the page 47 * forward/backward arrows. 48 * </p> 49 */ 50 public class CandidatesContainer extends RelativeLayout implements 51 OnTouchListener, AnimationListener, ArrowUpdater { 52 /** 53 * Alpha value to show an enabled arrow. 54 */ 55 private static int ARROW_ALPHA_ENABLED = 0xff; 56 57 /** 58 * Alpha value to show an disabled arrow. 59 */ 60 private static int ARROW_ALPHA_DISABLED = 0x40; 61 62 /** 63 * Animation time to show a new candidate view and dismiss the old one. 64 */ 65 private static int ANIMATION_TIME = 200; 66 67 /** 68 * Listener used to notify IME that user clicks a candidate, or navigate 69 * between them. 70 */ 71 private CandidateViewListener mCvListener; 72 73 /** 74 * The left arrow button used to show previous page. 75 */ 76 private ImageButton mLeftArrowBtn; 77 78 /** 79 * The right arrow button used to show next page. 80 */ 81 private ImageButton mRightArrowBtn; 82 83 /** 84 * Decoding result to show. 85 */ 86 private DecodingInfo mDecInfo; 87 88 /** 89 * The animation view used to show candidates. It contains two views. 90 * Normally, the candidates are shown one of them. When user navigates to 91 * another page, animation effect will be performed. 92 */ 93 private ViewFlipper mFlipper; 94 95 /** 96 * The x offset of the flipper in this container. 97 */ 98 private int xOffsetForFlipper; 99 100 /** 101 * Animation used by the incoming view when the user navigates to a left 102 * page. 103 */ 104 private Animation mInAnimPushLeft; 105 106 /** 107 * Animation used by the incoming view when the user navigates to a right 108 * page. 109 */ 110 private Animation mInAnimPushRight; 111 112 /** 113 * Animation used by the incoming view when the user navigates to a page 114 * above. If the page navigation is triggered by DOWN key, this animation is 115 * used. 116 */ 117 private Animation mInAnimPushUp; 118 119 /** 120 * Animation used by the incoming view when the user navigates to a page 121 * below. If the page navigation is triggered by UP key, this animation is 122 * used. 123 */ 124 private Animation mInAnimPushDown; 125 126 /** 127 * Animation used by the outgoing view when the user navigates to a left 128 * page. 129 */ 130 private Animation mOutAnimPushLeft; 131 132 /** 133 * Animation used by the outgoing view when the user navigates to a right 134 * page. 135 */ 136 private Animation mOutAnimPushRight; 137 138 /** 139 * Animation used by the outgoing view when the user navigates to a page 140 * above. If the page navigation is triggered by DOWN key, this animation is 141 * used. 142 */ 143 private Animation mOutAnimPushUp; 144 145 /** 146 * Animation used by the incoming view when the user navigates to a page 147 * below. If the page navigation is triggered by UP key, this animation is 148 * used. 149 */ 150 private Animation mOutAnimPushDown; 151 152 /** 153 * Animation object which is used for the incoming view currently. 154 */ 155 private Animation mInAnimInUse; 156 157 /** 158 * Animation object which is used for the outgoing view currently. 159 */ 160 private Animation mOutAnimInUse; 161 162 /** 163 * Current page number in display. 164 */ 165 private int mCurrentPage = -1; 166 CandidatesContainer(Context context, AttributeSet attrs)167 public CandidatesContainer(Context context, AttributeSet attrs) { 168 super(context, attrs); 169 } 170 initialize(CandidateViewListener cvListener, BalloonHint balloonHint, GestureDetector gestureDetector)171 public void initialize(CandidateViewListener cvListener, 172 BalloonHint balloonHint, GestureDetector gestureDetector) { 173 mCvListener = cvListener; 174 175 mLeftArrowBtn = (ImageButton) findViewById(R.id.arrow_left_btn); 176 mRightArrowBtn = (ImageButton) findViewById(R.id.arrow_right_btn); 177 mLeftArrowBtn.setOnTouchListener(this); 178 mRightArrowBtn.setOnTouchListener(this); 179 180 mFlipper = (ViewFlipper) findViewById(R.id.candidate_flipper); 181 mFlipper.setMeasureAllChildren(true); 182 183 invalidate(); 184 requestLayout(); 185 186 for (int i = 0; i < mFlipper.getChildCount(); i++) { 187 CandidateView cv = (CandidateView) mFlipper.getChildAt(i); 188 cv.initialize(this, balloonHint, gestureDetector, mCvListener); 189 } 190 } 191 showCandidates(PinyinIME.DecodingInfo decInfo, boolean enableActiveHighlight)192 public void showCandidates(PinyinIME.DecodingInfo decInfo, 193 boolean enableActiveHighlight) { 194 if (null == decInfo) return; 195 mDecInfo = decInfo; 196 mCurrentPage = 0; 197 198 if (decInfo.isCandidatesListEmpty()) { 199 showArrow(mLeftArrowBtn, false); 200 showArrow(mRightArrowBtn, false); 201 } else { 202 showArrow(mLeftArrowBtn, true); 203 showArrow(mRightArrowBtn, true); 204 } 205 206 for (int i = 0; i < mFlipper.getChildCount(); i++) { 207 CandidateView cv = (CandidateView) mFlipper.getChildAt(i); 208 cv.setDecodingInfo(mDecInfo); 209 } 210 stopAnimation(); 211 212 CandidateView cv = (CandidateView) mFlipper.getCurrentView(); 213 cv.showPage(mCurrentPage, 0, enableActiveHighlight); 214 215 updateArrowStatus(); 216 invalidate(); 217 } 218 getCurrentPage()219 public int getCurrentPage() { 220 return mCurrentPage; 221 } 222 enableActiveHighlight(boolean enableActiveHighlight)223 public void enableActiveHighlight(boolean enableActiveHighlight) { 224 CandidateView cv = (CandidateView) mFlipper.getCurrentView(); 225 cv.enableActiveHighlight(enableActiveHighlight); 226 invalidate(); 227 } 228 229 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)230 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 231 Environment env = Environment.getInstance(); 232 int measuredWidth = env.getScreenWidth(); 233 int measuredHeight = getPaddingTop(); 234 measuredHeight += env.getHeightForCandidates(); 235 widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, 236 MeasureSpec.EXACTLY); 237 heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight, 238 MeasureSpec.EXACTLY); 239 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 240 241 if (null != mLeftArrowBtn) { 242 xOffsetForFlipper = mLeftArrowBtn.getMeasuredWidth(); 243 } 244 } 245 activeCurseBackward()246 public boolean activeCurseBackward() { 247 if (mFlipper.isFlipping() || null == mDecInfo) { 248 return false; 249 } 250 251 CandidateView cv = (CandidateView) mFlipper.getCurrentView(); 252 253 if (cv.activeCurseBackward()) { 254 cv.invalidate(); 255 return true; 256 } else { 257 return pageBackward(true, true); 258 } 259 } 260 activeCurseForward()261 public boolean activeCurseForward() { 262 if (mFlipper.isFlipping() || null == mDecInfo) { 263 return false; 264 } 265 266 CandidateView cv = (CandidateView) mFlipper.getCurrentView(); 267 268 if (cv.activeCursorForward()) { 269 cv.invalidate(); 270 return true; 271 } else { 272 return pageForward(true, true); 273 } 274 } 275 pageBackward(boolean animLeftRight, boolean enableActiveHighlight)276 public boolean pageBackward(boolean animLeftRight, 277 boolean enableActiveHighlight) { 278 if (null == mDecInfo) return false; 279 280 if (mFlipper.isFlipping() || 0 == mCurrentPage) return false; 281 282 int child = mFlipper.getDisplayedChild(); 283 int childNext = (child + 1) % 2; 284 CandidateView cv = (CandidateView) mFlipper.getChildAt(child); 285 CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext); 286 287 mCurrentPage--; 288 int activeCandInPage = cv.getActiveCandiatePosInPage(); 289 if (animLeftRight) 290 activeCandInPage = mDecInfo.mPageStart.elementAt(mCurrentPage + 1) 291 - mDecInfo.mPageStart.elementAt(mCurrentPage) - 1; 292 293 cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight); 294 loadAnimation(animLeftRight, false); 295 startAnimation(); 296 297 updateArrowStatus(); 298 return true; 299 } 300 pageForward(boolean animLeftRight, boolean enableActiveHighlight)301 public boolean pageForward(boolean animLeftRight, 302 boolean enableActiveHighlight) { 303 if (null == mDecInfo) return false; 304 305 if (mFlipper.isFlipping() || !mDecInfo.preparePage(mCurrentPage + 1)) { 306 return false; 307 } 308 309 int child = mFlipper.getDisplayedChild(); 310 int childNext = (child + 1) % 2; 311 CandidateView cv = (CandidateView) mFlipper.getChildAt(child); 312 int activeCandInPage = cv.getActiveCandiatePosInPage(); 313 cv.enableActiveHighlight(enableActiveHighlight); 314 315 CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext); 316 mCurrentPage++; 317 if (animLeftRight) activeCandInPage = 0; 318 319 cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight); 320 loadAnimation(animLeftRight, true); 321 startAnimation(); 322 323 updateArrowStatus(); 324 return true; 325 } 326 getActiveCandiatePos()327 public int getActiveCandiatePos() { 328 if (null == mDecInfo) return -1; 329 CandidateView cv = (CandidateView) mFlipper.getCurrentView(); 330 return cv.getActiveCandiatePosGlobal(); 331 } 332 updateArrowStatus()333 public void updateArrowStatus() { 334 if (mCurrentPage < 0) return; 335 boolean forwardEnabled = mDecInfo.pageForwardable(mCurrentPage); 336 boolean backwardEnabled = mDecInfo.pageBackwardable(mCurrentPage); 337 338 if (backwardEnabled) { 339 enableArrow(mLeftArrowBtn, true); 340 } else { 341 enableArrow(mLeftArrowBtn, false); 342 } 343 if (forwardEnabled) { 344 enableArrow(mRightArrowBtn, true); 345 } else { 346 enableArrow(mRightArrowBtn, false); 347 } 348 } 349 enableArrow(ImageButton arrowBtn, boolean enabled)350 private void enableArrow(ImageButton arrowBtn, boolean enabled) { 351 arrowBtn.setEnabled(enabled); 352 if (enabled) 353 arrowBtn.setAlpha(ARROW_ALPHA_ENABLED); 354 else 355 arrowBtn.setAlpha(ARROW_ALPHA_DISABLED); 356 } 357 showArrow(ImageButton arrowBtn, boolean show)358 private void showArrow(ImageButton arrowBtn, boolean show) { 359 if (show) 360 arrowBtn.setVisibility(View.VISIBLE); 361 else 362 arrowBtn.setVisibility(View.INVISIBLE); 363 } 364 onTouch(View v, MotionEvent event)365 public boolean onTouch(View v, MotionEvent event) { 366 if (event.getAction() == MotionEvent.ACTION_DOWN) { 367 if (v == mLeftArrowBtn) { 368 mCvListener.onToRightGesture(); 369 } else if (v == mRightArrowBtn) { 370 mCvListener.onToLeftGesture(); 371 } 372 } else if (event.getAction() == MotionEvent.ACTION_UP) { 373 CandidateView cv = (CandidateView) mFlipper.getCurrentView(); 374 cv.enableActiveHighlight(true); 375 } 376 377 return false; 378 } 379 380 // The reason why we handle candiate view's touch events here is because 381 // that the view under the focused view may get touch events instead of the 382 // focused one. 383 @Override onTouchEvent(MotionEvent event)384 public boolean onTouchEvent(MotionEvent event) { 385 event.offsetLocation(-xOffsetForFlipper, 0); 386 CandidateView cv = (CandidateView) mFlipper.getCurrentView(); 387 cv.onTouchEventReal(event); 388 return true; 389 } 390 loadAnimation(boolean animLeftRight, boolean forward)391 public void loadAnimation(boolean animLeftRight, boolean forward) { 392 if (animLeftRight) { 393 if (forward) { 394 if (null == mInAnimPushLeft) { 395 mInAnimPushLeft = createAnimation(1.0f, 0, 0, 0, 0, 1.0f, 396 ANIMATION_TIME); 397 mOutAnimPushLeft = createAnimation(0, -1.0f, 0, 0, 1.0f, 0, 398 ANIMATION_TIME); 399 } 400 mInAnimInUse = mInAnimPushLeft; 401 mOutAnimInUse = mOutAnimPushLeft; 402 } else { 403 if (null == mInAnimPushRight) { 404 mInAnimPushRight = createAnimation(-1.0f, 0, 0, 0, 0, 1.0f, 405 ANIMATION_TIME); 406 mOutAnimPushRight = createAnimation(0, 1.0f, 0, 0, 1.0f, 0, 407 ANIMATION_TIME); 408 } 409 mInAnimInUse = mInAnimPushRight; 410 mOutAnimInUse = mOutAnimPushRight; 411 } 412 } else { 413 if (forward) { 414 if (null == mInAnimPushUp) { 415 mInAnimPushUp = createAnimation(0, 0, 1.0f, 0, 0, 1.0f, 416 ANIMATION_TIME); 417 mOutAnimPushUp = createAnimation(0, 0, 0, -1.0f, 1.0f, 0, 418 ANIMATION_TIME); 419 } 420 mInAnimInUse = mInAnimPushUp; 421 mOutAnimInUse = mOutAnimPushUp; 422 } else { 423 if (null == mInAnimPushDown) { 424 mInAnimPushDown = createAnimation(0, 0, -1.0f, 0, 0, 1.0f, 425 ANIMATION_TIME); 426 mOutAnimPushDown = createAnimation(0, 0, 0, 1.0f, 1.0f, 0, 427 ANIMATION_TIME); 428 } 429 mInAnimInUse = mInAnimPushDown; 430 mOutAnimInUse = mOutAnimPushDown; 431 } 432 } 433 434 mInAnimInUse.setAnimationListener(this); 435 436 mFlipper.setInAnimation(mInAnimInUse); 437 mFlipper.setOutAnimation(mOutAnimInUse); 438 } 439 createAnimation(float xFrom, float xTo, float yFrom, float yTo, float alphaFrom, float alphaTo, long duration)440 private Animation createAnimation(float xFrom, float xTo, float yFrom, 441 float yTo, float alphaFrom, float alphaTo, long duration) { 442 AnimationSet animSet = new AnimationSet(getContext(), null); 443 Animation trans = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 444 xFrom, Animation.RELATIVE_TO_SELF, xTo, 445 Animation.RELATIVE_TO_SELF, yFrom, Animation.RELATIVE_TO_SELF, 446 yTo); 447 Animation alpha = new AlphaAnimation(alphaFrom, alphaTo); 448 animSet.addAnimation(trans); 449 animSet.addAnimation(alpha); 450 animSet.setDuration(duration); 451 return animSet; 452 } 453 startAnimation()454 private void startAnimation() { 455 mFlipper.showNext(); 456 } 457 stopAnimation()458 private void stopAnimation() { 459 mFlipper.stopFlipping(); 460 } 461 onAnimationEnd(Animation animation)462 public void onAnimationEnd(Animation animation) { 463 if (!mLeftArrowBtn.isPressed() && !mRightArrowBtn.isPressed()) { 464 CandidateView cv = (CandidateView) mFlipper.getCurrentView(); 465 cv.enableActiveHighlight(true); 466 } 467 } 468 onAnimationRepeat(Animation animation)469 public void onAnimationRepeat(Animation animation) { 470 } 471 onAnimationStart(Animation animation)472 public void onAnimationStart(Animation animation) { 473 } 474 } 475