I am making a little chat in my Android app and I am using emojis (emoticons) that displays in EditText and TextView with SpannableString.
For that I made a class (code is below). Also I made a gridview which loads all the emojis. The problem is that all works is very slow (because I have 500 emojis) It takes a lot to load and display the emojis. Below is the code I'm using.
I am looking for a better algorithm to replace the String with an emoji or another way load the emojis faster.
public class EmoticonHandler {
private static final Map<String, Integer> emoticons = new HashMap<String, Integer>();
private static void addPattern(Map<String, Integer> map, String smile,
int resource) {
map.put(smile, resource);
}
// Add the items to the HasMap
static {
// Smileys
addPattern(emoticons, "#ce001#", R.drawable._ce001_);
addPattern(emoticons, "#ce002#", R.drawable._ce002_);
addPattern(emoticons, "#ce003#", R.drawable._ce003_);
addPattern(emoticons, "#ce004#", R.drawable._ce004_);
// Here comes the other 500 emojis
}
// Get image for each text smiles
public static void getSmiledText(Context context, Spannable span, int size) {
int index;
for (index = 0; index < span.length(); index++) {
for (Entry<String, Integer> entry : emoticons.entrySet()) {
int length = entry.getKey().length();
if (index + length > span.length())
continue;
if (span.subSequence(index, index + length).toString()
.equals(entry.getKey())) {
span.setSpan(new EmoticonSpan(context, entry.getValue(),
size), index, index + length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
index += length - 1;
break;
}
}
}
}}
Here is the code for the EmoticonSpan
public class EmoticonSpan extends DynamicDrawableSpan {
private Context context;
private int resourceID;
private int size;
private Drawable drawable;
public EmoticonSpan(Context context, int resourceID, int size) {
super();
this.context = context;
this.resourceID = resourceID;
this.size = size;
}
#Override
public Drawable getDrawable() {
if (drawable == null) {
try {
drawable = context.getResources().getDrawable(resourceID);
drawable.setBounds(0, 0, size, size);
} catch (Exception e) {
// Swallow
}
}
return drawable;
}
}
You can use android guide recommendation to display grid view images smoothly and off your main up thread.
http://developer.android.com/training/displaying-bitmaps/display-bitmap.html#gridview
I had a similar problem. Ended up using Emoji maps. Just cram Emojis on a (single) image of no more than 2048X2048 (the biggest Android can handle), and show that as the input grid. Then calculate the X/Y position and find out which Emoji was pointed by the user.
Just remember to take into consideration different device sizes (mdpi ... xxxdpi), and calculate position with correct scroll, and resize offsets.
Here is good code for you :
public class EmojiKeyboard {
private static final String TAG = "EmojiKeyboard";
private static final String PREF_KEY_HEIGHT_KB = "EmojiKbHeight";
private Context context;
private int screenHeight = -1;
private int emojiKbHeight = -1;
private PopupWindow emojiKeyboardPopup;
private View view;
private SharedPreferences preferences;
public EmojiKeyboard(Context context, View view) {
if (context instanceof Activity) {
this.context = context;
this.view = view;
preferences = context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE);
//Restore EmojiKeyboard Height
emojiKbHeight = preferences.getInt(PREF_KEY_HEIGHT_KB, -1);
//TODO support less then 11 API, and not perfect resizing when switched the keyboard
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
/*
* Get root view height
* */
screenHeight = screenHeight == -1 && bottom > oldBottom
? bottom
: screenHeight;
/*
* Calculate soft keyboard height
* */
int dHeight = oldBottom - bottom;
boolean validHeight = emojiKbHeight == -1 && dHeight > 80 && bottom != oldBottom;
/*
* Сheck twice because the keyboard may have been switched
* */
emojiKbHeight = validHeight
? dHeight : emojiKbHeight != (dHeight) && dHeight > 0
? dHeight
: emojiKbHeight;
/*
* Store emoji keyboard height into SharedPreferences
* */
preferences.edit().putInt(PREF_KEY_HEIGHT_KB, emojiKbHeight).commit();
/*
* If layout returned to a standard height then dismissing keyboard (OnBackPressed)
* */
if (screenHeight == bottom) {
dismissEmojiKeyboard();
}
/*
* Resize emoji on the go when a user switches between keyboards
* */
resizeEmoji();
}
});
}
}
public void showEmoji() {
if (emojiKeyboardPopup == null) {
createEmojiKeyboard();
}
if (!isShowed()) {
new Handler().postDelayed(new Runnable() {
public void run() {
emojiKeyboardPopup.showAtLocation(view, Gravity.BOTTOM, 0, 0);
resizeEmoji();
}
}, 10L);
} else {
dismissEmojiKeyboard();
}
}
public void createEmojiKeyboard() {
EmojiView emojiKeyboard = new EmojiView(context, EmojiView.EMOJI_DARK_STYLE, new EmojiView.onEmojiClickListener() {
public void onBackspace() {
if (((Activity) context).getWindow().getCurrentFocus() instanceof EditText) {
((Activity) context).getWindow().getCurrentFocus().dispatchKeyEvent(new KeyEvent(0, 67));
}
}
public void onEmojiSelected(Emojicon emojicon) {
if (((Activity) context).getWindow().getCurrentFocus() instanceof EditText) {
EmojiView.input((EditText) ((Activity) context).getWindow().getCurrentFocus(), emojicon);
}
}
});
emojiKeyboardPopup = new PopupWindow(emojiKeyboard);
emojiKeyboardPopup.setHeight(View.MeasureSpec.makeMeasureSpec(setEmojiKeyboardHeight(), View.MeasureSpec.EXACTLY));
emojiKeyboardPopup.setWidth(View.MeasureSpec.makeMeasureSpec(getDisplayDimensions(context).x, View.MeasureSpec.EXACTLY));
emojiKeyboardPopup.setAnimationStyle(0);
}
public void dismissEmojiKeyboard() {
if (isShowed()) {
emojiKeyboardPopup.dismiss();
}
}
public boolean isShowed() {
return emojiKeyboardPopup != null && emojiKeyboardPopup.isShowing();
}
/*
* Emoji set up size
* */
public void resizeEmoji() {
if (isShowed()) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) emojiKeyboardPopup.getContentView().getLayoutParams();
layoutParams.height = setEmojiKeyboardHeight();
wm.updateViewLayout(emojiKeyboardPopup.getContentView(), layoutParams);
}
}
public int setEmojiKeyboardHeight() {
return emojiKbHeight == -1 && emojiKbHeight != screenHeight && emojiKbHeight < 80
? (getDisplayDimensions(context).y / 2)
: emojiKbHeight;
}
public Point getDisplayDimensions(Context context) {
Point size = new Point();
WindowManager w = ((Activity) context).getWindowManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
w.getDefaultDisplay().getSize(size);
} else {
Display d = w.getDefaultDisplay();
size.x = d.getWidth();
size.y = d.getHeight();
}
return size;
}
}
Related
Background
After a lot of searching for the best solution of auto-resizing TextView (according to content, size, min&max lines, and font-size restrictions), I've made a merged solution for it all, here.
NOTE: I don't use other solutions because they don't work well, each has its own issues (something isn't supported, text goes outside of TextView, text get truncated,...) .
Demonstration of it works:
https://raw.githubusercontent.com/AndroidDeveloperLB/AutoFitTextView/master/animationPreview.gif
The problem
On some cases, the last character of one line wraps to the next line, as such:
Green is the boundaries of the TextView, red is outside of it.
The code
Basically, given the size of the TextView, its min&max font size and min&max lines, and the content (text) that's supposed to be within, it finds (using binary search) what font size should fit within the boundaries of the TextView.
The code is available in Github already, but here it is just in case :
public class AutoResizeTextView extends AppCompatTextView {
private static final int NO_LINE_LIMIT = -1;
private final RectF _availableSpaceRect = new RectF();
private final SizeTester _sizeTester;
private float _maxTextSize, _spacingMult = 1.0f, _spacingAdd = 0.0f, _minTextSize;
private int _widthLimit, _maxLines;
private boolean _initialized = false;
private TextPaint _paint;
private interface SizeTester {
/**
* #param suggestedSize Size of text to be tested
* #param availableSpace available space in which text must fit
* #return an integer < 0 if after applying {#code suggestedSize} to
* text, it takes less space than {#code availableSpace}, > 0
* otherwise
*/
int onTestSize(int suggestedSize, RectF availableSpace);
}
public AutoResizeTextView(final Context context) {
this(context, null, android.R.attr.textViewStyle);
}
public AutoResizeTextView(final Context context, final AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public AutoResizeTextView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
// using the minimal recommended font size
_minTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics());
_maxTextSize = getTextSize();
_paint = new TextPaint(getPaint());
if (_maxLines == 0)
// no value was assigned during construction
_maxLines = NO_LINE_LIMIT;
// prepare size tester:
_sizeTester = new SizeTester() {
final RectF textRect = new RectF();
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public int onTestSize(final int suggestedSize, final RectF availableSPace) {
_paint.setTextSize(suggestedSize);
final TransformationMethod transformationMethod = getTransformationMethod();
final String text;
if (transformationMethod != null)
text = transformationMethod.getTransformation(getText(), AutoResizeTextView.this).toString();
else
text = getText().toString();
final boolean singleLine = getMaxLines() == 1;
if (singleLine) {
textRect.bottom = _paint.getFontSpacing();
textRect.right = _paint.measureText(text);
} else {
final StaticLayout layout = new StaticLayout(text, _paint, _widthLimit, Alignment.ALIGN_NORMAL, _spacingMult, _spacingAdd, true);
// return early if we have more lines
if (getMaxLines() != NO_LINE_LIMIT && layout.getLineCount() > getMaxLines())
return 1;
textRect.bottom = layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++)
if (maxWidth < layout.getLineRight(i) - layout.getLineLeft(i))
maxWidth = (int) layout.getLineRight(i) - (int) layout.getLineLeft(i);
textRect.right = maxWidth;
}
textRect.offsetTo(0, 0);
if (availableSPace.contains(textRect))
// may be too small, don't worry we will find the best match
return -1;
// else, too big
return 1;
}
};
_initialized = true;
}
#Override
public void setAllCaps(boolean allCaps) {
super.setAllCaps(allCaps);
adjustTextSize();
}
#Override
public void setTypeface(final Typeface tf) {
super.setTypeface(tf);
adjustTextSize();
}
#Override
public void setTextSize(final float size) {
_maxTextSize = size;
adjustTextSize();
}
#Override
public void setMaxLines(final int maxlines) {
super.setMaxLines(maxlines);
_maxLines = maxlines;
adjustTextSize();
}
#Override
public int getMaxLines() {
return _maxLines;
}
#Override
public void setSingleLine() {
super.setSingleLine();
_maxLines = 1;
adjustTextSize();
}
#Override
public void setSingleLine(final boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine)
_maxLines = 1;
else _maxLines = NO_LINE_LIMIT;
adjustTextSize();
}
#Override
public void setLines(final int lines) {
super.setLines(lines);
_maxLines = lines;
adjustTextSize();
}
#Override
public void setTextSize(final int unit, final float size) {
final Context c = getContext();
Resources r;
if (c == null)
r = Resources.getSystem();
else r = c.getResources();
_maxTextSize = TypedValue.applyDimension(unit, size, r.getDisplayMetrics());
adjustTextSize();
}
#Override
public void setLineSpacing(final float add, final float mult) {
super.setLineSpacing(add, mult);
_spacingMult = mult;
_spacingAdd = add;
}
/**
* Set the lower text size limit and invalidate the view
*
* #param minTextSize
*/
public void setMinTextSize(final float minTextSize) {
_minTextSize = minTextSize;
adjustTextSize();
}
private void adjustTextSize() {
// This is a workaround for truncated text issue on ListView, as shown here: https://github.com/AndroidDeveloperLB/AutoFitTextView/pull/14
// TODO think of a nicer, elegant solution.
// post(new Runnable()
// {
// #Override
// public void run()
// {
if (!_initialized)
return;
final int startSize = (int) _minTextSize;
final int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
_widthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
if (_widthLimit <= 0)
return;
_paint = new TextPaint(getPaint());
_availableSpaceRect.right = _widthLimit;
_availableSpaceRect.bottom = heightLimit;
superSetTextSize(startSize);
// }
// });
}
private void superSetTextSize(int startSize) {
int textSize = binarySearch(startSize, (int) _maxTextSize, _sizeTester, _availableSpaceRect);
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
private int binarySearch(final int start, final int end, final SizeTester sizeTester, final RectF availableSpace) {
int lastBest = start, lo = start, hi = end - 1, mid;
while (lo <= hi) {
mid = lo + hi >>> 1;
final int midValCmp = sizeTester.onTestSize(mid, availableSpace);
if (midValCmp < 0) {
lastBest = lo;
lo = mid + 1;
} else if (midValCmp > 0) {
hi = mid - 1;
lastBest = hi;
} else return mid;
}
// make sure to return last best
// this is what should always be returned
return lastBest;
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
super.onTextChanged(text, start, before, after);
adjustTextSize();
}
#Override
protected void onSizeChanged(final int width, final int height, final int oldwidth, final int oldheight) {
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight)
adjustTextSize();
}
}
The question
Why does it occur? What can I do to fix this?
Seems it's possible using support library:
<TextView
android:layout_width="250dp" android:layout_height="wrap_content" android:background="#f00"
android:breakStrategy="balanced" android:hyphenationFrequency="none"
android:text="This is an example text" android:textSize="30dp" app:autoSizeTextType="uniform"/>
Sadly, it has 2 disadvantages:
Doesn't always spread words nicely. Reported here.
Requires Android API 23 and above (here).
More information here.
I had similar problem in my project. Long-time Googling, StackOverflow (with your question among others). Nothing.
And my final "solution" was BreakIterator for words + measure them all to check this situation.
UPDATE (2018-08-10):
static public boolean isTextFitWidth(final #Nullable String source, final #NonNull BreakIterator bi, final #NonNull TextPaint paint, final int width, final float textSize)
{
if (null == source || source.length() <= 0) {
return true;
}
TextPaint paintCopy = new TextPaint();
paintCopy.set(paint);
paintCopy.setTextSize(textSize);
bi.setText(source);
int start = bi.first();
for (int end = bi.next(); BreakIterator.DONE != end; start = end, end = bi.next()) {
int wordWidth = (int)Math.ceil(paintCopy.measureText(source, start, end));
if (wordWidth > width) {
return false;
}
}
return true;
}
static public boolean isTextFitWidth(final #NonNull TextView textView, final #NonNull BreakIterator bi, final int width, final #Nullable Float textSize)
{
final int textWidth = width - textView.getPaddingLeft() - textView.getPaddingRight();
return isTextFitWidth(textView.getText().toString(), bi, textView.getPaint(), textWidth,
null != textSize ? textSize : textView.getTextSize());
}
Could someone help me understand or guide me on some reading material on how to create emoticons and how they work on android?
I need to know the whole process from a layman versus programmatical point of view.
We cannot create our own custom emoticons for generic keyboard in android. Because these images are stored in form of codes. which may not be implemented by facebook,skype and other. We have to follow the build in emoticons See list,
If you want to use them within your app. Use this
See this and this.
See SoftKeyboard sample here
And tutorial on Creating a Custom keyboard
See the article : https://www.androidhive.info/2016/11/android-integrate-emojis-keyboard-app/
to understand how emojis works and how to implement emoji feature for your application.
See the article : https://apps.timwhitlock.info/emoji/tables/unicode
to determine the unicode of emojis.
you can get input emoji from keyboard. resize it according to your requirement.for rotation and flipping support you can play with the layout, holding the emoji.
use custom library :
https://github.com/rockerhieu/emojicon
public class EmojiKeyboard {
private static final String TAG = "EmojiKeyboard";
private static final String PREF_KEY_HEIGHT_KB = "EmojiKbHeight";
private Context context;
private int screenHeight = -1;
private int emojiKbHeight = -1;
private PopupWindow emojiKeyboardPopup;
private View view;
private SharedPreferences preferences;
public EmojiKeyboard(Context context, View view) {
if (context instanceof Activity) {
this.context = context;
this.view = view;
preferences = context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE);
//Restore EmojiKeyboard Height
emojiKbHeight = preferences.getInt(PREF_KEY_HEIGHT_KB, -1);
//TODO support less then 11 API, and not perfect resizing when switched the keyboard
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
/*
* Get root view height
* */
screenHeight = screenHeight == -1 && bottom > oldBottom
? bottom
: screenHeight;
/*
* Calculate soft keyboard height
* */
int dHeight = oldBottom - bottom;
boolean validHeight = emojiKbHeight == -1 && dHeight > 80 && bottom != oldBottom;
/*
* Сheck twice because the keyboard may have been switched
* */
emojiKbHeight = validHeight
? dHeight : emojiKbHeight != (dHeight) && dHeight > 0
? dHeight
: emojiKbHeight;
/*
* Store emoji keyboard height into SharedPreferences
* */
preferences.edit().putInt(PREF_KEY_HEIGHT_KB, emojiKbHeight).commit();
/*
* If layout returned to a standard height then dismissing keyboard (OnBackPressed)
* */
if (screenHeight == bottom) {
dismissEmojiKeyboard();
}
/*
* Resize emoji on the go when a user switches between keyboards
* */
resizeEmoji();
}
});
}
}
public void showEmoji() {
if (emojiKeyboardPopup == null) {
createEmojiKeyboard();
}
if (!isShowed()) {
new Handler().postDelayed(new Runnable() {
public void run() {
emojiKeyboardPopup.showAtLocation(view, Gravity.BOTTOM, 0, 0);
resizeEmoji();
}
}, 10L);
} else {
dismissEmojiKeyboard();
}
}
public void createEmojiKeyboard() {
EmojiView emojiKeyboard = new EmojiView(context, EmojiView.EMOJI_DARK_STYLE, new EmojiView.onEmojiClickListener() {
public void onBackspace() {
if (((Activity) context).getWindow().getCurrentFocus() instanceof EditText) {
((Activity) context).getWindow().getCurrentFocus().dispatchKeyEvent(new KeyEvent(0, 67));
}
}
public void onEmojiSelected(Emojicon emojicon) {
if (((Activity) context).getWindow().getCurrentFocus() instanceof EditText) {
EmojiView.input((EditText) ((Activity) context).getWindow().getCurrentFocus(), emojicon);
}
}
});
emojiKeyboardPopup = new PopupWindow(emojiKeyboard);
emojiKeyboardPopup.setHeight(View.MeasureSpec.makeMeasureSpec(setEmojiKeyboardHeight(), View.MeasureSpec.EXACTLY));
emojiKeyboardPopup.setWidth(View.MeasureSpec.makeMeasureSpec(getDisplayDimensions(context).x, View.MeasureSpec.EXACTLY));
emojiKeyboardPopup.setAnimationStyle(0);
}
public void dismissEmojiKeyboard() {
if (isShowed()) {
emojiKeyboardPopup.dismiss();
}
}
public boolean isShowed() {
return emojiKeyboardPopup != null && emojiKeyboardPopup.isShowing();
}
/*
* Emoji set up size
* */
public void resizeEmoji() {
if (isShowed()) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) emojiKeyboardPopup.getContentView().getLayoutParams();
layoutParams.height = setEmojiKeyboardHeight();
wm.updateViewLayout(emojiKeyboardPopup.getContentView(), layoutParams);
}
}
public int setEmojiKeyboardHeight() {
return emojiKbHeight == -1 && emojiKbHeight != screenHeight && emojiKbHeight < 80
? (getDisplayDimensions(context).y / 2)
: emojiKbHeight;
}
public Point getDisplayDimensions(Context context) {
Point size = new Point();
WindowManager w = ((Activity) context).getWindowManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
w.getDefaultDisplay().getSize(size);
} else {
Display d = w.getDefaultDisplay();
size.x = d.getWidth();
size.y = d.getHeight();
}
return size;
}
}
use lib github https://github.com/rockerhieu/emojicon
public class EmojiKeyboard {
private static final String TAG = "EmojiKeyboard";
private static final String PREF_KEY_HEIGHT_KB = "EmojiKbHeight";
private Context context;
private int screenHeight = -1;
private int emojiKbHeight = -1;
private PopupWindow emojiKeyboardPopup;
private View view;
private SharedPreferences preferences;
public EmojiKeyboard(Context context, View view) {
if (context instanceof Activity) {
this.context = context;
this.view = view;
preferences = context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE);
//Restore EmojiKeyboard Height
emojiKbHeight = preferences.getInt(PREF_KEY_HEIGHT_KB, -1);
//TODO support less then 11 API, and not perfect resizing when switched the keyboard
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
/*
* Get root view height
* */
screenHeight = screenHeight == -1 && bottom > oldBottom
? bottom
: screenHeight;
/*
* Calculate soft keyboard height
* */
int dHeight = oldBottom - bottom;
boolean validHeight = emojiKbHeight == -1 && dHeight > 80 && bottom != oldBottom;
/*
* Сheck twice because the keyboard may have been switched
* */
emojiKbHeight = validHeight
? dHeight : emojiKbHeight != (dHeight) && dHeight > 0
? dHeight
: emojiKbHeight;
/*
* Store emoji keyboard height into SharedPreferences
* */
preferences.edit().putInt(PREF_KEY_HEIGHT_KB, emojiKbHeight).commit();
/*
* If layout returned to a standard height then dismissing keyboard (OnBackPressed)
* */
if (screenHeight == bottom) {
dismissEmojiKeyboard();
}
/*
* Resize emoji on the go when a user switches between keyboards
* */
resizeEmoji();
}
});
}
}
public void showEmoji() {
if (emojiKeyboardPopup == null) {
createEmojiKeyboard();
}
if (!isShowed()) {
new Handler().postDelayed(new Runnable() {
public void run() {
emojiKeyboardPopup.showAtLocation(view, Gravity.BOTTOM, 0, 0);
resizeEmoji();
}
}, 10L);
} else {
dismissEmojiKeyboard();
}
}
public void createEmojiKeyboard() {
EmojiView emojiKeyboard = new EmojiView(context, EmojiView.EMOJI_DARK_STYLE, new EmojiView.onEmojiClickListener() {
public void onBackspace() {
if (((Activity) context).getWindow().getCurrentFocus() instanceof EditText) {
((Activity) context).getWindow().getCurrentFocus().dispatchKeyEvent(new KeyEvent(0, 67));
}
}
public void onEmojiSelected(Emojicon emojicon) {
if (((Activity) context).getWindow().getCurrentFocus() instanceof EditText) {
EmojiView.input((EditText) ((Activity) context).getWindow().getCurrentFocus(), emojicon);
}
}
});
emojiKeyboardPopup = new PopupWindow(emojiKeyboard);
emojiKeyboardPopup.setHeight(View.MeasureSpec.makeMeasureSpec(setEmojiKeyboardHeight(), View.MeasureSpec.EXACTLY));
emojiKeyboardPopup.setWidth(View.MeasureSpec.makeMeasureSpec(getDisplayDimensions(context).x, View.MeasureSpec.EXACTLY));
emojiKeyboardPopup.setAnimationStyle(0);
}
public void dismissEmojiKeyboard() {
if (isShowed()) {
emojiKeyboardPopup.dismiss();
}
}
public boolean isShowed() {
return emojiKeyboardPopup != null && emojiKeyboardPopup.isShowing();
}
/*
* Emoji set up size
* */
public void resizeEmoji() {
if (isShowed()) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) emojiKeyboardPopup.getContentView().getLayoutParams();
layoutParams.height = setEmojiKeyboardHeight();
wm.updateViewLayout(emojiKeyboardPopup.getContentView(), layoutParams);
}
}
public int setEmojiKeyboardHeight() {
return emojiKbHeight == -1 && emojiKbHeight != screenHeight && emojiKbHeight < 80
? (getDisplayDimensions(context).y / 2)
: emojiKbHeight;
}
public Point getDisplayDimensions(Context context) {
Point size = new Point();
WindowManager w = ((Activity) context).getWindowManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
w.getDefaultDisplay().getSize(size);
} else {
Display d = w.getDefaultDisplay();
size.x = d.getWidth();
size.y = d.getHeight();
}
return size;
}
}
Hi i am having Listview where each item is having different layout.and i want to perform drag and drop on this listview.i have searched many examples and tried but all works for listview of strings or something like that,none is working on listview of views.finally i decided to go with
DevBytes: ListView Cell Dragging and Rearranging this one.i implemented dynaliclistview but it is crashing as this also using strings in listview.Following is my listview adapter
public class ListViewPagerAdapter extends BaseAdapter {
ViewPagerAdapter mViewPagerAdapter;
private Context context;
private int selectedIndex;
FragmentManager mFragmentManager;
static ViewPager vp;
LayoutInflater inflater;
private ArrayList<Integer> mContent;
public ListViewPagerAdapter(Context context,FragmentManager fg)
{
super();
this.context = context;
mFragmentManager = fg;
}
#Override
public int getCount() {
return 4;
}
public void setSelectedIndex(int position) {
selectedIndex = position;
notifyDataSetChanged();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if (convertView == null) {
inflater = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if(position==0)
{
convertView = inflater.inflate(R.layout.titlebar, null);
}
}
if(position==0)
{
convertView = inflater.inflate(R.layout.titlebar, null);
}
if(position==1)
{LayoutInflater inflater1 = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater1.inflate(R.layout.calendarwidget_layout, null);
}
if(position==2)
{
LayoutInflater inflater2 = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater2.inflate(R.layout.view_pager_list_view, null);
vp = (ViewPager) convertView.findViewById(R.id.list_pager);
mViewPagerAdapter = new ViewPagerAdapter(mFragmentManager);
vp.setAdapter(mViewPagerAdapter);
}
if(position==3)
{
LayoutInflater inflater2 = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater2.inflate(R.layout.view_pager_list_view, null);
vp = (ViewPager) convertView.findViewById(R.id.list_pager);
mViewPagerAdapter = new ViewPagerAdapter(mFragmentManager);
vp.setAdapter(mViewPagerAdapter);
mViewPagerAdapter.notifyDataSetChanged();
}
return convertView;
}
#Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
}
and following is code for dynamiclisview (same as from mentioned example)
public class DynamicListView extends ListView {
private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15;
private final int MOVE_DURATION = 150;
private final int LINE_THICKNESS = 15;
public ArrayList<String> mCheeseList;
private int mLastEventY = -1;
private int mDownY = -1;
private int mDownX = -1;
private int mTotalOffset = 0;
private boolean mCellIsMobile = false;
private boolean mIsMobileScrolling = false;
private int mSmoothScrollAmountAtEdge = 0;
private final int INVALID_ID = -1;
private long mAboveItemId = INVALID_ID;
private long mMobileItemId = INVALID_ID;
private long mBelowItemId = INVALID_ID;
private BitmapDrawable mHoverCell;
private Rect mHoverCellCurrentBounds;
private Rect mHoverCellOriginalBounds;
private final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private boolean mIsWaitingForScrollFinish = false;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
public DynamicListView(Context context) {
super(context);
init(context);
}
public DynamicListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public DynamicListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public void init(Context context) {
setOnItemLongClickListener(mOnItemLongClickListener);
setOnScrollListener(mScrollListener);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mSmoothScrollAmountAtEdge = (int)(SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density);
}
/**
* Listens for long clicks on any items in the listview. When a cell has
* been selected, the hover cell is created and set up.
*/
private AdapterView.OnItemLongClickListener mOnItemLongClickListener =
new AdapterView.OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int pos, long id) {
mTotalOffset = 0;
int position = pointToPosition(mDownX, mDownY);
int itemNum = position - getFirstVisiblePosition();
View selectedView = getChildAt(itemNum);
mMobileItemId = getAdapter().getItemId(position);
mHoverCell = getAndAddHoverView(selectedView);
selectedView.setVisibility(INVISIBLE);
mCellIsMobile = true;
updateNeighborViewsForID(mMobileItemId);
return true;
}
};
/**
* Creates the hover cell with the appropriate bitmap and of appropriate
* size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
* single time an invalidate call is made.
*/
private BitmapDrawable getAndAddHoverView(View v) {
int w = v.getWidth();
int h = v.getHeight();
int top = v.getTop();
int left = v.getLeft();
Bitmap b = getBitmapWithBorder(v);
BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
drawable.setBounds(mHoverCellCurrentBounds);
return drawable;
}
/** Draws a black border over the screenshot of the view passed in. */
private Bitmap getBitmapWithBorder(View v) {
Bitmap bitmap = getBitmapFromView(v);
Canvas can = new Canvas(bitmap);
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(LINE_THICKNESS);
paint.setColor(Color.BLACK);
can.drawBitmap(bitmap, 0, 0, null);
can.drawRect(rect, paint);
return bitmap;
}
/** Returns a bitmap showing a screenshot of the view passed in. */
private Bitmap getBitmapFromView(View v) {
Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas (bitmap);
v.draw(canvas);
return bitmap;
}
/**
* Stores a reference to the views above and below the item currently
* corresponding to the hover cell. It is important to note that if this
* item is either at the top or bottom of the list, mAboveItemId or mBelowItemId
* may be invalid.
*/
private void updateNeighborViewsForID(long itemID) {
int position = getPositionForID(itemID);
ListViewPagerAdapter adapter = ((ListViewPagerAdapter)getAdapter());
mAboveItemId = adapter.getItemId(position - 1);
mBelowItemId = adapter.getItemId(position + 1);
}
/** Retrieves the view in the list corresponding to itemID */
public View getViewForID (long itemID) {
int firstVisiblePosition = getFirstVisiblePosition();
ListViewPagerAdapter adapter = ((ListViewPagerAdapter)getAdapter());
for(int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
int position = firstVisiblePosition + i;
long id = adapter.getItemId(position);
if (id == itemID) {
return v;
}
}
return null;
}
/** Retrieves the position in the list corresponding to itemID */
public int getPositionForID (long itemID) {
View v = getViewForID(itemID);
if (v == null) {
return -1;
} else {
return getPositionForView(v);
}
}
/**
* dispatchDraw gets invoked when all the child views are about to be drawn.
* By overriding this method, the hover cell (BitmapDrawable) can be drawn
* over the listview's items whenever the listview is redrawn.
*/
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHoverCell != null) {
mHoverCell.draw(canvas);
}
}
#Override
public boolean onTouchEvent (MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownX = (int)event.getX();
mDownY = (int)event.getY();
mActivePointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER_ID) {
break;
}
int pointerIndex = event.findPointerIndex(mActivePointerId);
mLastEventY = (int) event.getY(pointerIndex);
int deltaY = mLastEventY - mDownY;
if (mCellIsMobile) {
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left,
mHoverCellOriginalBounds.top + deltaY + mTotalOffset);
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
handleCellSwitch();
mIsMobileScrolling = false;
handleMobileCellScroll();
return false;
}
break;
case MotionEvent.ACTION_UP:
touchEventsEnded();
break;
case MotionEvent.ACTION_CANCEL:
touchEventsCancelled();
break;
case MotionEvent.ACTION_POINTER_UP:
/* If a multitouch event took place and the original touch dictating
* the movement of the hover cell has ended, then the dragging event
* ends and the hover cell is animated to its corresponding position
* in the listview. */
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
touchEventsEnded();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
/**
* This method determines whether the hover cell has been shifted far enough
* to invoke a cell swap. If so, then the respective cell swap candidate is
* determined and the data set is changed. Upon posting a notification of the
* data set change, a layout is invoked to place the cells in the right place.
* Using a ViewTreeObserver and a corresponding OnPreDrawListener, we can
* offset the cell being swapped to where it previously was and then animate it to
* its new position.
*/
private void handleCellSwitch() {
final int deltaY = mLastEventY - mDownY;
int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffset + deltaY;
View belowView = getViewForID(mBelowItemId);
View mobileView = getViewForID(mMobileItemId);
View aboveView = getViewForID(mAboveItemId);
boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop());
boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getTop());
if (isBelow || isAbove) {
final long switchItemID = isBelow ? mBelowItemId : mAboveItemId;
View switchView = isBelow ? belowView : aboveView;
final int originalItem = getPositionForView(mobileView);
if (switchView == null) {
updateNeighborViewsForID(mMobileItemId);
return;
}
swapElements(mCheeseList, originalItem, getPositionForView(switchView));
((BaseAdapter) getAdapter()).notifyDataSetChanged();
mDownY = mLastEventY;
final int switchViewStartTop = switchView.getTop();
mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);
updateNeighborViewsForID(mMobileItemId);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#SuppressLint("NewApi")
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
View switchView = getViewForID(switchItemID);
mTotalOffset += deltaY;
int switchViewNewTop = switchView.getTop();
int delta = switchViewStartTop - switchViewNewTop;
switchView.setTranslationY(delta);
ObjectAnimator animator = ObjectAnimator.ofFloat(switchView,
View.TRANSLATION_Y, 0);
animator.setDuration(MOVE_DURATION);
animator.start();
return true;
}
});
}
}
private void swapElements(ArrayList arrayList, int indexOne, int indexTwo) {
Object temp = arrayList.get(indexOne);
arrayList.set(indexOne, arrayList.get(indexTwo));
arrayList.set(indexTwo, temp);
}
/**
* Resets all the appropriate fields to a default state while also animating
* the hover cell back to its correct location.
*/
private void touchEventsEnded () {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile|| mIsWaitingForScrollFinish) {
mCellIsMobile = false;
mIsWaitingForScrollFinish = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
// If the autoscroller has not completed scrolling, we need to wait for it to
// finish in order to determine the final location of where the hover cell
// should be animated to.
if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
mIsWaitingForScrollFinish = true;
return;
}
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mobileView.getTop());
ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds",
sBoundEvaluator, mHoverCellCurrentBounds);
hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
setEnabled(false);
}
#Override
public void onAnimationEnd(Animator animation) {
mAboveItemId = INVALID_ID;
mMobileItemId = INVALID_ID;
mBelowItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
setEnabled(true);
invalidate();
}
});
hoverViewAnimator.start();
} else {
touchEventsCancelled();
}
}
/**
* Resets all the appropriate fields to a default state.
*/
private void touchEventsCancelled () {
View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile) {
mAboveItemId = INVALID_ID;
mMobileItemId = INVALID_ID;
mBelowItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
invalidate();
}
mCellIsMobile = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
}
/**
* This TypeEvaluator is used to animate the BitmapDrawable back to its
* final location when the user lifts his finger by modifying the
* BitmapDrawable's bounds.
*/
private final static TypeEvaluator<Rect> sBoundEvaluator = new TypeEvaluator<Rect>() {
public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
return new Rect(interpolate(startValue.left, endValue.left, fraction),
interpolate(startValue.top, endValue.top, fraction),
interpolate(startValue.right, endValue.right, fraction),
interpolate(startValue.bottom, endValue.bottom, fraction));
}
public int interpolate(int start, int end, float fraction) {
return (int)(start + fraction * (end - start));
}
};
/**
* Determines whether this listview is in a scrolling state invoked
* by the fact that the hover cell is out of the bounds of the listview;
*/
private void handleMobileCellScroll() {
mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
}
/**
* This method is in charge of determining if the hover cell is above
* or below the bounds of the listview. If so, the listview does an appropriate
* upward or downward smooth scroll so as to reveal new items.
*/
public boolean handleMobileCellScroll(Rect r) {
int offset = computeVerticalScrollOffset();
int height = getHeight();
int extent = computeVerticalScrollExtent();
int range = computeVerticalScrollRange();
int hoverViewTop = r.top;
int hoverHeight = r.height();
if (hoverViewTop <= 0 && offset > 0) {
smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
public void setCheeseList(ArrayList<String> cheeseList) {
mCheeseList = cheeseList;
}
/**
* This scroll listener is added to the listview in order to handle cell swapping
* when the cell is either at the top or bottom edge of the listview. If the hover
* cell is at either edge of the listview, the listview will begin scrolling. As
* scrolling takes place, the listview continuously checks if new cells became visible
* and determines whether they are potential candidates for a cell swap.
*/
private AbsListView.OnScrollListener mScrollListener = new AbsListView.OnScrollListener () {
private int mPreviousFirstVisibleItem = -1;
private int mPreviousVisibleItemCount = -1;
private int mCurrentFirstVisibleItem;
private int mCurrentVisibleItemCount;
private int mCurrentScrollState;
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mCurrentFirstVisibleItem = firstVisibleItem;
mCurrentVisibleItemCount = visibleItemCount;
mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem
: mPreviousFirstVisibleItem;
mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount
: mPreviousVisibleItemCount;
checkAndHandleFirstVisibleCellChange();
checkAndHandleLastVisibleCellChange();
mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
mPreviousVisibleItemCount = mCurrentVisibleItemCount;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
mScrollState = scrollState;
isScrollCompleted();
}
/**
* This method is in charge of invoking 1 of 2 actions. Firstly, if the listview
* is in a state of scrolling invoked by the hover cell being outside the bounds
* of the listview, then this scrolling event is continued. Secondly, if the hover
* cell has already been released, this invokes the animation for the hover cell
* to return to its correct position after the listview has entered an idle scroll
* state.
*/
private void isScrollCompleted() {
if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
if (mCellIsMobile && mIsMobileScrolling) {
handleMobileCellScroll();
} else if (mIsWaitingForScrollFinish) {
touchEventsEnded();
}
}
}
/**
* Determines if the listview scrolled up enough to reveal a new cell at the
* top of the list. If so, then the appropriate parameters are updated.
*/
public void checkAndHandleFirstVisibleCellChange() {
if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForID(mMobileItemId);
handleCellSwitch();
}
}
}
/**
* Determines if the listview scrolled down enough to reveal a new cell at the
* bottom of the list. If so, then the appropriate parameters are updated.
*/
public void checkAndHandleLastVisibleCellChange() {
int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
if (currentLastVisibleItem != previousLastVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForID(mMobileItemId);
handleCellSwitch();
}
}
}
};
}
And following is list.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<com.bin.widget.DynamicListView
android:id="#+id/campaignListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0000"
>
</com.bin.widget.DynamicListView>
</RelativeLayout>
please help me out...thanks in advance
HI I finally solved this problem myself,above code works by making few changes in adapter class of listview.In adapter's public long getItemId(int position) make necessary changes so that it will give you id of passed positions view.Dont just return position. Also,in dynamicListview class after swapping position refresh of listview is being done so in listadapter's getview() method,we need to make changes accordingly.
That's it,then it works like charm.Hope this helps somebody else too.
I'm making a little chat in my app and I have an Emoticon Fragment (just like the one from whatsapp or telegram). How can I switch between the fragment and the keyboard without any weird animation?
I already have the fragment wih the emojis and the custom EditText. I just want to switch beween that fragment and keyboard. I really want it to work like whatsapp or Telegram.
For the emoticon Fragment I made a library. I add a fragment (Grid view with SpannableTextViews for each emoji) in the same layout as the EditText.
Any help will be really appreciated.
You don't need to replace the keyboard, you can put your fragment over activity using PopupWindow as Telegram does. Just look at the source: method showEmojiPopup creates EmojiView and put it inside PopupWindow then calculates appropriate size and shows it.
emojiPopup.setHeight(View.MeasureSpec.makeMeasureSpec(currentHeight, View.MeasureSpec.EXACTLY));
emojiPopup.setWidth(View.MeasureSpec.makeMeasureSpec(contentView.getWidth(), View.MeasureSpec.EXACTLY));
emojiPopup.showAtLocation(parentActivity.getWindow().getDecorView(), 83, 0, 0);
You need to code :
public class EmojiKeyboard {
private static final String TAG = "EmojiKeyboard";
private static final String PREF_KEY_HEIGHT_KB = "EmojiKbHeight";
private Context context;
private int screenHeight = -1;
private int emojiKbHeight = -1;
private PopupWindow emojiKeyboardPopup;
private View view;
private SharedPreferences preferences;
public EmojiKeyboard(Context context, View view) {
if (context instanceof Activity) {
this.context = context;
this.view = view;
preferences = context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE);
//Restore EmojiKeyboard Height
emojiKbHeight = preferences.getInt(PREF_KEY_HEIGHT_KB, -1);
//TODO support less then 11 API, and not perfect resizing when switched the keyboard
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
/*
* Get root view height
* */
screenHeight = screenHeight == -1 && bottom > oldBottom
? bottom
: screenHeight;
/*
* Calculate soft keyboard height
* */
int dHeight = oldBottom - bottom;
boolean validHeight = emojiKbHeight == -1 && dHeight > 80 && bottom != oldBottom;
/*
* Сheck twice because the keyboard may have been switched
* */
emojiKbHeight = validHeight
? dHeight : emojiKbHeight != (dHeight) && dHeight > 0
? dHeight
: emojiKbHeight;
/*
* Store emoji keyboard height into SharedPreferences
* */
preferences.edit().putInt(PREF_KEY_HEIGHT_KB, emojiKbHeight).commit();
/*
* If layout returned to a standard height then dismissing keyboard (OnBackPressed)
* */
if (screenHeight == bottom) {
dismissEmojiKeyboard();
}
/*
* Resize emoji on the go when a user switches between keyboards
* */
resizeEmoji();
}
});
}
}
public void showEmoji() {
if (emojiKeyboardPopup == null) {
createEmojiKeyboard();
}
if (!isShowed()) {
new Handler().postDelayed(new Runnable() {
public void run() {
emojiKeyboardPopup.showAtLocation(view, Gravity.BOTTOM, 0, 0);
resizeEmoji();
}
}, 10L);
} else {
dismissEmojiKeyboard();
}
}
public void createEmojiKeyboard() {
EmojiView emojiKeyboard = new EmojiView(context, EmojiView.EMOJI_DARK_STYLE, new EmojiView.onEmojiClickListener() {
public void onBackspace() {
if (((Activity) context).getWindow().getCurrentFocus() instanceof EditText) {
((Activity) context).getWindow().getCurrentFocus().dispatchKeyEvent(new KeyEvent(0, 67));
}
}
public void onEmojiSelected(Emojicon emojicon) {
if (((Activity) context).getWindow().getCurrentFocus() instanceof EditText) {
EmojiView.input((EditText) ((Activity) context).getWindow().getCurrentFocus(), emojicon);
}
}
});
emojiKeyboardPopup = new PopupWindow(emojiKeyboard);
emojiKeyboardPopup.setHeight(View.MeasureSpec.makeMeasureSpec(setEmojiKeyboardHeight(), View.MeasureSpec.EXACTLY));
emojiKeyboardPopup.setWidth(View.MeasureSpec.makeMeasureSpec(getDisplayDimensions(context).x, View.MeasureSpec.EXACTLY));
emojiKeyboardPopup.setAnimationStyle(0);
}
public void dismissEmojiKeyboard() {
if (isShowed()) {
emojiKeyboardPopup.dismiss();
}
}
public boolean isShowed() {
return emojiKeyboardPopup != null && emojiKeyboardPopup.isShowing();
}
/*
* Emoji set up size
* */
public void resizeEmoji() {
if (isShowed()) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) emojiKeyboardPopup.getContentView().getLayoutParams();
layoutParams.height = setEmojiKeyboardHeight();
wm.updateViewLayout(emojiKeyboardPopup.getContentView(), layoutParams);
}
}
public int setEmojiKeyboardHeight() {
return emojiKbHeight == -1 && emojiKbHeight != screenHeight && emojiKbHeight < 80
? (getDisplayDimensions(context).y / 2)
: emojiKbHeight;
}
public Point getDisplayDimensions(Context context) {
Point size = new Point();
WindowManager w = ((Activity) context).getWindowManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
w.getDefaultDisplay().getSize(size);
} else {
Display d = w.getDefaultDisplay();
size.x = d.getWidth();
size.y = d.getHeight();
}
return size;
}
}
Solve problem
I have a list of events which are seperated by month and year (Jun 2010, Jul 2010 etc.). I have enabled fast scrolling because the list is really long. I've also implemented SectionIndexer so that people can see what month and year they are currently viewing when scrolling down the list of events at speed.
I don't have any problem with the implementation, just how the information is shown. Fast scrolling with SectionIndexer seems to only really be able to support a label with a single letter. If the list was alphabetised this would be perfect, however I want it to display a bit more text.
If you look at the screenshot bellow you'll see the problem I'm having.
(source: matto1990.com)
What I want to know is: is it possible to change how the text in the centre of the screen is displayed. Can I change it somehow to make it look right (with the background covering all of the text).
Thanks in advance. If you need any clarification, or code just ask.
EDIT: Full sample code for this solution available here.
I had this same problem - I needed to display full text in the overlay rectangle rather than just a single character. I managed to solve it using the following code as an example: http://code.google.com/p/apps-for-android/source/browse/trunk/RingsExtended/src/com/example/android/rings_extended/FastScrollView.java
The author said that this was copied from the Contacts app, which apparently uses its own implementation rather than just setting fastScrollEnabled="true" on the ListView. I altered it a little bit so that you can customize the overlay rectangle width, overlay rectangle height, overlay text size, and scroll thumb width.
For the record, the final result looks like this: http://nolanwlawson.files.wordpress.com/2011/03/pokedroid_1.png
All you need to do is add these values to your res/values/attrs.xml:
<declare-styleable name="CustomFastScrollView">
<attr name="overlayWidth" format="dimension"/>
<attr name="overlayHeight" format="dimension"/>
<attr name="overlayTextSize" format="dimension"/>
<attr name="overlayScrollThumbWidth" format="dimension"/>
</declare-styleable>
And then use this CustomFastScrollView instead of the one in the link:
public class CustomFastScrollView extends FrameLayout
implements OnScrollListener, OnHierarchyChangeListener {
private Drawable mCurrentThumb;
private Drawable mOverlayDrawable;
private int mThumbH;
private int mThumbW;
private int mThumbY;
private RectF mOverlayPos;
// custom values I defined
private int mOverlayWidth;
private int mOverlayHeight;
private float mOverlayTextSize;
private int mOverlayScrollThumbWidth;
private boolean mDragging;
private ListView mList;
private boolean mScrollCompleted;
private boolean mThumbVisible;
private int mVisibleItem;
private Paint mPaint;
private int mListOffset;
private Object [] mSections;
private String mSectionText;
private boolean mDrawOverlay;
private ScrollFade mScrollFade;
private Handler mHandler = new Handler();
private BaseAdapter mListAdapter;
private boolean mChangedBounds;
public static interface SectionIndexer {
Object[] getSections();
int getPositionForSection(int section);
int getSectionForPosition(int position);
}
public CustomFastScrollView(Context context) {
super(context);
init(context, null);
}
public CustomFastScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public CustomFastScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void useThumbDrawable(Drawable drawable) {
mCurrentThumb = drawable;
mThumbW = mOverlayScrollThumbWidth;//mCurrentThumb.getIntrinsicWidth();
mThumbH = mCurrentThumb.getIntrinsicHeight();
mChangedBounds = true;
}
private void init(Context context, AttributeSet attrs) {
// set all attributes from xml
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.CustomFastScrollView);
mOverlayHeight = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayHeight, 0);
mOverlayWidth = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayWidth, 0);
mOverlayTextSize = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayTextSize, 0);
mOverlayScrollThumbWidth = typedArray.getDimensionPixelSize(
R.styleable.CustomFastScrollView_overlayScrollThumbWidth, 0);
}
// Get both the scrollbar states drawables
final Resources res = context.getResources();
Drawable thumbDrawable = res.getDrawable(R.drawable.scrollbar_handle_accelerated_anim2);
useThumbDrawable(thumbDrawable);
mOverlayDrawable = res.getDrawable(android.R.drawable.alert_dark_frame);
mScrollCompleted = true;
setWillNotDraw(false);
// Need to know when the ListView is added
setOnHierarchyChangeListener(this);
mOverlayPos = new RectF();
mScrollFade = new ScrollFade();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(mOverlayTextSize);
mPaint.setColor(0xFFFFFFFF);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
private void removeThumb() {
mThumbVisible = false;
// Draw one last time to remove thumb
invalidate();
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (!mThumbVisible) {
// No need to draw the rest
return;
}
final int y = mThumbY;
final int viewWidth = getWidth();
final CustomFastScrollView.ScrollFade scrollFade = mScrollFade;
int alpha = -1;
if (scrollFade.mStarted) {
alpha = scrollFade.getAlpha();
if (alpha < ScrollFade.ALPHA_MAX / 2) {
mCurrentThumb.setAlpha(alpha * 2);
}
int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
mCurrentThumb.setBounds(left, 0, viewWidth, mThumbH);
mChangedBounds = true;
}
canvas.translate(0, y);
mCurrentThumb.draw(canvas);
canvas.translate(0, -y);
// If user is dragging the scroll bar, draw the alphabet overlay
if (mDragging && mDrawOverlay) {
mOverlayDrawable.draw(canvas);
final Paint paint = mPaint;
float descent = paint.descent();
final RectF rectF = mOverlayPos;
canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2,
(int) (rectF.bottom + rectF.top) / 2 + descent, paint);
} else if (alpha == 0) {
scrollFade.mStarted = false;
removeThumb();
} else {
invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mCurrentThumb != null) {
mCurrentThumb.setBounds(w - mThumbW, 0, w, mThumbH);
}
final RectF pos = mOverlayPos;
pos.left = (w - mOverlayWidth) / 2;
pos.right = pos.left + mOverlayWidth;
pos.top = h / 10; // 10% from top
pos.bottom = pos.top + mOverlayHeight;
mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
(int) pos.right, (int) pos.bottom);
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
if (totalItemCount - visibleItemCount > 0 && !mDragging) {
mThumbY = ((getHeight() - mThumbH) * firstVisibleItem) / (totalItemCount - visibleItemCount);
if (mChangedBounds) {
final int viewWidth = getWidth();
mCurrentThumb.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
mChangedBounds = false;
}
}
mScrollCompleted = true;
if (firstVisibleItem == mVisibleItem) {
return;
}
mVisibleItem = firstVisibleItem;
if (!mThumbVisible || mScrollFade.mStarted) {
mThumbVisible = true;
mCurrentThumb.setAlpha(ScrollFade.ALPHA_MAX);
}
mHandler.removeCallbacks(mScrollFade);
mScrollFade.mStarted = false;
if (!mDragging) {
mHandler.postDelayed(mScrollFade, 1500);
}
}
private void getSections() {
Adapter adapter = mList.getAdapter();
if (adapter instanceof HeaderViewListAdapter) {
mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
}
if (adapter instanceof SectionIndexer) {
mListAdapter = (BaseAdapter) adapter;
mSections = ((SectionIndexer) mListAdapter).getSections();
}
}
public void onChildViewAdded(View parent, View child) {
if (child instanceof ListView) {
mList = (ListView)child;
mList.setOnScrollListener(this);
getSections();
}
}
public void onChildViewRemoved(View parent, View child) {
if (child == mList) {
mList = null;
mListAdapter = null;
mSections = null;
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mThumbVisible && ev.getAction() == MotionEvent.ACTION_DOWN) {
if (ev.getX() > getWidth() - mThumbW && ev.getY() >= mThumbY &&
ev.getY() <= mThumbY + mThumbH) {
mDragging = true;
return true;
}
}
return false;
}
private void scrollTo(float position) {
int count = mList.getCount();
mScrollCompleted = false;
final Object[] sections = mSections;
int sectionIndex;
if (sections != null && sections.length > 1) {
final int nSections = sections.length;
int section = (int) (position * nSections);
if (section >= nSections) {
section = nSections - 1;
}
sectionIndex = section;
final SectionIndexer baseAdapter = (SectionIndexer) mListAdapter;
int index = baseAdapter.getPositionForSection(section);
// Given the expected section and index, the following code will
// try to account for missing sections (no names starting with..)
// It will compute the scroll space of surrounding empty sections
// and interpolate the currently visible letter's range across the
// available space, so that there is always some list movement while
// the user moves the thumb.
int nextIndex = count;
int prevIndex = index;
int prevSection = section;
int nextSection = section + 1;
// Assume the next section is unique
if (section < nSections - 1) {
nextIndex = baseAdapter.getPositionForSection(section + 1);
}
// Find the previous index if we're slicing the previous section
if (nextIndex == index) {
// Non-existent letter
while (section > 0) {
section--;
prevIndex = baseAdapter.getPositionForSection(section);
if (prevIndex != index) {
prevSection = section;
sectionIndex = section;
break;
}
}
}
// Find the next index, in case the assumed next index is not
// unique. For instance, if there is no P, then request for P's
// position actually returns Q's. So we need to look ahead to make
// sure that there is really a Q at Q's position. If not, move
// further down...
int nextNextSection = nextSection + 1;
while (nextNextSection < nSections &&
baseAdapter.getPositionForSection(nextNextSection) == nextIndex) {
nextNextSection++;
nextSection++;
}
// Compute the beginning and ending scroll range percentage of the
// currently visible letter. This could be equal to or greater than
// (1 / nSections).
float fPrev = (float) prevSection / nSections;
float fNext = (float) nextSection / nSections;
index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev)
/ (fNext - fPrev));
// Don't overflow
if (index > count - 1) index = count - 1;
mList.setSelectionFromTop(index + mListOffset, 0);
} else {
int index = (int) (position * count);
mList.setSelectionFromTop(index + mListOffset, 0);
sectionIndex = -1;
}
if (sectionIndex >= 0) {
String text = mSectionText = sections[sectionIndex].toString();
mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
sectionIndex < sections.length;
} else {
mDrawOverlay = false;
}
}
private void cancelFling() {
// Cancel the list fling
MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mList.onTouchEvent(cancelFling);
cancelFling.recycle();
}
#Override
public boolean onTouchEvent(MotionEvent me) {
if (me.getAction() == MotionEvent.ACTION_DOWN) {
if (me.getX() > getWidth() - mThumbW
&& me.getY() >= mThumbY
&& me.getY() <= mThumbY + mThumbH) {
mDragging = true;
if (mListAdapter == null && mList != null) {
getSections();
}
cancelFling();
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_UP) {
if (mDragging) {
mDragging = false;
final Handler handler = mHandler;
handler.removeCallbacks(mScrollFade);
handler.postDelayed(mScrollFade, 1000);
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_MOVE) {
if (mDragging) {
final int viewHeight = getHeight();
mThumbY = (int) me.getY() - mThumbH + 10;
if (mThumbY < 0) {
mThumbY = 0;
} else if (mThumbY + mThumbH > viewHeight) {
mThumbY = viewHeight - mThumbH;
}
// If the previous scrollTo is still pending
if (mScrollCompleted) {
scrollTo((float) mThumbY / (viewHeight - mThumbH));
}
return true;
}
}
return super.onTouchEvent(me);
}
public class ScrollFade implements Runnable {
long mStartTime;
long mFadeDuration;
boolean mStarted;
static final int ALPHA_MAX = 200;
static final long FADE_DURATION = 200;
void startFade() {
mFadeDuration = FADE_DURATION;
mStartTime = SystemClock.uptimeMillis();
mStarted = true;
}
int getAlpha() {
if (!mStarted) {
return ALPHA_MAX;
}
int alpha;
long now = SystemClock.uptimeMillis();
if (now > mStartTime + mFadeDuration) {
alpha = 0;
} else {
alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration);
}
return alpha;
}
public void run() {
if (!mStarted) {
startFade();
invalidate();
}
if (getAlpha() > 0) {
final int y = mThumbY;
final int viewWidth = getWidth();
invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
} else {
mStarted = false;
removeThumb();
}
}
}
}
You can also tweak the translucency of the scroll thumb using ALPHA_MAX.
Then put something like this in your layout xml file:
<com.myapp.CustomFastScrollView android:layout_width="wrap_content"
android:layout_height="fill_parent"
myapp:overlayWidth="175dp" myapp:overlayHeight="110dp" myapp:overlayTextSize="36dp"
myapp:overlayScrollThumbWidth="60dp" android:id="#+id/fast_scroll_view">
<ListView android:id="#android:id/list" android:layout_width="wrap_content"
android:layout_height="fill_parent"/>
<TextView android:id="#android:id/empty"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="" />
</com.myapp.CustomFastScrollView>
Don't forget to declare your attributes in that layout xml file as well:
... xmlns:myapp= "http://schemas.android.com/apk/res/com.myapp" ...
You'll also need to grab the R.drawable.scrollbar_handle_accelerated_anim2 drawables from that Android source code. The link above only contains the mdpi one.
The FastScroller widget is responsible for drawing the overlay. You should probably take a look at its source:
https://android.googlesource.com/platform/frameworks/base/+/gingerbread-release/core/java/android/widget/FastScroller.java
Search for comment:
// If user is dragging the scroll bar, draw the alphabet overlay