diff --git a/res/drawable/suggestion_separator.xml b/res/drawable/suggestion_separator.xml
new file mode 100644
index 00000000..ba68ceec
--- /dev/null
+++ b/res/drawable/suggestion_separator.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/res/layout/mainview.xml b/res/layout/mainview.xml
index 9aec8501..d6b85c99 100644
--- a/res/layout/mainview.xml
+++ b/res/layout/mainview.xml
@@ -1,61 +1,72 @@
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
-
+
+
+
+
+
-
+
-
+
+
diff --git a/res/layout/suggestion_list_view.xml b/res/layout/suggestion_list_view.xml
new file mode 100644
index 00000000..08d37408
--- /dev/null
+++ b/res/layout/suggestion_list_view.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/res/values/colors.xml b/res/values/colors.xml
index d3fd4d2a..b0e0dea4 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -1,8 +1,8 @@
- #FF000000
- #ffc66ac3
- #ff68f0e9
- #e19185df
- #FFFFFFFF
+ #EFEBE9
+ #333333
+ #CCCCCC
+ #555555
+ #888888
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a75e468b..7ddcbbd6 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -1,26 +1,10 @@
-
-
+ 25sp
+ 17sp
+ 6sp
+ 1sp
- 16sp
- 6sp
-
+ 36dp
+ 24sp
diff --git a/src/io/github/sspanak/tt9/ime/KeyPadHandler.java b/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
index 3a2484b0..a8c2f6a1 100644
--- a/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
+++ b/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
@@ -8,13 +8,11 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import io.github.sspanak.tt9.preferences.T9Preferences;
-import io.github.sspanak.tt9.ui.CandidateView;
abstract class KeyPadHandler extends InputMethodService {
protected InputConnection currentInputConnection = null;
- protected CandidateView mSuggestionView;
protected T9Preferences prefs;
// editing mode
@@ -77,19 +75,6 @@ abstract class KeyPadHandler extends InputMethodService {
}
- /**
- * Called by the framework when your view for showing candidates needs to be
- * generated, like {@link #onCreateInputView}.
- */
- @Override
- public View onCreateCandidatesView() {
- if (mSuggestionView == null) {
- mSuggestionView = new CandidateView(this);
- }
- return mSuggestionView;
- }
-
-
/**
* This is the main point where we do our initialization of the input method
* to begin operating on an application. At this point we have been bound to
diff --git a/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java b/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
index 090f84e8..f6ce2162 100644
--- a/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
+++ b/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
@@ -8,11 +8,11 @@ import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ui.UI;
class SoftKeyHandler implements View.OnTouchListener {
- private static final int[] buttons = { R.id.main_left, R.id.main_right, R.id.main_mid };
+ private static final int[] buttons = { R.id.main_left, R.id.main_mid, R.id.main_right };
private final TraditionalT9 tt9;
private View view = null;
- public SoftKeyHandler(LayoutInflater layoutInflater, TraditionalT9 tt9) {
+ public SoftKeyHandler(LayoutInflater layoutInflater, TraditionalT9 tt9) {
this.tt9 = tt9;
createView(layoutInflater);
@@ -31,6 +31,10 @@ class SoftKeyHandler implements View.OnTouchListener {
return view;
}
+ View getView() {
+ return view;
+ }
+
void show() {
if (view != null) {
diff --git a/src/io/github/sspanak/tt9/ime/TraditionalT9.java b/src/io/github/sspanak/tt9/ime/TraditionalT9.java
index 3410d6a7..af9756a0 100644
--- a/src/io/github/sspanak/tt9/ime/TraditionalT9.java
+++ b/src/io/github/sspanak/tt9/ime/TraditionalT9.java
@@ -16,6 +16,7 @@ import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
+import io.github.sspanak.tt9.ui.SuggestionsView;
import io.github.sspanak.tt9.ui.UI;
public class TraditionalT9 extends KeyPadHandler {
@@ -31,6 +32,7 @@ public class TraditionalT9 extends KeyPadHandler {
// soft key view
private SoftKeyHandler softKeyHandler = null;
+ private SuggestionsView mSuggestionView = null;
public static Context getMainContext() {
@@ -59,6 +61,10 @@ public class TraditionalT9 extends KeyPadHandler {
softKeyHandler = new SoftKeyHandler(getLayoutInflater(), this);
}
+ if (mSuggestionView == null) {
+ mSuggestionView = new SuggestionsView(softKeyHandler.getView());
+ }
+
loadPreferences();
prefs.clearLastWord();
}
diff --git a/src/io/github/sspanak/tt9/preferences/T9Preferences.java b/src/io/github/sspanak/tt9/preferences/T9Preferences.java
index b85acfd9..7f1f7643 100644
--- a/src/io/github/sspanak/tt9/preferences/T9Preferences.java
+++ b/src/io/github/sspanak/tt9/preferences/T9Preferences.java
@@ -167,6 +167,8 @@ public class T9Preferences {
public int getSuggestionsMax() { return 20; }
public int getSuggestionsMin() { return 8; }
+ public int getSuggestionSelectAnimationDuration() { return 66; }
+ public int getSuggestionTranslateAnimationDuration() { return 0; }
/************* add word, last word *************/
diff --git a/src/io/github/sspanak/tt9/ui/CandidateView.java b/src/io/github/sspanak/tt9/ui/CandidateView.java
deleted file mode 100644
index d3a17996..00000000
--- a/src/io/github/sspanak/tt9/ui/CandidateView.java
+++ /dev/null
@@ -1,231 +0,0 @@
-package io.github.sspanak.tt9.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import io.github.sspanak.tt9.R;
-
-public class CandidateView extends View {
-
- private List mSuggestions = new ArrayList<>();
- protected int mSelectedIndex;
-
- private Drawable mSelectionHighlight;
-
- private Rect mBgPadding;
-
- private static final int MAX_SUGGESTIONS = 32;
- private static final int CANDIDATE_SCROLL_STEP = 20; // px
-
- private int[] mWordWidth = new int[MAX_SUGGESTIONS];
- private int[] mWordX = new int[MAX_SUGGESTIONS];
-
- private static final int X_GAP = 10;
-
- private static final List EMPTY_LIST = new ArrayList<>();
-
- private int mColorNormal;
- private int mColorRecommended;
- private int mColorOther;
- private int mVerticalPadding;
- private Paint mPaint;
- private int mTargetScrollX;
-
- private int mTotalWidth;
-
- Rect mPadding;
-
- /**
- * Construct a CandidateView for showing suggested words for completion.
- */
- public CandidateView(Context context) {
- super(context);
- mSelectionHighlight = context.getResources().getDrawable(
- android.R.drawable.list_selector_background);
- mSelectionHighlight.setState(new int[] {
- android.R.attr.state_enabled, android.R.attr.state_focused,
- android.R.attr.state_window_focused, android.R.attr.state_pressed });
-
- Resources r = context.getResources();
-
- setBackgroundColor(r.getColor(R.color.candidate_background));
-
- mColorNormal = r.getColor(R.color.candidate_normal);
- mColorRecommended = r.getColor(R.color.candidate_recommended);
- mColorOther = r.getColor(R.color.candidate_other);
- mVerticalPadding = r.getDimensionPixelSize(R.dimen.candidate_vertical_padding);
-
- mPaint = new Paint();
- mPaint.setColor(mColorNormal);
- mPaint.setAntiAlias(true);
- mPaint.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_font_height));
- mPaint.setStrokeWidth(0);
-
- mPadding = new Rect();
-
- setHorizontalFadingEdgeEnabled(true);
- setWillNotDraw(false);
- setHorizontalScrollBarEnabled(false);
- setVerticalScrollBarEnabled(false);
- }
-
- @Override
- public int computeHorizontalScrollRange() {
- return mTotalWidth;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int measuredWidth = resolveSize(50, widthMeasureSpec);
-
- // Get the desired height of the icon menu view (last row of items does
- // not have a divider below)
-
- mSelectionHighlight.getPadding(mPadding);
- final int desiredHeight = ((int) mPaint.getTextSize()) + mVerticalPadding + mPadding.top
- + mPadding.bottom;
-
- // Maximum possible width and desired height
- setMeasuredDimension(measuredWidth, resolveSize(desiredHeight, heightMeasureSpec));
- }
-
- /**
- * If the canvas is null, then only touch calculations are performed to pick
- * the target candidate.
- */
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- mTotalWidth = 0;
- if (mSuggestions == null)
- return;
-
- if (mBgPadding == null) {
- mBgPadding = new Rect(0, 0, 0, 0);
- if (getBackground() != null) {
- getBackground().getPadding(mBgPadding);
- }
- }
- int x = 0;
- final int count = mSuggestions.size();
- final int height = getHeight();
- final Rect bgPadding = mBgPadding;
- final Paint paint = mPaint;
- final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent());
-
- for (int i = 0; i < count; i++) {
- String suggestion = mSuggestions.get(i);
- if (suggestion.equals("\n")) {
- suggestion = "⏎"; // make it more clear it is a new line
- }
-
- float textWidth = paint.measureText(suggestion);
- final int wordWidth = (int) textWidth + X_GAP * 2;
-
- mWordX[i] = x;
- mWordWidth[i] = wordWidth;
- paint.setColor(mColorNormal);
- // if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth &&
- // !scrolled) {
- if (i == mSelectedIndex) {
- canvas.translate(x, 0);
- mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height);
- mSelectionHighlight.draw(canvas);
- canvas.translate(-x, 0);
- paint.setFakeBoldText(true);
- paint.setColor(mColorRecommended);
- } else {
- paint.setColor(mColorOther);
- }
-
- canvas.drawText(suggestion, x + X_GAP, y, paint);
- paint.setColor(mColorOther);
- canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top, x + wordWidth + 0.5f, height + 1,
- paint);
- paint.setFakeBoldText(false);
-
- x += wordWidth;
- }
- mTotalWidth = x;
- scrollTargetIntoView();
- }
-
- private void scrollTargetIntoView() {
- int currentX = getScrollX();
- int scrollDistance = mTargetScrollX - currentX;
-
- if (scrollDistance == 0) {
- return;
- }
-
- int maxStepX = (Math.abs(scrollDistance) > getWidth() * 0.5) ? (int)(CANDIDATE_SCROLL_STEP * 2.25) : CANDIDATE_SCROLL_STEP;
- int stepX = Integer.signum(scrollDistance) * Math.min(Math.abs(scrollDistance), maxStepX);
-
- scrollTo(currentX + stepX, getScrollY());
- invalidate();
- }
-
- public int getCurrentIndex() {
- return mSelectedIndex;
- }
-
- public String getCurrentSuggestion() {
- return getSuggestion(mSelectedIndex);
- }
-
- public String getSuggestion(int id) {
- return mSuggestions != null && id >= 0 && id < mSuggestions.size() ? mSuggestions.get(id) : "";
- }
-
- public void setSuggestions(List suggestions, int initialSel) {
- clear();
- if (suggestions != null) {
- mSuggestions = suggestions;
- mSelectedIndex = Math.max(initialSel, 0);
- }
- scrollTo(0, 0);
- mTargetScrollX = 0;
- // Compute the total width
- // onDraw(null);
- invalidate();
- requestLayout();
- }
-
- protected void clear() {
- mSuggestions = EMPTY_LIST;
- mSelectedIndex = -1;
- invalidate();
- }
-
- public void scrollToSuggestion(int increment) {
- if (mSuggestions != null && mSuggestions.size() > 1) {
- mSelectedIndex = mSelectedIndex + increment;
- if (mSelectedIndex == mSuggestions.size()) {
- mSelectedIndex = 0;
- } else if (mSelectedIndex < 0) {
- mSelectedIndex = mSuggestions.size() - 1;
- }
-
- int fullsize = getWidth();
- int halfsize = fullsize / 2;
- mTargetScrollX = mWordX[mSelectedIndex] + (mWordWidth[mSelectedIndex] / 2) - halfsize;
- if (mTargetScrollX < 0) {
- mTargetScrollX = 0;
- } else if (mTargetScrollX > (mTotalWidth - fullsize)) {
- mTargetScrollX = mTotalWidth - fullsize;
- }
-
- invalidate();
- }
- }
-
-}
diff --git a/src/io/github/sspanak/tt9/ui/SuggestionsAdapter.java b/src/io/github/sspanak/tt9/ui/SuggestionsAdapter.java
new file mode 100644
index 00000000..da56430d
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/SuggestionsAdapter.java
@@ -0,0 +1,67 @@
+package io.github.sspanak.tt9.ui;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+
+public class SuggestionsAdapter extends RecyclerView.Adapter {
+ private final int colorHighlight;
+ private final int layout;
+ private final int textViewResourceId;
+ private final LayoutInflater mInflater;
+ private final List mSuggestions;
+
+ private int selectedIndex = 0;
+
+
+ public SuggestionsAdapter(Context context, int layout, int textViewResourceId, int highLightColor, List suggestions) {
+ this.colorHighlight = highLightColor;
+ this.layout = layout;
+ this.textViewResourceId = textViewResourceId;
+ this.mInflater = LayoutInflater.from(context);
+ this.mSuggestions = suggestions;
+ }
+
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new ViewHolder(mInflater.inflate(layout, parent, false));
+ }
+
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ holder.suggestionItem.setText(mSuggestions.get(position));
+ holder.suggestionItem.setBackgroundColor(selectedIndex == position ? colorHighlight : Color.TRANSPARENT);
+ }
+
+
+ @Override
+ public int getItemCount() {
+ return mSuggestions.size();
+ }
+
+
+ public void setSelection(int index) {
+ selectedIndex = index;
+ }
+
+
+ public class ViewHolder extends RecyclerView.ViewHolder {
+ TextView suggestionItem;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ suggestionItem = itemView.findViewById(textViewResourceId);
+ }
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/SuggestionsView.java b/src/io/github/sspanak/tt9/ui/SuggestionsView.java
new file mode 100644
index 00000000..d039d297
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/SuggestionsView.java
@@ -0,0 +1,137 @@
+package io.github.sspanak.tt9.ui;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.DefaultItemAnimator;
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.preferences.T9Preferences;
+
+public class SuggestionsView {
+ private final List suggestions = new ArrayList<>();
+ protected int selectedIndex = 0;
+
+ private final RecyclerView mView;
+ private SuggestionsAdapter mSuggestionsAdapter;
+
+
+ public SuggestionsView(View mainView) {
+ super();
+
+ mView = mainView.findViewById(R.id.main_suggestions_list);
+ mView.setLayoutManager(new LinearLayoutManager(mainView.getContext(), RecyclerView.HORIZONTAL,false));
+
+ initDataAdapter(mainView.getContext());
+ initSeparator(mainView.getContext());
+ configureAnimation();
+ }
+
+
+ private void configureAnimation() {
+ DefaultItemAnimator animator = new DefaultItemAnimator();
+
+ int translateDuration = T9Preferences.getInstance().getSuggestionTranslateAnimationDuration();
+ int selectDuration = T9Preferences.getInstance().getSuggestionSelectAnimationDuration();
+
+ animator.setMoveDuration(selectDuration);
+ animator.setChangeDuration(translateDuration);
+ animator.setAddDuration(translateDuration);
+ animator.setRemoveDuration(translateDuration);
+
+ mView.setItemAnimator(animator);
+ }
+
+
+ private void initDataAdapter(Context context) {
+ mSuggestionsAdapter = new SuggestionsAdapter(
+ context,
+ R.layout.suggestion_list_view,
+ R.id.suggestion_list_item,
+ ContextCompat.getColor(context, R.color.candidate_selected),
+ suggestions
+ );
+ mView.setAdapter(mSuggestionsAdapter);
+ }
+
+
+ private void initSeparator(Context context) {
+ // Extra XML is required instead of a ColorDrawable object, because setting the highlight color
+ // erases the borders defined using the ColorDrawable.
+ Drawable separatorDrawable = ContextCompat.getDrawable(context, R.drawable.suggestion_separator);
+ if (separatorDrawable == null) {
+ return;
+ }
+
+ DividerItemDecoration separator = new DividerItemDecoration(mView.getContext(), RecyclerView.HORIZONTAL);
+ separator.setDrawable(separatorDrawable);
+ mView.addItemDecoration(separator);
+ }
+
+
+ public boolean isShown() {
+ return suggestions.size() > 0;
+ }
+
+
+ public int getCurrentIndex() {
+ return selectedIndex;
+ }
+
+
+ public String getCurrentSuggestion() {
+ return getSuggestion(selectedIndex);
+ }
+
+
+ public String getSuggestion(int id) {
+ return id >= 0 && id < suggestions.size() ? suggestions.get(id) : "";
+ }
+
+
+ @SuppressLint("NotifyDataSetChanged")
+ public void setSuggestions(List newSuggestions, int initialSel) {
+ suggestions.clear();
+ selectedIndex = 0;
+
+ if (newSuggestions != null) {
+ suggestions.addAll(newSuggestions);
+ selectedIndex = Math.max(initialSel, 0);
+ }
+
+ mSuggestionsAdapter.setSelection(selectedIndex);
+ mSuggestionsAdapter.notifyDataSetChanged();
+ mView.scrollToPosition(selectedIndex);
+ }
+
+
+ public void scrollToSuggestion(int increment) {
+ if (suggestions.size() <= 1) {
+ return;
+ }
+
+ int oldIndex = selectedIndex;
+
+ selectedIndex = selectedIndex + increment;
+ if (selectedIndex == suggestions.size()) {
+ selectedIndex = 0;
+ } else if (selectedIndex < 0) {
+ selectedIndex = suggestions.size() - 1;
+ }
+
+ mSuggestionsAdapter.setSelection(selectedIndex);
+ mSuggestionsAdapter.notifyItemChanged(oldIndex);
+ mSuggestionsAdapter.notifyItemChanged(selectedIndex);
+
+ mView.scrollToPosition(selectedIndex);
+ }
+}