• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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