How to retrieve BackgroundColorSpan for selected text in android
Asked Answered
R

5

12

I am trying to set BackgroundColorSpan to selected text in my Edit-text. So when i select any text and click on button it will set Background color to that particular text and then i am saving that note to my SDCard with .html format and then again i am retrieving that note to edit again in the same format.

The problem i am facing right now is when i apply BackgroundColorSpan to selected text it show's that string with background color which i applied but once i save that note to my SDCard and re-open, it does not show Background color to that particular string instead of that it show me normal string without background color.

Below is my code Which i used to set Background Color to Edit-text selected area

mSpannable.setSpan(new BackgroundColorSpan(color),startSelection, endSelection, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

and below is code to save my notes to SDcard.

            Spanned spannedText = edtNoteDescription.getText();
            StringBuilder output = new StringBuilder();
            AppHtml.withinHtml(output, spannedText);
            File mFile = new File(Environment.getExternalStorageDirectory()
                    + File.separator + "MyNote/");
            }
            File myFile = new File(mFile, "/" + strNoteTitle + ".html/");
            myFile.createNewFile();
            FileOutputStream fOut = new FileOutputStream(myFile);
            OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut);
            myOutWriter.append(output);
            myOutWriter.close();
            fOut.close();

With this above code i am successfully able to save my file in HTML format, but i am not getting the string with Background color.

I tried to print that string in Log and then pasted that string in w3School then i get exact result what i expect it to be but in android i don't know why it is not working.

String which i get in Logcat is below

<p><font color ="#7dff00">This</font> <font color ="#ff5100">Is</font>&#160; a&#160; <font color ="#04ff00"><b><font style = "background-color:#2929dd">String</font></b></font>... </p>

You can try this string here which gives perfect result with background color to string but while setting to android Edit-ext i don't know what is happening and it's not setting as i expect it to be.

Edit

Below is code which i used to retrieve text from my SDcard file

            Bundle bundle = getIntent().getExtras();
            strGetPath = bundle.getString(GlobalClass.notesPath);
            filePath = new File(strGetPath);
            fileInputStream = new FileInputStream(filePath);
            int size = fileInputStream.available();
            bytbuffer = new byte[size];
            fileInputStream.read(bytbuffer);
            fileInputStream.close();
            String strGetData = new String(bytbuffer);
            Spanned spanHTMLData = AppHtml.fromHtml(strGetData);
            LogM.e("===== Getting Data =====" + strGetData);
            edtNoteDescription.setText(spanHTMLData);
Rosellaroselle answered 8/9, 2014 at 12:2 Comment(1)
Can you show the code which reads the text from file and applies it to EditText?Noise
C
6

The same Problem I faced while creating a project of saving the HTML notes.

As you said that you have made your customized HTML.java class I also customized the same class for my purpose.

The default Html.java class does not contain the functionality for backgroundcolor, font size, bullet etc.

So Here i am sharing the content of that class so you can get the idea from it to set the Backround Color to you HTML Note.

Let's assume your customized Html.java = AppHtml.java so other can understand it better.

1) First add the background color tag in your AppHtml.java class. Add this below code to your withinParagraph method.

if (style[j] instanceof BackgroundColorSpan) {
                out.append("<font style = \"background-color:#");
                String color = Integer
                        .toHexString(((BackgroundColorSpan) style[j])
                                .getBackgroundColor() + 0x01000000);
                while (color.length() < 6) {
                    color = "0" + color;
                }
                out.append(color);
                out.append("\">");
            }

then complete the font style that you have started

if (style[j] instanceof BackgroundColorSpan) {
                out.append("</font>");
            }

2) Here is my Font class

private static class Font {
    public String mColor;
    public String mFace;
    public String mbgColor;
    public String mSize;

    public Font(String color, String face, String bgColor, String size) {
        mColor = color;
        mFace = face;
        mbgColor = bgColor;
        mSize = size;
    }
}

3) Here is my startFont method.

private static void startFont(SpannableStringBuilder text,
        Attributes attributes) {
    String color = attributes.getValue("", "color");
    String face = attributes.getValue("", "face");
    String bgColor = attributes.getValue("", "style");
    String size = attributes.getValue("", "size");

    int len = text.length();
    text.setSpan(new Font(color, face, bgColor, size), len, len,
            Spannable.SPAN_MARK_MARK);
}

4) Here is my endFont method.

