最近接到一个需求:Android上不使用WebView 来加载 带标签式的网页,并且,要能加载和 文字样式
就像这样:
title
paragraph
大概思路就是 找个组件来解析,然后用个容器加载出来。
所以,问题就是:
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)
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 = "文字
" +
"";
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 = "文字
" +
"";
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里的
来看看效果图:
什么鬼...图片怎么这么小...那我要怎么看清妹纸
在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;
}
};
来看看现在的效果
现在处理,基本OK了
接下来就是文字的处理的
经过尝试,不能处理CSS样式。也就是说style样式都不适用
如果要更改文字颜色,使用example就可以了
测试了一下,不是所有标签都能解析,我把能解析的标签贴在下方
换行符 | |
段落标签 | |
分区标签 | |
强调文本 | |
粗体文本 | |
斜体显示 | |
斜体显示 | |
斜体显示 | |
斜体显示 | |
大号字体 | |
小号字体 | |
字体标签 | |
标签定义块引用 | |
字体显示为等宽字体 | |
超链接 | |
下划线 | |
上标 | |
下标 | |
~ |
标题标签 |
图片标签 |
然后我在试标签的时候,发现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);
运行发现:
能捕捉到
既然能捕捉到自定义的
那接下来就好办了,只需要放大字体就好
//自定义标签
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可自己设置
}
//自定义 标签
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 startIndex;
private Stack 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 = "文字
" +
"";
final String html_1 = "文字
" +
"文字
" +
"";
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();
}
//自定义标签----开始----
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"));
}
//自定义标签====结束====
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