/* * Copyright (C) 2009 The Android Open Source Project * * 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. */ package com.android.inputmethod.pinyin; import com.android.inputmethod.pinyin.PinyinIME.DecodingInfo; import android.content.Context; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.TranslateAnimation; import android.view.animation.Animation.AnimationListener; import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.ViewFlipper; interface ArrowUpdater { void updateArrowStatus(); } /** * Container used to host the two candidate views. When user drags on candidate * view, animation is used to dismiss the current candidate view and show a new * one. These two candidate views and their parent are hosted by this container. *

* Besides the candidate views, there are two arrow views to show the page * forward/backward arrows. *

*/ public class CandidatesContainer extends RelativeLayout implements OnTouchListener, AnimationListener, ArrowUpdater { /** * Alpha value to show an enabled arrow. */ private static int ARROW_ALPHA_ENABLED = 0xff; /** * Alpha value to show an disabled arrow. */ private static int ARROW_ALPHA_DISABLED = 0x40; /** * Animation time to show a new candidate view and dismiss the old one. */ private static int ANIMATION_TIME = 200; /** * Listener used to notify IME that user clicks a candidate, or navigate * between them. */ private CandidateViewListener mCvListener; /** * The left arrow button used to show previous page. */ private ImageButton mLeftArrowBtn; /** * The right arrow button used to show next page. */ private ImageButton mRightArrowBtn; /** * Decoding result to show. */ private DecodingInfo mDecInfo; /** * The animation view used to show candidates. It contains two views. * Normally, the candidates are shown one of them. When user navigates to * another page, animation effect will be performed. */ private ViewFlipper mFlipper; /** * The x offset of the flipper in this container. */ private int xOffsetForFlipper; /** * Animation used by the incoming view when the user navigates to a left * page. */ private Animation mInAnimPushLeft; /** * Animation used by the incoming view when the user navigates to a right * page. */ private Animation mInAnimPushRight; /** * Animation used by the incoming view when the user navigates to a page * above. If the page navigation is triggered by DOWN key, this animation is * used. */ private Animation mInAnimPushUp; /** * Animation used by the incoming view when the user navigates to a page * below. If the page navigation is triggered by UP key, this animation is * used. */ private Animation mInAnimPushDown; /** * Animation used by the outgoing view when the user navigates to a left * page. */ private Animation mOutAnimPushLeft; /** * Animation used by the outgoing view when the user navigates to a right * page. */ private Animation mOutAnimPushRight; /** * Animation used by the outgoing view when the user navigates to a page * above. If the page navigation is triggered by DOWN key, this animation is * used. */ private Animation mOutAnimPushUp; /** * Animation used by the incoming view when the user navigates to a page * below. If the page navigation is triggered by UP key, this animation is * used. */ private Animation mOutAnimPushDown; /** * Animation object which is used for the incoming view currently. */ private Animation mInAnimInUse; /** * Animation object which is used for the outgoing view currently. */ private Animation mOutAnimInUse; /** * Current page number in display. */ private int mCurrentPage = -1; public CandidatesContainer(Context context, AttributeSet attrs) { super(context, attrs); } public void initialize(CandidateViewListener cvListener, BalloonHint balloonHint, GestureDetector gestureDetector) { mCvListener = cvListener; mLeftArrowBtn = (ImageButton) findViewById(R.id.arrow_left_btn); mRightArrowBtn = (ImageButton) findViewById(R.id.arrow_right_btn); mLeftArrowBtn.setOnTouchListener(this); mRightArrowBtn.setOnTouchListener(this); mFlipper = (ViewFlipper) findViewById(R.id.candidate_flipper); mFlipper.setMeasureAllChildren(true); invalidate(); requestLayout(); for (int i = 0; i < mFlipper.getChildCount(); i++) { CandidateView cv = (CandidateView) mFlipper.getChildAt(i); cv.initialize(this, balloonHint, gestureDetector, mCvListener); } } public void showCandidates(PinyinIME.DecodingInfo decInfo, boolean enableActiveHighlight) { if (null == decInfo) return; mDecInfo = decInfo; mCurrentPage = 0; if (decInfo.isCandidatesListEmpty()) { showArrow(mLeftArrowBtn, false); showArrow(mRightArrowBtn, false); } else { showArrow(mLeftArrowBtn, true); showArrow(mRightArrowBtn, true); } for (int i = 0; i < mFlipper.getChildCount(); i++) { CandidateView cv = (CandidateView) mFlipper.getChildAt(i); cv.setDecodingInfo(mDecInfo); } stopAnimation(); CandidateView cv = (CandidateView) mFlipper.getCurrentView(); cv.showPage(mCurrentPage, 0, enableActiveHighlight); updateArrowStatus(); invalidate(); } public int getCurrentPage() { return mCurrentPage; } public void enableActiveHighlight(boolean enableActiveHighlight) { CandidateView cv = (CandidateView) mFlipper.getCurrentView(); cv.enableActiveHighlight(enableActiveHighlight); invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Environment env = Environment.getInstance(); int measuredWidth = env.getScreenWidth(); int measuredHeight = getPaddingTop(); measuredHeight += env.getHeightForCandidates(); widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY); heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (null != mLeftArrowBtn) { xOffsetForFlipper = mLeftArrowBtn.getMeasuredWidth(); } } public boolean activeCurseBackward() { if (mFlipper.isFlipping() || null == mDecInfo) { return false; } CandidateView cv = (CandidateView) mFlipper.getCurrentView(); if (cv.activeCurseBackward()) { cv.invalidate(); return true; } else { return pageBackward(true, true); } } public boolean activeCurseForward() { if (mFlipper.isFlipping() || null == mDecInfo) { return false; } CandidateView cv = (CandidateView) mFlipper.getCurrentView(); if (cv.activeCursorForward()) { cv.invalidate(); return true; } else { return pageForward(true, true); } } public boolean pageBackward(boolean animLeftRight, boolean enableActiveHighlight) { if (null == mDecInfo) return false; if (mFlipper.isFlipping() || 0 == mCurrentPage) return false; int child = mFlipper.getDisplayedChild(); int childNext = (child + 1) % 2; CandidateView cv = (CandidateView) mFlipper.getChildAt(child); CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext); mCurrentPage--; int activeCandInPage = cv.getActiveCandiatePosInPage(); if (animLeftRight) activeCandInPage = mDecInfo.mPageStart.elementAt(mCurrentPage + 1) - mDecInfo.mPageStart.elementAt(mCurrentPage) - 1; cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight); loadAnimation(animLeftRight, false); startAnimation(); updateArrowStatus(); return true; } public boolean pageForward(boolean animLeftRight, boolean enableActiveHighlight) { if (null == mDecInfo) return false; if (mFlipper.isFlipping() || !mDecInfo.preparePage(mCurrentPage + 1)) { return false; } int child = mFlipper.getDisplayedChild(); int childNext = (child + 1) % 2; CandidateView cv = (CandidateView) mFlipper.getChildAt(child); int activeCandInPage = cv.getActiveCandiatePosInPage(); cv.enableActiveHighlight(enableActiveHighlight); CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext); mCurrentPage++; if (animLeftRight) activeCandInPage = 0; cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight); loadAnimation(animLeftRight, true); startAnimation(); updateArrowStatus(); return true; } public int getActiveCandiatePos() { if (null == mDecInfo) return -1; CandidateView cv = (CandidateView) mFlipper.getCurrentView(); return cv.getActiveCandiatePosGlobal(); } public void updateArrowStatus() { if (mCurrentPage < 0) return; boolean forwardEnabled = mDecInfo.pageForwardable(mCurrentPage); boolean backwardEnabled = mDecInfo.pageBackwardable(mCurrentPage); if (backwardEnabled) { enableArrow(mLeftArrowBtn, true); } else { enableArrow(mLeftArrowBtn, false); } if (forwardEnabled) { enableArrow(mRightArrowBtn, true); } else { enableArrow(mRightArrowBtn, false); } } private void enableArrow(ImageButton arrowBtn, boolean enabled) { arrowBtn.setEnabled(enabled); if (enabled) arrowBtn.setAlpha(ARROW_ALPHA_ENABLED); else arrowBtn.setAlpha(ARROW_ALPHA_DISABLED); } private void showArrow(ImageButton arrowBtn, boolean show) { if (show) arrowBtn.setVisibility(View.VISIBLE); else arrowBtn.setVisibility(View.INVISIBLE); } public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (v == mLeftArrowBtn) { mCvListener.onToRightGesture(); } else if (v == mRightArrowBtn) { mCvListener.onToLeftGesture(); } } else if (event.getAction() == MotionEvent.ACTION_UP) { CandidateView cv = (CandidateView) mFlipper.getCurrentView(); cv.enableActiveHighlight(true); } return false; } // The reason why we handle candiate view's touch events here is because // that the view under the focused view may get touch events instead of the // focused one. @Override public boolean onTouchEvent(MotionEvent event) { event.offsetLocation(-xOffsetForFlipper, 0); CandidateView cv = (CandidateView) mFlipper.getCurrentView(); cv.onTouchEventReal(event); return true; } public void loadAnimation(boolean animLeftRight, boolean forward) { if (animLeftRight) { if (forward) { if (null == mInAnimPushLeft) { mInAnimPushLeft = createAnimation(1.0f, 0, 0, 0, 0, 1.0f, ANIMATION_TIME); mOutAnimPushLeft = createAnimation(0, -1.0f, 0, 0, 1.0f, 0, ANIMATION_TIME); } mInAnimInUse = mInAnimPushLeft; mOutAnimInUse = mOutAnimPushLeft; } else { if (null == mInAnimPushRight) { mInAnimPushRight = createAnimation(-1.0f, 0, 0, 0, 0, 1.0f, ANIMATION_TIME); mOutAnimPushRight = createAnimation(0, 1.0f, 0, 0, 1.0f, 0, ANIMATION_TIME); } mInAnimInUse = mInAnimPushRight; mOutAnimInUse = mOutAnimPushRight; } } else { if (forward) { if (null == mInAnimPushUp) { mInAnimPushUp = createAnimation(0, 0, 1.0f, 0, 0, 1.0f, ANIMATION_TIME); mOutAnimPushUp = createAnimation(0, 0, 0, -1.0f, 1.0f, 0, ANIMATION_TIME); } mInAnimInUse = mInAnimPushUp; mOutAnimInUse = mOutAnimPushUp; } else { if (null == mInAnimPushDown) { mInAnimPushDown = createAnimation(0, 0, -1.0f, 0, 0, 1.0f, ANIMATION_TIME); mOutAnimPushDown = createAnimation(0, 0, 0, 1.0f, 1.0f, 0, ANIMATION_TIME); } mInAnimInUse = mInAnimPushDown; mOutAnimInUse = mOutAnimPushDown; } } mInAnimInUse.setAnimationListener(this); mFlipper.setInAnimation(mInAnimInUse); mFlipper.setOutAnimation(mOutAnimInUse); } private Animation createAnimation(float xFrom, float xTo, float yFrom, float yTo, float alphaFrom, float alphaTo, long duration) { AnimationSet animSet = new AnimationSet(getContext(), null); Animation trans = new TranslateAnimation(Animation.RELATIVE_TO_SELF, xFrom, Animation.RELATIVE_TO_SELF, xTo, Animation.RELATIVE_TO_SELF, yFrom, Animation.RELATIVE_TO_SELF, yTo); Animation alpha = new AlphaAnimation(alphaFrom, alphaTo); animSet.addAnimation(trans); animSet.addAnimation(alpha); animSet.setDuration(duration); return animSet; } private void startAnimation() { mFlipper.showNext(); } private void stopAnimation() { mFlipper.stopFlipping(); } public void onAnimationEnd(Animation animation) { if (!mLeftArrowBtn.isPressed() && !mRightArrowBtn.isPressed()) { CandidateView cv = (CandidateView) mFlipper.getCurrentView(); cv.enableActiveHighlight(true); } } public void onAnimationRepeat(Animation animation) { } public void onAnimationStart(Animation animation) { } }