CandidateViewpublic class CandidateView extends android.view.View View to show candidate list. There two candidate view instances which are
used to show animation when user navigates between pages. |
Fields Summary |
---|
private static final float | MIN_ITEM_WIDTHThe minimum width to show a item. | private static final String | SUSPENSION_POINTSSuspension points used to display long items. | private int | mContentWidthThe width to draw candidates. | private int | mContentHeightThe height to draw candidate content. | private boolean | mShowFootnoteWhether footnotes are displayed. Footnote is shown when hardware keyboard
is available. | private BalloonHint | mBalloonHintBalloon hint for candidate press/release. | private int[] | mHintPositionToInputViewDesired position of the balloon to the input view. | private com.android.inputmethod.pinyin.PinyinIME.DecodingInfo | mDecInfoDecoding result to show. | private CandidateViewListener | mCvListenerListener used to notify IME that user clicks a candidate, or navigate
between them. | private ArrowUpdater | mArrowUpdaterUsed to notify the container to update the status of forward/backward
arrows. | private boolean | mUpdateArrowStatusWhenDrawIf true, update the arrow status when drawing candidates. | private int | mPageNoPage number of the page displayed in this view. | private int | mActiveCandInPageActive candidate position in this page. | private boolean | mEnableActiveHighlightUsed to decided whether the active candidate should be highlighted or
not. If user changes focus to composing view (The view to show Pinyin
string), the highlight in candidate view should be removed. | private int | mPageNoCalculatedThe page which is just calculated. | private android.graphics.drawable.Drawable | mActiveCellDrawableThe Drawable used to display as the background of the high-lighted item. | private android.graphics.drawable.Drawable | mSeparatorDrawableThe Drawable used to display as separators between candidates. | private int | mImeCandidateColorColor to draw normal candidates generated by IME. | private int | mRecommendedCandidateColorColor to draw normal candidates Recommended by application. | private int | mNormalCandidateColorColor to draw the normal(not highlighted) candidates, it can be one of
{@link #mImeCandidateColor} or {@link #mRecommendedCandidateColor}. | private int | mActiveCandidateColorColor to draw the active(highlighted) candidates, including candidates
from IME and candidates from application. | private int | mImeCandidateTextSizeText size to draw candidates generated by IME. | private int | mRecommendedCandidateTextSizeText size to draw candidates recommended by application. | private int | mCandidateTextSizeThe current text size to draw candidates. It can be one of
{@link #mImeCandidateTextSize} or {@link #mRecommendedCandidateTextSize}. | private android.graphics.Paint | mCandidatesPaintPaint used to draw candidates. | private android.graphics.Paint | mFootnotePaintUsed to draw footnote. | private float | mSuspensionPointsWidthThe width to show suspension points. | private android.graphics.RectF | mActiveCellRectRectangle used to draw the active candidate. | private float | mCandidateMarginLeft and right margins for a candidate. It is specified in xml, and is
the minimum margin for a candidate. The actual gap between two candidates
is 2 * {@link #mCandidateMargin} + {@link #mSeparatorDrawable}.
getIntrinsicWidth(). Because length of candidate is not fixed, there can
be some extra space after the last candidate in the current page. In
order to achieve best look-and-feel, this extra space will be divided and
allocated to each candidates. | private float | mCandidateMarginExtraLeft and right extra margins for a candidate. | private Vector | mCandRectsRectangles for the candidates in this page. | private android.graphics.Paint.FontMetricsInt | mFmiCandidatesFontMetricsInt used to measure the size of candidates. | private android.graphics.Paint.FontMetricsInt | mFmiFootnoteFontMetricsInt used to measure the size of footnotes. | private PressTimer | mTimer | private android.view.GestureDetector | mGestureDetector | private int[] | mLocationTmp |
Constructors Summary |
---|
public CandidateView(android.content.Context context, android.util.AttributeSet attrs)
super(context, attrs);
Resources r = context.getResources();
Configuration conf = r.getConfiguration();
if (conf.keyboard == Configuration.KEYBOARD_NOKEYS
|| conf.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
mShowFootnote = false;
}
mActiveCellDrawable = r.getDrawable(R.drawable.candidate_hl_bg);
mSeparatorDrawable = r.getDrawable(R.drawable.candidates_vertical_line);
mCandidateMargin = r.getDimension(R.dimen.candidate_margin_left_right);
mImeCandidateColor = r.getColor(R.color.candidate_color);
mRecommendedCandidateColor = r.getColor(R.color.recommended_candidate_color);
mNormalCandidateColor = mImeCandidateColor;
mActiveCandidateColor = r.getColor(R.color.active_candidate_color);
mCandidatesPaint = new Paint();
mCandidatesPaint.setAntiAlias(true);
mFootnotePaint = new Paint();
mFootnotePaint.setAntiAlias(true);
mFootnotePaint.setColor(r.getColor(R.color.footnote_color));
mActiveCellRect = new RectF();
mCandRects = new Vector<RectF>();
|
Methods Summary |
---|
public boolean | activeCurseBackward()
if (mActiveCandInPage > 0) {
showPage(mPageNo, mActiveCandInPage - 1, true);
return true;
}
return false;
| public boolean | activeCursorForward()
if (!mDecInfo.pageReady(mPageNo)) return false;
int pageSize = mDecInfo.mPageStart.get(mPageNo + 1)
- mDecInfo.mPageStart.get(mPageNo);
if (mActiveCandInPage + 1 < pageSize) {
showPage(mPageNo, mActiveCandInPage + 1, true);
return true;
}
return false;
| private boolean | calculatePage(int pageNo)
if (pageNo == mPageNoCalculated) return true;
mContentWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
mContentHeight = (int) ((mMeasuredHeight - mPaddingTop - mPaddingBottom) * 0.95f);
if (mContentWidth <= 0 || mContentHeight <= 0) return false;
int candSize = mDecInfo.mCandidatesList.size();
// If the size of page exists, only calculate the extra margin.
boolean onlyExtraMargin = false;
int fromPage = mDecInfo.mPageStart.size() - 1;
if (mDecInfo.mPageStart.size() > pageNo + 1) {
onlyExtraMargin = true;
fromPage = pageNo;
}
// If the previous pages have no information, calculate them first.
for (int p = fromPage; p <= pageNo; p++) {
int pStart = mDecInfo.mPageStart.get(p);
int pSize = 0;
int charNum = 0;
float lastItemWidth = 0;
float xPos;
xPos = 0;
xPos += mSeparatorDrawable.getIntrinsicWidth();
while (xPos < mContentWidth && pStart + pSize < candSize) {
int itemPos = pStart + pSize;
String itemStr = mDecInfo.mCandidatesList.get(itemPos);
float itemWidth = mCandidatesPaint.measureText(itemStr);
if (itemWidth < MIN_ITEM_WIDTH) itemWidth = MIN_ITEM_WIDTH;
itemWidth += mCandidateMargin * 2;
itemWidth += mSeparatorDrawable.getIntrinsicWidth();
if (xPos + itemWidth < mContentWidth || 0 == pSize) {
xPos += itemWidth;
lastItemWidth = itemWidth;
pSize++;
charNum += itemStr.length();
} else {
break;
}
}
if (!onlyExtraMargin) {
mDecInfo.mPageStart.add(pStart + pSize);
mDecInfo.mCnToPage.add(mDecInfo.mCnToPage.get(p) + charNum);
}
float marginExtra = (mContentWidth - xPos) / pSize / 2;
if (mContentWidth - xPos > lastItemWidth) {
// Must be the last page, because if there are more items,
// the next item's width must be less than lastItemWidth.
// In this case, if the last margin is less than the current
// one, the last margin can be used, so that the
// look-and-feeling will be the same as the previous page.
if (mCandidateMarginExtra <= marginExtra) {
marginExtra = mCandidateMarginExtra;
}
} else if (pSize == 1) {
marginExtra = 0;
}
mCandidateMarginExtra = marginExtra;
}
mPageNoCalculated = pageNo;
return true;
| private float | drawVerticalSeparator(android.graphics.Canvas canvas, float xPos)
mSeparatorDrawable.setBounds((int) xPos, mPaddingTop, (int) xPos
+ mSeparatorDrawable.getIntrinsicWidth(), getMeasuredHeight()
- mPaddingBottom);
mSeparatorDrawable.draw(canvas);
return mSeparatorDrawable.getIntrinsicWidth();
| public void | enableActiveHighlight(boolean enableActiveHighlight)
if (enableActiveHighlight == mEnableActiveHighlight) return;
mEnableActiveHighlight = enableActiveHighlight;
invalidate();
| public int | getActiveCandiatePosGlobal()
return mDecInfo.mPageStart.get(mPageNo) + mActiveCandInPage;
| public int | getActiveCandiatePosInPage()
return mActiveCandInPage;
| private java.lang.String | getLimitedCandidateForDrawing(java.lang.String rawCandidate, float widthToDraw)
int subLen = rawCandidate.length();
if (subLen <= 1) return rawCandidate;
do {
subLen--;
float width = mCandidatesPaint.measureText(rawCandidate, 0, subLen);
if (width + mSuspensionPointsWidth <= widthToDraw || 1 >= subLen) {
return rawCandidate.substring(0, subLen) +
SUSPENSION_POINTS;
}
} while (true);
| public void | initialize(ArrowUpdater arrowUpdater, BalloonHint balloonHint, android.view.GestureDetector gestureDetector, CandidateViewListener cvListener)
mArrowUpdater = arrowUpdater;
mBalloonHint = balloonHint;
mGestureDetector = gestureDetector;
mCvListener = cvListener;
| private int | mapToItemInPage(int x, int y)
// mCandRects.size() == 0 happens when the page is set, but
// touch events occur before onDraw(). It usually happens with
// monkey test.
if (!mDecInfo.pageReady(mPageNo) || mPageNoCalculated != mPageNo
|| mCandRects.size() == 0) {
return -1;
}
int pageStart = mDecInfo.mPageStart.get(mPageNo);
int pageSize = mDecInfo.mPageStart.get(mPageNo + 1) - pageStart;
if (mCandRects.size() < pageSize) {
return -1;
}
// If not found, try to find the nearest one.
float nearestDis = Float.MAX_VALUE;
int nearest = -1;
for (int i = 0; i < pageSize; i++) {
RectF r = mCandRects.elementAt(i);
if (r.left < x && r.right > x && r.top < y && r.bottom > y) {
return i;
}
float disx = (r.left + r.right) / 2 - x;
float disy = (r.top + r.bottom) / 2 - y;
float dis = disx * disx + disy * disy;
if (dis < nearestDis) {
nearestDis = dis;
nearest = i;
}
}
return nearest;
| protected void | onDraw(android.graphics.Canvas canvas)
super.onDraw(canvas);
// The invisible candidate view(the one which is not in foreground) can
// also be called to drawn, but its decoding result and candidate list
// may be empty.
if (null == mDecInfo || mDecInfo.isCandidatesListEmpty()) return;
// Calculate page. If the paging information is ready, the function will
// return at once.
calculatePage(mPageNo);
int pStart = mDecInfo.mPageStart.get(mPageNo);
int pSize = mDecInfo.mPageStart.get(mPageNo + 1) - pStart;
float candMargin = mCandidateMargin + mCandidateMarginExtra;
if (mActiveCandInPage > pSize - 1) {
mActiveCandInPage = pSize - 1;
}
mCandRects.removeAllElements();
float xPos = mPaddingLeft;
int yPos = (getMeasuredHeight() -
(mFmiCandidates.bottom - mFmiCandidates.top)) / 2
- mFmiCandidates.top;
xPos += drawVerticalSeparator(canvas, xPos);
for (int i = 0; i < pSize; i++) {
float footnoteSize = 0;
String footnote = null;
if (mShowFootnote) {
footnote = Integer.toString(i + 1);
footnoteSize = mFootnotePaint.measureText(footnote);
assert (footnoteSize < candMargin);
}
String cand = mDecInfo.mCandidatesList.get(pStart + i);
float candidateWidth = mCandidatesPaint.measureText(cand);
float centerOffset = 0;
if (candidateWidth < MIN_ITEM_WIDTH) {
centerOffset = (MIN_ITEM_WIDTH - candidateWidth) / 2;
candidateWidth = MIN_ITEM_WIDTH;
}
float itemTotalWidth = candidateWidth + 2 * candMargin;
if (mActiveCandInPage == i && mEnableActiveHighlight) {
mActiveCellRect.set(xPos, mPaddingTop + 1, xPos
+ itemTotalWidth, getHeight() - mPaddingBottom - 1);
mActiveCellDrawable.setBounds((int) mActiveCellRect.left,
(int) mActiveCellRect.top, (int) mActiveCellRect.right,
(int) mActiveCellRect.bottom);
mActiveCellDrawable.draw(canvas);
}
if (mCandRects.size() < pSize) mCandRects.add(new RectF());
mCandRects.elementAt(i).set(xPos - 1, yPos + mFmiCandidates.top,
xPos + itemTotalWidth + 1, yPos + mFmiCandidates.bottom);
// Draw footnote
if (mShowFootnote) {
canvas.drawText(footnote, xPos + (candMargin - footnoteSize)
/ 2, yPos, mFootnotePaint);
}
// Left margin
xPos += candMargin;
if (candidateWidth > mContentWidth - xPos - centerOffset) {
cand = getLimitedCandidateForDrawing(cand,
mContentWidth - xPos - centerOffset);
}
if (mActiveCandInPage == i && mEnableActiveHighlight) {
mCandidatesPaint.setColor(mActiveCandidateColor);
} else {
mCandidatesPaint.setColor(mNormalCandidateColor);
}
canvas.drawText(cand, xPos + centerOffset, yPos,
mCandidatesPaint);
// Candidate and right margin
xPos += candidateWidth + candMargin;
// Draw the separator between candidates.
xPos += drawVerticalSeparator(canvas, xPos);
}
// Update the arrow status of the container.
if (null != mArrowUpdater && mUpdateArrowStatusWhenDraw) {
mArrowUpdater.updateArrowStatus();
mUpdateArrowStatusWhenDraw = false;
}
| protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
int mOldWidth = mMeasuredWidth;
int mOldHeight = mMeasuredHeight;
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),
heightMeasureSpec));
if (mOldWidth != mMeasuredWidth || mOldHeight != mMeasuredHeight) {
onSizeChanged();
}
| private void | onSizeChanged()
mContentWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
mContentHeight = (int) ((mMeasuredHeight - mPaddingTop - mPaddingBottom) * 0.95f);
/**
* How to decide the font size if the height for display is given?
* Now it is implemented in a stupid way.
*/
int textSize = 1;
mCandidatesPaint.setTextSize(textSize);
mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
while (mFmiCandidates.bottom - mFmiCandidates.top < mContentHeight) {
textSize++;
mCandidatesPaint.setTextSize(textSize);
mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
}
mImeCandidateTextSize = textSize;
mRecommendedCandidateTextSize = textSize * 3 / 4;
if (null == mDecInfo) {
mCandidateTextSize = mImeCandidateTextSize;
mCandidatesPaint.setTextSize(mCandidateTextSize);
mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
mSuspensionPointsWidth =
mCandidatesPaint.measureText(SUSPENSION_POINTS);
} else {
// Reset the decoding information to update members for painting.
setDecodingInfo(mDecInfo);
}
textSize = 1;
mFootnotePaint.setTextSize(textSize);
mFmiFootnote = mFootnotePaint.getFontMetricsInt();
while (mFmiFootnote.bottom - mFmiFootnote.top < mContentHeight / 2) {
textSize++;
mFootnotePaint.setTextSize(textSize);
mFmiFootnote = mFootnotePaint.getFontMetricsInt();
}
textSize--;
mFootnotePaint.setTextSize(textSize);
mFmiFootnote = mFootnotePaint.getFontMetricsInt();
// When the size is changed, the first page will be displayed.
mPageNo = 0;
mActiveCandInPage = 0;
| public boolean | onTouchEvent(android.view.MotionEvent event)
return super.onTouchEvent(event);
| public boolean | onTouchEventReal(android.view.MotionEvent event)
// The page in the background can also be touched.
if (null == mDecInfo || !mDecInfo.pageReady(mPageNo)
|| mPageNoCalculated != mPageNo) return true;
int x, y;
x = (int) event.getX();
y = (int) event.getY();
if (mGestureDetector.onTouchEvent(event)) {
mTimer.removeTimer();
mBalloonHint.delayedDismiss(0);
return true;
}
int clickedItemInPage = -1;
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
clickedItemInPage = mapToItemInPage(x, y);
if (clickedItemInPage >= 0) {
invalidate();
mCvListener.onClickChoice(clickedItemInPage
+ mDecInfo.mPageStart.get(mPageNo));
}
mBalloonHint.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
break;
case MotionEvent.ACTION_DOWN:
clickedItemInPage = mapToItemInPage(x, y);
if (clickedItemInPage >= 0) {
showBalloon(clickedItemInPage, true);
mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,
clickedItemInPage);
}
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
clickedItemInPage = mapToItemInPage(x, y);
if (clickedItemInPage >= 0
&& (clickedItemInPage != mTimer.getActiveCandOfPageToShow() || mPageNo != mTimer
.getPageToShow())) {
showBalloon(clickedItemInPage, true);
mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,
clickedItemInPage);
}
}
return true;
| public void | setDecodingInfo(com.android.inputmethod.pinyin.PinyinIME.DecodingInfo decInfo)
if (null == decInfo) return;
mDecInfo = decInfo;
mPageNoCalculated = -1;
if (mDecInfo.candidatesFromApp()) {
mNormalCandidateColor = mRecommendedCandidateColor;
mCandidateTextSize = mRecommendedCandidateTextSize;
} else {
mNormalCandidateColor = mImeCandidateColor;
mCandidateTextSize = mImeCandidateTextSize;
}
if (mCandidatesPaint.getTextSize() != mCandidateTextSize) {
mCandidatesPaint.setTextSize(mCandidateTextSize);
mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
mSuspensionPointsWidth =
mCandidatesPaint.measureText(SUSPENSION_POINTS);
}
// Remove any pending timer for the previous list.
mTimer.removeTimer();
| private void | showBalloon(int candPos, boolean delayedShow)
mBalloonHint.removeTimer();
RectF r = mCandRects.elementAt(candPos);
int desired_width = (int) (r.right - r.left);
int desired_height = (int) (r.bottom - r.top);
mBalloonHint.setBalloonConfig(mDecInfo.mCandidatesList
.get(mDecInfo.mPageStart.get(mPageNo) + candPos), 44, true,
mImeCandidateColor, desired_width, desired_height);
getLocationOnScreen(mLocationTmp);
mHintPositionToInputView[0] = mLocationTmp[0]
+ (int) (r.left - (mBalloonHint.getWidth() - desired_width) / 2);
mHintPositionToInputView[1] = -mBalloonHint.getHeight();
long delay = BalloonHint.TIME_DELAY_SHOW;
if (!delayedShow) delay = 0;
mBalloonHint.dismiss();
if (!mBalloonHint.isShowing()) {
mBalloonHint.delayedShow(delay, mHintPositionToInputView);
} else {
mBalloonHint.delayedUpdate(0, mHintPositionToInputView, -1, -1);
}
| public void | showPage(int pageNo, int activeCandInPage, boolean enableActiveHighlight)Show a page in the decoding result set previously.
if (null == mDecInfo) return;
mPageNo = pageNo;
mActiveCandInPage = activeCandInPage;
if (mEnableActiveHighlight != enableActiveHighlight) {
mEnableActiveHighlight = enableActiveHighlight;
}
if (!calculatePage(mPageNo)) {
mUpdateArrowStatusWhenDraw = true;
} else {
mUpdateArrowStatusWhenDraw = false;
}
invalidate();
|
|