├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── widget │ │ └── textviewforurl │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── widget │ │ │ └── textviewforurl │ │ │ ├── HttpTextView.java │ │ │ ├── MainActivity.java │ │ │ └── SpannableStringBuilderAllVer.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── widget │ └── textviewforurl │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | .DS_Store 5 | /build 6 | # built application files 7 | *.apk 8 | *.ap_ 9 | 10 | # files for the dex VM 11 | *.dex 12 | 13 | # Java class files 14 | *.class 15 | .DS_Store 16 | 17 | # generated files 18 | bin/ 19 | gen/ 20 | Wiki/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Eclipse project files 26 | .classpath 27 | .project 28 | .settings/ 29 | 30 | # Proguard folder generated by Eclipse 31 | proguard/ 32 | 33 | #Android Studio 34 | build/ 35 | 36 | # Intellij project files 37 | *.iml 38 | *.ipr 39 | *.iws 40 | .idea/ 41 | 42 | #gradle 43 | .gradle/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HttpUrlTextView 2 | ## 3 | // 自识别url的textview,利用正则表达式检测url,并对其设置span,达到点击事件的效果。支持原文有span的text导入并保持原来的span点击效果不变
4 | // 2015-11 :增加是否解析的开关
5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | applicationId "widget.textviewforurl" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.0.1' 26 | } 27 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\AndroidSDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/widget/textviewforurl/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package widget.textviewforurl; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/widget/textviewforurl/HttpTextView.java: -------------------------------------------------------------------------------- 1 | package widget.textviewforurl; 2 | 3 | import android.content.Context; 4 | import android.text.Spanned; 5 | import android.text.TextPaint; 6 | import android.text.method.LinkMovementMethod; 7 | import android.text.style.ClickableSpan; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | import java.util.LinkedList; 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | /** 17 | * Created by 大灯泡 on 2015/11/4. 18 | * 识别url的textView 19 | */ 20 | public class HttpTextView extends TextView { 21 | //测试文字 22 | public String testText = 23 | "1.测试测试测试google.cn测试曹娥U去我如\n" + "2.侧首IU包宿123124 baidu.com报道锁人副I我去额555\n" 24 | + "3.博啊us豆腐啊哦I吧安静哦.博爱us都I人.dsaboauo www.weiju.ba/xx2/b54\n" 25 | + "4.这是一个测试哟http://www.baidu.com,这是测试哟\n" 26 | + "5.测试测试啊是的赴欧 我们的网址是:qq.164701463.net测试测试哟\n" 27 | + "6.的撒发吧额听歌:https://xx.125.com 654987打飞机阿伯I安\n" 28 | + "7.把儿童的方向:ftp://4399.com多发生部位,大师傅帮你\n" 29 | + "8.这次是个多网址哟 www.baidu.com哈哈哈www.google.com垃圾都是泪放假啊是的佛I 8264.com\n" 30 | + "9.你敢相信这是一个测试?www.baidu.com/?html=12354bhb35&ask=dasoiubao\n" 31 | + "10.这是一个下载地址哟 www.baidu.com/img/xxxx.jpg\n" 32 | + "11.baidu.com这个地址在开头\n" 33 | + "12.这个地址在末尾baidu.com\n" 34 | + "13.这是文字加地址加哈哈baba.ba 微笑掉地赴澳IU发qq.com微笑"; 35 | /* 36 | * 正则文本 37 | * ((http|ftp|https)://)(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\&%_\./-~-]*)?|(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\&%_\./-~-]*)? 38 | * */ 39 | private String pattern = 40 | "((http|ftp|https)://)(([a-zA-Z0-9\\._-]+\\.[a-zA-Z]{2,6})|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\\&%_\\./-~-]*)?|(([a-zA-Z0-9\\._-]+\\.[a-zA-Z]{2,6})|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\\&%_\\./-~-]*)?"; 41 | // 创建 Pattern 对象 42 | Pattern r = Pattern.compile(pattern); 43 | // 现在创建 matcher 对象 44 | Matcher m; 45 | //记录网址的list 46 | LinkedList mStringList; 47 | //记录该网址所在位置的list 48 | LinkedList mUrlInfos; 49 | int flag=Spanned.SPAN_POINT_MARK; 50 | 51 | private boolean needToRegionUrl = true;//是否开启识别URL,默认开启 52 | 53 | public HttpTextView(Context context) { 54 | this(context, null); 55 | } 56 | 57 | public HttpTextView(Context context, AttributeSet attrs) { 58 | this(context, attrs, 0); 59 | } 60 | 61 | public HttpTextView(Context context, AttributeSet attrs, int defStyle) { 62 | super(context, attrs, defStyle); 63 | mStringList = new LinkedList<>(); 64 | mUrlInfos = new LinkedList<>(); 65 | } 66 | public void setUrlText(CharSequence text) { 67 | if (needToRegionUrl) { 68 | SpannableStringBuilderAllVer stringBuilderAllVer = recognUrl(text); 69 | super.setText(stringBuilderAllVer); 70 | this.setMovementMethod(LinkMovementMethod.getInstance()); 71 | } else { 72 | super.setText(text); 73 | } 74 | } 75 | 76 | public boolean getIsNeedToRegionUrl() { 77 | return needToRegionUrl; 78 | } 79 | 80 | public void setOpenRegionUrl(boolean needToRegionUrl) { 81 | this.needToRegionUrl = needToRegionUrl; 82 | } 83 | 84 | private SpannableStringBuilderAllVer recognUrl(CharSequence text) { 85 | mStringList.clear(); 86 | mUrlInfos.clear(); 87 | 88 | CharSequence contextText; 89 | CharSequence clickText; 90 | text = text == null ? "" : text; 91 | //以下用于拼接本来存在的spanText 92 | SpannableStringBuilderAllVer span = new SpannableStringBuilderAllVer(text); 93 | ClickableSpan[] clickableSpans = span.getSpans(0, text.length(), ClickableSpan.class); 94 | if (clickableSpans.length > 0) { 95 | int start=0; 96 | int end=0; 97 | for (int i=0;i 0) { 133 | //只有一个网址 134 | if (mStringList.size() == 1) { 135 | String preStr = contentText.toString().substring(0, mUrlInfos.get(0).start); 136 | spanBuilder.append(preStr); 137 | String url = mStringList.get(0); 138 | spanBuilder.append(url, new URLClick(url), flag); 139 | String nextStr = contentText.toString().substring(mUrlInfos.get(0).end); 140 | spanBuilder.append(nextStr); 141 | } else { 142 | //有多个网址 143 | for (int i = 0; i < mStringList.size(); i++) { 144 | if (i == 0) { 145 | //拼接第1个span的前面文本 146 | String headStr = 147 | contentText.toString().substring(0, mUrlInfos.get(0).start); 148 | spanBuilder.append(headStr); 149 | } 150 | if (i == mStringList.size() - 1) { 151 | //拼接最后一个span的后面的文本 152 | spanBuilder.append(mStringList.get(i), new URLClick(mStringList.get(i)), 153 | flag); 154 | String footStr = contentText.toString().substring(mUrlInfos.get(i).end); 155 | spanBuilder.append(footStr); 156 | } 157 | if (i != mStringList.size() - 1) { 158 | //拼接两两span之间的文本 159 | spanBuilder.append(mStringList.get(i), new URLClick(mStringList.get(i)), flag); 160 | String betweenStr = contentText.toString() 161 | .substring(mUrlInfos.get(i).end, 162 | mUrlInfos.get(i + 1).start); 163 | spanBuilder.append(betweenStr); 164 | } 165 | } 166 | } 167 | } else { 168 | spanBuilder.append(contentText); 169 | } 170 | 171 | return spanBuilder; 172 | } 173 | 174 | //------------------------------------------定义----------------------------------------------- 175 | class UrlInfo { 176 | public int start; 177 | public int end; 178 | } 179 | 180 | class URLClick extends ClickableSpan { 181 | private String text; 182 | 183 | public URLClick(String text) { 184 | this.text = text; 185 | } 186 | 187 | @Override 188 | public void onClick(View widget) { 189 | Toast.makeText(widget.getContext(),text,Toast.LENGTH_SHORT).show(); 190 | } 191 | 192 | @Override 193 | public void updateDrawState(TextPaint ds) { 194 | super.updateDrawState(ds); 195 | ds.setColor(0xff517fae); 196 | ds.setUnderlineText(false); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /app/src/main/java/widget/textviewforurl/MainActivity.java: -------------------------------------------------------------------------------- 1 | package widget.textviewforurl; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.text.TextPaint; 7 | import android.text.style.ClickableSpan; 8 | import android.view.View; 9 | import android.view.View.OnClickListener; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.Toast; 13 | 14 | /** 15 | * Created by 大灯泡 on 2015/11/4. 16 | */ 17 | public class MainActivity extends Activity implements OnClickListener{ 18 | private HttpTextView mHttpTextView; 19 | private EditText mEditText; 20 | private Button mButton; 21 | private Button mButtonWithSpan; 22 | 23 | private String testStr="大灯泡说。。。:"; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | initView(); 30 | } 31 | 32 | private void initView() { 33 | mHttpTextView= (HttpTextView) findViewById(R.id.tx_test); 34 | mEditText= (EditText) findViewById(R.id.ed_test); 35 | mButton= (Button) findViewById(R.id.btn_test); 36 | mButtonWithSpan=(Button) findViewById(R.id.btn_test2); 37 | 38 | mButton.setOnClickListener(this); 39 | mButtonWithSpan.setOnClickListener(this); 40 | } 41 | 42 | @Override 43 | public void onClick(View v) { 44 | switch (v.getId()){ 45 | case R.id.btn_test: 46 | test1(); 47 | break; 48 | case R.id.btn_test2: 49 | test2(); 50 | break; 51 | default:break; 52 | } 53 | 54 | } 55 | 56 | private void test1() { 57 | if (mEditText.getText().toString().equals("")){ 58 | mHttpTextView.setUrlText(mHttpTextView.testText); 59 | }else { 60 | mHttpTextView.setUrlText(mEditText.getText().toString()); 61 | } 62 | } 63 | 64 | private void test2() { 65 | SpannableStringBuilderAllVer spannableStringBuilderAllVer=new SpannableStringBuilderAllVer(); 66 | spannableStringBuilderAllVer.append(testStr+"1",new TestClick(testStr+"1"),0); 67 | spannableStringBuilderAllVer.append(testStr+"2",new TestClick(testStr+"2"),0); 68 | spannableStringBuilderAllVer.append(testStr+"3",new TestClick(testStr+"3"),0); 69 | spannableStringBuilderAllVer.append('\n'); 70 | spannableStringBuilderAllVer.append(mHttpTextView.testText); 71 | mHttpTextView.setUrlText(spannableStringBuilderAllVer); 72 | 73 | } 74 | //=====================测试用的clickspan======================================== 75 | class TestClick extends ClickableSpan{ 76 | private String text; 77 | 78 | public TestClick(String text) { 79 | this.text = text; 80 | } 81 | 82 | @Override 83 | public void onClick(View widget) { 84 | Toast.makeText(widget.getContext(), text, Toast.LENGTH_SHORT).show(); 85 | } 86 | @Override 87 | public void updateDrawState(TextPaint ds) { 88 | super.updateDrawState(ds); 89 | ds.setColor(Color.RED); 90 | ds.setUnderlineText(false); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/widget/textviewforurl/SpannableStringBuilderAllVer.java: -------------------------------------------------------------------------------- 1 | package widget.textviewforurl; 2 | 3 | import android.text.SpannableStringBuilder; 4 | 5 | /** 6 | * Created by 大灯泡 on 2015/9/30. 7 | * 兼容低版本SpannableStringBuilder 8 | */ 9 | public class SpannableStringBuilderAllVer extends SpannableStringBuilder{ 10 | public SpannableStringBuilderAllVer() { 11 | super(""); 12 | } 13 | public SpannableStringBuilderAllVer(CharSequence text) { 14 | super(text, 0, text.length()); 15 | } 16 | public SpannableStringBuilderAllVer(CharSequence text, int start, int end){ 17 | super(text,start,end); 18 | } 19 | 20 | public SpannableStringBuilderAllVer append(CharSequence text) { 21 | if (text == null) return this; 22 | int length = length(); 23 | return (SpannableStringBuilderAllVer)replace(length, length, text, 0, text.length()); 24 | } 25 | 26 | 27 | /**该方法在原API里面只支持API21或者以上,这里抽取出来以适应低版本*/ 28 | public SpannableStringBuilderAllVer append(CharSequence text, Object what, int flags) { 29 | if (text == null) return this; 30 | int start = length(); 31 | append(text); 32 | setSpan(what, start, length(), flags); 33 | return this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 13 |