I am trying to implement code highlighting using spanned text and html.fromhtml() function in an edittext than implements text watcher.
The problem occurs when i try to manipulate cursor for custom brackets, the app crashes due to some spannable string setspan error.
How do i use spannable code highlighting and set cursor position adjusting according to the spanned text.
Edit:
The function:
Spanned matchtext(String s)
{
//Pattern p =Pattern.compile(check[0]);
String a=s;
for(int i=0;i<Constants.keyWords.length;i++) {
a = a.replaceAll(Constants.keyWords[i], "<font color=\"#c5c5c5\">" + Constants.keyWords[i] + "</font>");
//a = s.replaceAll(";", "<font color=\"#c5c5c5\">" + ";" + "</font>");
}
Spanned ab = Html.fromHtml(a);
return ab;
}
And the function call:
mCodeEditText.removeTextChangedListener(tt);
bs = matchtext(s.toString());
mCodeEditText.setText(bs);
mCodeEditText.addTextChangedListener(tt);
Edit 2:
This is my new implementation, I just can't get the highlighting to work.
Spannable matchtext(String s, int pos) {
Spannable abc = new SpannableString(s);
for (int i = 0; i < Constants.keyWords.length; i++) {
if (pos - Constants.keyWords[i].length() >= 0) {
int j = s.indexOf(Constants.keyWords[i]);
if (j != -1) {
if ((s.subSequence(j, pos)).equals(Constants.keyWords[i]))
abc.setSpan(new ForegroundColorSpan(Color.BLUE), j, pos, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
}
}
return abc;
}
Related
working perfect but
when i am highlight text using html then some text can not be view perfect(Hindi text).
android
String str="रिश्ते भले ही कम ही बनाओ लेकिन दिल से निभाओ,\n" +
"क्योंकि आज कल इंसान अच्छाई के चक्कर में अच्छे खो देते है।";
//textview.setText(str);
textview.setText(Html.fromHtml(String.format(colorfulltext(str))), TextView.BufferType.SPANNABLE);
// highlight text
public String colorfulltext(String text) {
String[] colors = new String[]{"#fdc113", "#fdc113", "#fdc113","#fdc113", "#fdc113" ,"#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc","#fcfcfc","#fcfcfc","#fcfcfc","#fcfcfc"};
StringBuilder finals = new StringBuilder();
int size = colors.length;
int k = 0;
for (int item = 0; item < text.length(); item++) {
if (k >= size) {
k = 0;
}
finals.append("<font color='" + colors[k] + "'>" + text.charAt(item) + "</font>");
k++;
}
return finals.toString();
}
screen
Why do you convert a String to html to apply fontcolor for a static text??
You have to follow the following steps:
Create entries in Strings.xml for each text. For instance, रिश्ते
has a different color and needs to be a separate entry in
strings.xml.
add this to a Util class:
public static void addColoredPart(SpannableStringBuilder ssb,
String word, int color,String... texts) {
for(String text : texts) {
if (word != null) {
int idx1 = text.indexOf(word);
if (idx1 == -1) {
return;
}
int idx2 = idx1 + word.length();
ssb.setSpan(new ForegroundColorSpan(color), idx1, idx2,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
apply style the following way:
String string1 = context.getString(R.string.String_id1)
String string2 = context.getString(R.string.String_id2)
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
spannableStringBuilder.append(string2)
spannableStringBuilder.append(string2)
SpannableUtil.addColoredPart(
spannableStringBuilder,
spannableStringBuilder.toString(), color, string1, string2);
I have a text and an array (textviewArray) with 7 words. In my text exist these 7 words. I want to show these 7 words in this text in bold.
I have this code but the last word is bold, not all seven words:
textviewS.Text = "My Text ....";
strD = new SpannableStringBuilder("My Text ....");
bss = new StyleSpan(TypefaceStyle.Bold);
for (int i = 0; i < 7; i++)
{
if (textviewS.Text.IndexOf(textviewArray[i]) >= 0)
{
strD.SetSpan(bss, textviewS.Text.IndexOf(textviewArray[i]), (textviewS.Text.IndexOf(textviewArray[i]) + textviewArray[i].Length), 0);
}
}
textviewُ.SetText(strD, TextView.BufferType.Normal);
Try
String text = "text here";
String[] wordsToBold = {"word1", "word2", "word3", "word4", "word5", "word6", "word7"}
SpannableString spanString = FontUtility.getMultiFontText(context, "fonts/myfont.ttf", text, wordsToBold);
public static SpannableString getMultiFontText(Context context, int fontPathFromAssets, String text, String[] wordsToBold){
SpannableString spannableString = new SpannableString(text);
for(String word : wordsToBold) {
int startIndex = text.indexOf(word);
while(startIndex >= 0) {
int endIndex = startIndex+word.length();
spannableString.setSpan(new CustomTypefaceSpan(context, context.getString(fontPathFromAssets)), startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
startIndex = text.indexOf(word, endIndex + 1);
}
}
return spannableString;
}
I want to add underline and colour to textview, but it is a simple text, not link, not phone number, just simple "Hello world", that I want to have with underline and that blue link-like colour.
It failed to do so:
view.setMovementMethod(LinkMovementMethod.getInstance());
Linkify.addLinks(view, Linkify.ALL);
Thank you! I just underlined the text as Const suggested and changed color of textview. But I guess xoxol_89's answer is correct in my case and should be accepted.
Do you try to use Spannable
For example
/**
* Method allocates filtering substring in all contacts yellow color,
* that satisfy the user's search
* #param inputText - DisplayName
* filtText - filtering Text
* #return String with allocating substring (Spannable)
*/
public static Spannable changeBackgroungFiltText(CharSequence inputText, String filtText, int color) {
Spannable str = null;
if(inputText != null)
{
String inputStr = inputText.toString();
String inputLowerCaseStr = inputStr.toLowerCase();
String filtLowerCaseStr = filtText.toLowerCase();
// Spannable str = new SpannableStringBuilder(inputStr);
str = new SpannableStringBuilder(inputStr);
if (filtText.length() != 0)
{
int indexStart = 0;
while (true)
{
int indexCur = inputLowerCaseStr.indexOf(filtLowerCaseStr, indexStart);
if (indexCur != -1) {
int start = indexCur;
int end = indexCur + filtText.length();
int flag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
str.setSpan(new ForegroundColorSpan(color),start, end, flag);
//str.setSpan(new BackgroundColorSpan(highlightColor), start, end, flag);
indexStart = indexCur + 1;
} else {
return str;
}
}
} else {
return str;
}
}
return str;
}
You can do that in 4 ways:
1.Automatically linkifies using android:autoLink=”all”
2.Link text by setMovementMethod
3.Link as html code using Html.fromHtml()
4.Link string by SpannableString
and you can find the examples here
You can use SpannableString like this:
final SpannableString text = new SpannableString("Hello World!");
final int startAt = 0;
final int endAt = text.length();
final int sampleColor = Color.parseColor("#3333ff");
text.setSpan(new UnderlineSpan(), startAt, endAt, 0);
text.setSpan(new ForegroundColorSpan(sampleColor), startAt, endAt, 0);
textView.setText(text);
I am new to android programing. I added a context menu to edittext. I wish to get the word under the cursor on long press.
I can get selected text by following code.
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
EditText edittext = (EditText)findViewById(R.id.editText1);
menu.setHeaderTitle(edittext.getText().toString().substring(edittext.getSelectionStart(), edittext.getSelectionEnd()));
menu.add("Copy");
}
edittext has some text e.g "Some text. Some more text". When the user clicks on "more", the cursor will be in some where in the word "more". When the user long presses the word I want to get the word "more" and other words under the cursor.
There is better and simpler solution : using pattern in android
public String getCurrentWord(EditText editText) {
Spannable textSpan = editText.getText();
final int selection = editText.getSelectionStart();
final Pattern pattern = Pattern.compile("\\w+");
final Matcher matcher = pattern.matcher(textSpan);
int start = 0;
int end = 0;
String currentWord = "";
while (matcher.find()) {
start = matcher.start();
end = matcher.end();
if (start <= selection && selection <= end) {
currentWord = textSpan.subSequence(start, end).toString();
break;
}
}
return currentWord; // This is current word
}
EditText et = (EditText) findViewById(R.id.xx);
int startSelection = et.getSelectionStart();
String selectedWord = "";
int length = 0;
for(String currentWord : et.getText().toString().split(" ")) {
System.out.println(currentWord);
length = length + currentWord.length() + 1;
if(length > startSelection) {
selectedWord = currentWord;
break;
}
}
System.out.println("Selected word is: " + selectedWord);
Please try following code as it is optimized. Let me know if you has more specification.
//String str = editTextView.getText().toString(); //suppose edittext has "Hello World!"
int selectionStart = editTextView.getSelectionStart(); // Suppose cursor is at 2 position
int lastSpaceIndex = str.lastIndexOf(" ", selectionStart - 1);
int indexOf = str.indexOf(" ", lastSpaceIndex + 1);
String searchToken = str.substring(lastSpaceIndex + 1, indexOf == -1 ? str.length() : indexOf);
Toast.makeText(this, "Current word is :" + searchToken, Toast.LENGTH_SHORT).show();
I believe that a BreakIterator is the superior solution here. It avoids having to loop over the entire string and do the pattern matching yourself. It also finds word boundaries besides just a simple space character (commas, periods, etc.).
// assuming that only the cursor is showing, no selected range
int cursorPosition = editText.getSelectionStart();
// initialize the BreakIterator
BreakIterator iterator = BreakIterator.getWordInstance();
iterator.setText(editText.getText().toString());
// find the word boundaries before and after the cursor position
int wordStart;
if (iterator.isBoundary(cursorPosition)) {
wordStart = cursorPosition;
} else {
wordStart = iterator.preceding(cursorPosition);
}
int wordEnd = iterator.following(cursorPosition);
// get the word
CharSequence word = editText.getText().subSequence(wordStart, wordEnd);
If you want to get it on a long press then just put this in the onLongPress method of your GestureDetector.
See also
How does BreakIterator work in Android?
#Ali Thank you for providing your solution.
Here is an optimized variant, which does break if the word has been found.
This solution does not create a Spannable, because it is not needed to find the word.
#NonNull
public static String getWordAtIndex(#NonNull String text, #IntRange(from = 0) int index) {
String wordAtIndex = "";
// w = word character: [a-zA-Z_0-9]
final Pattern pattern = Pattern.compile("\\w+");
final Matcher matcher = pattern.matcher(text);
int startIndex;
int endIndex;
while (matcher.find()) {
startIndex = matcher.start();
endIndex = matcher.end();
if ((startIndex <= index) && (index <= endIndex)) {
wordAtIndex = text.subSequence(startIndex, endIndex).toString();
break;
}
}
return wordAtIndex;
}
Example: Get the word at the current cursor position:
String text = editText.getText().toString();
int cursorPosition = editText.getSelectionStart();
String wordAtCursorPosition = getWordAtIndex(text, cursorPosition);
Use this instead if you want to find all connected characters (including punctuation):
// S = non-whitespace character: [^\s]
final Pattern pattern = Pattern.compile("\\S+");
Java regex documentation (regular-expression): https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
Original String:
Lorem ##ipsum## dolar ##sit## atem. Lorem ipsum dolar sit ##atem##.
After formating:
Lorem #ipsum dolar #sit atem. Lorem ipsum dolar sit #atem.
But only the last one has the Formating i want. See image below.
CODE
private void format() {
CharSequence text = editContent.getText();
MovementMethod movementMethod = editContent.getMovementMethod();
if ((movementMethod == null) || !(movementMethod instanceof LinkMovementMethod))
{
editContent.setMovementMethod(LinkMovementMethod.getInstance());
}
text = setSpanBetweenTokens(text, "##", new ForegroundColorSpan(0xFF0099FF), new UnderlineSpan(), new ClickableSpan() {
#Override
public void onClick(View widget) {
Toast.makeText(getApplicationContext(), "click", Toast.LENGTH_SHORT).show();
}
});
editContent.setText(text);
}
private static CharSequence setSpanBetweenTokens(CharSequence text, String token, CharacterStyle... characterStyle) {
int tokenLen = token.length();
int start = text.toString().indexOf(token) + 1;
int end = text.toString().indexOf(token, start);
while (start > -1 && end > -1)
{
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
for (CharacterStyle c : characterStyle) {
spannableStringBuilder.setSpan(c, start, end, 0);
}
spannableStringBuilder.delete(end, end + tokenLen);
spannableStringBuilder.delete(start - 1, start);
text = spannableStringBuilder;
start = text.toString().indexOf(token) + 1;
end = text.toString().indexOf(token, start);
}
return text;
}
EDIT
My final Solution
private void format() {
CharSequence text = editContent.getText();
MovementMethod movementMethod = editContent.getMovementMethod();
if ((movementMethod == null) || !(movementMethod instanceof LinkMovementMethod))
{
editContent.setMovementMethod(LinkMovementMethod.getInstance());
}
text = setSpanBetweenTokens(text, "##");
editContent.setText(text);
}
private static CharSequence setSpanBetweenTokens(CharSequence text, String token) {
int tokenLen = token.length();
int start = text.toString().indexOf(token) + 1;
int end = text.toString().indexOf(token, start);
while (start > -1 && end > -1)
{
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
spannableStringBuilder.setSpan(new ForegroundColorSpan(0xFF0099FF), start, end, 0);
spannableStringBuilder.setSpan(new UnderlineSpan(), start, end, 0);
spannableStringBuilder.setSpan(new ClickableSpan() {
#Override
public void onClick(View widget) {
Log.d("DEBUG", "Click");
}
}, start, end, 0);
spannableStringBuilder.delete(end, end + tokenLen);
spannableStringBuilder.delete(start - 1, start);
text = spannableStringBuilder;
start = text.toString().indexOf(token) + 1;
end = text.toString().indexOf(token, start);
}
return text;
}
Pass different object for each span:
spannableStringBuilder.setSpan(c, start, end, 0);
You're passing the same object for each span:
new ForegroundColorSpan(0xFF0099FF)
When span object exists in spannableStringBuilder then it changes bounds only, not a new span is added.
I would suggest a simpler way. If your formatting needs are basic, a simple regex + Html.fromHtml() should do the trick:
private void format() {
String mText = editContent.getText();
Spanned mSpannedText = Html.fromHtml(mText.replaceAll("##(.*?)##)","<font color=\"0xFF0099\">#$1</font>"),
editContent.setText(mSpannedText);
}
The final solution correctly loops however your first token will not be correctly deleted as you have used
int start = text.toString().indexOf(token) + 1;
which would only work if your token was 1 character in length. Since your chosen token is ## change the above code to utilise the already created variable tokenLen
int start = text.toString().indexOf(token) + tokenLen;
this will ensure your text is correctly edited and all trace of your tokens are removed.