云vps怎么搭建网站,电子商务网站建设的核心硬件,wordpress不能访问首页,规范网站建设情况的报告*这篇文章的主要目的是想要大家学习如何了解实现#xff0c;修改实现#xff0c;以达到举一反三#xff0c;自行解决问题的目的。
某天遇到这么一个需求#xff1a;在TextView中的文本链接要支持跳转#xff0c;嗯#xff0c;这个好办#xff0c;TextView本身是支持的修改实现以达到举一反三自行解决问题的目的。
某天遇到这么一个需求在TextView中的文本链接要支持跳转嗯这个好办TextView本身是支持的我们只用添加一项属性就可以搞定 android:autoLinkweb
在添加后发现确实是有效果了。但是如果我们不想使用系统默认的浏览器而是想要这个地址跳入某个页面或者自己应用内的浏览器该怎么办呢
好接下来就是我们要实现的步骤。
俗话说知己知彼百战不殆。所以将我们的步骤分为两步
1.了解autoLink的实现。2.修改autoLink的实现。3.运行测试
了解autoLink的实现
既然我们可以知道设置autoLink属性就可以实现链接的自动识别与跳转那么我们就从autoLink开始分析。
打开TextView.java寻找autoLink的相关配置读取参数 case com.android.internal.R.styleable.TextView_autoLink:mAutoLinkMask a.getInt(attr, 0);break;
我们发现与autoLink有关的是一个名为mAutoLinkMask的成员属性那也就是说所有与autoLink有关的配置都有这个成员属性脱不了干系。
那我们就可以在整个TextView的实现中寻找mAutoLinkMask的身影
public void append(CharSequence text, int start, int end) {if (!(mText instanceof Editable)) {setText(mText, BufferType.EDITABLE);}((Editable) mText).append(text, start, end);if (mAutoLinkMask ! 0) {boolean linksWereAdded Linkify.addLinks((Spannable) mText, mAutoLinkMask);if (linksWereAdded mLinksClickable !textCanBeSelected()) {setMovementMethod(LinkMovementMethod.getInstance());}}}...private void setText(CharSequence text, BufferType type,boolean notifyBefore, int oldlen) {...if (mAutoLinkMask ! 0) {Spannable s2;if (type BufferType.EDITABLE || text instanceof Spannable) {s2 (Spannable) text;} else {s2 mSpannableFactory.newSpannable(text);}if (Linkify.addLinks(s2, mAutoLinkMask)) {text s2;type (type BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;/** We must go ahead and set the text before changing the* movement method, because setMovementMethod() may call* setText() again to try to upgrade the buffer type.*/mText text;// Do not change the movement method for text that support text selection as it// would prevent an arbitrary cursor displacement.if (mLinksClickable !textCanBeSelected()) {setMovementMethod(LinkMovementMethod.getInstance());}}}...}...Overridepublic boolean onTouchEvent(MotionEvent event) {...if (touchIsFinished mLinksClickable mAutoLinkMask ! 0 textIsSelectable) {// The LinkMovementMethod which should handle taps on links has not been installed// on non editable text that support text selection.// We reproduce its behavior here to open links for these.ClickableSpan[] links ((Spannable) mText).getSpans(getSelectionStart(),getSelectionEnd(), ClickableSpan.class);if (links.length 0) {links[0].onClick(this);handled true;}}...return superResult;}
mAutoLinkMask出现的地方并不多除了基本的get、set方法之外它出现在了3个地方分别是append(CharSequence text, int start, int end)、setText(CharSequence text, BufferType type)和onTouchEvent(MotionEvent event)。
其中append方法与setText方法都是用于添加文本的方法也就说所有填入TextView的文本都会被加上autoLink的功能。这两个方法内部都调用了Linkify.addLinks(Spannable text, int mask)方法。
Linkify.addLinks(Spannable text, int mask)的注释是这么写的 Scans the text of the provided Spannable and turns all occurrences of the link types indicated in the mask into clickable links. If the mask is nonzero, it also removes any existing URLSpans attached to the Spannable, to avoid problems if you call it repeatedly on the same text. 这段话说了什么呢翻译一下 首先对给定的文本进行扫描然后将所有的链接文本转换为可点击的链接。如果第二个参数不为空那么它还是会将已有的URLSpan移除来避免一些问题。 然后我们进入这个方法探一探究竟看看它是怎么实现的 public static final boolean addLinks(NonNull Spannable text, LinkifyMask int mask) {if (mask 0) {return false;}URLSpan[] old text.getSpans(0, text.length(), URLSpan.class);for (int i old.length - 1; i 0; i--) {text.removeSpan(old[i]);}ArrayListLinkSpec links new ArrayListLinkSpec();if ((mask WEB_URLS) ! 0) {gatherLinks(links, text, Patterns.AUTOLINK_WEB_URL,new String[] { http://, https://, rtsp:// },sUrlMatchFilter, null);}if ((mask EMAIL_ADDRESSES) ! 0) {gatherLinks(links, text, Patterns.AUTOLINK_EMAIL_ADDRESS,new String[] { mailto: },null, null);}if ((mask PHONE_NUMBERS) ! 0) {gatherTelLinks(links, text);}if ((mask MAP_ADDRESSES) ! 0) {gatherMapLinks(links, text);}pruneOverlaps(links);if (links.size() 0) {return false;}for (LinkSpec link: links) {applyLink(link.url, link.start, link.end, text);}return true;}
这个方法做了以下工作
1.对旧的Span进行移除我们看到这里获取Span返回的类型是URLSpan请留意一下我们待会会看到它很多次。2.对给定的WEB_URLS、EMAIL_ADDRESSES、PHONE_NUMBERS、MAP_ADDRESSES类型进行链接查找。3.生成新的Span。
这是最后生成新的Span的方法它这里用了URLSpan private static final void applyLink(String url, int start, int end, Spannable text) {URLSpan span new URLSpan(url);text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
这里的URLSpan是个什么鬼和我们想了解的有什么关系
其实我们才刚刚了解到生成我们应该还没忘记TextView的onTouchEvent方法还没讲到onTouchEvent方法内部也是有mAutoLinkMask标志的我们回去看。
在onTouchEvent方法内有很重要的一段 if (touchIsFinished mLinksClickable mAutoLinkMask ! 0 textIsSelectable) {ClickableSpan[] links ((Spannable) mText).getSpans(getSelectionStart(),getSelectionEnd(), ClickableSpan.class);if (links.length 0) {links[0].onClick(this);handled true;}}我们这个时候应该明白那些链接也走的是TextView的onTouchEvent方法这当然是理所当然的。不过在这里链接的点击是通过ClickableSpan的onClick方法实现的那这里的ClickableSpan究竟是谁呢
我们通过查阅文档发现ClickableSpan的唯一子类就是我们刚刚见过的URLSpan。但这仅仅是我们的猜测我们还需要通过实际的运行来查看是否就是URLSpan在作用链接的点击事件。
我们写一个小小的实现 TextViewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:autoLinkwebandroid:textHello! https://developer.android.google.cn/reference/android/text/style/ClickableSpan.html /
然后运行看看TextView的mText的属性内部组成
我们可以发现在mText的mSpans属性中的有一个URLSpan的存在。那到此为止点击的处理就确信是URLSpan的作用无疑了。
那我们可以看看URLSpan自己是怎么实现的
public class URLSpan extends ClickableSpan implements ParcelableSpan {private final String mURL;public URLSpan(String url) {mURL url;}public URLSpan(Parcel src) {mURL src.readString();}public int getSpanTypeId() {return TextUtils.URL_SPAN;}public int describeContents() {return 0;}public void writeToParcel(Parcel dest, int flags) {dest.writeString(mURL);}public String getURL() {return mURL;}Overridepublic void onClick(View widget) {Uri uri Uri.parse(getURL());Context context widget.getContext();Intent intent new Intent(Intent.ACTION_VIEW, uri);intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());context.startActivity(intent);}
}
它的实现很简洁我们看到了我们想找的onClick方法就是这处理了我们的链接点击事件了。那么我们该如何更改呢
修改autoLink的实现
如果有对热修复了解的话那么肯定对修改dexElements不会陌生。在这里我们也是相同的思路通过反射将mSpans属性中URLSpan对象改为我们自己创建的自定义对象。
那么接下来就是我们的实现过程
为了方便使用我们扩展一下TextView新建一个自定义View并继承TextView我们将这个自定义View命名为:AutoLinkTextView。
我们在它的构造方法内分别设置WEB属性否则不会自动识别网址链接。
代码实现如下 public AutoLinkTextView(Context context) {super(context);setAutoLinkMask(Linkify.WEB_URLS);}public AutoLinkTextView(Context context, AttributeSet attrs) {super(context, attrs);setAutoLinkMask(Linkify.WEB_URLS);}public AutoLinkTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setAutoLinkMask(Linkify.WEB_URLS);}
好做好了铺垫之后我们在上面了解到mAutoLinkMask这个标志属性出现在了append(CharSequence text, int start, int end)及setText(CharSequence text, BufferType type)这两个方法内。所以我们需要对这两个方法进行扩展。
在AutoLinkTextView的类中复写这两个方法 Overridepublic void setText(CharSequence text, BufferType type) {super.setText(text, type);replace();}Overridepublic void append(CharSequence text, int start, int end) {super.append(text, start, end);replace();}
这两个方法除了调用基类的方法之外还调用了一个名为replace的方法。这个方法就是接下来我们对原有的URLSpan进行替换的地方。
replace()方法的实现如下 private void replace() {CharSequence text getText();if (text instanceof SpannableString) {SpannableString spannableString (SpannableString) text;Class? extends SpannableString aClass spannableString.getClass();try {//mSpans属性属于SpannableString的父类成员Class? aClassSuperclass aClass.getSuperclass();Field mSpans aClassSuperclass.getDeclaredField(mSpans);mSpans.setAccessible(true);Object o mSpans.get(spannableString);if (o.getClass().isArray()) {Object objs[] (Object[]) o;if (objs.length 1) {//这里的第0个位置不稳妥实际环境可能会有多个链接地址Object obj objs[0];if (obj.getClass().equals(URLSpan.class)) {//获取URLSpan的mURL值用于新的URLSpan的生成Field oldUrlField obj.getClass().getDeclaredField(mURL);oldUrlField.setAccessible(true);Object o1 oldUrlField.get(obj);//生成新的自定义的URLSpan这里我们将这个自定义URLSpan命名为ExtendUrlSpanConstructor? constructor ExtendUrlSpan.class.getConstructor(String.class);constructor.setAccessible(true);Object newUrlField constructor.newInstance(o1.toString());//替换objs[0] newUrlField;}}}} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}
}
在上面的方法中提到了一个ExtendUrlSpan类这是我们自己写的扩展类用于定义自己的实现。代码如下
public class ExtendUrlSpan extends URLSpan {public ExtendUrlSpan(String url) {super(url);}public ExtendUrlSpan(Parcel src) {super(src);}Overridepublic void onClick(View widget) {//这个方法会在点击链接的时候调用可以实现自定义事件Toast.makeText(widget.getContext(), getURL(), Toast.LENGTH_SHORT).show(); }
}
为了示例说明这里在点击时显示了一个吐司吐司的内容是点击的链接地址。
到此为止我们更改结束。接下来看运行效果。
运行测试
我们将原有的TextView更换为刚刚实现的AutoLinkTextView com.sahadev.support.AutoLinkTextViewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:autoLinkwebandroid:textHello! https://developer.android.google.cn/reference/android/text/style/ClickableSpan.html /
启动运行 这说明我们的更改是生效的。 *项目的相关地址为https://code.csdn.net/u011064099/android-textview-autolink-click-reflect/tree/master