fixed suggestion list flashing when typing fast or holding backspace (rendering is now debounced)
This commit is contained in:
parent
f8d7af669d
commit
02af8561e2
3 changed files with 60 additions and 58 deletions
|
|
@ -57,7 +57,7 @@ public class SuggestionOps {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public String get(int index) {
|
public String get(int index) {
|
||||||
return suggestionBar != null ? suggestionBar.getSuggestion(index) : "";
|
return suggestionBar != null ? suggestionBar.get(index) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -69,20 +69,20 @@ public class SuggestionOps {
|
||||||
|
|
||||||
public void set(ArrayList<String> suggestions) {
|
public void set(ArrayList<String> suggestions) {
|
||||||
if (suggestionBar != null) {
|
if (suggestionBar != null) {
|
||||||
suggestionBar.setSuggestions(suggestions, 0, false);
|
suggestionBar.setMany(suggestions, 0, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(ArrayList<String> suggestions, boolean containsGenerated) {
|
public void set(ArrayList<String> suggestions, boolean containsGenerated) {
|
||||||
if (suggestionBar != null) {
|
if (suggestionBar != null) {
|
||||||
suggestionBar.setSuggestions(suggestions, 0, containsGenerated);
|
suggestionBar.setMany(suggestions, 0, containsGenerated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void set(ArrayList<String> suggestions, int selectIndex, boolean containsGenerated) {
|
public void set(ArrayList<String> suggestions, int selectIndex, boolean containsGenerated) {
|
||||||
if (suggestionBar != null) {
|
if (suggestionBar != null) {
|
||||||
suggestionBar.setSuggestions(suggestions, selectIndex, containsGenerated);
|
suggestionBar.setMany(suggestions, selectIndex, containsGenerated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ public class SettingsStore extends SettingsHotkeys {
|
||||||
public final static int SUGGESTIONS_MAX = 20;
|
public final static int SUGGESTIONS_MAX = 20;
|
||||||
public final static int SUGGESTIONS_MIN = 8;
|
public final static int SUGGESTIONS_MIN = 8;
|
||||||
public final static int SUGGESTIONS_POSITIONS_LIMIT = 100;
|
public final static int SUGGESTIONS_POSITIONS_LIMIT = 100;
|
||||||
|
public final static int SUGGESTIONS_RENDER_DEBOUNCE_TIME = 25; // ms
|
||||||
|
public final static int SUGGESTIONS_RENDER_CLEAR_DEBOUNCE_TIME = 60; // ms
|
||||||
public final static int SUGGESTIONS_SELECT_ANIMATION_DURATION = 66;
|
public final static int SUGGESTIONS_SELECT_ANIMATION_DURATION = 66;
|
||||||
public final static int SUGGESTIONS_TRANSLATE_ANIMATION_DURATION = 0;
|
public final static int SUGGESTIONS_TRANSLATE_ANIMATION_DURATION = 0;
|
||||||
public final static int TEXT_INPUT_DEBOUNCE_TIME = 500; // ms
|
public final static int TEXT_INPUT_DEBOUNCE_TIME = 500; // ms
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,9 @@ public class SuggestionsBar {
|
||||||
private final String STEM_PUNCTUATION_VARIATION_PREFIX = "";
|
private final String STEM_PUNCTUATION_VARIATION_PREFIX = "";
|
||||||
@NonNull private String stem = "";
|
@NonNull private String stem = "";
|
||||||
|
|
||||||
private int backgroundColor = Color.TRANSPARENT;
|
private int defaultBackgroundColor = Color.TRANSPARENT;
|
||||||
|
private int backgroundColor;
|
||||||
|
|
||||||
private double lastClickTime = 0;
|
private double lastClickTime = 0;
|
||||||
private int lastScrollIndex = 0;
|
private int lastScrollIndex = 0;
|
||||||
private int selectedIndex = 0;
|
private int selectedIndex = 0;
|
||||||
|
|
@ -47,7 +49,7 @@ public class SuggestionsBar {
|
||||||
private SuggestionsAdapter mSuggestionsAdapter;
|
private SuggestionsAdapter mSuggestionsAdapter;
|
||||||
private Vibration vibration;
|
private Vibration vibration;
|
||||||
|
|
||||||
private final Handler alternativeScrollingHandler = new Handler();
|
private final Handler delayedDisplayHandler = new Handler();
|
||||||
|
|
||||||
|
|
||||||
public SuggestionsBar(@NonNull SettingsStore settings, @NonNull ResizableMainView mainView, @NonNull Runnable onItemClick) {
|
public SuggestionsBar(@NonNull SettingsStore settings, @NonNull ResizableMainView mainView, @NonNull Runnable onItemClick) {
|
||||||
|
|
@ -136,7 +138,7 @@ public class SuggestionsBar {
|
||||||
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public String getSuggestion(int id) {
|
public String get(int id) {
|
||||||
if (id < 0 || id >= visibleSuggestions.size()) {
|
if (id < 0 || id >= visibleSuggestions.size()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
@ -186,17 +188,17 @@ public class SuggestionsBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setSuggestions(@Nullable List<String> newSuggestions, int initialSel, boolean containsGenerated) {
|
public void setMany(@Nullable List<String> newSuggestions, int initialSel, boolean containsGenerated) {
|
||||||
suggestions = newSuggestions;
|
suggestions = newSuggestions;
|
||||||
ecoSetBackground(newSuggestions);
|
|
||||||
|
|
||||||
visibleSuggestions.clear();
|
|
||||||
selectedIndex = newSuggestions == null || newSuggestions.isEmpty() ? 0 : Math.max(initialSel, 0);
|
selectedIndex = newSuggestions == null || newSuggestions.isEmpty() ? 0 : Math.max(initialSel, 0);
|
||||||
|
|
||||||
|
visibleSuggestions.clear();
|
||||||
setStem(newSuggestions, containsGenerated);
|
setStem(newSuggestions, containsGenerated);
|
||||||
addManySuggestions(newSuggestions, mView != null ? SettingsStore.SUGGESTIONS_MAX : Integer.MAX_VALUE);
|
addMany(newSuggestions, mView != null ? SettingsStore.SUGGESTIONS_MAX : Integer.MAX_VALUE);
|
||||||
|
|
||||||
selectedIndex = Math.min(selectedIndex, visibleSuggestions.size() - 1);
|
selectedIndex = Math.min(selectedIndex, visibleSuggestions.size() - 1);
|
||||||
displaySuggestions();
|
|
||||||
|
renderDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -235,13 +237,13 @@ public class SuggestionsBar {
|
||||||
* for performance reasons, hence the "limit" parameter. When they are too many, the SHOW_MORE_SUGGESTION,
|
* for performance reasons, hence the "limit" parameter. When they are too many, the SHOW_MORE_SUGGESTION,
|
||||||
* will be displayed at the end.
|
* will be displayed at the end.
|
||||||
*/
|
*/
|
||||||
private void addManySuggestions(List<String> newSuggestions, int limit) {
|
private void addMany(List<String> newSuggestions, int limit) {
|
||||||
if (newSuggestions == null) {
|
if (newSuggestions == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0, end = Math.min(limit, newSuggestions.size()); i < end; i++) {
|
for (int i = 0, end = Math.min(limit, newSuggestions.size()); i < end; i++) {
|
||||||
addSuggestion(newSuggestions.get(i));
|
add(newSuggestions.get(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newSuggestions.size() > limit) {
|
if (newSuggestions.size() > limit) {
|
||||||
|
|
@ -250,14 +252,14 @@ public class SuggestionsBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addSuggestion(@NonNull String suggestion) {
|
private void add(@NonNull String suggestion) {
|
||||||
// shorten the stem variations
|
// shorten the stem variations
|
||||||
if (!stem.isEmpty() && suggestion.length() == stem.length() + 1 && suggestion.toLowerCase().startsWith(stem.toLowerCase())) {
|
if (!stem.isEmpty() && suggestion.length() == stem.length() + 1 && suggestion.toLowerCase().startsWith(stem.toLowerCase())) {
|
||||||
String trimmedSuggestion = suggestion.substring(stem.length());
|
String trimmedSuggestion = suggestion.substring(stem.length());
|
||||||
char firstChar = trimmedSuggestion.charAt(0);
|
char firstChar = trimmedSuggestion.charAt(0);
|
||||||
|
|
||||||
String prefix = Character.isAlphabetic(firstChar) && !Characters.isCombiningPunctuation(firstChar) ? STEM_VARIATION_PREFIX : STEM_PUNCTUATION_VARIATION_PREFIX;
|
String prefix = Character.isAlphabetic(firstChar) && !Characters.isCombiningPunctuation(firstChar) ? STEM_VARIATION_PREFIX : STEM_PUNCTUATION_VARIATION_PREFIX;
|
||||||
prefix = Characters.isFathatan(firstChar) ? " " : prefix; // Fix incorrect display of fathatan without a base character. It is a combining character, but since it is a letter, we must include a base character not to break it, with a "..." prefix
|
prefix = Characters.isFathatan(firstChar) ? " " : prefix; // Fix incorrect display of Fathatan without a base character. It is a combining character, but since it is a letter, we must include a base character not to break it, with a "..." prefix
|
||||||
visibleSuggestions.add(prefix + formatUnreadableSuggestion(trimmedSuggestion));
|
visibleSuggestions.add(prefix + formatUnreadableSuggestion(trimmedSuggestion));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -266,11 +268,24 @@ public class SuggestionsBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void displaySuggestions() {
|
/**
|
||||||
|
* Reduces flashing of the suggestions bar when the suggestions are empty and saves some resources
|
||||||
|
* by reducing the calls to render().
|
||||||
|
*/
|
||||||
|
private void renderDebounced() {
|
||||||
|
final int delay = visibleSuggestions.isEmpty() ? SettingsStore.SUGGESTIONS_RENDER_CLEAR_DEBOUNCE_TIME : SettingsStore.SUGGESTIONS_RENDER_DEBOUNCE_TIME;
|
||||||
|
delayedDisplayHandler.removeCallbacksAndMessages(null);
|
||||||
|
delayedDisplayHandler.postDelayed(this::render, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void render() {
|
||||||
if (mView == null) {
|
if (mView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBackground(false);
|
||||||
|
|
||||||
boolean smooth = settings.getSuggestionSmoothScroll() && visibleSuggestions.size() <= SettingsStore.SUGGESTIONS_MAX + 1;
|
boolean smooth = settings.getSuggestionSmoothScroll() && visibleSuggestions.size() <= SettingsStore.SUGGESTIONS_MAX + 1;
|
||||||
mView.setItemAnimator(smooth ? animator : null);
|
mView.setItemAnimator(smooth ? animator : null);
|
||||||
|
|
||||||
|
|
@ -282,9 +297,10 @@ public class SuggestionsBar {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If addManySuggestions() constrained the visible suggestions, the end of the list will contain
|
* If addMany() constrained the visible suggestions, the end of the list will contain
|
||||||
* the SHOW_MORE_SUGGESTION. This method will display remove the SHOW_MORE_SUGGESTION and display
|
* the SHOW_MORE_SUGGESTION. This method will remove the SHOW_MORE_SUGGESTION, prepare
|
||||||
* all the hidden suggestions. It also scrolls correctly to the new visible suggestion.
|
* all hidden suggestions for displaying, and will scroll correctly to the new visible suggestion.
|
||||||
|
* After that, you must call render(), to visualize the changes.
|
||||||
*/
|
*/
|
||||||
private boolean appendHiddenSuggestionsIfNeeded(boolean scrollBack) {
|
private boolean appendHiddenSuggestionsIfNeeded(boolean scrollBack) {
|
||||||
if (mView == null || !visibleSuggestions.get(selectedIndex).equals(SHOW_MORE_SUGGESTION)) {
|
if (mView == null || !visibleSuggestions.get(selectedIndex).equals(SHOW_MORE_SUGGESTION)) {
|
||||||
|
|
@ -292,7 +308,7 @@ public class SuggestionsBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
visibleSuggestions.clear();
|
visibleSuggestions.clear();
|
||||||
addManySuggestions(suggestions, Integer.MAX_VALUE);
|
addMany(suggestions, Integer.MAX_VALUE);
|
||||||
selectedIndex = scrollBack || selectedIndex >= visibleSuggestions.size() ? visibleSuggestions.size() - 1 : selectedIndex;
|
selectedIndex = scrollBack || selectedIndex >= visibleSuggestions.size() ? visibleSuggestions.size() - 1 : selectedIndex;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -320,9 +336,9 @@ public class SuggestionsBar {
|
||||||
|
|
||||||
calculateScrollIndex(increment);
|
calculateScrollIndex(increment);
|
||||||
if (appendHiddenSuggestionsIfNeeded(increment < 0)) {
|
if (appendHiddenSuggestionsIfNeeded(increment < 0)) {
|
||||||
displaySuggestions();
|
render();
|
||||||
}
|
}
|
||||||
scrollToIndex();
|
scrollToSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -338,7 +354,7 @@ public class SuggestionsBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void scrollToIndex() {
|
private void scrollToSelected() {
|
||||||
if (mView == null) {
|
if (mView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -346,19 +362,19 @@ public class SuggestionsBar {
|
||||||
mSuggestionsAdapter.setSelection(selectedIndex);
|
mSuggestionsAdapter.setSelection(selectedIndex);
|
||||||
|
|
||||||
if (settings.getSuggestionScrollingDelay() > 0) {
|
if (settings.getSuggestionScrollingDelay() > 0) {
|
||||||
alternativeScrollingHandler.removeCallbacksAndMessages(null);
|
delayedDisplayHandler.removeCallbacksAndMessages(null);
|
||||||
alternativeScrollingHandler.postDelayed(this::scrollView, settings.getSuggestionScrollingDelay());
|
delayedDisplayHandler.postDelayed(this::renderScroll, settings.getSuggestionScrollingDelay());
|
||||||
} else {
|
} else {
|
||||||
scrollView();
|
renderScroll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells the adapter to scroll. Always call scrollToIndex() first,
|
* Tells the adapter to scroll. Always call scrollToSelected() first,
|
||||||
* to set the selected index in the adapter.
|
* to set the selected index in the adapter.
|
||||||
*/
|
*/
|
||||||
private void scrollView() {
|
private void renderScroll() {
|
||||||
if (mView == null) {
|
if (mView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -383,12 +399,12 @@ public class SuggestionsBar {
|
||||||
|
|
||||||
Context context = mView.getContext();
|
Context context = mView.getContext();
|
||||||
|
|
||||||
backgroundColor = ContextCompat.getColor(context, R.color.keyboard_background);
|
defaultBackgroundColor = ContextCompat.getColor(context, R.color.keyboard_background);
|
||||||
mSuggestionsAdapter.setColorDefault(ContextCompat.getColor(context, R.color.keyboard_text));
|
mSuggestionsAdapter.setColorDefault(ContextCompat.getColor(context, R.color.keyboard_text));
|
||||||
mSuggestionsAdapter.setColorHighlight(ContextCompat.getColor(context, R.color.suggestion_selected_text));
|
mSuggestionsAdapter.setColorHighlight(ContextCompat.getColor(context, R.color.suggestion_selected_text));
|
||||||
mSuggestionsAdapter.setBackgroundHighlight(ContextCompat.getColor(context, R.color.suggestion_selected_background));
|
mSuggestionsAdapter.setBackgroundHighlight(ContextCompat.getColor(context, R.color.suggestion_selected_background));
|
||||||
|
|
||||||
setBackground(visibleSuggestions);
|
setBackground(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -397,36 +413,20 @@ public class SuggestionsBar {
|
||||||
* Makes the background transparent, when there are no suggestions and theme-colored,
|
* Makes the background transparent, when there are no suggestions and theme-colored,
|
||||||
* when there are suggestions.
|
* when there are suggestions.
|
||||||
*/
|
*/
|
||||||
private void setBackground(List<String> newSuggestions) {
|
private void setBackground(boolean force) {
|
||||||
if (mView == null) {
|
if (mView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int newSuggestionsSize = newSuggestions != null ? newSuggestions.size() : 0;
|
boolean mustChange = (
|
||||||
if (newSuggestionsSize == 0) {
|
(backgroundColor == Color.TRANSPARENT && !visibleSuggestions.isEmpty()) ||
|
||||||
mView.setBackgroundColor(Color.TRANSPARENT);
|
(backgroundColor == defaultBackgroundColor && visibleSuggestions.isEmpty())
|
||||||
return;
|
);
|
||||||
|
|
||||||
|
if (force || mustChange) {
|
||||||
|
backgroundColor = visibleSuggestions.isEmpty() ? Color.TRANSPARENT : defaultBackgroundColor;
|
||||||
|
mView.setBackgroundColor(backgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
mView.setBackgroundColor(backgroundColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ecoSetBackground
|
|
||||||
* A performance-optimized version of "setBackground().
|
|
||||||
* Determines if the suggestions have changed and only then it changes the background.
|
|
||||||
*/
|
|
||||||
private void ecoSetBackground(List<String> newSuggestions) {
|
|
||||||
int newSuggestionsSize = newSuggestions != null ? newSuggestions.size() : 0;
|
|
||||||
if (
|
|
||||||
(newSuggestionsSize == 0 && visibleSuggestions.isEmpty())
|
|
||||||
|| (newSuggestionsSize > 0 && !visibleSuggestions.isEmpty())
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setBackground(newSuggestions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -438,7 +438,7 @@ public class SuggestionsBar {
|
||||||
vibration.vibrate();
|
vibration.vibrate();
|
||||||
selectedIndex = position;
|
selectedIndex = position;
|
||||||
if (appendHiddenSuggestionsIfNeeded(false)) {
|
if (appendHiddenSuggestionsIfNeeded(false)) {
|
||||||
displaySuggestions();
|
render();
|
||||||
} else {
|
} else {
|
||||||
onItemClick.run();
|
onItemClick.run();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue