Implement expandable candidates pane

This change removes horizontal scroll from candidates strip. Instead
of that this change introduces "fixed 3 items candidates strip" and
"expandable candidates pane".

Bug: 4175031

Change-Id: Ia367d9074436fdea76d3b653d81798ce2749170e
main
Tadashi G. Takaoka 2011-06-14 16:28:57 +09:00
parent 90cb2e6296
commit 86e815a142
10 changed files with 251 additions and 41 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2011, 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.
*/
-->
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
>
<include
android:id="@+id/candidate_left"
layout="@layout/candidate" />
<include
layout="@layout/candidate_divider" />
<include
android:id="@+id/candidate_center"
layout="@layout/candidate" />
<include
layout="@layout/candidate_divider" />
<LinearLayout
android:orientation="horizontal"
android:layout_weight="1.0"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
>
<include
android:id="@+id/candidate_right"
layout="@layout/candidate" />
<!-- TODO: These images' drawable must be determined depending on theme. -->
<ImageButton
android:id="@+id/expand_candidates_pane"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/btn_expand_candidates_pane"
android:visibility="gone"
style="?attr/suggestionBackgroundStyle" />
<ImageButton
android:id="@+id/close_candidates_pane"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/btn_close_candidates_pane"
android:visibility="gone"
style="?attr/suggestionBackgroundStyle" />
</LinearLayout>
</merge>

View File

@ -32,30 +32,45 @@
android:id="@+id/candidates_container" android:id="@+id/candidates_container"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/candidate_strip_minimum_height"
android:gravity="bottom"
>
<View
android:layout_width="@dimen/candidate_strip_padding"
android:layout_height="@dimen/candidate_strip_height"
style="?attr/suggestionsStripBackgroundStyle" />
<com.android.inputmethod.latin.CandidateView
android:id="@+id/candidates"
android:layout_weight="1.0"
android:layout_width="0dp"
android:layout_height="@dimen/candidate_strip_height"
android:gravity="center_vertical"
style="?attr/suggestionsStripBackgroundStyle" />
<View
android:layout_width="@dimen/candidate_strip_padding"
android:layout_height="@dimen/candidate_strip_height"
style="?attr/suggestionsStripBackgroundStyle" />
</LinearLayout>
<LinearLayout
android:id="@+id/candidates_pane_container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"
style="?attr/suggestionsStripBackgroundStyle" style="?attr/suggestionsStripBackgroundStyle"
> >
<View <View
android:layout_width="@dimen/candidate_strip_padding" android:layout_width="@dimen/candidate_strip_padding"
android:layout_height="@dimen/candidate_strip_height" android:layout_height="@dimen/candidate_strip_height" />
style="?attr/suggestionsStripBackgroundStyle" /> <FrameLayout
<HorizontalScrollView android:id="@+id/candidates_pane"
android:layout_width="match_parent" android:layout_weight="1.0"
android:layout_height="wrap_content" android:layout_width="0dp"
android:fadingEdge="horizontal" android:layout_height="match_parent" />
android:fadingEdgeLength="@dimen/candidate_strip_fading_edge_length"
android:scrollbars="none"
>
<com.android.inputmethod.latin.CandidateView
android:id="@+id/candidates"
android:layout_width="match_parent"
android:layout_height="@dimen/candidate_strip_height"
android:gravity="center_vertical" />
</HorizontalScrollView>
<View <View
android:layout_width="@dimen/candidate_strip_padding" android:layout_width="@dimen/candidate_strip_padding"
android:layout_height="@dimen/candidate_strip_height" android:layout_height="@dimen/candidate_strip_height" />
style="?attr/suggestionsStripBackgroundStyle" />
</LinearLayout> </LinearLayout>
<com.android.inputmethod.keyboard.LatinKeyboardView <com.android.inputmethod.keyboard.LatinKeyboardView

View File

@ -57,6 +57,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private SubtypeSwitcher mSubtypeSwitcher; private SubtypeSwitcher mSubtypeSwitcher;
private SharedPreferences mPrefs; private SharedPreferences mPrefs;
private View mCurrentInputView;
private LatinKeyboardView mKeyboardView; private LatinKeyboardView mKeyboardView;
private LatinIME mInputMethodService; private LatinIME mInputMethodService;
@ -294,7 +295,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
} }
public boolean isInputViewShown() { public boolean isInputViewShown() {
return mKeyboardView != null && mKeyboardView.isShown(); return mCurrentInputView != null && mCurrentInputView.isShown();
} }
public boolean isKeyboardAvailable() { public boolean isKeyboardAvailable() {
@ -714,9 +715,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
return createInputView(mThemeIndex, true); return createInputView(mThemeIndex, true);
} }
// Instance variable only for {@link #createInputView(int, boolean)}.
private View mCurrentInputView;
private View createInputView(final int newThemeIndex, final boolean forceRecreate) { private View createInputView(final int newThemeIndex, final boolean forceRecreate) {
if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate) if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate)
return mCurrentInputView; return mCurrentInputView;

