style - ios textview html




Android:如何使用Html.TagHandler? (4)

我正在尝试为留言板构建一个Android应用程序。 要显示帖子内容的格式化html,我选择了TextView和Html.fromHtml()方法。 遗憾的是,这只涵盖了几个html标签。 未知标记由实现TagHandler的类处理,并且必须由我自己生成。

现在,我搜索了很多,无法找到这个类应该如何工作的例子。 让我们考虑我有一个用于标记某些文本的u标记(我知道这已被弃用,但无论如何)。 我的TagHandler是怎样的?

它以下列方式调用:

public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {

前两个论点都没问题。 我想我必须使用output.append()修改输出。 但是我如何附加在那里加下划线的东西?


此解决方案可在Android sdk中找到

android.text.html 。 596 - 626行。复制/粘贴

private static <T> Object getLast(Spanned text, Class<T> kind) {
    /*
     * This knows that the last returned object from getSpans()
     * will be the most recently added.
     */
    Object[] objs = text.getSpans(0, text.length(), kind);

    if (objs.length == 0) {
        return null;
    } else {
        return objs[objs.length - 1];
    }
}

private static void start(SpannableStringBuilder text, Object mark) {
    int len = text.length();
    text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
}

private static <T> void end(SpannableStringBuilder text, Class<T> kind,
                        Object repl) {
    int len = text.length();
    Object obj = getLast(text, kind);
    int where = text.getSpanStart(obj);

    text.removeSpan(obj);

    if (where != len) {
        text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
}

要使用,请覆盖TagHandler,如下所示:

public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {

    if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {

        if(opening){
            start((SpannableStringBuilder) output, new Strike();

        } else {
            end((SpannableStringBuilder) output, Strike.class, new StrikethroughSpan());
        }
    }       
}

/* 
 * Notice this class. It doesn't really do anything when it spans over the text. 
 * The reason is we just need to distinguish what needs to be spanned, then on our closing
 * tag, we will apply the spannable. For each of your different spannables you implement, just 
 * create a class here. 
 */
 private static class Strike{}

我们已经在内部开发了这个库https://github.com/square1-io/rich-text-android一段时间了,我们在许多内容密集型新闻应用程序中使用它。

该库可以解析最常见的html标签,包括视频和img以及远程下载图像。 然后,可以使用自定义视图RichTextView替换TextView以显示已解析的内容。

我们最近公开发布了它,因此文档仍然不完整,但提供的示例应该很容易理解,看它是否符合您的需求。


所以,我终于自己弄明白了。

public class MyHtmlTagHandler implements TagHandler {

    public void handleTag(boolean opening, String tag, Editable output,
            XMLReader xmlReader) {
        if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
            processStrike(opening, output);
        }
    }

    private void processStrike(boolean opening, Editable output) {
        int len = output.length();
        if(opening) {
            output.setSpan(new StrikethroughSpan(), len, len, Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, StrikethroughSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new StrikethroughSpan(), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private Object getLast(Editable text, Class kind) {
        Object[] objs = text.getSpans(0, text.length(), kind);

        if (objs.length == 0) {
            return null;
        } else {
            for(int i = objs.length;i>0;i--) {
                if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) {
                    return objs[i-1];
                }
            }
            return null;
        }
    }


}

对于TextView,您可以将其称为:

myTextView.setText (Html.fromHtml(text.toString(), null, new MyHtmlTagHandler()));

如果有人需要它。

干杯


虽然我可以在Html.java API中看到它,但样式和text-align应该可以与标签<p><div>等一起使用。我无法使用<p align="center"><p style="text-align: center">和许多其他变体。 无法进行文本的中心对齐,以及其他样式,如字体大小,来自我的ttf文件的多个字体,背景颜色,我已经基于TextView创建了自己的htmlTextView,但是使用了我自己的tagHandler类。 鉴于一两个轻微的刺激,大多数标签都很好,但我的自定义对齐标签, 右,仅在特殊条件下工作(我不明白),否则。 他们不工作或崩溃的应用程序! 这是我的对齐标记句柄。 它具有与所有其他自定义标记处理程序相同的结构,但实际上表现得很奇怪! 我的标签处理程序的基本形式是相同的, 并不是我构思的 ! 我在网上搜索了几个小时后找到了taghandler模​​板。 我很感激发布它的人,但我的记忆和组织能力使我无法记住谁或在哪里,所以如果你认识这段代码,请告诉我。 我唯一的链接(在这里)是在我的代码中: :Android:如何使用Html.TagHandler?

  private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
    int len = output.length();
    if (opening) {
        output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
    } else {
        Object obj = getLast(output, AlignmentSpan.Standard.class);
        int where = output.getSpanStart(obj);

        output.removeSpan(obj);

        if (where != len) {
            output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }
}

我认为问题是结束标记没有连接正确的开始标记。

private Object getLast(Editable text, Class kind) {
            Object[] objs = text.getSpans(0, text.length(), kind);

            if (objs.length == 0) {
                return null;
            } else {
                for (int i = objs.length - 1; i >= 0; --i) {
                    if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
                        return objs[i];
                    }
                }
                return null;
            }
        }

这是总课程,有些事情是不对的。 最大的组成部分是我的理解! 也许有人可以帮助我更好地理解......

public class htmlTextView extends AppCompatTextView {
static Typeface mLogo;
static Typeface mGAMZ;
static Typeface mChalk;
static Typeface mSouvenir;
int GS_PAINTFLAGS = FILTER_BITMAP_FLAG | ANTI_ALIAS_FLAG | SUBPIXEL_TEXT_FLAG | HINTING_ON;

public htmlTextView(Context context) {
    super(context);
    initialise();
}

public htmlTextView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    initialise();
}

public htmlTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initialise();
}


private void initialise() {
    mLogo = Typeface.createFromAsset(theAssetManager, "fonts/logo.ttf");
    mGAMZ = Typeface.createFromAsset(theAssetManager, "fonts/GAMZ One.ttf");
    mChalk = Typeface.createFromAsset(theAssetManager, "fonts/swapfix.ttf");
    mSouvenir = Typeface.createFromAsset(theAssetManager, "fonts/Souvenir Regular.ttf");

    setPaintFlags(GS_PAINTFLAGS);
}

public void setDefaultTypefaceSouvenir() {
    setTypeface(mSouvenir);
}

public void setDefaultTypefaceGAMZ() {
    setTypeface(mGAMZ);
}

public void setDefaultTypefaceChalk() {
    setTypeface(mChalk);
}

/*public myTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
}*/

public void setHTML(String htmltext) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Nougat API 24
        setText(Html.fromHtml(htmltext, Html.FROM_HTML_MODE_LEGACY,
                null, new TypefaceTagHandler()));
    } else {
        setText(Html.fromHtml(htmltext, null, new TypefaceTagHandler()));
    }
}

