I have been attempting to implement a custom SoftKeyboard (IME) which contains and outputs icons for keys. Think emoji. I have skinned the keys, but injecting the selected drawable into the String Builder has proven difficult. I decided to implement my own ImageGetter so that I could control what was returned by Html.fromHtml(). The fromHtml method continues to act as if it's returning null even though I know that the drawable is not null.
Any help would be greatly appreciated.
I based this project off of the SoftKeyboard sample. I targeted the file, SoftKeyboard.java an instance of
public class SoftKeyboard extends InputMethodService
implements KeyboardView.OnKeyboardActionListener
This class instantiates an object mComposing, this is used to store the text entered into the edit box from the soft keyboard.
private StringBuilder mComposing = new StringBuilder();
I thought that I could append the images as a Spanned to the StringBuilder and display the image that way. What happens here is that a white square with "obj" text in it shows instead of the selected icon, meaning getDrawable() is returning null.
I have tried a few things with the bounds but I am not sure if I am close or if I am going down the wrong path.
Declaring my drawable in onCreate:
mHangingDrawable = getResources().getDrawable(R.drawable.hanging_tiny);
mHangingDrawable.setBounds(0, 0, mHangingDrawable.getIntrinsicWidth(), mHangingDrawable.getIntrinsicHeight());
In handleCharacter(int, int[]) I wrote this catch for codes that I have personally mapped.
else if (primaryCode == 50) {
String htmlSource = "<img src='hanging_small' />";
ImageGetter ig = new ImageGetter(){
#Override
public Drawable getDrawable(String source) {
return mHangingDrawable;
}
};
Spanned htmlSpan = Html.fromHtml(htmlSource, ig, null);
mComposing.append(htmlSpan);
getCurrentInputConnection().commitText(mComposing, 1);
}
This article about Creating Input Methods was a helpful resource.
Related
I am working on chat project. For showing chat messages I am using the following layout
<TextView
android:id="#+id/messageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="web"
android:longClickable="true"
android:textColor="#000000"
tools:text="hello hello hello hello" />
And on java side, I am simply formatted the message text and then set it to the TextView.
I am facing 2 main issues:
When I click on weblink in the message, screen scrolls either top or bottom. This does not happen when I click the non-weblink portion of the message.
Click doesn't navigate to the corresponding webpage. With too much difficulty, sometimes it happens that I am able to navigate to the webpage.
Things I have tried:
I tried using the setMovementMethod(context) method after setting the text but didn't work.
I also tried things like removing the autoLink="web" from layout as suggested by some.
I also tried setLinksClickable(true) and setAutoMask(0) but nothing is working.
I have spent quite some time on it now. Can someone help me here.
Thanks in advance.
you must use an inherited class from ClickableSpan, as such setLinksClickable TextView's method is not enough, each link in the text assigned to TextView must have the click event.
The solution is coded in Xamarin, but i thinks is easily translatable to Java.
With this function, we check de html string text, and we get the links and make each of them clickable with MakeLinkClickable function.
public static void SetTextAsHtmlWithOverrideLinks(Android.Widget.TextView textview, string text)
{
var sequence = text.GetTextAsHtml();
var strBuilder = new Android.Text.SpannableStringBuilder(sequence);
var urls = strBuilder.GetSpans(0, sequence.Length(), Java.Lang.Class.FromType(typeof(Android.Text.Style.URLSpan)));
foreach (Android.Text.Style.URLSpan span in urls) {
MakeLinkClickable(strBuilder, span);
}
textview.MovementMethod = Android.Text.Method.LinkMovementMethod.Instance;
textview.TextFormatted = strBuilder;
}
This function makes each link clickable with LinkSpan class, which has our action on link click
public static void MakeLinkClickable(Android.Text.SpannableStringBuilder strBuilder, Android.Text.Style.URLSpan span)
{
strBuilder.SetSpan(new LinkSpan(span), strBuilder.GetSpanStart(span), strBuilder.GetSpanEnd(span),strBuilder.GetSpanFlags(span));
strBuilder.RemoveSpan(span);
}
public class LinkSpan : Android.Text.Style.ClickableSpan
{
public Android.Text.Style.URLSpan Link { get; }
public LinkSpan(Android.Text.Style.URLSpan span) {
Link = span;
}
public override void OnClick(Android.Views.View widget)
{
if (widget is Android.Widget.TextView textView)
// Here open url or another action with your own method;
widget.Invalidate();
}
}
This function converts our html text to a spannable text to handle it.
public static Android.Text.ISpanned GetTextAsHtml(this string input)
{
if (input == null)
input = string.Empty;
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.N)
return Android.Text.Html.FromHtml(input,Android.Text.FromHtmlOptions.ModeLegacy);
else
return Android.Text.Html.FromHtml(input);
}
Use:
SetTextAsHtmlWithOverrideLinks(yourtextView, theHTMLTextString);
I am trying to use this library for my emojicons https://github.com/ankushsachdeva/emojicon.
I have implemented it with my soft keyboard but it's not doing anything as in the code (MainActivity) of the given link, it uses EmojiconEditText instead of normal text view of any android app. Is there any way so that I can use edit text of any app instead of this custom one. My current source code for emoji is this.
popup = new EmojiconsPopup(mInputView, this);
popup.setOnEmojiconClickedListener(new EmojiconGridView.OnEmojiconClickedListener() {
#Override
public void onEmojiconClicked(Emojicon emojicon) {
if (emojicon == null){
return;
}
emojicon.getEmoji();
}
});
In this, I cannot figure out how to fetch Unicode for that particular emoji. If someone can help me how do I fetch emoji Unicode?
The code for emoji Unicode is in this https://github.com/ankushsachdeva/emojicon/tree/master/lib/src/github/ankushsachdeva/emojicon/emoji
I have got the answer after some struggle. I just need to call that string of unicodes in it with current input connection.
String text = emojicon.getEmoji();
mComposing.append(text);
commitTyped(getCurrentInputConnection());
Currently my edit text view checks if the searched term contains one space as follows:
if(mSearchView.getText().toString().contains(" ")
How do I make it such that it makes sure it checks if the searchview contains 2 spaces between 3 search terms for example: "here it is"
You can use a regular expression to do that. Use code like this one:
if(Pattern.matches("^\\w+\\s\\w+\\s\\w+$", mSearchView.getText().toString()))
Also make sure to check if mSearchView.getText() is not null - you probably will get a NullReferenceException with a blank EditText content.
In the end you may want to create a method like this one:
public static boolean containsTwoSpaces(Editable text) {
if (text == null) return false;
return Pattern.matches("^\\w+\\s\\w+\\s\\w+$", text.toString());
}
just for convenience, clearance and making sure you don't bump into a NullPointerException.
See here.
Pattern pattern = Pattern.compile("\\s");
Matcher matcher = pattern.matcher(s);
boolean found = matcher.find();
int mms=matcher.groupCount();
Why links in ListView are lost, when scrolling? From debugging it's clear, that spans are not added second time on a TextView from the convertView.
Here's a piece of code which is called from adapter's getView.
...
String body = MyItemDetails.getBody(); // String to linkify
final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
viewHolder.textView.setText(spannable);
viewHolder.textView.setTextIsSelectable(true); // adds additional spans
viewHolder.textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
viewHolder.textView.setAutoLinkMask(Linkify.WEB_URLS);
...
MyCustomUri.addHashtagSpans() creates a SpannableString with MyCustomSpan with extends URLSpan.
Problem is that when I scroll up and down in the ListView links are lost. Whereas when screen is opened 1st time it's set correctly.
Now I made a dirty fix by disabling reuse of convertView :( Any ideas how to solve this problem better?
Some of the spannable information is likely being lost when the textview's data is written to a parcel for retention.
See TextView.onSaveInstanceState(), TextView.onRestoreInstanceState(), and TextView.SavedState.
It can often be very frustrating to determine what android will and will not retain. I often just setSaveEnabled(false) on my views to disable the unpredictable default behaviours of the base widgets.
Also, the viewholder pattern is only really intended for retaining view/layout instance hierarchies. To save you from having to inflate or find your views every getView(). It's always your responsibility to update a view's data when presenting it from getView().
You don't need to completely disable the viewholder pattern, instead just simply update the text every getView(), as you may already be doing.
Hello Use this custom class
public class MyCustomSpannable extends ClickableSpan {
String Url;
Context mContext;
public MyCustomSpannable(String Url, Context context) {
this.Url = Url;
mContext = context;
}
#Override
public void updateDrawState(TextPaint ds) {
// Customize your Text Look if required
ds.setColor(mContext.getResources().getColor(R.color.red_text));
ds.setFakeBoldText(true);
// ds.setStrikeThruText(true);
ds.setTypeface(CommonFunctios.getfontNormal(mContext));
// ds.setUnderlineText(true);
// ds.setShadowLayer(10, 1, 1, Color.WHITE);
// ds.setTextSize(15);
}
#Override
public void onClick(View widget) {
}
public String getUrl() {
return Url;
}
}
and in adapter replace your code with this
String text = holder.txt_terms.getText().toString();
SpannableStringBuilder stringBuilder = new SpannableStringBuilder(text);
MyCustomSpannable customSpannable = new MyCustomSpannable(text,
mcontext) {
#Override
public void onClick(View widget) {
Log.e("on click", "message");
((OpticalOffersActivity) mcontext).callDialogBox(position);
}
};
stringBuilder.setSpan(customSpannable, 0, text.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
holder.txt_terms.setText(stringBuilder, BufferType.SPANNABLE.SPANNABLE);
holder.txt_terms.setMovementMethod(LinkMovementMethod.getInstance());
Hope it will help you.
if(convertView==null)
{
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
...
String body = MyItemDetails.getBody(); // String to linkify
final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
viewHolder.textView.setText(spannable);
viewHolder.textView.setTextIsSelectable(true); // adds additional spans
viewHolder.textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
viewHolder.textView.setAutoLinkMask(Linkify.WEB_URLS);
...
That spannable code must be placed outside the if-else loop in the getView() method, like the way I did it in the above code.
There are a couple problems at play here, so let me address them one at a time. The issues you've asked about directly (links disappearing) is a side effect of the fact that the auto linking behavior in TextView doesn't necessarily work that well when you are also adding your own spans to the text manually...best not to use it. Remove the setAutoLinkMask() trigger and the disappearing links issue will go away.
Instead, you can easily add the same web linking behavior directly into your text span with Linkify. However, this is only part of your problem. The MovementMethod you have chosen isn't really compatible with clickable links. The reason it (partially) works in your code now is because the auto link mask is causing the MovementMethod of the view to be secretly massaged under the hood to a LinkMovementMethod...which then gets reset after the view is recycled. A pattern I typically use (applied to your code example) would be:
final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
Linkify.addLinks(spannable, Linkify.WEB_URLS);
viewHolder.textView.setText(spannable);
addLinkMovementMethod(textView);
Where addLinkMovementMethod() is a helper I have that looks like this:
private void addLinkMovementMethod(TextView t) {
MovementMethod m = t.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod)) {
if (t.getLinksClickable()) {
t.setMovementMethod(LinkMovementMethod.getInstance());
}
}
}
This simply keeps from resetting the value on each view recycle if it isn't necessary. The previous code block will give you links that click properly and never disappear...
However, I'm guessing from the methods you've called that you are also attempting to make the linked text in the list selectable (e.g. calling setTextIsSelectable() and choosing the ArrowKeyMovementMethod). This gets a little trickier because of the MovementMethod issue I discussed above. In order to create a MovementMethod that supports both link clicks and text selection, I'll direct you to this existing SO post on the subject which includes sample code on the customizations you need to make: Can a TextView be selectable AND contain links?
I need to push text into WebView. But there are a lot of different HTML tags in this text and I want to parse it before.
Spanned html = Html.fromHtml(texts.get(i));
But I need to change URLs in the text to call one function in my activity. In TextView I may do it like that:
ClickableSpan[] items = spans.getSpans(0, spans.length(), ClickableSpan.class);
for (ClickableSpan s : items) {
final String url = ((URLSpan)s).getURL();
int spanStart = spans.getSpanStart(s);
int spanEnd = spans.getSpanEnd(s);
spans.removeSpan(s);
spans.setSpan(new URLSpan(url) {
#Override
public void onClick(View widget) {
//code for overriding onClick goes here
}
}, spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
Is it possible to use Html.fromHTML() AND to change links in text to call my function when using WebView instead of TextView?
Not really, because WebView understands HTML, not a Spanned. You are welcome to try to convert the Spanned back into HTML via Html.toHtml(), but the round-trip conversion from HTML to Spanned to HTML is likely to make a hash of any complex formatting, because they are not complete HTML parsers or generators.
I would recommend that you find and use some Java library that is designed to parse and help you fix up HTML.