private static void endFont(SpannableStringBuilder text) {
    int len = text.length();
    Object obj = getLast(text, Font.class);
    int where = text.getSpanStart(obj);

    text.removeSpan(obj);

    if (where != len) {
        Font f = (Font) obj;
        if (f.mColor != null) {
            if (!TextUtils.isEmpty(f.mColor)) {
                if (f.mColor.startsWith("@")) {
                    Resources res = Resources.getSystem();
                    String name = f.mColor.substring(1);
                    int colorRes = res.getIdentifier(name, "color",
                            "android");
                    if (colorRes != 0) {
                        ColorStateList colors = res
                                .getColorStateList(colorRes);
                        text.setSpan(new TextAppearanceSpan(null, 0, 0,
                                colors, null), where, len,
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                } else {
                    int c = getHtmlColor(f.mColor);
                    if (c != -1) {
                        text.setSpan(
                                new ForegroundColorSpan(c | 0xFF000000),
                                where, len,
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                }
            }
        }
        if (f.mFace != null) {
            text.setSpan(new TypefaceSpan(f.mFace), where, len,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        if (f.mbgColor != null) {
            String bg_COLOR = f.mbgColor.substring(
                    f.mbgColor.lastIndexOf("#"), f.mbgColor.length());

            if (!TextUtils.isEmpty(bg_COLOR)) {
                if (bg_COLOR.startsWith("@")) {
                    Resources res = Resources.getSystem();
                    String name = bg_COLOR.substring(1);
                    int colorRes = res.getIdentifier(name, "color",
                            "android");
                    if (colorRes != 0) {
                        ColorStateList colors = res
                                .getColorStateList(colorRes);
                        text.setSpan(new TextAppearanceSpan(null, 0, 0,
                                colors, null), where, len,
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                } else {
                    int c = getHtmlColor(bg_COLOR);
                    if (c != -1) {
                        text.setSpan(
                                new BackgroundColorSpan(c | 0xFF000000),
                                where, len,
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                }
            }
        }

        if (f.mSize != null) {

            if (!TextUtils.isEmpty(f.mSize)) {

                int size = Integer.parseInt(f.mSize);

                text.setSpan((new AbsoluteSizeSpan(size)), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            }
        }

    }
}
Courbet answered 13/9, 2014 at 5:20 Comment(6)
Hiii, im trying to implement this, but the class HTml is private i cant extend or implement, and if i try to copy/paste, there are a lot of classes missing, like ArrayUtils and other, i dont know why but they are missing, can you post a working class please? thank you so much =)Giverin
@Giverin You can get the class from here :grepcode.com/file/repo1.maven.org/maven2/com.google.android/…Courbet
yes im downloading, but i have to download more than 10, you did the same thing? or your AndroidStudio/Eclipse found easily the files? i saw that i have in the sdk path a folder named "sources" some files are there, but why my Android Studio dont see this files? do you know? thx =)Giverin
@Giverin Yes I do the same you need to add all the files that require or you can check google for the working class for HTML.javaCourbet
humm a lot of work =~~, but i will do, thank you, i already tried, the HTMLSPanner too, but nothing worked...Giverin
lol its impossible, i was until now adding classes, for each class, i need to add 2 more, why? i cant understand =(Giverin
K
4

I do not know what is AppHtml in your source code but for Html<->Spannable parsing in Android we use android.text.Html class. This class though doesn't support BackgroundColorSpan for some reason.

I would suggest experimenting with Html.fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) method and pass a TagHandler to support what you need. Docs are here.

Kendakendal answered 11/9, 2014 at 7:15 Comment(2)
Thank you, I will try this and let you know. +1 for the great answer.Rosellaroselle
Html.TagHandler will be called when parser does know how to interpret tag. In this case parser knows how to interpret font tag. Issue is about handling font tag attributes. Current implementation only handles font's color and typeface attributes.Noise
H
3
  • Create your own custom format and save it to a file

I don't know whether this is exactly what you are looking for, but I don't see the point to use HTML here. As other answers pointed out, fromHtml() is quite limited and using you custom file format will solve your saving / restoring problem in no time.

You need to store on a file the following pieces information:

  1. How many spans you have

  2. start, end, color for each span

  3. your text

The following code snippet shows how to implement the store and load methods. It uses the Spanned text in a TextView. The complete Eclipse project took me about half an hour and can be found here.

public void store(View v)
{
    Spanned s = (Spanned) mTextView.getText();
    BackgroundColorSpan[] spans = s.getSpans(0, s.length(), BackgroundColorSpan.class);

    BufferedWriter bw = null;
    try
    {
        int len = spans.length;
        bw = new BufferedWriter(new FileWriter(mFile));
        bw.write(String.valueOf(len));
        bw.newLine();
        for (BackgroundColorSpan span : spans)
        {
            int start = s.getSpanStart(span);
            int end = s.getSpanEnd(span);
            int color = span.getBackgroundColor();
            bw.write("" + start + "," + end + "," + color);
            bw.newLine();
        }
        bw.write(mText);
        clear(v);
    }
    catch (IOException e)
    {
        Log.e(TAG, "IO error", e);
    }
    finally
    {
        closeQuietly(bw);
    }
}

public void load(View v)
{
    BufferedReader br = null;
    try
    {
        br = new BufferedReader(new FileReader(mFile));

        int len = Integer.parseInt(br.readLine());
        BackgroundColorSpan[] spans = new BackgroundColorSpan[len];
        int[] starts = new int[len];
        int[] ends = new int[len];
        for (int i = 0; i < len; i++)
        {
            String[] tokens = br.readLine().split(",");
            starts[i] = Integer.parseInt(tokens[0]);
            ends[i] = Integer.parseInt(tokens[1]);
            int color = Integer.parseInt(tokens[2]);
            spans[i] = new BackgroundColorSpan(color);
        }
        mText = br.readLine();

        SpannableString s = new SpannableString(mText);
        for (int i = 0; i < len; i++)
        {
            s.setSpan(spans[i], starts[i], ends[i], Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        mTextView.setText(s);
    }
    catch (IOException e)
    {
        Log.e(TAG, "IO error", e);
    }
    finally
    {
        closeQuietly(br);
    }
}
Haag answered 12/9, 2014 at 15:57 Comment(6)
Rereading this after some years I realized you can implement the same logic with Scanner tricks in probably one third of the code.Haag
I have a similar code however it takes time looping to respan the text when user spans alot of chars.Wriggle
@OmarAhmed: what takes a lot of time? s.setSpan? Or reading the file?Haag
The loop that resets the span for each word's starting and ending index. If there are too many words to span it takes time.Wriggle
@OmarAhmed: I see. I'm curious about what specific operation takes time. I don't think the loop is the problem as any rendering function will have to go through the text anyway. So I'd bet on the I/O or maybe the setSpan method. A profiler would help find out where the bottleneck is.Haag
I found out the re span actually is whats taking time its insignificant at first but when lots of text is spanned it takes more time.Wriggle
N
2

Current implementation of Html.fromHtml only supports Font's color and typeface attributes. You can check the same here.

As you said you are using modified version of Html, it is much more easier to support additional attributes by yourself.

While parsing the XML, when font open tag is encountered, all the font attribute values are stored in Font private class. When font close tag is encountered, for each of those attributes, apply corresponding span styles.

Step 1 : In Font private class add a data member for holding the background color and modify the constructor to accept additional paramter.

private static class Font {
    ...
    public String mBackgroundColor; // Data member to hold background color

    public Font(String color, String face, String backgroundColor) {
        ...
        mBackgroundColor = backgroundColor;
    }
    ...
}

Step 2 : In startFont method, handle the background-color attribute.

private static void startFont(SpannableStringBuilder text,
                                  Attributes attributes) {
    ...
    String backgroundColor = null;
    // In this specific example, background-color attribute is present in style attribute.
    String style = attributes.getValue("", "style");
    if(style != null  && style.contains("background-color")) {
        String[] array = style.split(":");
        if(array.length == 2)
           backgroundColor = array[1]; 
    } else {
        // If background-color is specified as an attribute itself use this
        backgroundColor = attributes.getValue("", "background-color");
    }

    // Pass the background-color to the Font constructor
    text.setSpan(new Font(color, face, backgroundColor), len, len, Spannable.SPAN_MARK_MARK);

}

Step 3: In endFont method, add the BackgroundColorSpan to the text.

private static void endFont(SpannableStringBuilder text) {
    ...
    if(f.mBackgroundColor != null) {
        text.setSpan(new BackgroundColorSpan(Color.parseColor(f.mBackgroundColor)), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
}

P.S. : Since Html constructor is private, specializing the Html is not possible.

Noise answered 11/9, 2014 at 10:18 Comment(0)
K
1

Unfortunately fromHtml() is pretty limited and it can't parse the font style attribute.

A rather old post in Commonware's blog has the list of the working attributes.

You have a few options:

  • Set the background spans by hand
  • Create your own parser that supports this and share it with the public (yay)
  • Use a WebView

Now using a WebView seems like the most straighforward solution, so here's an example:

WebView webView = (WebView) findViewById(R.id.webView);
String strGetData = "<p><font color =\"#7dff00\">This</font> <font color =\"#ff5100\">Is</font>&#160; a&#160; <font color =\"#04ff00\"><b><font style = \"background-color:#2929dd\">String</font></b></font>... </p>\n";

webView.loadData(strGetData, "text/html", "utf-8");
Krp answered 11/9, 2014 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.