@Override
protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

@Override
public Bitmap getDrawingCache(boolean autoScale) {
    return super.getDrawingCache(autoScale);
}

@Override
public void draw(Canvas canvas) {
    super.draw(canvas);
}

// http://.com/questions/4044509/android-how-to-use-the-html-taghandler
private static class TypefaceTagHandler implements Html.TagHandler {


    private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, AlignmentSpan.Standard.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private void ProcessTypefaceTag(Typeface tf, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new CustomTypefaceSpan("", tf), len, len,
                    Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, CustomTypefaceSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new CustomTypefaceSpan("", tf), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private void ProcessScaleTag(float scalefactor, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new RelativeSizeSpan(scalefactor), len, len,
                    Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, RelativeSizeSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new RelativeSizeSpan(scalefactor), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private void ProcessBox(int colour, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new BackgroundColorSpan(colour), len, len,
                    Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, BackgroundColorSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new BackgroundColorSpan(colour), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private void ProcessTextColour(int colour, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new ForegroundColorSpan(colour), len, len,
                    Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, ForegroundColorSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new ForegroundColorSpan(colour), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    final HashMap<String, String> attributes = new HashMap<>();

    @Override
    public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
        String Attr = "";
        //if (!opening) attributes.clear();
        processAttributes(xmlReader);

        if ("txt".equalsIgnoreCase(tag)) {
            Attr = attributes.get("clr");
            System.out.println("clr Attr: " + Attr + ", opening: " + opening);
            if (Attr == null || Attr.isEmpty()
                    || "black".equalsIgnoreCase(Attr)
                    || Attr.charAt(0) == 'k') {
                System.out.println("did black, opening: " + opening);
                ProcessTextColour(parseColor("#000000"), opening, output);
            } else {
                if (Attr.equalsIgnoreCase("g")) {
                    ProcessTextColour(parseColor("#b2b3b3"), opening, output);
                } else {
                    System.out.println("did colour, opening: " + opening);
                    ProcessTextColour(parseColor(Attr), opening, output);
                }
            }
            return;
        }

        if ("box".equalsIgnoreCase(tag)) {
            ProcessBox(parseColor("#d7d6d5"), opening, output);
            return;
        }


        if ("scl".equalsIgnoreCase(tag)) {
            Attr = attributes.get("fac");
            System.out.println("scl Attr: " + Attr);
            if (Attr != null && !Attr.isEmpty()) {
                ProcessScaleTag(parseFloat(Attr), opening, output);
            }
            return;
        }

        if ("left".equalsIgnoreCase(tag)) {
            ProcessAlignment(Layout.Alignment.ALIGN_NORMAL, opening, output);
            return;
        }

        if ("centre".equalsIgnoreCase(tag)) {
            ProcessAlignment(Layout.Alignment.ALIGN_CENTER, opening, output);
            return;
        }

        if ("right".equalsIgnoreCase(tag)) {
            ProcessAlignment(Layout.Alignment.ALIGN_OPPOSITE, opening, output);
            return;
        }

        if ("logo".equalsIgnoreCase(tag)) {
            ProcessTypefaceTag(mLogo, opening, output);
            return;
        }
        if ("gamz".equalsIgnoreCase(tag)) {
            ProcessTypefaceTag(mGAMZ, opening, output);
            return;
        }

        if ("chalk".equalsIgnoreCase(tag)) {
            System.out.println("chalk " + (opening ? "opening" : "closing"));
            ProcessTypefaceTag(mChalk, opening, output);
            return;
        }
    }

    private Object getLast(Editable text, Class kind) {
        Object[] objs = text.getSpans(0, text.length(), kind);

        if (objs.length == 0) {
            return null;
        } else {
            for (int i = objs.length - 1; i >= 0; --i) {
                if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
                    return objs[i];
                }
            }
            return null;
        }
    }

    private void processAttributes(final XMLReader xmlReader) {
        try {
            Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");
            elementField.setAccessible(true);
            Object element = elementField.get(xmlReader);
            Field attsField = element.getClass().getDeclaredField("theAtts");
            attsField.setAccessible(true);
            Object atts = attsField.get(element);
            Field dataField = atts.getClass().getDeclaredField("data");
            dataField.setAccessible(true);
            String[] data = (String[])dataField.get(atts);
            Field lengthField = atts.getClass().getDeclaredField("length");
            lengthField.setAccessible(true);
            int len = (Integer)lengthField.get(atts);

            /**
             * MSH: Look for supported attributes and add to hash map.
             * This is as tight as things can get :)
             * The data index is "just" where the keys and values are stored.
             */
            for(int i = 0; i < len; i++)
                attributes.put(data[i * 5 + 1], data[i * 5 + 4]);
        }
        catch (Exception e) {
            Log.d(TAG, "Exception: " + e);
        }
    }

}

private static class CustomTypefaceSpan extends TypefaceSpan {
    private final Typeface newType;

    public CustomTypefaceSpan(String family, Typeface type) {
        super(family);
        newType = type;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        applyCustomTypeFace(ds, newType);
    }

    @Override
    public void updateMeasureState(TextPaint paint) {
        applyCustomTypeFace(paint, newType);
    }

    private void applyCustomTypeFace(Paint paint, Typeface tf) {
        int oldStyle;
        Typeface old = paint.getTypeface();
        if (old == null) {
            oldStyle = 0;
        } else {
            oldStyle = old.getStyle();
        }

        int fake = oldStyle & ~tf.getStyle();
        if ((fake & Typeface.BOLD) != 0) {
            paint.setFakeBoldText(true);
        }

        if ((fake & Typeface.ITALIC) != 0) {
            paint.setTextSkewX(-0.25f);
        }
        paint.setTypeface(tf);
    }
}

}

htmlTextView是从活动创建的:

 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    theAssetManager = getAssets();
    htmlTextView tv = new htmlTextView(this);
    tv.setDefaultTypefaceSouvenir();
    tv.setTextColor(BLACK);
    tv.setBackgroundColor(0xfff0f0f0);
    tv.setPadding(4, 4, 4, 4);
    tv.setTextSize(30);
    tv.setMovementMethod(new ScrollingMovementMethod());
    tv.setHTML(getString(R.string.htmljumblies));
    //tv.setHTML(getString(R.string.htmltest));
    RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
    rl.addView(tv);
}

