Commit 99e2ef44 by zhengnw@sobot.com

widget 1.1.0 html 富文本,webview

parent 29b542d6
......@@ -7,10 +7,8 @@ import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.widget.ImageView;
import android.widget.TextView;
import com.sobot.common.login.SobotLoginTools;
import com.sobot.common.login.callback.SobotResultBlock;
import com.sobot.common.login.callback.SobotResultCode;
import com.sobot.common.utils.SobotImageUtils;
import com.sobot.pictureframe.SobotBitmapUtil;
import com.sobot.utils.SobotLogUtils;
......@@ -23,6 +21,7 @@ import com.sobot.widget.refresh.layout.footer.ClassicsFooter;
import com.sobot.widget.refresh.layout.header.ClassicsHeader;
import com.sobot.widget.ui.SobotMarkConfig;
import com.sobot.widget.ui.base.SobotBaseActivity;
import com.sobot.widget.ui.rich.HtmlToolUtils;
import com.sobot.widget.ui.toast.SobotToastUtil;
import java.io.File;
......@@ -34,6 +33,7 @@ public class MainActivity extends SobotBaseActivity {
private ImageView img;
private SobotPhotoView img2;
private SobotLoadingLayout loadinglayout;
private TextView tv;
@Override
......@@ -43,7 +43,8 @@ public class MainActivity extends SobotBaseActivity {
@Override
protected void initView() {
tv = findViewById(R.id.tv);
HtmlToolUtils.getInstance(MainActivity.this).setRichText(tv, "<h3><span style=\"color: rgb(255, 77, 79);\"><strong>【若摄像机因出现自动呼叫导致设备离线,建议您</strong></span><span style=\"color: rgb(0, 0, 0);\"><strong>优先更换电</strong></span><span style=\"color: rgb(255, 77, 79);\"><strong>源适配器尝试。若</strong></span><span style=\"color: rgb(235, 144, 58);\"><strong>设备恢复正常建</strong></span><span style=\"color: rgb(255, 77, 79);\"><strong>议联系在</strong></span><span style=\"color: rgb(115, 209, 61);\"><strong>线人工客服</strong></span><span style=\"color: rgb(255, 77, 79);\"><strong>咨询(早9-晚21),</strong></span><span style=\"color: rgb(38, 38, 38);\"><strong>若仍无法解决</strong></span><span style=\"color: rgb(255, 77, 79);\"><strong>建议您按以下方式排查】</strong></span></h3><h2>您好,若是摄像机联网成功后,突然离线,建议您按以下方式排查:一、离线后,请确认最近摄像机环境是否有变化,是否更换过路由器,目前仅<span style=\"color: rgb(225, 60, 57);\">AB2L、P8MAX、K6pro、P8 pro</span>型号摄像机支持5GWiFi,其余型号摄像机需连接2.4G网络。请查看摄像机信息里面的WIFI信号强度(尽可能大于80%),检查摄像机所处环境中电源、网络是否正常。</h2><h2>二、重新插拔电源线,如果设备没有问题,18510555567每次通电后,都是先绿灯常亮然后是绿灯闪烁。如果不亮,可能是适配器电源线或者摄像机有问题,这种情况建议您更换适配器或电源线,还不行则需要寄回售后检测维修。</h2><p>三、建议您重新连接(如果有安装内存卡,建议取出内存卡,不排除内存卡损坏导致设备离线的可能),重新连接方式如下:</p><p>1)直接点击右上角加号,选择“连接我的摄像机”2)摄像机设置中,选择“让摄像机连接到其他WIFI”</p><p>四、建议更换网络WiFi或连接手机热点尝试。五、查看固件 <a href=\"https://www.baidu.com\" target=\"_blank\">百度</a> 版本是否最新,若不是建议升级固件,并重新联网尝试。</p><p>若仍无法解决,可点击【自助报修】或通过“360智慧生活服务”公众号报修。&lt;/p&gt;</p>", R.color.sobot_common_green);
SobotToastUtil.showCustomToast(getSobotBaseActivity(), "sdafdsafsadfasdfsdaf");
SobotWidgetApi.setSwitchMarkStatus(SobotMarkConfig.SHOW_PERMISSION_TIPS_POP, true);
setTitle("ddddd");
......
......@@ -19,6 +19,7 @@
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
......
......@@ -13,7 +13,7 @@ ext {
PUBLISH_GROUP_ID = "com.sobot.library" //项目包名
PUBLISH_ARTIFACT_ID = 'widget' //项目名
// PUBLISH_ARTIFACT_ID = 'widget_x' //项目名
PUBLISH_VERSION = '1.0.9' //版本号
PUBLISH_VERSION = '1.1.0' //版本号
}
......
......@@ -23,5 +23,12 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/sobot_provider_paths" />
</provider>
<activity
android:name=".ui.webview.SobotWebViewActivity"
android:configChanges="orientation|keyboardHidden|screenSize|touchscreen|navigation|locale|fontScale|uiMode|screenLayout|smallestScreenSize"
android:screenOrientation="behind"
android:theme="@style/sobot_activity_def_theme"
android:windowSoftInputMode="adjustResize" />
</application>
</manifest>
\ No newline at end of file
package com.sobot.widget.ui.rich;
import android.app.Activity;
import android.content.Context;
import android.support.v4.app.ShareCompat;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.view.View;
public class EmailSpan extends ClickableSpan {
private String email;
private int color;
private Context context;
public EmailSpan(Context context, String email, int color) {
this.email = email;
this.context=context;
this.color = context.getResources().getColor(color);
}
@Override
public void onClick(View widget) {
if (SobotOption.hyperlinkListener != null) {
//如果返回true,拦截;false 不拦截
boolean isIntercept = SobotOption.hyperlinkListener.onEmailClick(context,email);
if (isIntercept) {
return;
}
}
try {
ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder
.from((Activity) widget.getContext());
builder.setType("message/rfc822");
builder.addEmailTo(email);
builder.setSubject("");
builder.setChooserTitle("");
builder.startChooser();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(color);
ds.setUnderlineText(false); // 去掉下划线
}
}
\ No newline at end of file
package com.sobot.widget.ui.rich;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.widget.TextView;
import com.sobot.widget.ui.rich.html.SobotCustomTagHandler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HtmlToolUtils {
private static HtmlToolUtils instance;
public static HtmlToolUtils getInstance(Context context) {
if (instance == null) {
instance = new HtmlToolUtils(context.getApplicationContext());
}
return instance;
}
/**
* Regular expression to match all IANA top-level domains for WEB_URL. List
* accurate as of 2011/07/18. List taken from:
* http://data.iana.org/TLD/tlds-alpha-by-domain.txt This pattern is
* auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
*/
public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL = "(?:"
+ "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ "|(?:biz|b[abdefghijmnorstvwyz])"
+ "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+ "|d[ejkmoz]"
+ "|(?:edu|e[cegrstu])"
+ "|f[ijkmor]"
+ "|(?:gov|g[abdefghilmnpqrstuwy])"
+ "|h[kmnrtu]"
+ "|(?:info|int|i[delmnoqrst])"
+ "|(?:jobs|j[emop])"
+ "|k[eghimnprwyz]"
+ "|l[abcikrstuvy]"
+ "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+ "|(?:name|net|n[acefgilopruz])"
+ "|(?:org|om)"
+ "|(?:pro|p[aefghklmnrstwy])"
+ "|qa"
+ "|r[eosuw]"
+ "|s[abcdeghijklmnortuvyz]"
+ "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+ "|u[agksyz]"
+ "|v[aceginu]"
+ "|w[fs]"
+ "|(?:\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)"
+ "|y[et]" + "|z[amw]))";
/**
* Good characters for Internationalized Resource Identifiers (IRI). This
* comprises most common used Unicode characters allowed in IRI as detailed
* in RFC 3987. Specifically, those two byte Unicode characters are not
* included.
*/
public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
/**
* Regular expression pattern to match most part of RFC 3987
* Internationalized URLs, aka IRIs. Commonly used Unicode characters are
* added.
*/
public static Pattern WEB_URL = Pattern
.compile("(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]");
public static final Pattern EMAIL_ADDRESS = Pattern
.compile("[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + "\\@"
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + "(" + "\\."
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + ")+");
/**
* 电话号码正则表达式
* 默认为"d{3}-\d{8}|\d{3}-\d{7}|\d{4}-\d{8}|\d{4}-\d{7}|1+[34578]+\d{9}|\+\d{2}1+[34578]+\d{9}|400\d{7}|400-\d{3}-\d{4}|\d{12}|\d{11}|\d{10}|\d{8}|\d{7}"
* 例如:82563452、01082563234、010-82543213、031182563234、0311-82563234
* 、+8613691080322、4008881234、400-888-1234
*/
public static Pattern PHONE_NUMBER = Pattern.compile("\\d{3}-\\d{8}|\\d{3}-\\d{7}|\\d{4}-\\d{8}|\\d{4}-\\d{7}|1+[34578]+\\d{9}|\\+\\d{2}1+[34578]+\\d{9}|400\\d{7}|400-\\d{3}-\\d{4}|\\d{12}|\\d{11}|\\d{10}|\\d{8}|\\d{7}");
public static Pattern getPhoneNumberPattern() {
return PHONE_NUMBER;
}
public static void setPhoneNumberPattern(Pattern phoneNumberPattern) {
PHONE_NUMBER = phoneNumberPattern;
}
public static Pattern getWebUrl() {
return WEB_URL;
}
public static void setWebUrl(Pattern webUrlPattern) {
WEB_URL = webUrlPattern;
}
//public static final Pattern PHONE_NUMBER = Pattern.compile("^((13[0-9])|(14[5,7,9])|(15[^4])|(18[0-9])|(17[0,1,3,5,6,7,8]))\\d{8}$");
public static final Pattern EMOJI = Pattern
.compile("\\[(([\u4e00-\u9fa5]+)|([a-zA-z]+))\\]");
public static final Pattern EMOJI_NUMBERS = Pattern
.compile("\\[[(0-9)]+\\]");
private String textImagePath;
private Context context;
private HtmlToolUtils(Context context) {
super();
this.context = context.getApplicationContext();
}
// public void loadPic(final TextView textView, String source, final String htmlContent,
// String fileString, final int color) {
// // 启动新线程下载
//
// final File file = new File(fileString);
//
// HttpUtils.getInstance().download(source, file, null, new FileCallBack() {
//
// @Override
// public void onResponse(File result) {
// setRichText(textView, htmlContent, color);
// }
//
// @Override
// public void onError(Exception e, String msg, int responseCode) {
// SobotLogUtils.i(" 文本图片的下载失败", e);
// }
//
// @Override
// public void inProgress(int progress) {
// SobotLogUtils.i(" 文本图片的下载进度" + progress);
// }
// });
// }
/**
* 设置富文本
*
* @param widget
* @param content
* @param color 要显示的颜色
*/
public void setRichText(TextView widget, String content, int color, boolean showBottomLine) {
if (TextUtils.isEmpty(content)) {
return;
}
if (content.contains("<p>")) {
content = content.replaceAll("<p>", "").replaceAll("</p>", "<br/>").replaceAll("\n", "<br/>");
}
while (content.length() > 5 && "<br/>".equals(content.substring(content.length() - 5, content.length()))) {
content = content.substring(0, content.length() - 5);
}
if (!TextUtils.isEmpty(content) && content.length() > 0 && "\n".equals(content.substring(content.length() - 1, content.length()))) {
for (int i = 0; i < content.length(); i++) {
int aa = content.lastIndexOf("\n");
if (aa == (content.length() - 1)) {
content = content.substring(0, content.length() - 1);
} else {
break;
}
}
}
widget.setMovementMethod(LinkMovementClickMethod.getInstance());
Spanned span = formatRichTextWithPic(widget, content.replace("\n", "<br/>"), color);
// 显示链接
parseLinkText(context, widget, span, color, showBottomLine);
}
/**
* 设置富文本
*
* @param widget
* @param content
* @param color 要显示的颜色
*/
public void setRichText(TextView widget, String content, int color) {
setRichText(widget, content, color, false);
}
/**
* 获取处理后的富文本
*
* @param content
*/
public String getRichContent(String content) {
if (TextUtils.isEmpty(content)) {
return "";
}
if (content.contains("<p>")) {
content = content.replaceAll("<p>", "").replaceAll("</p>", "<br/>").replaceAll("\n", "<br/>");
}
while (content.length() > 5 && "<br/>".equals(content.substring(content.length() - 5, content.length()))) {
content = content.substring(0, content.length() - 5);
}
if (!TextUtils.isEmpty(content) && content.length() > 0 && "\n".equals(content.substring(content.length() - 1, content.length()))) {
for (int i = 0; i < content.length(); i++) {
int aa = content.lastIndexOf("\n");
if (aa == (content.length() - 1)) {
content = content.substring(0, content.length() - 1);
} else {
break;
}
}
}
return content;
}
/**
* 获取带图片的富文本如果本地没有就开启下载
*
* @param textView
* @param htmlContent
* @param color
* @return
*/
public Spanned formatRichTextWithPic(final TextView textView, final String htmlContent, final int color) {
return Html.fromHtml(htmlContent.replace("span", "sobotspan"), new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
// if (!TextUtils.isEmpty(source)) {
// textImagePath = SobotSDCardUtils.getSDCardRootPath(context);
// Drawable drawable = null;
// String fileString = textImagePath
// + String.valueOf(source.hashCode());
// if (new File(fileString).exists()) {
// SobotLogUtils.i(" 网络下载 文本中的图片信息 " + fileString + " eixts");
// // 获取本地文件返回Drawable
// drawable = Drawable.createFromPath(fileString);
// if (drawable != null) {
// // 设置图片边界
// SobotLogUtils.i(" 图文并茂中 图片的 大小 width: "
// + drawable.getIntrinsicWidth() + "--height:"
// + drawable.getIntrinsicWidth());
// drawable.setBounds(0, 0, drawable.getIntrinsicWidth() * 4,
// drawable.getIntrinsicHeight() * 4);
// }
// return drawable;
// } else {
// SobotLogUtils.i(fileString + " Do not eixts");
// if (source.startsWith("https://") || source.startsWith("http://")) {
// loadPic(textView, source, htmlContent, fileString, color);
// return drawable;
// } else
// return null;
// }
// }
return null;
}
}, new SobotCustomTagHandler(context, textView.getTextColors()));
}
/**
* 显示超链接
*
* @param context
* @param widget
* @param spanhtml
* @param color 要显示的颜色
*/
public static void parseLinkText(Context context, TextView widget, Spanned spanhtml, int color, boolean showLine) {
CharSequence text = spanhtml;
if (text instanceof Spannable) {
Spannable sp = (Spannable) spanhtml;
// 检查出所有EMAIL地址
Matcher m = EMAIL_ADDRESS.matcher(sp);
while (m.find()) {
int start = m.start();
int e = m.end();
if (sp.getSpans(start, e, URLSpan.class).length == 0) {
sp.setSpan(new EmailSpan(context.getApplicationContext(), m.group(), color), start, e,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// 检查出所有链接
m = getWebUrl().matcher(sp);
while (m.find()) {
int start = m.start();
int e = m.end();
if (sp.getSpans(start, e, URLSpan.class).length == 0) {
sp.setSpan(new MyURLSpan(context.getApplicationContext(), m.group(), color, true), start, e,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// 检查出所有电话
m = getPhoneNumberPattern().matcher(sp);
while (m.find()) {
int start = m.start();
int e = m.end();
if (sp.getSpans(start, e, URLSpan.class).length == 0) {
sp.setSpan(new PhoneSpan(context.getApplicationContext(), m.group(), color), start, e,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
int end = text.length();
URLSpan[] urls = sp.getSpans(0, end, URLSpan.class);
URLSpan[] htmlurls = spanhtml != null ? spanhtml.getSpans(0, end,
URLSpan.class) : new URLSpan[]{};
if (urls.length == 0 && htmlurls.length == 0) {
widget.setText(sp);
return;
}
SpannableStringBuilder style = new SpannableStringBuilder(text);
for (URLSpan url : htmlurls) {
style.removeSpan(url);// 只需要移除之前的URL样式,再重新设置
MyURLSpan myURLSpan = new MyURLSpan(context.getApplicationContext(), url.getURL(), color, showLine);
style.setSpan(myURLSpan, spanhtml.getSpanStart(url),
spanhtml.getSpanEnd(url),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
widget.setText(style);
}
}
public static boolean isHasPatterns(String url) {
if (TextUtils.isEmpty(url)) {
return false;
}
if (getWebUrl().matcher(url.toString()).matches()) {
return true;
} else {
return false;
}
}
public String getHTMLStr(String htmlStr) {
if (TextUtils.isEmpty(htmlStr)) {
return "";
}
//先将换行符保留,然后过滤标签
Pattern p_enter = Pattern.compile("<br/>", Pattern.CASE_INSENSITIVE);
Matcher m_enter = p_enter.matcher(htmlStr);
htmlStr = m_enter.replaceAll("\n");
//过滤html标签
Pattern p_html = Pattern.compile("<[^>]+>", Pattern.CASE_INSENSITIVE);
Matcher m_html = p_html.matcher(htmlStr);
return m_html.replaceAll("");
}
/**
* 设置超链接的点击事件监听
* 根据返回值用户可分开动态设置是否拦截,举例 监听到有订单编号,返回true 拦截;商品
*
* @param hyperlinkListener
*/
public static void setNewHyperlinkListener(HyperlinkListener hyperlinkListener) {
SobotOption.hyperlinkListener = hyperlinkListener;
}
/**
* 替换消息中手机或固话识别的正则表达式
* 默认为"d{3}-\d{8}|\d{3}-\d{7}|\d{4}-\d{8}|\d{4}-\d{7}|1+[34578]+\d{9}|\+\d{2}1+[34578]+\d{9}|400\d{7}|400-\d{3}-\d{4}|\d{11}|\d{10}|\d{8}|\d{7}"
* * 例如:82563452、01082563234、010-82543213、031182563234、0311-82563234
* 、+8613691080322、4008881234、400-888-1234
*
* @param regex 手机或固话的正则表达
*/
public static void replacePhoneNumberPattern(String regex) {
setPhoneNumberPattern(Pattern.compile(regex));
}
/**
* 替换消息中手网址识别的正则表达式
* 默认为"((http[s]{0,1}|ftp)://[a-zA-Z0-9\.\-]+\.([a-zA-Z]{2,4})(:\d+)?(/[a-zA-Z0-9\.\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\.\-]+\.([a-zA-Z]{2,4})(:\d+)?(/[a-zA-Z0-9\.\-~!@#$%^&*+?:_/=<>]*)?)|([a-zA-Z0-9\.\-]+\.([a-zA-Z]{2,4})(:\d+)?(/[a-zA-Z0-9\.\-~!@#$%^&*+?:_/=<>]*)?)"
*
* @param regex 手机或固话的正则表达
*/
public static void replaceWebUrlPattern(String regex) {
setWebUrl(Pattern.compile(regex));
}
}
\ No newline at end of file
package com.sobot.widget.ui.rich;
import android.content.Context;
/**
* 超链接点击的监听事件
*/
public interface HyperlinkListener {
// 链接的点击事件, 根据返回结果判断是否拦截 如果返回true,拦截;false 不拦截
boolean onUrlClick(Context context, String url);
//邮箱的点击拦截事件, 根据返回结果判断是否拦截 如果返回true,拦截;false 不拦截
boolean onEmailClick(Context context, String email);
//电话的点击拦截事件, 根据返回结果判断是否拦截 如果返回true,拦截;false 不拦截
boolean onPhoneClick(Context context, String phone);
}
\ No newline at end of file
package com.sobot.widget.ui.rich;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.widget.TextView;
public class LinkMovementClickMethod extends LinkMovementMethod {
private long lastClickTime;
private static final long CLICK_DELAY = 500l;
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
if(System.currentTimeMillis() - lastClickTime < CLICK_DELAY){
link[0].onClick(widget);
}
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
lastClickTime = System.currentTimeMillis();
}
return true;
} else {
Selection.removeSelection(buffer);
}
}
return super.onTouchEvent(widget, buffer, event);
}
public static LinkMovementClickMethod getInstance(){
if(null == sInstance){
sInstance = new LinkMovementClickMethod();
}
return sInstance;
}
private static LinkMovementClickMethod sInstance;
}
package com.sobot.widget.ui.rich;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextPaint;
import android.text.style.URLSpan;
import android.view.View;
import com.sobot.utils.SobotLogUtils;
import com.sobot.widget.ui.webview.SobotWebViewActivity;
public class MyURLSpan extends URLSpan {
private Context context;
private int color;
private boolean isShowLine;// 下划线
public MyURLSpan(Context context, String url, int color) {
this(context, url, color, false);
}
public MyURLSpan(Context context, String url, int color, boolean isShowLine) {
super(url);
this.context = context;
this.color = context.getResources().getColor(color);
this.isShowLine = isShowLine;
}
@Override
public void onClick(View widget) {
String url = getURL();
// LogUtils.i("url:" + url);
if (url.startsWith("innerUrl:")) {
//不是超链接 而是自己内部的逻辑链接
if (SobotOption.hyperlinkListener != null) {
//如果返回true,拦截;false 不拦截
boolean isIntercept = SobotOption.hyperlinkListener.onUrlClick(context, url);
if (isIntercept) {
return;
}
}
} else {
if (url.endsWith(".doc") || url.endsWith(".docx") || url.endsWith(".xls")
|| url.endsWith(".txt") || url.endsWith(".ppt") || url.endsWith(".pptx")
|| url.endsWith(".xlsx") || url.endsWith(".pdf") || url.endsWith(".rar")
|| url.endsWith(".zip")) {// 内部浏览器不支持,所以打开外部
if (SobotOption.hyperlinkListener != null) {
//如果返回true,拦截;false 不拦截
boolean isIntercept = SobotOption.hyperlinkListener.onUrlClick(context, url);
if (isIntercept) {
return;
}
}
url = fixUrl(url);
// 外部浏览器
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri content = Uri.parse(url);
intent.setData(content);
context.startActivity(intent);
} else if (url.startsWith("tel:")) {
if (SobotOption.hyperlinkListener != null) {
boolean isIntercept = SobotOption.hyperlinkListener.onPhoneClick(context, url);
if (isIntercept) {
return;
}
}
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse(url));// mobile为你要拨打的电话号码,模拟器中为模拟器编号也可
context.startActivity(intent);
} else {
// 内部浏览器
if (SobotOption.hyperlinkListener != null) {
//如果返回true,拦截;false 不拦截
boolean isIntercept = SobotOption.hyperlinkListener.onUrlClick(context, url);
if (isIntercept) {
return;
}
}
url = fixUrl(url);
Intent intent = new Intent(context, SobotWebViewActivity.class);
intent.putExtra("url", url);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
}
private String fixUrl(String url) {
if (!(url.startsWith("http://") || url.startsWith("https://"))) {
url = "https://" + url;
SobotLogUtils.i("url:" + url);
}
return url;
}
@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(color);
ds.setUnderlineText(isShowLine);
}
}
\ No newline at end of file
package com.sobot.widget.ui.rich;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.view.View;
import com.sobot.widget.R;
import com.sobot.widget.ui.toast.SobotToastUtil;
public class PhoneSpan extends ClickableSpan {
private String phone;
private int color;
private Context context;
public PhoneSpan(Context context, String phone, int color) {
this.phone = phone;
this.color = context.getResources().getColor(color);
this.context = context;
}
@Override
public void onClick(View widget) {
if (SobotOption.hyperlinkListener != null) {
boolean isIntercept = SobotOption.hyperlinkListener.onPhoneClick(context,"tel:" + phone);
if (isIntercept) {
return;
}
}
callUp(phone, context);
}
@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(color);
ds.setUnderlineText(false); // 去掉下划线
}
public static void callUp(String phone, Context context) {
try {
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse("tel:" + phone));// mobile为你要拨打的电话号码,模拟器中为模拟器编号也可
context.startActivity(intent);
} catch (Exception e) {
SobotToastUtil.showCustomToast(context, context.getString(R.string.sobot_srl_no_support_call));
e.printStackTrace();
}
}
}
\ No newline at end of file
package com.sobot.widget.ui.rich;
public class SobotOption {
public static HyperlinkListener hyperlinkListener;//超链接的点击监听事件
}
package com.sobot.widget.ui.rich.html;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.text.Editable;
import android.text.Html;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.UnderlineSpan;
import com.sobot.utils.SobotDensityUtil;
import com.sobot.utils.SobotStringUtils;
import org.xml.sax.XMLReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 自定义的一html标签解析
* html 中的span 标签需要替换成 自定义的sobotfont,因为高版本系统html.formhtml()已经识别了,就不会走自定义的
*/
public class SobotCustomTagHandler implements Html.TagHandler {
private final String TAG = "CustomTagHandler";
//标签
public static final String NEW_FONT = "myfont";
public static final String HTML_FONT = "font";
public static final String NEW_SPAN = "sobotspan";
public static final String HTML_SPAN = "span";
private List<SobotHtmlLabelBean> labelBeanList = new ArrayList<>();//顺序添加的Bean
private List<SobotHtmlLabelBean> tempRemoveLabelList = new ArrayList<>();
private ColorStateList mOriginColors;
private Context mContext;
public SobotCustomTagHandler(Context context, ColorStateList originColors) {
mContext = context;
mOriginColors = originColors;
}
@Override
public void handleTag(boolean opening, String tag, Editable output,
XMLReader xmlReader) {
try {
processAttributes(xmlReader);
if (opening) {
startFont(tag, output, xmlReader);
} else {
endFont(tag, output, xmlReader);
attributes.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
final HashMap<String, String> attributes = new HashMap<String, String>();
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) {
}
}
public void startFont(String tag, Editable output, XMLReader xmlReader) {
int startIndex = output.length();
SobotHtmlLabelBean bean = new SobotHtmlLabelBean();
bean.startIndex = startIndex;
bean.tag = tag;
String color = null;
String size = null;
//字体加粗的值CSS font-weight属性:,normal,bold,bolder,lighter,也可以指定的值(100-900,其中400是normal)
//说这么多,这里只支持bold,如果是bold则加粗,否则就不加粗
String fontWeight = null;
if (NEW_FONT.equals(tag)) {
color = attributes.get("color");
size = attributes.get("size");
} else if (NEW_SPAN.equals(tag)) {
String colorstr = attributes.get("color");
if (!TextUtils.isEmpty(colorstr)) {
color = colorstr;
}
String sizestr = attributes.get("size");
if (!TextUtils.isEmpty(sizestr)) {
size = sizestr;
}
String style = attributes.get("style");
if (!TextUtils.isEmpty(style)) {
analysisStyle(bean, style);
}
}
labelBeanList.add(bean);
//Log.d(TAG, "opening:开" + "tag:<" + tag + " startIndex:" + startIndex + " 当前遍历的开的集合长度:" + labelBeanList.size());
}
/**
* 解析style属性
*
* @param style
*/
private void analysisStyle(SobotHtmlLabelBean bean, String style) {
// Log.e(TAG, "style:" + style);
String[] attrArray = style.split(";");
Map<String, String> attrMap = new HashMap<>();
if (null != attrArray) {
for (String attr : attrArray) {
String[] keyValueArray = attr.split(":");
if (null != keyValueArray && keyValueArray.length == 2) {
// 记住要去除前后空格
attrMap.put(keyValueArray[0].trim(), keyValueArray[1].trim());
}
}
}
// Log.i(TAG, "attrMap:" + attrMap.toString());
bean.color = attrMap.get("color");
bean.fontSize = attrMap.get("font-size");
bean.textdecoration = attrMap.get("text-decoration");
bean.textdecorationline = attrMap.get("text-decoration-line");
bean.backgroundColor = attrMap.get("background-color");
bean.background = attrMap.get("background");
bean.fontweight = attrMap.get("font-weight");
bean.fontstyle = attrMap.get("font-style");
}
/**
* 计算影响的范围
*
* @param bean
*/
private void optBeanRange(SobotHtmlLabelBean bean) {
if (bean.ranges == null) {
bean.ranges = new ArrayList<>();
}
if (tempRemoveLabelList.size() == 0) {
SobotHtmlLabelRangeBean range = new SobotHtmlLabelRangeBean();
range.start = bean.startIndex;
range.end = bean.endIndex;
bean.ranges.add(range);
} else {
int size = tempRemoveLabelList.size();
//逆向找到 第一个结束位置<=当前结束位置
//逆向找到最后一个开始位置>=当前开始位置
int endRangePosition = -1;
int startRangePosition = -1;
for (int i = size - 1; i >= 0; i--) {
SobotHtmlLabelBean bean1 = tempRemoveLabelList.get(i);
if (bean1.endIndex <= bean.endIndex) {
//找第一个
if (endRangePosition == -1)
endRangePosition = i;
}
if (bean1.startIndex >= bean.startIndex) {
//找最后一个,符合条件的都覆盖之前的
startRangePosition = i;
}
}
if (startRangePosition != -1 && endRangePosition != -1) {
SobotHtmlLabelBean lastBean = null;
//有包含关系
for (int i = startRangePosition; i <= endRangePosition; i++) {
SobotHtmlLabelBean removeBean = tempRemoveLabelList.get(i);
lastBean = removeBean;
SobotHtmlLabelRangeBean range;
if (i == startRangePosition) {
range = new SobotHtmlLabelRangeBean();
range.start = bean.startIndex;
range.end = removeBean.startIndex;
bean.ranges.add(range);
} else {
range = new SobotHtmlLabelRangeBean();
SobotHtmlLabelBean bean1 = tempRemoveLabelList.get(i - 1);
range.start = bean1.endIndex;
range.end = removeBean.startIndex;
bean.ranges.add(range);
}
}
SobotHtmlLabelRangeBean range = new SobotHtmlLabelRangeBean();
range.start = lastBean.endIndex;
range.end = bean.endIndex;
bean.ranges.add(range);
} else {
//表示将要并列添加,那么影响的范围就是自己的角标范围
SobotHtmlLabelRangeBean range = new SobotHtmlLabelRangeBean();
range.start = bean.startIndex;
range.end = bean.endIndex;
bean.ranges.add(range);
}
}
}
public void endFont(String tag, Editable output, XMLReader xmlReader) {
int stopIndex = output.length();
// Log.d(TAG, "opening:关" + "tag:" + tag + "/> endIndex:" + stopIndex);
int lastLabelByTag = getLastLabelByTag(tag);
if (lastLabelByTag != -1) {
SobotHtmlLabelBean bean = labelBeanList.get(lastLabelByTag);
bean.endIndex = stopIndex;
optBeanRange(bean);
// Log.d(TAG, "完整的TagBean解析完成:" + bean.toString());
for (SobotHtmlLabelRangeBean range : bean.ranges) {
String color = bean.color;
String fontSize = bean.fontSize;
String textdecoration = bean.textdecoration;
String textdecorationline = bean.textdecorationline;
String backgroundColor = bean.backgroundColor;
String background = bean.background;
String fontweight = bean.fontweight;
String fontstyle = bean.fontstyle;
//斜体
if (!TextUtils.isEmpty(fontstyle) && (("italic".equalsIgnoreCase(fontstyle) || "oblique".equalsIgnoreCase(fontstyle)))) {
output.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
//粗体
if (!TextUtils.isEmpty(fontweight) && SobotStringUtils.isNumber(fontweight) && Integer.parseInt(fontweight) >= 700) {
output.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //粗体
}
if (!TextUtils.isEmpty(fontweight) && "bold".equalsIgnoreCase(fontweight)) {
output.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //粗体
}
//设置字体大小
if (!TextUtils.isEmpty(fontSize)) {
fontSize = fontSize.split("px")[0];
}
if (!TextUtils.isEmpty(fontSize)) {
int fontSizePx = 16;
if (null != mContext) {
fontSizePx = SobotDensityUtil.sp2px(mContext, Integer.parseInt(fontSize));
}
output.setSpan(new AbsoluteSizeSpan(fontSizePx), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
//不支持上划线overline,闪烁blink
if (!TextUtils.isEmpty(textdecoration) && !textdecoration.equalsIgnoreCase("none") && !textdecoration.equalsIgnoreCase("overline") && !textdecoration.equalsIgnoreCase("blink")) {
if (textdecoration.equalsIgnoreCase("line-through")) {
//中划线
output.setSpan(new StrikethroughSpan(), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
output.setSpan(new UnderlineSpan(), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
//不支持上划线overline,闪烁blink
if (!TextUtils.isEmpty(textdecorationline) && !textdecorationline.equalsIgnoreCase("none") && !textdecorationline.equalsIgnoreCase("overline") && !textdecorationline.equalsIgnoreCase("blink")) {
if (textdecorationline.equalsIgnoreCase("line-through")) {
//中划线
output.setSpan(new StrikethroughSpan(), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
output.setSpan(new UnderlineSpan(), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
//设置字体前景色
if (!TextUtils.isEmpty(color)) {
if (color.startsWith("@")) {
Resources res = Resources.getSystem();
String name = color.substring(1);
int colorRes = res.getIdentifier(name, "color", "android");
if (colorRes != 0) {
output.setSpan(new ForegroundColorSpan(colorRes), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
try {
output.setSpan(new ForegroundColorSpan(parseHtmlColor(color)), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
e.printStackTrace();
reductionFontColor(range.start, stopIndex, output);
}
}
}
//设置字体背景色
if (!TextUtils.isEmpty(backgroundColor)) {
output.setSpan(new BackgroundColorSpan(parseHtmlColor(backgroundColor)), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
//设置字体背景色
if (!TextUtils.isEmpty(background)) {
output.setSpan(new BackgroundColorSpan(parseHtmlColor(background)), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
//从顺序添加的集合中删除已经遍历完结束标签
labelBeanList.remove(lastLabelByTag);
optRemoveByAddBean(bean);
}
}
/**
* 获取最后一个与当前tag匹配的Bean的位置
* 从后往前找
*
* @param tag
* @return
*/
private int getLastLabelByTag(String tag) {
for (int size = labelBeanList.size(), i = size - 1; i >= 0; i--) {
if (!TextUtils.isEmpty(tag) &&
!TextUtils.isEmpty(labelBeanList.get(i).tag) &&
labelBeanList.get(i).tag.equals(tag)) {
return i;
}
}
return -1;
}
/**
* 操作删除的Bean,将其添加到删除的队列中
*
* @param removeBean
*/
private void optRemoveByAddBean(SobotHtmlLabelBean removeBean) {
int isAdd = 0;
for (int size = tempRemoveLabelList.size(), i = size - 1; i >= 0; i--) {
SobotHtmlLabelBean bean = tempRemoveLabelList.get(i);
if (removeBean.startIndex <= bean.startIndex && removeBean.endIndex >= bean.endIndex) {
if (isAdd == 0) {
tempRemoveLabelList.set(i, removeBean);
isAdd = 1;
} else {
//表示已经把isAdd = 1;当前删除的bean,添加到了删除队列中,如果再次找到了可以removeBean可以替代的bean,则删除
tempRemoveLabelList.remove(i);
}
}
}
if (isAdd == 0) {
tempRemoveLabelList.add(removeBean);
}
// Log.d(TAG, "已经删除的完整开关结点的集合长度:" + tempRemoveLabelList.size());
}
/**
* 将dp单位的值转换为px为单位的值
*
* @param context 上下文对象
* @param dipValue dp为单位的值
* @return 返回转换后的px为单位的值
*/
public static int dip2px(Context context, float dipValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5F);
}
/**
* 将px单位的值转换为dp为单位的值
*
* @param context 上下文对象
* @param pxValue px为单位的值
* @return 返回转换后的dp为单位的值
*/
public static int px2dip(Context context, float pxValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5F);
}
/**
* 将sp值转换为px值,保证文字大小不变
*
* @param context
* @param spValue (DisplayMetrics类中属性scaledDensity)
* @return
*/
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
//解析颜色,四类:red等特定指、#000000、rgb(255,0,0)、rgba(255,255,0,0)
public static int parseHtmlColor(String colorString) {
if (colorString.charAt(0) == '#') {
if (colorString.length() == 4) {
StringBuilder sb = new StringBuilder("#");
for (int i = 1; i < colorString.length(); i++) {
char c = colorString.charAt(i);
sb.append(c).append(c);
}
colorString = sb.toString();
}
long color = Long.parseLong(colorString.substring(1), 16);
if (colorString.length() == 7) {
// Set the alpha value
color |= 0x00000000ff000000;
} else if (colorString.length() != 9) {
return 0x000000;
}
return (int) color;
} else if ((colorString.startsWith("rgb(") || colorString.startsWith("rgba(")) && colorString.endsWith(")")) {
colorString = colorString.substring(colorString.indexOf("(") + 1, colorString.indexOf(")"));
colorString = colorString.replaceAll(" ", "");
String[] colorArray = colorString.split(",");
if (colorArray.length == 3) {
return Color.argb(255, Integer.parseInt(colorArray[0]), Integer.parseInt(colorArray[1]), Integer.parseInt(colorArray[2]));
} else if (colorArray.length == 4) {
return Color.argb(Integer.parseInt(colorArray[3]), Integer.parseInt(colorArray[0]), Integer.parseInt(colorArray[1]), Integer.parseInt(colorArray[2]));
}
} else if ("red".equalsIgnoreCase(colorString.trim())) {
return Color.RED;
} else if ("blue".equalsIgnoreCase(colorString.trim())) {
return Color.BLUE;
} else if ("black".equalsIgnoreCase(colorString.trim())) {
return Color.BLACK;
} else if ("gray".equalsIgnoreCase(colorString.trim())) {
return Color.GRAY;
} else if ("green".equalsIgnoreCase(colorString.trim())) {
return Color.GREEN;
} else if ("yellow".equalsIgnoreCase(colorString.trim())) {
return Color.YELLOW;
} else if ("white".equalsIgnoreCase(colorString.trim())) {
return Color.WHITE;
}
return 0x000000;
}
/**
* 还原为原来的颜色
*
* @param startIndex
* @param stopIndex
* @param editable
*/
private void reductionFontColor(int startIndex, int stopIndex, Editable editable) {
if (null != mOriginColors) {
editable.setSpan(new TextAppearanceSpan(null, 0, 0, mOriginColors, null),
startIndex, stopIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
editable.setSpan(new ForegroundColorSpan(0xff2b2b2b), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
\ No newline at end of file
package com.sobot.widget.ui.rich.html;
import java.util.List;
public class SobotHtmlLabelBean {
public String tag;//当前Tag
public int startIndex;//tag开始角标
public int endIndex;//tag结束的角标
public List<SobotHtmlLabelRangeBean> ranges;
public String color;
public String fontSize;
public String textdecoration;
public String textdecorationline;
public String backgroundColor;
public String background;
public String fontweight;
public String fontstyle;
}
package com.sobot.widget.ui.rich.html;
public class SobotHtmlLabelRangeBean {
public int start;//tag开始角标
public int end;//tag结束的角标
}
package com.sobot.widget.ui.webview;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
/**
* 自定义webivew使用改成super(context.getApplicationContext(),避免(vivo(Android5.1),使用原生WebView空白)
*/
public class CustomWebview extends WebView {
public CustomWebview(Context context) {
super(context.getApplicationContext());
}
public CustomWebview(Context context, AttributeSet attrs) {
super(context.getApplicationContext(), attrs);
}
public CustomWebview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context.getApplicationContext(), attrs, defStyleAttr);
}
}
package com.sobot.widget.ui.webview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.DownloadListener;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.sobot.utils.SobotLogUtils;
import com.sobot.utils.SobotNetUtils;
import com.sobot.widget.R;
import com.sobot.widget.ui.base.SobotBaseActivity;
import com.sobot.widget.ui.toast.SobotToastUtil;
@SuppressLint("SetJavaScriptEnabled")
public class SobotWebViewActivity extends SobotBaseActivity implements View.OnClickListener {
private WebView mWebView;
private ProgressBar mProgressBar;
private RelativeLayout sobot_rl_net_error;
private Button sobot_btn_reconnect;
private TextView sobot_txt_loading;
private TextView sobot_textReConnect;
private String mUrl = "";
private LinearLayout sobot_webview_toolsbar;
private ImageView sobot_webview_goback;
private ImageView sobot_webview_forward;
private ImageView sobot_webview_reload;
private ImageView sobot_webview_copy;
//根据冲入的url判断是否url true:是;false:不是
private boolean isUrlOrText = true;
@Override
protected int getContentViewResId() {
return R.layout.sobot_activity_webview;
}
@Override
protected void initBundleData(Bundle savedInstanceState) {
if (savedInstanceState == null) {
if (getIntent() != null && !TextUtils.isEmpty(getIntent().getStringExtra("url"))) {
mUrl = getIntent().getStringExtra("url");
isUrlOrText = isURL(mUrl);
}
} else {
mUrl = savedInstanceState.getString("url");
isUrlOrText = isURL(mUrl);
}
}
public static boolean isURL(String str) {
//转换为小写
str = str.toLowerCase();
if (str.startsWith("http") || str.startsWith("https") || str.startsWith("ftp") || str.startsWith("file")) {
return true;
} else {
return false;
}
}
@Override
protected void initView() {
setTitle("");
showLeftMenu(getResDrawableId("sobot_icon_back"), "", true, new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
mWebView = (WebView) findViewById(R.id.sobot_mWebView);
mProgressBar = (ProgressBar) findViewById(R.id.sobot_loadProgress);
sobot_rl_net_error = (RelativeLayout) findViewById(R.id.sobot_rl_net_error);
sobot_webview_toolsbar = (LinearLayout) findViewById(R.id.sobot_webview_toolsbar);
sobot_btn_reconnect = (Button) findViewById(R.id.sobot_btn_reconnect);
sobot_btn_reconnect.setText(R.string.sobot_srl_reunicon);
sobot_btn_reconnect.setOnClickListener(this);
sobot_textReConnect = (TextView) findViewById(R.id.sobot_textReConnect);
sobot_textReConnect.setText(R.string.sobot_srl_try_again);
sobot_txt_loading = (TextView) findViewById(R.id.sobot_txt_loading);
sobot_webview_goback = (ImageView) findViewById(R.id.sobot_webview_goback);
sobot_webview_forward = (ImageView) findViewById(R.id.sobot_webview_forward);
sobot_webview_reload = (ImageView) findViewById(R.id.sobot_webview_reload);
sobot_webview_copy = (ImageView) findViewById(R.id.sobot_webview_copy);
sobot_webview_goback.setOnClickListener(this);
sobot_webview_forward.setOnClickListener(this);
sobot_webview_reload.setOnClickListener(this);
sobot_webview_copy.setOnClickListener(this);
sobot_webview_goback.setEnabled(false);
sobot_webview_forward.setEnabled(false);
displayInNotch(mWebView);
resetViewDisplay();
initWebView();
if (isUrlOrText) {
//加载url
mWebView.loadUrl(mUrl);
sobot_webview_copy.setVisibility(View.VISIBLE);
} else {
//修改图片高度为自适应宽度
mUrl = "<!DOCTYPE html>\n" +
"<html>\n" +
" <head>\n" +
" <meta charset=\"utf-8\">\n" +
" <title></title>\n" +
" <style>\n" +
" img{\n" +
" width: auto;\n" +
" height:auto;\n" +
" max-height: 100%;\n" +
" max-width: 100%;\n" +
" }\n" +
" </style>\n" +
" </head>\n" +
" <body>" + mUrl + " </body>\n" +
"</html>";
//显示文本内容
mWebView.loadDataWithBaseURL("about:blank", mUrl.replace("</p>", "<br/>").replace("<P>", "").replace("</P>", "<br/>"), "text/html", "utf-8", null);
}
SobotLogUtils.i("webViewActivity---" + mUrl);
}
@Override
protected void initData() {
}
@Override
protected void onLeftMenuClick(View view) {
finish();
}
@Override
public void onClick(View view) {
if (view == sobot_btn_reconnect) {
if (!TextUtils.isEmpty(mUrl)) {
resetViewDisplay();
}
} else if (view == sobot_webview_forward) {
mWebView.goForward();
} else if (view == sobot_webview_goback) {
mWebView.goBack();
} else if (view == sobot_webview_reload) {
mWebView.reload();
} else if (view == sobot_webview_copy) {
copyUrl(mUrl);
}
}
private void copyUrl(String url) {
if (TextUtils.isEmpty(url)) {
return;
}
if (Build.VERSION.SDK_INT >= 11) {
SobotLogUtils.i("API是大于11");
android.content.ClipboardManager cmb = (android.content.ClipboardManager) getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);
cmb.setText(url);
cmb.getText();
} else {
SobotLogUtils.i("API是小于11");
android.text.ClipboardManager cmb = (android.text.ClipboardManager) getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);
cmb.setText(url);
cmb.getText();
}
SobotToastUtil.showToast(getApplicationContext(), getResources().getString(R.string.sobot_srl_ctrl_v_success));
}
/**
* 根据有无网络显示不同的View
*/
private void resetViewDisplay() {
if (SobotNetUtils.isConnected(getApplicationContext())) {
mWebView.setVisibility(View.VISIBLE);
sobot_webview_toolsbar.setVisibility(View.VISIBLE);
sobot_rl_net_error.setVisibility(View.GONE);
} else {
mWebView.setVisibility(View.GONE);
sobot_webview_toolsbar.setVisibility(View.GONE);
sobot_rl_net_error.setVisibility(View.VISIBLE);
}
}
@SuppressLint("NewApi")
private void initWebView() {
if (Build.VERSION.SDK_INT >= 11) {
try {
mWebView.removeJavascriptInterface("searchBoxJavaBridge_");
} catch (Exception e) {
//ignor
}
}
mWebView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
//检测到下载文件就打开系统浏览器
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri content = Uri.parse(url);
intent.setData(content);
startActivity(intent);
}
});
mWebView.removeJavascriptInterface("searchBoxJavaBridge_");
mWebView.getSettings().setDefaultFontSize(16);
mWebView.getSettings().setTextZoom(100);
mWebView.getSettings().setAllowFileAccess(false);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
// 设置可以使用localStorage
mWebView.getSettings().setDomStorageEnabled(true);
mWebView.getSettings().setLoadsImagesAutomatically(true);
mWebView.getSettings().setBlockNetworkImage(false);
mWebView.getSettings().setSavePassword(false);
// mWebView.getSettings().setUserAgentString(mWebView.getSettings().getUserAgentString() + " sobot");
//关于webview的http和https的混合请求的,从Android5.0开始,WebView默认不支持同时加载Https和Http混合模式。
// 在API>=21的版本上面默认是关闭的,在21以下就是默认开启的,直接导致了在高版本上面http请求不能正确跳转。
if (Build.VERSION.SDK_INT >= 21) {
mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
}
//Android 4.4 以下的系统中存在一共三个有远程代码执行漏洞的隐藏接口
mWebView.removeJavascriptInterface("searchBoxJavaBridge_");
mWebView.removeJavascriptInterface("accessibility");
mWebView.removeJavascriptInterface("accessibilityTraversal");
// 应用可以有数据库
mWebView.getSettings().setDatabaseEnabled(true);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//注释的地方是打开其它应用,比如qq
/*if (url.startsWith("http") || url.startsWith("https")) {
return false;
} else {
Intent in = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(in);
return true;
}*/
return false;
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
sobot_webview_goback.setEnabled(mWebView.canGoBack());
sobot_webview_forward.setEnabled(mWebView.canGoForward());
if (isUrlOrText && !mUrl.replace("http://", "").replace("https://", "").equals(view.getTitle())) {
setTitle(view.getTitle());
}
}
});
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
SobotLogUtils.i("网页--title---:" + title);
if (isUrlOrText && !mUrl.replace("http://", "").replace("https://", "").equals(title)) {
setTitle(title);
}
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress > 0 && newProgress < 100) {
mProgressBar.setVisibility(View.VISIBLE);
mProgressBar.setProgress(newProgress);
} else if (newProgress == 100) {
mProgressBar.setVisibility(View.GONE);
}
}
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
uploadMessageAboveL = filePathCallback;
chooseAlbumPic();
return true;
}
});
}
@Override
protected void onResume() {
super.onResume();
if (mWebView != null) {
mWebView.onResume();
}
}
@Override
protected void onPause() {
if (mWebView != null) {
mWebView.onPause();
}
super.onPause();
}
@Override
protected void onDestroy() {
if (mWebView != null) {
mWebView.removeAllViews();
final ViewGroup viewGroup = (ViewGroup) mWebView.getParent();
if (viewGroup != null) {
viewGroup.removeView(mWebView);
}
mWebView.destroy();
}
super.onDestroy();
}
@Override
public void onBackPressed() {
if (mWebView != null && mWebView.canGoBack()) {
mWebView.goBack();
} else {
super.onBackPressed();
finish();
}
}
protected void onSaveInstanceState(Bundle outState) {
//被摧毁前缓存一些数据
outState.putString("url", mUrl);
super.onSaveInstanceState(outState);
}
private static final int REQUEST_CODE_ALBUM = 0x0111;
private ValueCallback<Uri> uploadMessage;
private ValueCallback<Uri[]> uploadMessageAboveL;
public Context getContext() {
return SobotWebViewActivity.this;
}
/**
* 选择相册照片
*/
private void chooseAlbumPic() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
// i.setType("image/*");
i.setType("video/*;image/*");
startActivityForResult(Intent.createChooser(i, "Image Chooser"), REQUEST_CODE_ALBUM);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_ALBUM) {
if (uploadMessage == null && uploadMessageAboveL == null) {
return;
}
if (resultCode != RESULT_OK) {
//一定要返回null,否则<input file> 就是没有反应
if (uploadMessage != null) {
uploadMessage.onReceiveValue(null);
uploadMessage = null;
}
if (uploadMessageAboveL != null) {
uploadMessageAboveL.onReceiveValue(null);
uploadMessageAboveL = null;
}
}
if (resultCode == RESULT_OK) {
Uri imageUri = null;
switch (requestCode) {
case REQUEST_CODE_ALBUM:
if (data != null) {
imageUri = data.getData();
}
break;
}
//上传文件
if (uploadMessage != null) {
uploadMessage.onReceiveValue(imageUri);
uploadMessage = null;
}
if (uploadMessageAboveL != null) {
uploadMessageAboveL.onReceiveValue(new Uri[]{imageUri});
uploadMessageAboveL = null;
}
}
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:angle="90"/>
<!-- stroke 描边 -->
<stroke
android:width="0.5dip"
android:color="#C3C3C3" />
<!-- corners 圆角 android:radius为角的弧度,值越大角越圆。 -->
<corners android:radius="5dip" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/sobot_webview_toolsbar_back_disable" android:state_enabled="false" />
<item android:drawable="@drawable/sobot_webview_toolsbar_back_normal" />
<item android:drawable="@drawable/sobot_webview_toolsbar_back_pressed" android:state_pressed="true" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/sobot_webview_toolsbar_copy_normal" />
<item android:drawable="@drawable/sobot_webview_toolsbar_copy_pressed" android:state_pressed="true" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/sobot_webview_toolsbar_forward_disable" android:state_enabled="false" />
<item android:drawable="@drawable/sobot_webview_toolsbar_forward_normal" />
<item android:drawable="@drawable/sobot_webview_toolsbar_forward_pressed" android:state_pressed="true" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/sobot_webview_toolsbar_reload_normal" />
<item android:drawable="@drawable/sobot_webview_toolsbar_reload_pressed" android:state_pressed="true" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/sobot_common_gray6"
android:orientation="vertical">
<include layout="@layout/sobot_common_layout_titlebar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<ProgressBar
android:id="@+id/sobot_loadProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="5dip"
android:max="100"
android:paddingLeft="1dip"
android:paddingRight="1dip"
android:progress="0"
android:paddingEnd="1dip"
android:paddingStart="1dip" />
<com.sobot.widget.ui.webview.CustomWebview
android:id="@+id/sobot_mWebView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
<LinearLayout
android:id="@+id/sobot_webview_toolsbar"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="vertical">
<View
android:layout_width="fill_parent"
android:layout_height="0.1dp"
android:background="#b5b5b5" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="43dp"
android:background="@color/sobot_white"
android:orientation="horizontal"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingStart="15dp"
android:paddingEnd="15dp">
<ImageView
android:id="@+id/sobot_webview_goback"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:src="@drawable/sobot_webview_btn_back_selector" />
<ImageView
android:id="@+id/sobot_webview_forward"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:src="@drawable/sobot_webview_btn_forward_selector" />
<ImageView
android:id="@+id/sobot_webview_reload"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:src="@drawable/sobot_webview_btn_reload_selector" />
<ImageView
android:id="@+id/sobot_webview_copy"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:src="@drawable/sobot_webview_btn_copy_selector"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
<include
android:id="@+id/sobot_rl_net_error"
layout="@layout/sobot_layout_net_error" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/sobot_common_gray6">
<!-- 显示加载文字的动画 -->
<TextView
android:id="@+id/sobot_txt_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:textSize="16sp"
android:visibility="gone" />
<TextView
android:id="@+id/sobot_textReConnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/sobot_txt_loading"
android:layout_centerInParent="true"
android:layout_marginTop="20dp"
android:layout_marginStart="15dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginEnd="15dp"
android:textColor="@color/sobot_common_gray1"
android:textSize="16sp" />
<ImageView
android:id="@+id/sobot_icon_nonet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/sobot_textReConnect"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:contentDescription="@null"
android:src="@drawable/sobot_icon_nonet" />
<Button
android:id="@+id/sobot_btn_reconnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/sobot_textReConnect"
android:layout_centerHorizontal="true"
android:layout_marginTop="15dp"
android:background="@drawable/sobot_button_style"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textColor="@color/sobot_common_gray1"
android:textSize="12sp"
android:paddingStart="10dp"
android:paddingEnd="10dp" />
</RelativeLayout>
\ No newline at end of file
......@@ -180,4 +180,9 @@
<string name="app_request_code_900006">Company info not found!</string>
<string name="app_request_code_900007">Blank token info. Get a new one!</string>
<string name="sobot_srl_reunicon">Tap to load again</string>
<string name="sobot_srl_ctrl_v_success">Copied successfully!</string>
<string name="sobot_srl_try_again">Network error. Please check the network and try again</string>
<string name="sobot_srl_no_support_call">The device does not support making calls</string>
</resources>
......@@ -182,4 +182,9 @@
<string name="app_request_code_900006">没有找到公司信息!</string>
<string name="app_request_code_900007">token信息为空,请重新获取! </string>
<!-- 状态码-->
<string name="sobot_srl_reunicon">点击重新连接</string>
<string name="sobot_srl_ctrl_v_success">复制成功!</string>
<string name="sobot_srl_try_again">网络错误,请检查网络后重试</string>
<string name="sobot_srl_no_support_call">该设备不支持拨打电话</string>
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment