不使用WebView加载HTML代码,用TextView加载HTML代码,加载网络图片,更改字体大小,更改字体颜色

学习之路 专栏收录该内容
11 篇文章 0 订阅

最近接到一个需求:Android上不使用WebView 来加载  带标签式的网页,并且,要能加载<img src="url">和 文字样式

就像这样:

<html>
    <head>
        <title>title</title>
    </head>
    <body>
        <p style='color:red;font-size:18px'>paragraph</p>
        <img src='url' />
    </body>
</html>

大概思路就是 找个组件来解析,然后用个容器加载出来。

所以,问题就是:

1.找到解析的组件

2.选择加载的容器

先看第一个问题,解析的组件

通过度娘了解到有个方法 fromHtml 可以解析网页代码

先看看fromHtml的源码吧:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.text;

import android.graphics.drawable.Drawable;
import org.xml.sax.XMLReader;

public class Html {

    ······

    /** @deprecated */
    @Deprecated
    public static Spanned fromHtml(String source) {
        throw new RuntimeException("Stub!");
    }

    public static Spanned fromHtml(String source, int flags) {
        throw new RuntimeException("Stub!");
    }

    /** @deprecated */
    @Deprecated
    public static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
        throw new RuntimeException("Stub!");
    }

    public static Spanned fromHtml(String source, int flags, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
        throw new RuntimeException("Stub!");
    }


    public interface TagHandler {
        void handleTag(boolean var1, String var2, Editable var3, XMLReader var4);
    }

    public interface ImageGetter {
        Drawable getDrawable(String var1);
    }
    
    ······

}

然后Spanned这个类型是继承CharSequence的。CharSequence又是什么东西...我也是这样想的。如果对CharSequence不熟悉,那对String、StringBuffer、StringBuilder应该比较熟悉吧。其实CharSequence是个接口,String、StringBuffer、StringBuilder都是继承CharSequence接口的。

他们的关系,大概是这样:

那大概了解Spanned之后,来看看fromHtml的参数:String source, int flags, Html.ImageGetter imageGetter, Html.TagHandler tagHandler.

source即要解析的网页代码

flags即html块元素之间换行符分隔数量:(API>24)

  • FROM_HTML_MODE_COMPACT:html块元素之间使用一个换行符分隔
  • FROM_HTML_MODE_LEGACY:html块元素之间使用两个换行符分隔

imageGetter即解析图片

tagHandler即解析其他标签,甚至可以是自定义的标签

因为需要解析网络图片,且API>24的局限,所以 我们用fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler)这个方法就行。

第二个问题选择加载的容器,选择的是TextView,TextView既能显示文本,又能显示图片。

好的,现在试一下fromHtml这个方法,先加入第一个参数String source:

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private Handler mHandler = new Handler();
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView();
        init();
    }

    public void findView() {
        textView = findViewById(R.id.tv);
    }

    public void init() {

        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message message) {
                if (message.what == 0x101) {
                    textView.setText((CharSequence) message.obj);
                }
                return false;
            }
        });
        final String html = "<html><font>文字</font><br/>" +
                "<img src=\"http://t2.hddhhn.com/uploads/tu/201712/9999/7d172ee5b2.jpg\"/>";
        
        Message msg = Message.obtain();
        CharSequence charSequence = Html.fromHtml(html, null, null);
        msg.what = 0x101;
        msg.obj = charSequence;
        mHandler.sendMessage(msg);
    }
    
}

再看看效果图

