FileDocCategorySizeDatePackage
CandidateView.javaAPI DocAndroid 1.5 API26190Wed May 06 22:42:48 BST 2009com.android.inputmethod.pinyin

CandidateView

public 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_WIDTH
The minimum width to show a item.
private static final String
SUSPENSION_POINTS
Suspension points used to display long items.
private int
mContentWidth
The width to draw candidates.
private int
mContentHeight
The height to draw candidate content.
private boolean
mShowFootnote
Whether footnotes are displayed. Footnote is shown when hardware keyboard is available.
private BalloonHint
mBalloonHint
Balloon hint for candidate press/release.
private int[]
mHintPositionToInputView
Desired position of the balloon to the input view.
private com.android.inputmethod.pinyin.PinyinIME.DecodingInfo
mDecInfo
Decoding result to show.
private CandidateViewListener
mCvListener
Listener used to notify IME that user clicks a candidate, or navigate between them.
private ArrowUpdater
mArrowUpdater
Used to notify the container to update the status of forward/backward arrows.
private boolean
mUpdateArrowStatusWhenDraw
If true, update the arrow status when drawing candidates.
private int
mPageNo
Page number of the page displayed in this view.
private int
mActiveCandInPage
Active candidate position in this page.
private boolean
mEnableActiveHighlight
Used 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
mPageNoCalculated
The page which is just calculated.
private android.graphics.drawable.Drawable
mActiveCellDrawable
The Drawable used to display as the background of the high-lighted item.
private android.graphics.drawable.Drawable
mSeparatorDrawable
The Drawable used to display as separators between candidates.
private int
mImeCandidateColor
Color to draw normal candidates generated by IME.
private int
mRecommendedCandidateColor
Color to draw normal candidates Recommended by application.
private int
mNormalCandidateColor
Color to draw the normal(not highlighted) candidates, it can be one of {@link #mImeCandidateColor} or {@link #mRecommendedCandidateColor}.
private int
mActiveCandidateColor
Color to draw the active(highlighted) candidates, including candidates from IME and candidates from application.
private int
mImeCandidateTextSize
Text size to draw candidates generated by IME.
private int
mRecommendedCandidateTextSize
Text size to draw candidates recommended by application.
private int
mCandidateTextSize
The current text size to draw candidates. It can be one of {@link #mImeCandidateTextSize} or {@link #mRecommendedCandidateTextSize}.
private android.graphics.Paint
mCandidatesPaint
Paint used to draw candidates.
private android.graphics.Paint
mFootnotePaint
Used to draw footnote.
private float
mSuspensionPointsWidth
The width to show suspension points.
private android.graphics.RectF
mActiveCellRect
Rectangle used to draw the active candidate.
private float
mCandidateMargin
Left 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
mCandidateMarginExtra
Left and right extra margins for a candidate.
private Vector
mCandRects
Rectangles for the candidates in this page.
private android.graphics.Paint.FontMetricsInt
mFmiCandidates
FontMetricsInt used to measure the size of candidates.
private android.graphics.Paint.FontMetricsInt
mFmiFootnote
FontMetricsInt 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 booleanactiveCurseBackward()

        if (mActiveCandInPage > 0) {
            showPage(mPageNo, mActiveCandInPage - 1, true);
            return true;
        }
        return false;
    
public booleanactiveCursorForward()

        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 booleancalculatePage(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 floatdrawVerticalSeparator(android.graphics.Canvas canvas, float xPos)

        mSeparatorDrawable.setBounds((int) xPos, mPaddingTop, (int) xPos
                + mSeparatorDrawable.getIntrinsicWidth(), getMeasuredHeight()
                - mPaddingBottom);
        mSeparatorDrawable.draw(canvas);
        return mSeparatorDrawable.getIntrinsicWidth();
    
public voidenableActiveHighlight(boolean enableActiveHighlight)

        if (enableActiveHighlight == mEnableActiveHighlight) return;

        mEnableActiveHighlight = enableActiveHighlight;
        invalidate();
    
public intgetActiveCandiatePosGlobal()

        return mDecInfo.mPageStart.get(mPageNo) + mActiveCandInPage;
    
public intgetActiveCandiatePosInPage()

        return mActiveCandInPage;
    
private java.lang.StringgetLimitedCandidateForDrawing(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 voidinitialize(ArrowUpdater arrowUpdater, BalloonHint balloonHint, android.view.GestureDetector gestureDetector, CandidateViewListener cvListener)

        mArrowUpdater = arrowUpdater;
        mBalloonHint = balloonHint;
        mGestureDetector = gestureDetector;
        mCvListener = cvListener;
    
private intmapToItemInPage(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 voidonDraw(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 voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

        int mOldWidth = mMeasuredWidth;
        int mOldHeight = mMeasuredHeight;

        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
                widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),
                heightMeasureSpec));

        if (mOldWidth != mMeasuredWidth || mOldHeight != mMeasuredHeight) {
            onSizeChanged();
        }
    
private voidonSizeChanged()

        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 booleanonTouchEvent(android.view.MotionEvent event)

        return super.onTouchEvent(event);
    
public booleanonTouchEventReal(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 voidsetDecodingInfo(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 voidshowBalloon(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 voidshowPage(int pageNo, int activeCandInPage, boolean enableActiveHighlight)
Show a page in the decoding result set previously.

param
pageNo Which page to show.
param
activeCandInPage Which candidate should be set as active item.
param
enableActiveHighlight When false, active item will not be highlighted.

        if (null == mDecInfo) return;
        mPageNo = pageNo;
        mActiveCandInPage = activeCandInPage;
        if (mEnableActiveHighlight != enableActiveHighlight) {
            mEnableActiveHighlight = enableActiveHighlight;
        }

        if (!calculatePage(mPageNo)) {
            mUpdateArrowStatusWhenDraw = true;
        } else {
            mUpdateArrowStatusWhenDraw = false;
        }

        invalidate();