├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── four.jpg
│ │ │ │ ├── one.jpg
│ │ │ │ ├── two.jpg
│ │ │ │ ├── three.jpg
│ │ │ │ └── drawable
│ │ │ │ │ ├── one.jpg
│ │ │ │ │ ├── two.jpg
│ │ │ │ │ ├── four.jpg
│ │ │ │ │ └── three.jpg
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.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
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── tpnet
│ │ │ └── spantextview
│ │ │ ├── MainActivity.java
│ │ │ └── SpanTextView.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── tpnet
│ │ │ └── spantextview
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── tpnet
│ │ └── spantextview
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── screen
└── GIF.gif
├── .idea
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/screen/GIF.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/screen/GIF.gif
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/four.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/drawable/four.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/one.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/drawable/one.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/two.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/drawable/two.jpg
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SpanTextView
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/three.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/drawable/three.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawable/one.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/drawable/drawable/one.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawable/two.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/drawable/drawable/two.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawable/four.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/drawable/drawable/four.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawable/three.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/drawable/drawable/three.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpnet/SpanTextView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Oct 31 10:56:04 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/tpnet/spantextview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.tpnet.spantextview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/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 D:\android-sdk-windows/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/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/tpnet/spantextview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.tpnet.spantextview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.tpnet.spantextview", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion '26.0.2'
6 | defaultConfig {
7 | applicationId "com.tpnet.spantextview"
8 | minSdkVersion 15
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
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 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.0.0'
28 | testCompile 'junit:junit:4.12'
29 | }
30 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
17 |
18 |
25 |
26 |
27 |
34 |
35 |
36 |
42 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpanTextView
2 |
3 |
4 | 
5 |
6 | TextView需要局部操作:点击某些局部文字的事件、颜色、大小、下划线、指定位置插入图片、添加图片、缩略显示、文字跑马灯等等,如果没经过封装,你需要在activty上写一堆的view操作代码。
7 |
8 | 简单封装一下之后,我在TextView要设置第3-5的字母点击事件 和 “链接1”字符点击事件,代码只需要如下
9 |
10 | ```
11 | //设置点击事件
12 | tvTest.setSpanLink("链接1",SIGN_ONE,false,Color.GREEN);
13 | tvTest.setSpanLink("链接2",SIGN_TWO,false);
14 | tvTest.setSpanLink("链接3",SIGN_THREE);
15 |
16 | //点击事件回调监听
17 | tvTest.setOnTextLinkClickListener(new SpanTextView.onTextLinkClickListener() {
18 | @Override
19 | public void onTextLinkClick(View view, String text,int position , String sign) {
20 | String toast = "";
21 | switch (sign){
22 | case SIGN_ONE:
23 | toast = "链接1";
24 | break;
25 | case SIGN_TWO:
26 | toast = "链接2";
27 |
28 | break;
29 | case SIGN_THREE:
30 | toast = "链接3";
31 |
32 | break;
33 | }
34 | Toast.makeText(MainActivity.this,"点击了"+toast,Toast.LENGTH_SHORT).show();
35 | Log.e(TAG, "点击了"+toast);
36 | }
37 | });
38 | ```
39 |
40 |
41 | 又例如替换图片,插入图片,代码只需要如下:
42 |
43 | ```
44 | //添加图片到最前面,文字的高度
45 | tvTest.addImageToFirst(R.drawable.one, SpanTextView.TEXT_SIZE);
46 |
47 | //添加图片到中间(中间添加的后面)
48 | tvTest.setImage(R.drawable.two, "中间添加", SpanTextView.AFTER_IMAGE);
49 |
50 | //添加图片到最后面
51 | tvTest.addImageToLast(R.drawable.four);
52 |
53 | //替换文本为图片
54 | //tvTest.setImage(R.drawable.three,"图片标识", SpanTextView.REPLACE_IMAGE);
55 | tvTest.replaceTextToImage("图片标识",R.drawable.three);
56 | ```
57 |
58 | 如果你添加的图片需要点击事件,ok,满足你的,只需要在添加图片方法最后一个参数加上一个标识字符串常量即可,然后利用一个事件去监听,例如
59 |
60 | ```
61 | tvTest.addImageToFirst(R.drawable.one, SpanTextView.TEXT_SIZE,SIGN_PIC_ONE);
62 | //添加图片到中间位置 ("中间添加"的后面)
63 | tvTest.setImage(R.drawable.two, "中间添加", SpanTextView.AFTER_IMAGE,SIGN_PIC_TWO);
64 | //添加图片到最后面
65 | tvTest.addImageToLast(R.drawable.four,SIGN_PIC_THREE);
66 |
67 | // 图片点击回调事件
68 | tvTest.setOnImageLinkClickListener(new SpanTextView.onImageLinkClickListener() {
69 | @Override
70 | public void onImageLinkClick(View view, int position, String sign) {
71 | String toast = "";
72 | switch (sign){
73 | case SIGN_PIC_ONE:
74 | toast = "图片链接1-" + position;
75 | break;
76 | case SIGN_PIC_TWO:
77 | toast = "图片链接2-" + position;
78 |
79 | break;
80 | case SIGN_PIC_THREE:
81 | toast = "图片链接3-" + position;
82 |
83 | //替换最后图片为图片1,
84 | tvTest.replaceImageSpan(sign, getResources().getDrawable(R.drawable.one), SpanTextView.TEXT_SIZE, new int[2]);
85 |
86 | break;
87 | case SIGN_PIC_FOUR:
88 | toast = "图片链接4-" + position;
89 | break;
90 | }
91 | Toast.makeText(MainActivity.this,"点击了"+toast,Toast.LENGTH_SHORT).show();
92 | Log.e(TAG, "点击了"+toast);
93 | }
94 | });
95 | ```
96 |
97 |
98 | 又例如其他操作 背景、颜色、下划线等等:
99 |
100 |
101 | ```
102 | //设置文本大小
103 | tvTest.setSpanTextSize("大小",30);
104 | //设置文本背景
105 | tvTest.setSpanTextBack("普通背景", Color.BLUE);
106 | //设置文本圆角背景
107 | tvTest.setSpanTextBack("圆角背景", Color.GREEN,10);
108 | //设置文本颜色
109 | tvTest.setSpanTextColor("颜色",Color.RED);
110 | //设置文本下划线
111 | tvTest.setUnderLine("下划线",true);
112 |
113 |
114 |
115 | ```
116 |
117 |
118 | 设置省略文字:
119 | ```
120 | //文本开启跑马灯效果
121 | tvTextThree.setMarquee(true);
122 | //设置跑马灯的次数
123 | //tvTextThree.setMarqueeRepeatLimit(10);
124 |
125 | ```
126 |
127 |
128 | 设置文本跑马灯效果:
129 |
130 | ```
131 | //文本开启跑马灯效果
132 | tvTextThree.setMarquee(true);
133 |
134 | ```
135 |
136 |
137 | 清除效果:
138 |
139 | ```
140 | //清除单个效果,参数在设置的时候进行返回
141 | tvTest.clearSpan(mBackSpan);
142 |
143 | //清除全部效果
144 | tvTest.clearAllSpan();
145 |
146 | ```
147 |
148 |
149 | 还有其他的功能,具体请查看源码方法
150 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tpnet/spantextview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.tpnet.spantextview;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Color;
5 | import android.os.Bundle;
6 | import android.text.style.TypefaceSpan;
7 | import android.util.Log;
8 | import android.view.View;
9 | import android.widget.Button;
10 | import android.widget.Toast;
11 |
12 | public class MainActivity extends Activity {
13 |
14 |
15 | private SpanTextView tvTest;
16 | private SpanTextView tvTestTwo;
17 | private Button btnClear;
18 | private Button btnClearOne;
19 | private SpanTextView tvTextThree;
20 |
21 |
22 |
23 | private SpanTextView.RadiusBackgroundSpan mBackSpan;
24 |
25 | final String TAG = this.getClass().getSimpleName();
26 |
27 | private final String SIGN_ONE = "one";
28 | private final String SIGN_TWO = "two";
29 | private final String SIGN_THREE = "three";
30 |
31 |
32 | private final String SIGN_PIC_ONE = "pic_one";
33 | private final String SIGN_PIC_TWO = "pic_two";
34 | private final String SIGN_PIC_THREE = "pic_three";
35 | private final String SIGN_PIC_FOUR = "pic_four";
36 | private final String SIGN_PIC_FIVE = "pic_five";
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_main);
42 | tvTest = (SpanTextView) findViewById(R.id.tv_test);
43 | tvTestTwo = (SpanTextView) findViewById(R.id.tv_test_two);
44 | btnClear = (Button) findViewById(R.id.btn_clear);
45 | btnClearOne = (Button) findViewById(R.id.btn_clear_one);
46 | tvTextThree = (SpanTextView) findViewById(R.id.tv_text_three);
47 |
48 |
49 | //文本开启跑马灯效果
50 | tvTextThree.setMarquee(true);
51 | //设置跑马灯的次数
52 | //tvTextThree.setMarqueeRepeatLimit(10);
53 |
54 |
55 | //设置文本大小
56 | tvTest.setSpanTextSize("大小",30);
57 |
58 | //设置文本背景
59 | tvTest.setSpanTextBack("普通背景", Color.BLUE);
60 | //设置文本圆角背景
61 | mBackSpan = tvTest.setSpanTextBack("圆角背景", Color.GREEN,10);
62 | //设置文本颜色
63 | tvTest.setSpanTextColor("颜色",Color.RED);
64 | //设置文本下划线
65 | tvTest.setUnderLine("下划线",true);
66 |
67 | //自定义设置Span
68 | tvTest.setSpann(tvTest.createSpan(tvTest.getText(), new TypefaceSpan("sans-serif"), 1, 5));
69 |
70 |
71 | //添加图片到最前面,文字的高度
72 | tvTest.addImageToFirst(R.drawable.one, SpanTextView.TEXT_SIZE,SIGN_PIC_ONE);
73 | //添加图片到中间位置 ("中间添加"的后面)
74 | tvTest.setImage(R.drawable.two, "中间添加", SpanTextView.AFTER_IMAGE,SIGN_PIC_TWO);
75 | //添加图片到最后面
76 | tvTest.addImageToLast(R.drawable.four,SIGN_PIC_THREE);
77 | //替换文本为图片
78 | //tvTest.setImage(R.drawable.three,"图片标识", SpanTextView.REPLACE_IMAGE);
79 | //tvTest.replaceTextToImage("图片标识",R.drawable.three);
80 |
81 | tvTest.replaceOneTextToImage("图片标识", 0, getResources().getDrawable(R.drawable.three), SpanTextView.TEXT_SIZE, new int[2], SIGN_PIC_FOUR);
82 |
83 |
84 |
85 | //设置点击事件
86 | tvTest.setSpanTextLink("链接1",SIGN_ONE,false,Color.GREEN);
87 | tvTest.setSpanTextLink("链接2",SIGN_TWO,false);
88 | tvTest.setSpanTextLink("链接3",SIGN_THREE);
89 |
90 | //点击事件回调监听
91 | tvTest.setOnTextLinkClickListener(new SpanTextView.onTextLinkClickListener() {
92 |
93 |
94 | @Override
95 | public void onTextLinkClick(View view, String text,int position , String sign) {
96 | String toast = "";
97 | switch (sign){
98 | case SIGN_ONE:
99 | toast = "链接1";
100 | break;
101 | case SIGN_TWO:
102 | toast = "链接2";
103 |
104 | break;
105 | case SIGN_THREE:
106 | toast = "链接3";
107 |
108 | break;
109 | }
110 | Toast.makeText(MainActivity.this,"点击了"+toast,Toast.LENGTH_SHORT).show();
111 | Log.e(TAG, "点击了"+toast);
112 | }
113 |
114 |
115 | });
116 |
117 |
118 | tvTest.setOnImageLinkClickListener(new SpanTextView.onImageLinkClickListener() {
119 | @Override
120 | public void onImageLinkClick(View view, int position, String sign) {
121 | String toast = "";
122 | switch (sign){
123 | case SIGN_PIC_ONE:
124 | toast = "图片链接1-" + position;
125 | break;
126 | case SIGN_PIC_TWO:
127 | toast = "图片链接2-" + position;
128 |
129 | break;
130 | case SIGN_PIC_THREE:
131 | toast = "图片链接3-" + position;
132 |
133 | //替换最后图片为图片1,
134 | tvTest.replaceImageSpan(sign, getResources().getDrawable(R.drawable.one), SpanTextView.TEXT_SIZE, new int[2]);
135 |
136 | break;
137 | case SIGN_PIC_FOUR:
138 | toast = "图片链接4-" + position;
139 | break;
140 | }
141 | Toast.makeText(MainActivity.this,"点击了"+toast,Toast.LENGTH_SHORT).show();
142 | Log.e(TAG, "点击了"+toast);
143 | }
144 | });
145 |
146 | //设置省略文字,只显示100个
147 | tvTestTwo.setOmit(100);
148 |
149 | //设置省略文字,超出则显示1/3。缩放支持设置了span的文本
150 | //tvTest.setOmit(100);
151 |
152 |
153 | btnClear.setOnClickListener(new View.OnClickListener() {
154 | @Override
155 | public void onClick(View v) {
156 | tvTest.clearAllSpan();
157 | }
158 | });
159 |
160 | btnClearOne.setOnClickListener(new View.OnClickListener() {
161 | @Override
162 | public void onClick(View v) {
163 | tvTest.clearSpan(mBackSpan);
164 | }
165 | });
166 |
167 |
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tpnet/spantextview/SpanTextView.java:
--------------------------------------------------------------------------------
1 | package com.tpnet.spantextview;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Paint.FontMetricsInt;
9 | import android.graphics.Rect;
10 | import android.graphics.RectF;
11 | import android.graphics.drawable.Drawable;
12 | import android.os.Build;
13 | import android.support.annotation.ColorInt;
14 | import android.support.annotation.DrawableRes;
15 | import android.support.annotation.IntDef;
16 | import android.support.annotation.NonNull;
17 | import android.support.annotation.StringRes;
18 | import android.text.Layout;
19 | import android.text.Selection;
20 | import android.text.Spannable;
21 | import android.text.SpannableString;
22 | import android.text.SpannableStringBuilder;
23 | import android.text.Spanned;
24 | import android.text.TextPaint;
25 | import android.text.TextUtils;
26 | import android.text.method.LinkMovementMethod;
27 | import android.text.method.Touch;
28 | import android.text.style.AbsoluteSizeSpan;
29 | import android.text.style.ClickableSpan;
30 | import android.text.style.ForegroundColorSpan;
31 | import android.text.style.ImageSpan;
32 | import android.text.style.ReplacementSpan;
33 | import android.text.style.UnderlineSpan;
34 | import android.util.AttributeSet;
35 | import android.util.Log;
36 | import android.view.MotionEvent;
37 | import android.view.View;
38 | import android.widget.TextView;
39 |
40 | import java.util.ArrayList;
41 | import java.util.HashMap;
42 | import java.util.List;
43 | import java.util.Map;
44 | import java.util.regex.Matcher;
45 | import java.util.regex.Pattern;
46 |
47 | /**
48 | * TextView富文本类
49 | * Created by Litp on 2017/2/23.
50 | */
51 | @SuppressLint("AppCompatCustomView")
52 | public class SpanTextView extends TextView implements View.OnClickListener {
53 |
54 |
55 | private int mDefaultHintColor = 0x6633B5E5; //点击时候的背景色,默认为淡绿色
56 |
57 | private final String mAddText = "拁"; //添加 替换为图片的文字、创建完整Span时候的文字
58 |
59 | private boolean mIsOpenMarquees; //跑马灯
60 |
61 | private boolean mLinkHit; //是否点击了局部链接
62 |
63 | //记录图片和文本的点击事件
64 | private Map mClickImageSpan;
65 |
66 |
67 | public final static int FRONT_IMAGE = 0x1; //在文字之前
68 | public final static int REPLACE_IMAGE = 0x2; //替换文字为照片
69 | public final static int AFTER_IMAGE = 0x3; //在文字之后
70 | @IntDef({FRONT_IMAGE, REPLACE_IMAGE, AFTER_IMAGE})
71 | public @interface TEXT_POSITION_FLAG {
72 | }
73 |
74 |
75 | public final static int AUTO_SIZE = 0x64; //原来的图片大小
76 | public final static int TEXT_SIZE = 0x65; //文字的高度
77 | public final static int APPOINT_SIZE = 0x66; //指定大小,在后面添加指定的宽高数组
78 | @IntDef({AUTO_SIZE, TEXT_SIZE, APPOINT_SIZE})
79 | public @interface IMAGE_SIZE_FLAG {}
80 |
81 | public SpanTextView(Context context) {
82 | this(context,null);
83 | }
84 |
85 |
86 | public SpanTextView(Context context, AttributeSet attrs) {
87 | super(context, attrs);
88 | mClickImageSpan = new HashMap<>();
89 | }
90 |
91 |
92 | protected void onFinishInflate() {
93 | super.onFinishInflate();
94 | if (Build.VERSION.SDK_INT >= 16) {
95 | this.mDefaultHintColor = getHighlightColor();
96 | }
97 | }
98 |
99 | //格式化文本
100 | public void setFormatText(@StringRes int formatText, Object... text) {
101 | setFormatText(getResources().getString(formatText), text);
102 | }
103 |
104 | //格式化文本
105 | public void setFormatText(String formatText, Object... text) {
106 | setText(String.format(formatText, text));
107 | }
108 |
109 |
110 | //格式化文本,map
111 | public void setFormatText(@StringRes int formatRes, Map map) {
112 | StringBuffer text = new StringBuffer();
113 | for (String key : map.keySet()) {
114 | text.append(map.get(key));
115 | text.append(" ");
116 | }
117 |
118 | setFormatText(formatRes, text.toString());
119 | }
120 |
121 | /**
122 | * 格式化文本
123 | *
124 | * @param formatText
125 | * @param text
126 | * @param color
127 | */
128 | public void setFormatText(@StringRes int formatText, @ColorInt int color, Object... text) {
129 | setFormatText(getResources().getString(formatText), text);
130 | setSpanTextColor(formatText, color);
131 | }
132 |
133 |
134 | public int getmDefaultHintColor() {
135 | return mDefaultHintColor;
136 | }
137 |
138 | public void setmDefaultHintColor(@ColorInt int mDefaultHintColor) {
139 | this.mDefaultHintColor = mDefaultHintColor;
140 | }
141 |
142 | /*--------------- 文字颜色 --------------*/
143 | public ForegroundColorSpan setSpanTextColor(int start, @ColorInt int color) {
144 | return setSpanTextColor(start, getText().length(), color);
145 | }
146 |
147 | public ForegroundColorSpan setSpanTextColor(String str, @ColorInt int color) {
148 | int[] size = getStartAndEnd(str);
149 | return setSpanTextColor(size[0], size[1], color);
150 | }
151 |
152 | /**
153 | * 设置文字颜色
154 | *
155 | * @param start 开始的位置
156 | * @param end 结束的位置
157 | * @param color 要设置的颜色
158 | */
159 | public ForegroundColorSpan setSpanTextColor(int start, int end, @ColorInt int color) {
160 | ForegroundColorSpan span = new ForegroundColorSpan(color);
161 | setSpann(createSpan(getText(), span, start, end));
162 | return span;
163 | }
164 |
165 |
166 | /*--------------- 文字大小 --------------*/
167 | public AbsoluteSizeSpan setSpanTextSize(int start, int textSize) {
168 | return setSpanTextSize(start, getText().length(), textSize);
169 | }
170 |
171 | public AbsoluteSizeSpan setSpanTextSize(String str, int textSize) {
172 | int[] size = getStartAndEnd(str);
173 | return setSpanTextSize(size[0], size[1], textSize);
174 | }
175 |
176 | /**
177 | * 设置文字颜色
178 | *
179 | * @param start 开始的位置
180 | * @param end 结束的位置
181 | * @param textSize 要设置的大小,单位px,请自行根据sp转换
182 | */
183 | public AbsoluteSizeSpan setSpanTextSize(int start, int end, int textSize) {
184 | AbsoluteSizeSpan span = new AbsoluteSizeSpan(textSize);
185 | setSpann(createSpan(getText(), span, start, end));
186 | return span;
187 | }
188 |
189 |
190 |
191 | /*--------------- 文字背景 --------------*/
192 |
193 | public RadiusBackgroundSpan setSpanTextBack(String str, @ColorInt int color) {
194 | int[] size = getStartAndEnd(str);
195 | return setSpanTextBack(size[0], size[1], color, 0);
196 | }
197 |
198 | public RadiusBackgroundSpan setSpanTextBack(String str, @ColorInt int color, int radius) {
199 | int[] size = getStartAndEnd(str);
200 | return setSpanTextBack(size[0], size[1], color, radius);
201 | }
202 |
203 | public RadiusBackgroundSpan setSpanTextBack(int start, int end, @ColorInt int color) {
204 | return setSpanTextBack(start, end, color, 0);
205 | }
206 |
207 | /**
208 | * 设置文字的背景
209 | *
210 | * @param start 开始的位置
211 | * @param end 结束的位置
212 | * @param color 要设置的颜色
213 | * @param radius 背景的圆角像素,单位px,直角就是0
214 | */
215 | public RadiusBackgroundSpan setSpanTextBack(int start, int end, @ColorInt int color, int radius) {
216 | RadiusBackgroundSpan span = new RadiusBackgroundSpan(color, radius);
217 | setSpann(createSpan(getText(),span , start, end));
218 | return span;
219 | }
220 |
221 |
222 | /*--------------- 文字链接 --------------*/
223 | public ClickTextSpan setSpanTextLink(String text, String sign) {
224 | return setSpanTextLink(text, sign, true);
225 | }
226 |
227 | public ClickTextSpan setSpanTextLink(String text, String sign, boolean isUnderLineVisiable) {
228 | return setSpanTextLink(text, sign, isUnderLineVisiable, 0);
229 | }
230 |
231 | public ClickTextSpan setSpanTextLink(String text, String sign, boolean isUnderLineVisiable, @ColorInt int color) {
232 | int[] array = getStartAndEnd(text);
233 | return setSpanTextLink(array[0], array[1], sign, isUnderLineVisiable, color);
234 | }
235 |
236 | public ClickTextSpan setSpanTextLink(int start, int end, String sign) {
237 | return setSpanTextLink(start, end, sign, true, 0);
238 | }
239 |
240 | /**
241 | * 设置文字链接
242 | *
243 | * @param start 开始的位置
244 | * @param end 结束的位置
245 | * @param sign 设置链接对应的标识,供给回调的时候判断
246 | * @param isUnderLineVisiable 是否显示下划线
247 | * @param color 文字的颜色
248 | */
249 | public ClickTextSpan setSpanTextLink(int start, int end, String sign, boolean isUnderLineVisiable, @ColorInt int color) {
250 | try {
251 | //防止字符不存在异常
252 |
253 | ClickTextSpan span = new ClickTextSpan(new ClickTextListener(getText().subSequence(start, end), start, sign), isUnderLineVisiable, color);
254 | setSpann(
255 | createSpan(getText(),
256 | span,
257 | start,
258 | end));
259 | setMovementMethod(LocalLinkMovementMethod.getInstance());
260 | return span;
261 | } catch (Exception e) {
262 | e.printStackTrace();
263 | }
264 |
265 | return null;
266 |
267 | }
268 |
269 | @Deprecated
270 | public void setSpanLink(String text, String sign) {
271 | setSpanLink(text, sign, true);
272 | }
273 |
274 | @Deprecated
275 | public void setSpanLink(String text, String sign, boolean isUnderLineVisiable) {
276 | setSpanLink(text, sign, isUnderLineVisiable, 0);
277 | }
278 |
279 | @Deprecated
280 | public void setSpanLink(String text, String sign, boolean isUnderLineVisiable, @ColorInt int color) {
281 | int[] array = getStartAndEnd(text);
282 | setSpanLink(array[0], array[1], sign, isUnderLineVisiable, color);
283 | }
284 |
285 | @Deprecated
286 | public void setSpanLink(int start, int end, String sign) {
287 | setSpanLink(start, end, sign, true, 0);
288 |
289 | }
290 |
291 | @Deprecated
292 | public void setSpanLink(int start, int end, String sign, boolean isUnderLineVisiable, @ColorInt int color) {
293 | try {
294 | //防止字符不存在异常
295 | setSpann(
296 | createSpan(getText(),
297 | new ClickTextSpan(new ClickTextListener(getText().subSequence(start, end), start, sign), isUnderLineVisiable, color),
298 | start,
299 | end));
300 | setMovementMethod(LocalLinkMovementMethod.getInstance());
301 | } catch (Exception e) {
302 | e.printStackTrace();
303 | }
304 |
305 | }
306 |
307 |
308 | /*--------------- 文字下横线 --------------*/
309 | public UnderlineSpan setUnderLine(String text, boolean isVisiable) {
310 | int[] array = getStartAndEnd(text);
311 | return setUnderLine(array[0], array[1], isVisiable);
312 | }
313 |
314 | public UnderlineSpan setUnderLine(int start, int end, boolean isVisiable) {
315 | UnderlineSpan span;
316 | if (isVisiable) {
317 | span = new UnderlineSpan();
318 | setSpann(createSpan(getText(),span, start, end));
319 | return span;
320 | }
321 | span = new NOUnderlineSpan();
322 | setSpann(createSpan(getText(), span, start, end));
323 | return span;
324 | }
325 |
326 |
327 | /**
328 | * 设置图片点击事件
329 | *
330 | * @param position 图片的位置,一个文本位置
331 | * @param sign 图片的标识
332 | */
333 | private void setImageLinkSpan(int position, String text, String sign) {
334 |
335 |
336 | if (!TextUtils.isEmpty(sign) && position != -1) {
337 |
338 | ClickImageSpan span = new ClickImageSpan(new ClickImageListener(position, text, sign));
339 |
340 | setSpann(
341 | createSpan(getText(),
342 | span ,
343 | position,
344 | position == 0 ? 1: position));
345 | setMovementMethod(LocalLinkMovementMethod.getInstance());
346 |
347 |
348 | mClickImageSpan.put(sign,span);
349 |
350 |
351 | }
352 |
353 | }
354 |
355 |
356 | /*--------------- 添加图片到最后面 --------------*/
357 |
358 | public CenterImageSpan addImageToLast(@DrawableRes int imgId, String... sign) {
359 | return addImageToLast(imgId, AUTO_SIZE, new int[2], sign);
360 | }
361 |
362 | public CenterImageSpan addImageToLast(@DrawableRes int imgId, @IMAGE_SIZE_FLAG int sizeFlag, String... sign) {
363 | return addImageToLast(imgId, sizeFlag, new int[2], sign);
364 | }
365 |
366 | public CenterImageSpan addImageToLast(@DrawableRes int imgId, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
367 | Drawable drawable = getResources().getDrawable(imgId);
368 | return addImageToLast(drawable, sizeFlag, size, sign);
369 | }
370 |
371 | public CenterImageSpan addImageToLast(Drawable drawable, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
372 |
373 | CenterImageSpan span = null;
374 |
375 | if (drawable != null) {
376 |
377 | size = calcDrawableSize(drawable, sizeFlag, size);
378 |
379 | drawable.setBounds(0, 0, size[0], size[1]);
380 |
381 |
382 | span = new CenterImageSpan(drawable);
383 |
384 | addSpann(true, createNormalSpan(span));
385 |
386 | if (sign.length > 0) {
387 | setImageLinkSpan(getText().length(), "", sign[0]);
388 | }
389 | }
390 |
391 | return span;
392 | }
393 |
394 | /*--------------- 添加图片到最前面 --------------*/
395 | public CenterImageSpan addImageToFirst(@DrawableRes int imgId, String... sign) {
396 | return addImageToFirst(imgId, AUTO_SIZE, sign);
397 | }
398 |
399 |
400 | public CenterImageSpan addImageToFirst(@DrawableRes int imgId, @IMAGE_SIZE_FLAG int sizeFlag, String... sign) {
401 | return addImageToFirst(imgId, sizeFlag, new int[2], sign);
402 | }
403 |
404 | public CenterImageSpan addImageToFirst(@DrawableRes int imgId, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
405 | Drawable drawable = getResources().getDrawable(imgId);
406 | return addImageToFirst(drawable, sizeFlag, size, sign);
407 | }
408 |
409 |
410 | public CenterImageSpan addImageToFirst(Drawable drawable, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
411 |
412 | CenterImageSpan span = null;
413 |
414 | if (drawable != null) {
415 |
416 | size = calcDrawableSize(drawable, sizeFlag, size);
417 |
418 | drawable.setBounds(0, 0, size[0], size[1]);
419 |
420 | //先在前面加一个字,再替换为图片
421 | addSpann(false, new SpannableString(mAddText), new SpannableString(getText()));
422 |
423 | span = new CenterImageSpan(drawable);
424 |
425 | //替换刚刚添加的文字
426 | setSpann(createSpan(getText(), span, 0, 1));
427 |
428 | if (sign != null && sign.length > 0) {
429 | setImageLinkSpan(0, "", sign[0]);
430 | }
431 |
432 | }
433 |
434 | return span;
435 | }
436 |
437 |
438 | /*--------------- 把文字替换成图片 --------------*/
439 |
440 | public List replaceTextToImage(String text, @DrawableRes int imgId) {
441 | return replaceTextToImage(text, imgId, AUTO_SIZE, new int[2]);
442 | }
443 |
444 |
445 | public List replaceTextToImage(String text, @DrawableRes int imgId, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
446 | return replaceTextToImage(text, getResources().getDrawable(imgId), sizeFlag, size, sign);
447 | }
448 |
449 | /**
450 | * 替换文本为图片,全部替换
451 | *
452 | * @param text 将被替换的文字
453 | * @param drawable 要换成的图片drawable
454 | * @param sizeFlag 图片显示的大小
455 | * @param size 固定图片时候要设置的宽高
456 | */
457 | public List replaceTextToImage(String text, Drawable drawable, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
458 |
459 | List list = new ArrayList<>();
460 |
461 | if (!TextUtils.isEmpty(text) || drawable != null) {
462 |
463 | Pattern pattern = Pattern.compile(text);
464 | Matcher matcher = pattern.matcher(getText());
465 |
466 | size = calcDrawableSize(drawable, sizeFlag, size);
467 | drawable.setBounds(0, 0, size[0], size[1]);
468 |
469 | //循环替换
470 | while (matcher.find()) {
471 | CenterImageSpan span = new CenterImageSpan(drawable);
472 | setSpann(createSpan(getText(),
473 | span,
474 | matcher.start(),
475 | matcher.end()));
476 | list.add(span);
477 | }
478 |
479 | }
480 | return list;
481 |
482 | }
483 |
484 |
485 | /**
486 | * 替换一个文本为图片 ,并且添加点击事件
487 | *
488 | * @param text
489 | * @param position 从哪个位置开始
490 | * @param drawable 图片
491 | * @param sign 点击事件标识
492 | */
493 | public CenterImageSpan replaceOneTextToImage(String text, int position, Drawable drawable, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String sign) {
494 |
495 | if (!TextUtils.isEmpty(text) || drawable != null) {
496 |
497 | Pattern pattern = Pattern.compile(text);
498 | Matcher matcher = pattern.matcher(getText());
499 |
500 | size = calcDrawableSize(drawable, sizeFlag, size);
501 | drawable.setBounds(0, 0, size[0], size[1]);
502 |
503 | int i = 0;
504 |
505 | //循环替换
506 | while (matcher.find()) {
507 |
508 | if (matcher.start() < position) {
509 | continue;
510 | }
511 |
512 | CenterImageSpan span = new CenterImageSpan(drawable);
513 | setSpann(createSpan(getText(),
514 | span,
515 | matcher.start(),
516 | matcher.end()));
517 |
518 | if (i == position) {
519 | setImageLinkSpan(matcher.start(), text, sign);
520 | return span;
521 | }
522 |
523 |
524 | i++;
525 |
526 | }
527 |
528 | }
529 |
530 | return null;
531 |
532 | }
533 |
534 |
535 | /**
536 | * 替换图片span
537 | *
538 | * @param sign 标识
539 | */
540 | public CenterImageSpan replaceImageSpan(String sign, Drawable drawable, @IMAGE_SIZE_FLAG int sizeFlag, int[] size) {
541 | ClickImageSpan imageSpan = mClickImageSpan.get(sign);
542 |
543 | if (null != imageSpan) {
544 | SpannableString span = (SpannableString) getText();
545 |
546 |
547 | if (imageSpan.clickListener.position == 0) { //前面添加的
548 | clearClickImageSpan(sign, 0);
549 | Log.e("@@", "前面");
550 | return addImageToFirst(drawable, sizeFlag, size, sign);
551 | } else if (imageSpan.clickListener.position == getText().length()) { //后面添加
552 | clearClickImageSpan(sign, 0);
553 | Log.e("@@", "最后");
554 | return addImageToLast(drawable, sizeFlag, size, sign);
555 | } else if (mAddText.equals(imageSpan.clickListener.text)) { //中间插入
556 | clearClickImageSpan(sign, 0);
557 | Log.e("@@", "中间插入");
558 | return replaceOneTextToImage(mAddText, imageSpan.clickListener.position, drawable, sizeFlag, size, sign);
559 | } else { //替换文本的
560 | clearClickImageSpan(sign, 0);
561 | Log.e("@@", "替换文本" + imageSpan.clickListener.position + " - " + getText().length());
562 | return replaceOneTextToImage(imageSpan.clickListener.text, imageSpan.clickListener.position, drawable, sizeFlag, size, sign);
563 | }
564 |
565 |
566 | }
567 |
568 | return null;
569 |
570 | }
571 |
572 |
573 |
574 |
575 | public void setImage(@DrawableRes int imgId, String text, @TEXT_POSITION_FLAG int flag, String... sign) {
576 | setImage(imgId, text, flag, AUTO_SIZE, new int[2], sign);
577 | }
578 |
579 | public void setImage(@DrawableRes int imgId, String text, @TEXT_POSITION_FLAG int flag, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
580 | setImage(getResources().getDrawable(imgId), text, flag, sizeFlag, size, sign);
581 | }
582 |
583 | /**
584 | * 插入图片到指定位置,两个字符串的中间
585 | *
586 | * @param drawable 图片资源
587 | * @param text 要操作的text
588 | * @param flag 图片插入模式,前中后
589 | * @param sizeFlag 图片显示的大小
590 | * @param size 固定图片时候要设置的宽高
591 | */
592 | public void setImage(Drawable drawable, String text, @TEXT_POSITION_FLAG int flag, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
593 | switch (flag) {
594 | case FRONT_IMAGE:
595 | insertImage(drawable, getStartAndEnd(text)[0], sizeFlag, size, sign);
596 | break;
597 | case REPLACE_IMAGE:
598 | replaceTextToImage(text, drawable, sizeFlag, size, sign);
599 | break;
600 | case AFTER_IMAGE:
601 | insertImage(drawable, getStartAndEnd(text)[1], sizeFlag, size, sign);
602 | break;
603 | }
604 | }
605 |
606 |
607 | /*------------ 插入图片-------------*/
608 | public void insertImage(@DrawableRes int imgId, int insertPosition, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
609 | insertImage(getResources().getDrawable(imgId), insertPosition, sizeFlag, size, sign);
610 | }
611 |
612 | /**
613 | * 插入图片到指定位置
614 | *
615 | * @param drawable 图片Drawable
616 | * @param insertPosition 要插入的位置,0为从开头,getText.length()为最后
617 | * @param sizeFlag 图片显示的大小
618 | * @param size 固定图片时候要设置的宽高
619 | */
620 | public void insertImage(Drawable drawable, int insertPosition, @IMAGE_SIZE_FLAG int sizeFlag, int[] size, String... sign) {
621 |
622 | if (drawable != null) {
623 |
624 | if (insertPosition == 0) {
625 | //开始
626 | addImageToFirst(drawable, sizeFlag, size);
627 | } else if (insertPosition == getText().length()) {
628 | //在后面添加
629 | addImageToLast(drawable, sizeFlag, size);
630 | } else {
631 |
632 | size = calcDrawableSize(drawable, sizeFlag, size);
633 | drawable.setBounds(0, 0, size[0], size[1]);
634 | //先在中间添加一个文字
635 | addSpann(false,
636 | new SpannableString(getText().subSequence(0, insertPosition)),
637 | new SpannableString(mAddText),
638 | new SpannableString(getText().subSequence(insertPosition, getText().length()))
639 | );
640 | //把加上去的文字替换为图片
641 | setSpann(createSpan(getText(), new CenterImageSpan(drawable), insertPosition, insertPosition + 1));
642 |
643 | if (sign != null && sign.length > 0) {
644 | setImageLinkSpan(insertPosition, mAddText,sign[0]);
645 | }
646 |
647 | }
648 | }
649 | }
650 |
651 |
652 | /**
653 | * 根据Flag计算要显示的图片的 宽高
654 | *
655 | * @param drawable 图片
656 | * @param sizeFlag 标识
657 | * @return 返回计算好的宽高
658 | */
659 | private int[] calcDrawableSize(Drawable drawable, @IMAGE_SIZE_FLAG int sizeFlag, int[]... s) {
660 | int[] size = new int[2];
661 | switch (sizeFlag) {
662 | case AUTO_SIZE:
663 | size[0] = drawable.getIntrinsicWidth();
664 | size[1] = drawable.getIntrinsicHeight();
665 | break;
666 | case APPOINT_SIZE:
667 | //指定宽高没有设置的话抛出异常
668 | if (s == null || s.length <= 0 || s[0].length <= 1) {
669 | throw new IllegalStateException("指定宽高状态下,需要添加参数设置宽高");
670 | } else if (s[0][0] < 0 || s[0][1] < 0) {
671 | throw new IllegalStateException("指定宽高状态下,宽或高不能为小于0");
672 | }
673 | break;
674 | case TEXT_SIZE:
675 | //文字的宽高度
676 | size[0] = getLineHeight() * drawable.getIntrinsicWidth() / drawable.getIntrinsicHeight();
677 | size[1] = getLineHeight();
678 | break;
679 | }
680 |
681 | return size;
682 | }
683 |
684 | public SpannableString createSpan(CharSequence text, Object spann, int start, int end) {
685 |
686 | if (start < 0 || end <= 0 || spann == null) {
687 | return null;
688 | }
689 |
690 | SpannableString spannableString = new SpannableString(text);
691 |
692 | spannableString.setSpan(spann, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
693 | return spannableString;
694 | }
695 |
696 |
697 | /**
698 | * 创建完整替换的Span
699 | *
700 | * @param spann 要创建的Span
701 | * @return
702 | */
703 | public SpannableString createNormalSpan(Object spann) {
704 | SpannableString spannableString = new SpannableString(mAddText);
705 | spannableString.setSpan(spann, 0, mAddText.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
706 | return spannableString;
707 | }
708 |
709 | public void setSpann(SpannableString spannableString) {
710 | if (spannableString == null) {
711 | return;
712 | }
713 | setText(spannableString);
714 | }
715 |
716 | /**
717 | * 在原有的基础上添加Spann在后面
718 | *
719 | * @param isContains 是否包含原来的内容
720 | * @param spannableString 要添加多个的spannableString
721 | */
722 | public SpannableStringBuilder addSpann(boolean isContains, SpannableString... spannableString) {
723 | SpannableStringBuilder builder = new SpannableStringBuilder();
724 | if (isContains) {
725 | builder.append(getText());
726 | }
727 |
728 | //循环添加在后面
729 | for (SpannableString string : spannableString) {
730 | builder.append(string);
731 | }
732 | setText(builder);
733 | return builder;
734 | }
735 |
736 | private int[] getStartAndEnd(String text) {
737 | int start = getText().toString().indexOf(text);
738 | return new int[]{start, text.length() + start};
739 | }
740 |
741 | /**
742 | * 清除所有效果
743 | */
744 | public void clearAllSpan() {
745 |
746 | int i = 0;
747 |
748 | //先清除图片添加的文本
749 | for (String s : mClickImageSpan.keySet()) {
750 | i += clearClickImageSpan(s,i);
751 | }
752 | setText(getText().toString());
753 | }
754 |
755 | /**
756 | * 清除列表span
757 | *
758 | * @param span 需要清除的span
759 | * @param span类型
760 | */
761 | public void clearSpan(T span, T... spanMore) {
762 | SpannableString ss = (SpannableString) getText();
763 | ss.removeSpan(span);
764 | for (T s : spanMore) {
765 | ss.removeSpan(span);
766 | }
767 | }
768 |
769 |
770 | public int clearClickImageSpan(String sign) {
771 | return clearClickImageSpan(sign,0);
772 | }
773 |
774 |
775 | /**
776 | * 清除点击的span
777 | * @param sign 标识
778 | * @param num 多次清除需要减去的数量
779 | * @return 返回被清除的文字的长度
780 | */
781 | private int clearClickImageSpan(String sign,int num){
782 |
783 | //清除图片添加的文字
784 | ClickImageSpan span = mClickImageSpan.get(sign);
785 |
786 | int position = span.clickListener.position - num;
787 |
788 | if(position == getText().length()){
789 | position--;
790 | }
791 |
792 | String str = String.valueOf(getText().charAt(position));
793 |
794 | mClickImageSpan.remove(span);
795 |
796 | if (str.equals(mAddText)){ //自定义文字的形式替换图片
797 | SpannableString spannableFront = new SpannableString(getText().subSequence(0,position));
798 | SpannableString spannableAfter = new SpannableString(getText().subSequence(position + 1,getText().length()));
799 | addSpann(false,spannableFront,spannableAfter);
800 |
801 | return mAddText.length();
802 |
803 | }
804 |
805 | return 0;
806 | }
807 |
808 |
809 | //没有省略字符时候的文本
810 | private CharSequence mAllText = "";
811 |
812 | public String mOmitText = "...展开全文"; //省略显示的文字
813 | public int mOmitShowNum; //显示的字符数
814 | public int mOmitShowColor; //显示的字符颜色
815 |
816 | public void setOmit(int showNum) {
817 | setOmit(showNum, getResources().getColor(R.color.colorAccent));
818 | }
819 |
820 |
821 | /**
822 | * 省略显示字符
823 | *
824 | * @param showNum 要显示的字符数量
825 | */
826 | public void setOmit(int showNum, @ColorInt int color) {
827 |
828 | mAllText = getText();
829 |
830 |
831 | if (mAllText.length() < showNum) {
832 | //如果要显示文字超出总的长度,则隐藏2/3;
833 | showNum = mAllText.length() / 3;
834 | }
835 |
836 | this.mOmitShowNum = showNum;
837 |
838 | this.mOmitShowColor = color;
839 |
840 | reSetOmit(true);
841 |
842 |
843 | setOnClickListener(this);
844 |
845 | //添加展开全文
846 | //setSpanLink(mOmitText,omitSign);
847 |
848 | }
849 |
850 | //恢复缩放文本
851 | public void reSetOmit(boolean isScale) {
852 | if (isScale) {
853 | //设置省略字段
854 | setText(getText().subSequence(0, mOmitShowNum));
855 |
856 | //添加展开全文
857 | addSpann(true, createSpan(mOmitText, new ForegroundColorSpan(mOmitShowColor), 0, mOmitText.length()));
858 |
859 | } else {
860 | setText(mAllText);
861 | }
862 | }
863 |
864 |
865 | @Override
866 | public void onClick(View v) {
867 |
868 | //注意这里不能用文本equals,如果文本设置了Span会导致不相等
869 | if (mAllText.length() == getText().length()) {
870 | //收缩
871 | reSetOmit(true);
872 | } else {
873 | //伸展
874 | reSetOmit(false);
875 | }
876 | }
877 |
878 |
879 | private OnOmitClickListener mOnOmitClickListener;
880 |
881 | public interface OnOmitClickListener {
882 | /**
883 | * @param isOmit 收起还是伸展
884 | */
885 | void onOmitClick(boolean isOmit);
886 | }
887 |
888 | public void setOnOmitClickListener(OnOmitClickListener listener) {
889 | this.mOnOmitClickListener = listener;
890 | }
891 |
892 |
893 | public void setOnTextLinkClickListener(onTextLinkClickListener listener) {
894 | this.mTextLinkClickListener = listener;
895 | }
896 |
897 | public void setOnImageLinkClickListener(onImageLinkClickListener listener) {
898 | this.mImageLinkClickListener = listener;
899 | }
900 |
901 | private onTextLinkClickListener mTextLinkClickListener;
902 | private onImageLinkClickListener mImageLinkClickListener;
903 |
904 | /**
905 | * 点击的监听器
906 | */
907 | class ClickTextListener implements OnClickListener {
908 | private String sign = ""; //标识
909 | private String text = ""; //点击的文本
910 | private int position = -1; //文本的起点位置
911 |
912 | ClickTextListener(String sign) {
913 | this.sign = sign;
914 | }
915 |
916 | ClickTextListener(CharSequence text, String sign) {
917 | this.sign = sign;
918 | this.text = String.valueOf(text);
919 | }
920 |
921 | ClickTextListener(CharSequence text, int position, String sign) {
922 | this.sign = sign;
923 | this.position = position;
924 | this.text = String.valueOf(text);
925 | }
926 |
927 |
928 | public String getSign() {
929 | return this.sign;
930 | }
931 |
932 | public void setSign(String sign) {
933 | this.sign = sign;
934 | }
935 |
936 | public void onClick(View v) {
937 |
938 | //回调点击事件
939 | mTextLinkClickListener.onTextLinkClick(v, this.text, position, this.sign);
940 |
941 | //实现点击显示一下背景
942 | //显示100毫秒背景后 变回透明
943 | postDelayed(new Runnable() {
944 | public void run() {
945 | setHighlightColor(Color.TRANSPARENT);
946 | setHighlightColor(mDefaultHintColor);
947 | }
948 | }, 100);
949 | }
950 | }
951 |
952 |
953 | class ClickImageListener implements OnClickListener {
954 | private String sign = ""; //标识
955 | private int position = -1; //起始位置
956 |
957 | private String text = ""; // 替换的文本
958 |
959 | ClickImageListener(int position) {
960 | this.position = position;
961 | }
962 |
963 | ClickImageListener(int position, String sign) {
964 | this.sign = sign;
965 | this.position = position;
966 | }
967 |
968 | ClickImageListener(int position, String text, String sign) {
969 | this.sign = sign;
970 | this.position = position;
971 | }
972 |
973 |
974 | public String getSign() {
975 | return this.sign;
976 | }
977 |
978 | public void setSign(String sign) {
979 | this.sign = sign;
980 | }
981 |
982 | public void onClick(View v) {
983 |
984 | //回调点击事件
985 | mImageLinkClickListener.onImageLinkClick(v, position, this.sign);
986 |
987 | }
988 | }
989 |
990 |
991 | /**
992 | * 点击的Span
993 | */
994 | class ClickTextSpan extends ClickableSpan {
995 |
996 | private ClickTextListener clickListener; //点击监听器
997 | private int textColor = 0; //文本颜色
998 | private boolean isUnderLineVisiable = true; //是否显示下划线
999 |
1000 | ClickTextSpan(OnClickListener listener) {
1001 | this.clickListener = (ClickTextListener) listener;
1002 | this.isUnderLineVisiable = this.isUnderLineVisiable;
1003 | }
1004 |
1005 | ClickTextSpan(OnClickListener listener, boolean isUnderLineVisiable) {
1006 | this.clickListener = (ClickTextListener) listener;
1007 | this.isUnderLineVisiable = isUnderLineVisiable;
1008 | }
1009 |
1010 | ClickTextSpan(OnClickListener listener, boolean isUnderLineVisiable, int textColor) {
1011 | this.clickListener = (ClickTextListener) listener;
1012 | this.isUnderLineVisiable = isUnderLineVisiable;
1013 | this.textColor = textColor;
1014 | }
1015 |
1016 | public void onClick(View view) {
1017 | this.clickListener.onClick(view);
1018 | }
1019 |
1020 | @Override
1021 | public void updateDrawState(TextPaint ds) {
1022 | if (this.textColor != 0) {
1023 | ds.setColor(this.textColor);
1024 | } else {
1025 | ds.setColor(ds.linkColor);
1026 | }
1027 | ds.setUnderlineText(this.isUnderLineVisiable);
1028 | ds.clearShadowLayer();
1029 | }
1030 | }
1031 |
1032 |
1033 | class ClickImageSpan extends ClickableSpan {
1034 |
1035 | private ClickImageListener clickListener; //点击监听器
1036 |
1037 | ClickImageSpan(OnClickListener listener) {
1038 | this.clickListener = (ClickImageListener) listener;
1039 | }
1040 |
1041 | public void onClick(View view) {
1042 | this.clickListener.onClick(view);
1043 | }
1044 |
1045 | }
1046 |
1047 |
1048 | /**
1049 | * 取消下划线类
1050 | */
1051 | @SuppressLint({"ParcelCreator"})
1052 | private class NOUnderlineSpan extends UnderlineSpan {
1053 | NOUnderlineSpan() {}
1054 |
1055 | public void updateDrawState(TextPaint ds) {
1056 | ds.setUnderlineText(false);
1057 | }
1058 | }
1059 |
1060 | /**
1061 | * 圆角背景类
1062 | */
1063 | @SuppressLint({"ParcelCreator"})
1064 | class RadiusBackgroundSpan extends ReplacementSpan {
1065 | private int mColor;
1066 | private int mRadius;
1067 | private int mSize;
1068 |
1069 | public RadiusBackgroundSpan(@ColorInt int color, int radius) {
1070 | this.mColor = color;
1071 | this.mRadius = radius;
1072 | }
1073 |
1074 | @Override
1075 | public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
1076 | this.mSize = (int) (paint.measureText(text, start, end) + ((float) (this.mRadius * 2)));
1077 | return this.mSize;
1078 | }
1079 |
1080 | @Override
1081 | public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
1082 | int color = paint.getColor();
1083 | paint.setColor(this.mColor);
1084 | paint.setAntiAlias(true);
1085 | canvas.drawRoundRect(new RectF(x, ((float) y) + paint.ascent(), ((float) this.mSize) + x, ((float) y) + paint.descent()), (float) this.mRadius, (float) this.mRadius, paint);
1086 | paint.setColor(color);
1087 | canvas.drawText(text, start, end, x + ((float) this.mRadius), (float) y, paint);
1088 | }
1089 | }
1090 |
1091 |
1092 | /**
1093 | * 图片和文字居中类
1094 | */
1095 | public class CenterImageSpan extends ImageSpan {
1096 |
1097 | public CenterImageSpan(Context context, final int drawableRes) {
1098 | super(context, drawableRes);
1099 | }
1100 |
1101 | public CenterImageSpan(Drawable drawables) {
1102 | super(drawables);
1103 | }
1104 |
1105 | @Override
1106 | public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
1107 | Drawable d = getDrawable();
1108 | Rect rect = d.getBounds();
1109 | if (fm != null) {
1110 | FontMetricsInt fmPaint = paint.getFontMetricsInt();
1111 | int fontHeight = fmPaint.bottom - fmPaint.top;
1112 | int drHeight = rect.bottom - rect.top;
1113 |
1114 | int top = drHeight / 2 - fontHeight / 4;
1115 | int bottom = drHeight / 2 + fontHeight / 4;
1116 |
1117 | fm.ascent = -bottom;
1118 | fm.top = -bottom;
1119 | fm.bottom = top;
1120 | fm.descent = top;
1121 | }
1122 | return rect.right;
1123 | }
1124 |
1125 | @Override
1126 | public void draw(@NonNull Canvas canvas, CharSequence text,
1127 | int start, int end, float x,
1128 | int top, int y, int bottom, @NonNull Paint paint) {
1129 | //要显示的Drawable
1130 | Drawable b = getDrawable();
1131 | // font metrics of text to be replaced
1132 | FontMetricsInt fm = paint.getFontMetricsInt();
1133 | int transY = (y + fm.descent + y + fm.ascent) / 2 - b.getBounds().bottom / 2;
1134 |
1135 | canvas.save();
1136 | canvas.translate(x, transY);
1137 | b.draw(canvas);
1138 | canvas.restore();
1139 | }
1140 | }
1141 |
1142 |
1143 | /**
1144 | * 设置跑马灯效果,默认是无限次。如果需要设置次数,则使用 {@link #setMarqueeRepeatLimit}
1145 | *
1146 | * @param isMarquees 是否开启
1147 | */
1148 | public void setMarquee(boolean isMarquees) {
1149 | if (isMarquees) {
1150 | setEllipsize(TextUtils.TruncateAt.MARQUEE);
1151 | setFocusable(true);
1152 | setFocusableInTouchMode(true);
1153 | setMarqueeRepeatLimit(-1);
1154 | } else {
1155 | setEllipsize(TextUtils.TruncateAt.END);
1156 | setFocusable(false);
1157 | setFocusableInTouchMode(false);
1158 | }
1159 | this.mIsOpenMarquees = isMarquees;
1160 | }
1161 |
1162 |
1163 | @Override
1164 | public boolean isFocused() {
1165 | return mIsOpenMarquees || super.isFocused();
1166 | }
1167 |
1168 |
1169 | //屏蔽点击
1170 | @Override
1171 | public boolean performClick() {
1172 | return mLinkHit || super.performClick();
1173 | }
1174 |
1175 | @SuppressLint("ClickableViewAccessibility")
1176 | @Override
1177 | public boolean onTouchEvent(MotionEvent event) {
1178 | mLinkHit = false;
1179 | return super.onTouchEvent(event);
1180 | }
1181 |
1182 | //设置的局部的点击之后,拦截View的点击事件
1183 | public static class LocalLinkMovementMethod extends LinkMovementMethod {
1184 |
1185 | static LocalLinkMovementMethod sInstance;
1186 |
1187 |
1188 | public static LocalLinkMovementMethod getInstance() {
1189 | if (sInstance == null)
1190 | sInstance = new LocalLinkMovementMethod();
1191 |
1192 | return sInstance;
1193 | }
1194 |
1195 | @Override
1196 | public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
1197 | int action = event.getAction();
1198 |
1199 | if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
1200 | int x = (int) event.getX();
1201 | int y = (int) event.getY();
1202 |
1203 | x -= widget.getTotalPaddingLeft();
1204 | y -= widget.getTotalPaddingTop();
1205 |
1206 | x += widget.getScrollX();
1207 | y += widget.getScrollY();
1208 |
1209 | Layout layout = widget.getLayout();
1210 | int line = layout.getLineForVertical(y);
1211 | int off = layout.getOffsetForHorizontal(line, x);
1212 |
1213 | ClickableSpan[] link = buffer.getSpans(
1214 | off, off, ClickableSpan.class);
1215 |
1216 | if (link.length != 0) {
1217 | if (action == MotionEvent.ACTION_UP) {
1218 | link[0].onClick(widget);
1219 | } else {
1220 | Selection.setSelection(buffer,
1221 | buffer.getSpanStart(link[0]),
1222 | buffer.getSpanEnd(link[0]));
1223 | }
1224 |
1225 | if (widget instanceof SpanTextView) {
1226 | ((SpanTextView) widget).mLinkHit = true;
1227 | }
1228 | return true;
1229 | } else {
1230 | Selection.removeSelection(buffer);
1231 | Touch.onTouchEvent(widget, buffer, event);
1232 | return false;
1233 | }
1234 | }
1235 | return Touch.onTouchEvent(widget, buffer, event);
1236 | }
1237 | }
1238 |
1239 |
1240 | public interface onTextLinkClickListener {
1241 | /**
1242 | * 所有的文本点击回调方法
1243 | *
1244 | * @param view 本身的Textview
1245 | * @param text 单击的文本
1246 | * @param sign 点击事件的标识
1247 | */
1248 | void onTextLinkClick(View view, String text, int position, String sign);
1249 | }
1250 |
1251 | public interface onImageLinkClickListener {
1252 | /**
1253 | * 所有图片的点击回调方法
1254 | *
1255 | * @param view 本身的Textview
1256 | * @param position 点击的图片的位置
1257 | * @param sign 点击事件的标识
1258 | */
1259 | void onImageLinkClick(View view, int position, String sign);
1260 | }
1261 |
1262 |
1263 | }
1264 |
--------------------------------------------------------------------------------