Home | History | Annotate | Download | only in pinyin
      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 {
     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 
    167     public CandidatesContainer(Context context, AttributeSet attrs) {
    168         super(context, attrs);
    169     }
    170 
    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 
    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 
    219     public int getCurrentPage() {
    220         return mCurrentPage;
    221     }
    222 
    223     public void enableActiveHighlight(boolean enableActiveHighlight) {
    224         CandidateView cv = (CandidateView) mFlipper.getCurrentView();
    225         cv.enableActiveHighlight(enableActiveHighlight);
    226         invalidate();
    227     }
    228 
    229     @Override
    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 
    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 
    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 
    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 
    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 
    327     public int getActiveCandiatePos() {
    328         if (null == mDecInfo) return -1;
    329         CandidateView cv = (CandidateView) mFlipper.getCurrentView();
    330         return cv.getActiveCandiatePosGlobal();
    331     }
    332 
    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 
    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 
    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 
    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
    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 
    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 
    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 
    454     private void startAnimation() {
    455         mFlipper.showNext();
    456     }
    457 
    458     private void stopAnimation() {
    459         mFlipper.stopFlipping();
    460     }
    461 
    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 
    469     public void onAnimationRepeat(Animation animation) {
    470     }
    471 
    472     public void onAnimationStart(Animation animation) {
    473     }
    474 }
    475