htmljumblies在strings.xml中定义如下。 此特定版本将使应用程序崩溃,但如果从第7行和第9行删除第一个<centre></centre>标记,则Jumblies将显示为集中式? 令人困惑和沮丧! 保留它们并删除包含Jumblies的<centre></centre>标签,没有任何反应。 标题中的行不是中心对齐的!

    <string name="htmljumblies">
    <![CDATA[&DoubleLongRightArrow;<logo><scl fac="1.1"><font color="#e5053a">GAMZ</font></scl></logo>
        <chalk><scl fac="1.8"> SWAP </scl></chalk>
        <scl fac="1.00">Set <b>1</b>, Game <b>1</b></scl>
        <br>
        <centre>
        <gamz><font color="#e5053a"><scl fac="1.50">a</scl></font><scl fac="0.90">(9)</scl></gamz>, <gamz><font color="#00a3dd"><scl fac="1.50">e</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#fba311"><scl fac="1.50">i</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bc5e1e"><scl fac="1.50">o</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bf30b5"><scl fac="1.50">u</scl></font><scl fac="0.90">(9)</scl></gamz>
        </centre>
        <br>
        This is an example of my custom <b>htmlTextView</b> drawn from HTML format
        text with custom tags to use custom fonts, colouring typeface sizing and highlight boxes.
        The default font is <b><i>Souvenir</i></b>, but 3 other fonts are used:<br>
        The <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
        <font color="#000080"><gamz><scl fac="0.8"><box>letter</box>
        <box>fonts</box><sc></gamz></font>
        and <chalk><scl fac="1.8">swapfix</scl></chalk>, essentially
        <chalk><scl fac="0.9">Staccato 555</scl></chalk>,
        as used in the words <chalk><scl fac="1.2">SWAP</scl></chalk> and
        <chalk><scl fac="1.2">FIX</scl></chalk>
        on the <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
        boxes.
        <br>
        <centre>
        <scl fac="2"><box><b> <u>The Jumblies</u> </b></box></scl><br>
        <font color="#0000ff">
        They went to sea in a Sieve, they did,<br>
        In a Sieve they went to sea:<br>
        In spite of all their friends could say,<br>
        On a winter\'s morn, on a stormy day,<br>
        In a Sieve they went to sea!<br>
        And when the Sieve turned round and round,<br>
        And every one cried, \'You\'ll all be drowned!\'<br>
        They called aloud, \'Our Sieve ain\'t big,<br>
        But we don\'t care a button! we don\'t care a fig!<br>
        In a Sieve we\'ll go to sea!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        They sailed away in a Sieve, they did,<br>
        In a Sieve they sailed so fast,<br>
        With only a beautiful pea-green veil<br>
        Tied with a riband by way of a sail,<br>
        To a small tobacco-pipe mast;<br>
        And every one said, who saw them go,<br>
        \'O won\'t they be soon upset, you know!<br>
        For the sky is dark, and the voyage is long,<br>
        And happen what may, it\'s extremely wrong<br>
        In a Sieve to sail so fast!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        The water it soon came in, it did,<br>
        The water it soon came in;<br>
        So to keep them dry, they wrapped their feet<br>
        In a pinky paper all folded neat,<br>
        And they fastened it down with a pin.<br>
        And they passed the night in a crockery-jar,<br>
        And each of them said, \'How wise we are!<br>
        Though the sky be dark, and the voyage be long,<br>
        Yet we never can think we were rash or wrong,<br>
        While round in our Sieve we spin!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        And all night long they sailed away;<br>
        And when the sun went down,<br>
        They whistled and warbled a moony song<br>
        To the echoing sound of a coppery gong,<br>
        In the shade of the mountains brown.<br>
        \'O Timballo! How happy we are,<br>
        When we live in a Sieve and a crockery-jar,<br>
        And all night long in the moonlight pale,<br>
        We sail away with a pea-green sail,<br>
        In the shade of the mountains brown!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        They sailed to the Western Sea, they did,<br>
        To a land all covered with trees,<br>
        And they bought an Owl, and a useful Cart,<br>
        And a pound of Rice, and a Cranberry Tart,<br>
        And a hive of silvery Bees.<br>
        And they bought a Pig, and some green Jack-daws,<br>
        And a lovely Monkey with lollipop paws,<br>
        And forty bottles of Ring-Bo-Ree,<br>
        And no end of Stilton Cheese.<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        And in twenty years they all came back,<br>
        In twenty years or more,<br>
        And every one said, \'How tall they\'ve grown!<br>
        For they\'ve been to the Lakes, and the Torrible Zone,<br>
        And the hills of the Chankly Bore!\'<br>
        And they drank their health, and gave them a feast<br>
        Of dumplings made of beautiful yeast;<br>
        And every one said, \'If we only live,<br>
        We too will go to sea in a Sieve,---<br>
        To the hills of the Chankly Bore!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.</centre></font>
    ]]>
</string>






textview