已经可以解析HTML代码了,继续加入第二个参数 Html.ImageGetter imageGetter:

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView();
        init();
    }

    public void findView() {
        textView = findViewById(R.id.tv);
        
    }

    public void init() {
        //设置handler 回调
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message message) {
                if (message.what == 0x101) {//当message.what 一样的时候,说明message已传入
                    textView.setText((CharSequence) message.obj);
                }
                return false;
            }
        });
        final String html = "<html><font>文字</font><br/>" +
                "<img src=\"http://t2.hddhhn.com/uploads/tu/201712/9999/7d172ee5b2.jpg\"/>";
        
        new Thread(new Runnable() {//图片是网络请求,所以新建一个线程来执行
            Message msg = Message.obtain();

            @Override
            public void run() {
                //设置图片长宽
                Html.ImageGetter imageGetter = new Html.ImageGetter() {
                    @Override
                    public Drawable getDrawable(String s) {
                        Drawable drawable = getImageFromNetwork(s);
                        
                        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());

                        return drawable;
                    }
                };
                


                CharSequence charSequence = Html.fromHtml(html, imageGetter, null);
                msg.what = 0x101;
                msg.obj = charSequence;
                mHandler.sendMessage(msg);
            }
        }).start();
    }

    

    //获取网络图片的方法,返回drawable
    public Drawable getImageFromNetwork(String imageUrl) {
        URL myFileUrl = null;
        Drawable drawable = null;
        try {
            myFileUrl = new URL(imageUrl);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        try {
            HttpURLConnection conn = (HttpURLConnection) myFileUrl.openConnection();
            conn.setDoInput(true);
            conn.connect();
            InputStream is = conn.getInputStream();
            drawable = Drawable.createFromStream(is, null);
            is.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return drawable;
    }

    
}

当然,AndroidMainfest.xml里的<mainfest>里要加上网络权限,不然会报错

<uses-permission android:name="android.permission.INTERNET" />

来看看效果图:

 

 

什么鬼...图片怎么这么小...那我要怎么看清妹纸 

在imageGetter里,不是有个setBounds吗?这个应该就是图片的宽高了

修改一下imageGetter

Html.ImageGetter imageGetter = new Html.ImageGetter() {
                    @Override
                    public Drawable getDrawable(String s) {
                        Drawable drawable = getImageFromNetwork(s);
                        //获取屏幕的宽高
                        int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
                        int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
                        //设置图片的宽 为屏幕宽的一半
                        int w = screenWidth / 2;
                        //根据图片比例,设置图片高度,如果不进行类型转换,被除数为零 报错
                        int h = (int) (w / ((float) drawable.getIntrinsicWidth() / drawable.getIntrinsicHeight()));
                        
                        drawable.setBounds(0, 0, w, h);

                        return drawable;
                    }
                };

来看看现在的效果

现在<img>处理,基本OK了

接下来就是文字的处理的

经过尝试,不能处理CSS样式。也就是说style样式都不适用

如果要更改文字颜色,使用<font color='red'>example</font>就可以了

测试了一下,不是所有标签都能解析,我把能解析的标签贴在下方

<br/>换行符
<p></p>段落标签
<div></div>分区标签
<strong></strong>强调文本
<b></b>粗体文本
<em></em>斜体显示
<cite></cite>斜体显示
<dfn></dfn>斜体显示
<i></i>斜体显示
<big></big>大号字体
<small></small>小号字体
<font></font>字体标签
<blockquote></blockquote>标签定义块引用
<tt></tt>字体显示为等宽字体
<a></a>超链接
<u></u>下划线
<sup></sup>上标
<sub></sub>下标
<h1>~<h6>标题标签
<img></img>图片标签

然后我在试<font>标签的时候,发现color和face两个属性有效,而size无效

如果直接使用textView.setTextSize(size);又显得太笨重,太蠢了。而且 容器设置字体大小 和网页字体大小 是两回事。

这时候,我们可以看看fromHtml的最后一个参数,Html.TagHandler tagHandler.

//设置自定义标签
Html.TagHandler tagHandler = new Html.TagHandler() {
    @Override
    public void handleTag(boolean b, String tag, Editable editable, XMLReader xmlReader) {
        Log.d(TAG, "handleTag: " + tag);
        if (b) {
            if (tag.equalsIgnoreCase("size")) {
                Log.d(TAG, "handleTag: start");
            }
        } else {
            if (tag.equalsIgnoreCase("size")) {
                Log.d(TAG, "handleTag: end");
            }
        }

     }
};

然后把taghandler放到方法内

CharSequence charSequence = Html.fromHtml(html_1, imageGetter, tagHandler);

运行发现:

能捕捉到<size>和</size>(其他标签也打印了,是因为if上方还有一个logd)

既然能捕捉到自定义的<size>

那接下来就好办了,只需要放大字体就好

//自定义<size>标签
    public void handlerStartSIZE(Editable output, XMLReader xmlReader) {
        if (startIndex == null) {
            startIndex = new Stack<>();
        }
        startIndex.push(output.length());
        if (propertyValue == null) {
            propertyValue = new Stack<>();
        }

        propertyValue.push(getProperty(xmlReader, "value"));//value可自己设置

    }
//自定义</size>标签
    public void handlerEndSIZE(Editable output) {
        if (!isEmpty(propertyValue)) {
            try {
                int value = Integer.parseInt(propertyValue.pop());
                output.setSpan(new AbsoluteSizeSpan(sp2px(MyApplication.getContext(), value)), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

效果图:

这样,就就可以改变文字的大小颜色了

最后附上关键代码的源码

package com.example.user.textviewloadimage;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.Html;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.util.Log;
import android.util.TypedValue;
import android.widget.TextView;

import org.xml.sax.XMLReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Stack;

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private Handler mHandler = new Handler();
    private String TAG = "TAG";
    private Stack<Integer> startIndex;
    private Stack<String> propertyValue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView();
        init();
    }

    public void findView() {
        textView = findViewById(R.id.tv);

    }

    public void init() {
        //设置handler 回调
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message message) {
                if (message.what == 0x101) {//当message.what 一样的时候,说明message已传入
                    textView.setText((CharSequence) message.obj);
                }
                return false;
            }
        });
        final String html = "<html><font color='red'>文字</font><br/>" +
                "<img src=\"http://t2.hddhhn.com/uploads/tu/201712/9999/7d172ee5b2.jpg\"/>";
        final String html_1 = "<html><font color='red'>文字</font><br/>" +
                "<font color='blue'><size value='20'>文字</size></font><br/>" +
                "<img src=\"http://t2.hddhhn.com/uploads/tu/201712/9999/7d172ee5b2.jpg\"/>";

        new Thread(new Runnable() {//图片是网络请求,所以新建一个线程来执行
            Message msg = Message.obtain();

            @Override
            public void run() {
                //设置图片长宽
                Html.ImageGetter imageGetter = new Html.ImageGetter() {
                    @Override
                    public Drawable getDrawable(String s) {
                        Drawable drawable = getImageFromNetwork(s);
                        //获取屏幕的宽高
                        int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
                        int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
                        //设置图片的宽 为屏幕宽的一半
                        int w = screenWidth / 2;
                        //根据图片比例,设置图片高度
                        int h = (int) (w / ((float) drawable.getIntrinsicWidth() / drawable.getIntrinsicHeight()));

                        drawable.setBounds(0, 0, w, h);

                        return drawable;
                    }
                };
                //设置自定义标签
                Html.TagHandler tagHandler = new Html.TagHandler() {
                    @Override
                    public void handleTag(boolean b, String tag, Editable editable, XMLReader xmlReader) {
                        Log.d(TAG, "handleTag: " + tag);
                        if (b) {
                            if (tag.equalsIgnoreCase("size")) {
                                handlerStartSIZE(editable, xmlReader);
                            }
                        } else {
                            if (tag.equalsIgnoreCase("size")) {
                                handlerEndSIZE(editable);
                            }
                        }

                    }
                };


                CharSequence charSequence = Html.fromHtml(html_1, imageGetter, tagHandler);
                msg.what = 0x101;
                msg.obj = charSequence;
                mHandler.sendMessage(msg);
            }
        }).start();
    }

    //自定义<size>标签----开始----
    public void handlerStartSIZE(Editable output, XMLReader xmlReader) {
        if (startIndex == null) {
            startIndex = new Stack<>();
        }
        startIndex.push(output.length());
        if (propertyValue == null) {
            propertyValue = new Stack<>();
        }

        propertyValue.push(getProperty(xmlReader, "value"));

    }

    //自定义<size>标签====结束====
    public void handlerEndSIZE(Editable output) {
        if (!isEmpty(propertyValue)) {
            try {
                int value = Integer.parseInt(propertyValue.pop());
                output.setSpan(new AbsoluteSizeSpan(sp2px(MyApplication.getContext(), value)), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //获取html标签的 value

    private String getProperty(XMLReader xmlReader, String property) {
        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);

            for (int i = 0; i < len; i++) {
                if (property.equals(data[i * 5 + 1])) {
                    return data[i * 5 + 4];
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    //判断是否为空
    public static boolean isEmpty(Collection collection) {
        return collection == null || collection.isEmpty();
    }

    public static int sp2px(Context context, float spValue) {
        return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, context.getResources().getDisplayMetrics()) + 0.5f);
    }


    public Drawable getImageFromNetwork(String imageUrl) {
        URL myFileUrl = null;
        Drawable drawable = null;
        try {
            myFileUrl = new URL(imageUrl);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        try {
            HttpURLConnection conn = (HttpURLConnection) myFileUrl.openConnection();
            conn.setDoInput(true);
            conn.connect();
            InputStream is = conn.getInputStream();
            drawable = Drawable.createFromStream(is, null);
            is.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return drawable;
    }


}

参考资料:

1.Android TextView(同时显示图片+文字) https://www.cnblogs.com/zhou-guobao/p/4651192.html

2.Android 在TextView 中显示图片的4种方式 https://blog.csdn.net/u012724237/article/details/79010741

3.Android 详解实现TextView加载带图片标签的Html并按比例缩放 https://blog.csdn.net/shaohx0518/article/details/50129511

4.Html.fromHtml()中Html.TagHandler()的使用 https://blog.csdn.net/baidu_34012226/article/details/53301047

 

  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值