View File

@ -56,13 +56,23 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
private static final int MAX_SUGGESTIONS = 16; // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
private static final int MAX_SUGGESTIONS = 18;
private static final int UNSPECIFIED_MEASURESPEC = MeasureSpec.makeMeasureSpec(
0, MeasureSpec.UNSPECIFIED);
private static final boolean DBG = LatinImeLogger.sDBG; private static final boolean DBG = LatinImeLogger.sDBG;
private static final int NUM_CANDIDATES_IN_STRIP = 3;
private final View mExpandCandidatesPane;
private final View mCloseCandidatesPane;
private ViewGroup mCandidatesPane;
private ViewGroup mCandidatesPaneContainer;
private View mKeyboardView;
private final ArrayList<TextView> mWords = new ArrayList<TextView>(); private final ArrayList<TextView> mWords = new ArrayList<TextView>();
private final ArrayList<View> mDividers = new ArrayList<View>(); private final ArrayList<View> mDividers = new ArrayList<View>();
private final int mCandidatePadding; private final int mCandidatePadding;
private final int mCandidateStripHeight;
private final boolean mConfigCandidateHighlightFontColorEnabled; private final boolean mConfigCandidateHighlightFontColorEnabled;
private final CharacterStyle mInvertedForegroundColorSpan; private final CharacterStyle mInvertedForegroundColorSpan;
private final CharacterStyle mInvertedBackgroundColorSpan; private final CharacterStyle mInvertedBackgroundColorSpan;
@ -132,8 +142,10 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
super(context, attrs); super(context, attrs);
Resources res = context.getResources(); Resources res = context.getResources();
mPreviewPopup = new PopupWindow(context);
LayoutInflater inflater = LayoutInflater.from(context); LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.candidates_strip, this);
mPreviewPopup = new PopupWindow(context);
mPreviewText = (TextView) inflater.inflate(R.layout.candidate_preview, null); mPreviewText = (TextView) inflater.inflate(R.layout.candidate_preview, null);
mPreviewPopup.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, mPreviewPopup.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT); ViewGroup.LayoutParams.WRAP_CONTENT);
@ -148,8 +160,26 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord); mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
mCandidatePadding = res.getDimensionPixelOffset(R.dimen.candidate_padding); mCandidatePadding = res.getDimensionPixelOffset(R.dimen.candidate_padding);
mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height);
for (int i = 0; i < MAX_SUGGESTIONS; i++) { for (int i = 0; i < MAX_SUGGESTIONS; i++) {
final TextView tv = (TextView)inflater.inflate(R.layout.candidate, null); final TextView tv;
switch (i) {
case 0:
tv = (TextView)findViewById(R.id.candidate_left);
tv.setPadding(mCandidatePadding, 0, 0, 0);
break;
case 1:
tv = (TextView)findViewById(R.id.candidate_center);
break;
case 2:
tv = (TextView)findViewById(R.id.candidate_right);
break;
default:
tv = (TextView)inflater.inflate(R.layout.candidate, null);
break;
}
if (i < NUM_CANDIDATES_IN_STRIP)
setLayoutWeight(tv, 1.0f);
tv.setTag(i); tv.setTag(i);
tv.setOnClickListener(this); tv.setOnClickListener(this);
if (i == 0) if (i == 0)
@ -157,19 +187,38 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
mWords.add(tv); mWords.add(tv);
if (i > 0) { if (i > 0) {
final View divider = inflater.inflate(R.layout.candidate_divider, null); final View divider = inflater.inflate(R.layout.candidate_divider, null);
divider.measure(UNSPECIFIED_MEASURESPEC, UNSPECIFIED_MEASURESPEC);
mDividers.add(divider); mDividers.add(divider);
} }
} }
scrollTo(0, getScrollY()); mExpandCandidatesPane = findViewById(R.id.expand_candidates_pane);
mExpandCandidatesPane.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
expandCandidatesPane();
}
});
mCloseCandidatesPane = findViewById(R.id.close_candidates_pane);
mCloseCandidatesPane.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
closeCandidatesPane();
}
});
} }
/** /**
* A connection back to the input method. * A connection back to the input method.
* @param listener * @param listener
*/ */
public void setListener(Listener listener) { public void setListener(Listener listener, View inputView) {
mListener = listener; mListener = listener;
mKeyboardView = inputView.findViewById(R.id.keyboard_view);
mCandidatesPane = (ViewGroup)inputView.findViewById(R.id.candidates_pane);
mCandidatesPane.setOnClickListener(this);
mCandidatesPaneContainer = (ViewGroup)inputView.findViewById(
R.id.candidates_pane_container);
} }
public void setSuggestions(SuggestedWords suggestions) { public void setSuggestions(SuggestedWords suggestions) {
@ -183,6 +232,15 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
} }
} }
private static void setLayoutWeight(View v, float weight) {
ViewGroup.LayoutParams lp = v.getLayoutParams();
if (lp instanceof LinearLayout.LayoutParams) {
LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
llp.width = 0;
llp.weight = weight;
}
}
private CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect) { private CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect) {
if (!isAutoCorrect) if (!isAutoCorrect)
return word; return word;
@ -216,7 +274,14 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
final List<SuggestedWordInfo> suggestedWordInfoList = suggestions.mSuggestedWordInfoList; final List<SuggestedWordInfo> suggestedWordInfoList = suggestions.mSuggestedWordInfoList;
clear(); clear();
final int paneWidth = getWidth();
final int dividerWidth = mDividers.get(0).getMeasuredWidth();
int x = 0;
int y = 0;
int fromIndex = NUM_CANDIDATES_IN_STRIP;
final int count = Math.min(mWords.size(), suggestions.size()); final int count = Math.min(mWords.size(), suggestions.size());
closeCandidatesPane();
mExpandCandidatesPane.setEnabled(count >= NUM_CANDIDATES_IN_STRIP);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
final CharSequence word = suggestions.getWord(i); final CharSequence word = suggestions.getWord(i);
if (word == null) continue; if (word == null) continue;
@ -233,33 +298,91 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
final boolean isPunctuationSuggestions = (word.length() == 1 && count > 1); final boolean isPunctuationSuggestions = (word.length() == 1 && count > 1);
final TextView tv = mWords.get(i); final TextView tv = mWords.get(i);
// TODO: Reorder candidates in strip as appropriate. The center candidate should hold
// the word when space is typed (valid typed word or auto corrected word).
tv.setTextColor(getCandidateTextColor(isAutoCorrect, tv.setTextColor(getCandidateTextColor(isAutoCorrect,
isSuggestedCandidate || isPunctuationSuggestions, info)); isSuggestedCandidate || isPunctuationSuggestions, info));
tv.setText(getStyledCandidateWord(word, isAutoCorrect)); tv.setText(getStyledCandidateWord(word, isAutoCorrect));
if (i == 0) { // TODO: call TextView.setTextScaleX() to fit the candidate in single line.
tv.setPadding(mCandidatePadding, 0, 0, 0); if (i >= NUM_CANDIDATES_IN_STRIP) {
} else if (i == count - 1) { tv.measure(UNSPECIFIED_MEASURESPEC, UNSPECIFIED_MEASURESPEC);
tv.setPadding(0, 0, mCandidatePadding, 0); final int width = tv.getMeasuredWidth();
} else { // TODO: Handle overflow case.
tv.setPadding(0, 0, 0, 0); if (dividerWidth + x + width >= paneWidth) {
centeringCandidates(fromIndex, i - 1, x, paneWidth);
x = 0;
y += mCandidateStripHeight;
fromIndex = i;
}
if (x != 0) {
final View divider = mDividers.get(i - NUM_CANDIDATES_IN_STRIP);
mCandidatesPane.addView(divider);
placeCandidateAt(divider, x, y);
x += dividerWidth;
}
mCandidatesPane.addView(tv);
placeCandidateAt(tv, x, y);
x += width;
} }
if (i > 0)
addView(mDividers.get(i - 1));
addView(tv);
if (DBG && info != null) { if (DBG && info != null) {
final TextView dv = new TextView(getContext(), null); final TextView dv = new TextView(getContext(), null);
dv.setTextSize(10.0f); dv.setTextSize(10.0f);
dv.setTextColor(0xff808080); dv.setTextColor(0xff808080);
dv.setText(info.getDebugString()); dv.setText(info.getDebugString());
addView(dv); // TODO: debug view for candidate strip needed.
mCandidatesPane.addView(dv);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)dv.getLayoutParams(); LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)dv.getLayoutParams();
lp.gravity = Gravity.BOTTOM; lp.gravity = Gravity.BOTTOM;
} }
} }
if (x != 0) {
// Centering last candidates row.
centeringCandidates(fromIndex, count - 1, x, paneWidth);
}
}
scrollTo(0, getScrollY()); private void placeCandidateAt(View v, int x, int y) {
requestLayout(); ViewGroup.LayoutParams lp = v.getLayoutParams();
if (lp instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)lp;
mlp.width = v.getMeasuredWidth();
mlp.height = v.getMeasuredHeight();
mlp.setMargins(x, y + (mCandidateStripHeight - mlp.height) / 2, 0, 0);
}
}
private void centeringCandidates(int from, int to, int width, int paneWidth) {
final ViewGroup pane = mCandidatesPane;
final int fromIndex = pane.indexOfChild(mWords.get(from));
final int toIndex = pane.indexOfChild(mWords.get(to));
final int offset = (paneWidth - width) / 2;
for (int index = fromIndex; index <= toIndex; index++) {
offsetMargin(pane.getChildAt(index), offset, 0);
}
}
private static void offsetMargin(View v, int dx, int dy) {
ViewGroup.LayoutParams lp = v.getLayoutParams();
if (lp instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)lp;
mlp.setMargins(mlp.leftMargin + dx, mlp.topMargin + dy, 0, 0);
}
}
private void expandCandidatesPane() {
mExpandCandidatesPane.setVisibility(View.GONE);
mCloseCandidatesPane.setVisibility(View.VISIBLE);
mCandidatesPaneContainer.setMinimumHeight(mKeyboardView.getMeasuredHeight());
mCandidatesPaneContainer.setVisibility(View.VISIBLE);
mKeyboardView.setVisibility(View.GONE);
}
private void closeCandidatesPane() {
mExpandCandidatesPane.setVisibility(View.VISIBLE);
mCloseCandidatesPane.setVisibility(View.GONE);
mCandidatesPaneContainer.setVisibility(View.GONE);
mKeyboardView.setVisibility(View.VISIBLE);
} }
public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) { public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) {
@ -310,7 +433,9 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
public void clear() { public void clear() {
mShowingAddToDictionary = false; mShowingAddToDictionary = false;
mShowingAutoCorrectionInverted = false; mShowingAutoCorrectionInverted = false;
removeAllViews(); for (int i = 0; i < NUM_CANDIDATES_IN_STRIP; i++)
mWords.get(i).setText(null);
mCandidatesPane.removeAllViews();
} }
private void hidePreview() { private void hidePreview() {
@ -349,9 +474,13 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
@Override @Override
public boolean onLongClick(View view) { public boolean onLongClick(View view) {
final int index = (Integer) view.getTag(); final Object tag = view.getTag();
if (!(tag instanceof Integer))
return true;
final int index = (Integer) tag;
if (index >= mSuggestions.size()) if (index >= mSuggestions.size())
return true; return true;
final CharSequence word = mSuggestions.getWord(index); final CharSequence word = mSuggestions.getWord(index);
if (word.length() < 2) if (word.length() < 2)
return false; return false;
@ -361,15 +490,22 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
@Override @Override
public void onClick(View view) { public void onClick(View view) {
final int index = (Integer) view.getTag(); final Object tag = view.getTag();
if (!(tag instanceof Integer))
return;
final int index = (Integer) tag;
if (index >= mSuggestions.size()) if (index >= mSuggestions.size())
return; return;
final CharSequence word = mSuggestions.getWord(index); final CharSequence word = mSuggestions.getWord(index);
if (mShowingAddToDictionary && index == 0) { if (mShowingAddToDictionary && index == 0) {
addToDictionary(word); addToDictionary(word);
} else { } else {
mListener.pickSuggestionManually(index, word); mListener.pickSuggestionManually(index, word);
} }
// Because some punctuation letters are not treated as word separator depending on locale,
// {@link #setSuggestions} might not be called and candidates pane left opened.
closeCandidatesPane();
} }
@Override @Override

View File

@ -502,7 +502,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
super.setInputView(view); super.setInputView(view);
mCandidateViewContainer = view.findViewById(R.id.candidates_container); mCandidateViewContainer = view.findViewById(R.id.candidates_container);
mCandidateView = (CandidateView) view.findViewById(R.id.candidates); mCandidateView = (CandidateView) view.findViewById(R.id.candidates);
mCandidateView.setListener(this); mCandidateView.setListener(this, view);
mCandidateStripHeight = (int)mResources.getDimension(R.dimen.candidate_strip_height); mCandidateStripHeight = (int)mResources.getDimension(R.dimen.candidate_strip_height);
} }

View File

@ -84,7 +84,7 @@ public class Suggest implements Dictionary.WordCallback {
private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>(); private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>(); private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
private int mPrefMaxSuggestions = 12; private int mPrefMaxSuggestions = 18;
private static final int PREF_MAX_BIGRAMS = 60; private static final int PREF_MAX_BIGRAMS = 60;