blob: 5b2a999a560c791275a11b5a8c3abc148a4895da [file] [log] [blame]
/*
* 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.
* <p>
* Besides the candidate views, there are two arrow views to show the page
* forward/backward arrows.
* </p>
*/
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) {
}
}