├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable │ │ │ ├── tag.png │ │ │ ├── label.png │ │ │ ├── test_img.jpg │ │ │ ├── ic_bulletin.png │ │ │ ├── timeline_card_small_web.png │ │ │ ├── timeline_card_small_video.png │ │ │ └── timeline_card_small_article.png │ │ ├── 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 │ │ └── cn │ │ └── iwgang │ │ └── simplifyspandemo │ │ └── MainActivity.java ├── proguard-rules.pro └── build.gradle ├── library ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── cn │ │ └── iwgang │ │ └── simplifyspan │ │ ├── other │ │ ├── OnClickStateChangeListener.java │ │ ├── SpecialGravity.java │ │ ├── SpecialConvertMode.java │ │ ├── OnClickableSpanListener.java │ │ └── CustomLinkMovementMethod.java │ │ ├── unit │ │ ├── SpecialRawSpanUnit.java │ │ ├── BaseSpecialUnit.java │ │ ├── SpecialClickableUnit.java │ │ ├── SpecialImageUnit.java │ │ ├── SpecialTextUnit.java │ │ └── SpecialLabelUnit.java │ │ ├── customspan │ │ ├── CustomAbsoluteSizeSpan.java │ │ ├── CustomClickableSpan.java │ │ ├── CustomImageSpan.java │ │ └── CustomLabelSpan.java │ │ └── SimplifySpanBuild.java ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── build_push_maven.gradle ├── settings.gradle ├── demo.apk ├── screenshot ├── s1.png ├── s2.gif └── s3.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── LICENSE ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | -------------------------------------------------------------------------------- /demo.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/demo.apk -------------------------------------------------------------------------------- /screenshot/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/screenshot/s1.png -------------------------------------------------------------------------------- /screenshot/s2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/screenshot/s2.gif -------------------------------------------------------------------------------- /screenshot/s3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/screenshot/s3.gif -------------------------------------------------------------------------------- /app/src/main/res/drawable/tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/drawable/tag.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SimplifySpan 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Library 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/drawable/label.png -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/test_img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/drawable/test_img.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bulletin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/drawable/ic_bulletin.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/timeline_card_small_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/drawable/timeline_card_small_web.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/timeline_card_small_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/drawable/timeline_card_small_video.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/timeline_card_small_article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwgang/SimplifySpan/HEAD/app/src/main/res/drawable/timeline_card_small_article.png -------------------------------------------------------------------------------- /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 | #Sat Nov 21 15:36:38 CST 2020 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-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # generated files 2 | bin/ 3 | gen/ 4 | *target/ 5 | *build/ 6 | .gradle/ 7 | .idea/ 8 | 9 | # built application files 10 | *.ap_ 11 | 12 | # files for the dex VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # Eclipse project files 19 | .project 20 | .classpath 21 | .settings/ 22 | 23 | # Other 24 | .DS_Store 25 | /local.properties 26 | *.iml -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/other/OnClickStateChangeListener.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.other; 2 | 3 | /** 4 | * Click State Change Listener 5 | * Created by iWgang on 15/12/19. 6 | * https://github.com/iwgang/SimplifySpan 7 | */ 8 | public interface OnClickStateChangeListener { 9 | void onStateChange(boolean isSelected, int pressBgColor); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/other/SpecialGravity.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.other; 2 | 3 | /** 4 | * Special Gravity 5 | * Created by iWgang on 15/12/3. 6 | * https://github.com/iwgang/SimplifySpan 7 | */ 8 | public class SpecialGravity { 9 | public static final int TOP = 1; 10 | public static final int CENTER = 2; 11 | public static final int BOTTOM = 3; 12 | } 13 | -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/other/SpecialConvertMode.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.other; 2 | 3 | /** 4 | * Special Convert Mode 5 | * Created by iWgang on 15/12/3. 6 | * https://github.com/iwgang/SimplifySpan 7 | */ 8 | public class SpecialConvertMode { 9 | public static final int ONLY_FIRST = 1; 10 | public static final int ONLY_LAST = 2; 11 | public static final int ALL = 3; 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/other/OnClickableSpanListener.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.other; 2 | 3 | import android.text.style.ClickableSpan; 4 | import android.widget.TextView; 5 | 6 | import cn.iwgang.simplifyspan.customspan.CustomClickableSpan; 7 | 8 | /** 9 | * ClickableSpan Listener 10 | * Created by iWgang on 15/12/3. 11 | * https://github.com/iwgang/SimplifySpan 12 | */ 13 | public interface OnClickableSpanListener { 14 | 15 | void onClick(TextView tv, CustomClickableSpan clickableSpan); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 10 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | } 24 | 25 | apply from: './build_push_maven.gradle' -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_GROUP_ID=com.github.iwgang 2 | POM_ARTIFACT_ID=simplifyspan 3 | POM_DESCRIPTION=A simplify to use and powerful Spannable library. 4 | POM_URL=https://github.com/iwgang/SimplifySpan 5 | 6 | POM_SCM_URL=https://github.com/iwgang/SimplifySpan 7 | POM_SCM_CONNECTION=scm:git@github.com:iwgang/SimplifySpan.git 8 | POM_SCM_DEVELOPER_CONNECTION=scm:git@github.com:iwgang/SimplifySpan.git 9 | 10 | POM_LICENCE_NAME=The MIT License (MIT) 11 | POM_LICENCE_URL=http://opensource.org/licenses/MIT 12 | POM_LICENCE_DISTRIBUTION=repo 13 | 14 | POM_DEVELOPER_ID=iwgang 15 | POM_DEVELOPER_NAME=iWgang 16 | POM_DEVELOPER_EMAIL=iwgang@163.com 17 | 18 | 19 | VERSION_NAME=2.2 -------------------------------------------------------------------------------- /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 /Users/iwgang/Dev/Android/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 | -------------------------------------------------------------------------------- /library/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 /Users/iwgang/Dev/Android/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/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | 7 | defaultConfig { 8 | applicationId "cn.iwgang.simplifyspandemo" 9 | minSdkVersion 10 10 | targetSdkVersion 25 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 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'com.android.support:appcompat-v7:25.4.0' 25 | implementation project(':library') 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/unit/SpecialRawSpanUnit.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.unit; 2 | 3 | import android.text.Spanned; 4 | 5 | /** 6 | * Special Raw Span Unit 7 | * Created by iWgang on 15/12/3. 8 | * https://github.com/iwgang/SimplifySpan 9 | */ 10 | public class SpecialRawSpanUnit extends BaseSpecialUnit { 11 | private Object spanObj; 12 | private int flags; 13 | 14 | public SpecialRawSpanUnit(String text, Object spanObj) { 15 | super(text); 16 | this.spanObj = spanObj; 17 | this.flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; 18 | } 19 | 20 | public SpecialRawSpanUnit(String text, Object spanObj, int flags) { 21 | super(text); 22 | this.spanObj = spanObj; 23 | this.flags = flags; 24 | } 25 | 26 | public Object getSpanObj() { 27 | return spanObj; 28 | } 29 | 30 | public int getFlags() { 31 | return flags; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 iWgang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/unit/BaseSpecialUnit.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.unit; 2 | 3 | import cn.iwgang.simplifyspan.other.SpecialConvertMode; 4 | import cn.iwgang.simplifyspan.other.SpecialGravity; 5 | 6 | /** 7 | * Base Special Unit 8 | * Created by iWgang on 15/12/3. 9 | * https://github.com/iwgang/SimplifySpan 10 | */ 11 | public class BaseSpecialUnit { 12 | protected String text; 13 | private int[] startPoss; 14 | protected int gravity = SpecialGravity.BOTTOM; 15 | protected int convertMode = SpecialConvertMode.ONLY_FIRST; 16 | 17 | public BaseSpecialUnit(String text) { 18 | this.text = text; 19 | } 20 | 21 | public String getText() { 22 | return text; 23 | } 24 | 25 | public int getGravity() { 26 | return gravity; 27 | } 28 | 29 | public int getConvertMode() { 30 | return convertMode; 31 | } 32 | 33 | /** 34 | * Use only in SimplifySpanBuild 35 | * @param startPoss start poss 36 | */ 37 | public void setStartPoss(int[] startPoss) { 38 | this.startPoss = startPoss; 39 | } 40 | 41 | public int[] getStartPoss() { 42 | return startPoss; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/customspan/CustomAbsoluteSizeSpan.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.customspan; 2 | 3 | import android.graphics.Rect; 4 | import android.text.TextPaint; 5 | import android.text.style.AbsoluteSizeSpan; 6 | import android.widget.TextView; 7 | 8 | import cn.iwgang.simplifyspan.other.SpecialGravity; 9 | 10 | /** 11 | * Custom AbsoluteSizeSpan 12 | * Created by iWgang on 15/12/5. 13 | * https://github.com/iwgang/SimplifySpan 14 | */ 15 | public class CustomAbsoluteSizeSpan extends AbsoluteSizeSpan { 16 | private TextPaint mTextPaint; 17 | private int mGravity; 18 | private int mOffsetBaselineShift; 19 | private String mText; 20 | private Rect mTextViewRect = new Rect(); 21 | private Rect mTextRect = new Rect(); 22 | private String mNormalSizeText; 23 | 24 | public CustomAbsoluteSizeSpan(String normalSizeText, String text, int size, TextPaint textView, int gravity) { 25 | super(size, true); 26 | mText = text; 27 | mTextPaint = textView; 28 | mGravity = gravity; 29 | mNormalSizeText = normalSizeText; 30 | } 31 | 32 | @Override 33 | public void updateDrawState(TextPaint ds) { 34 | super.updateDrawState(ds); 35 | 36 | if (mGravity == SpecialGravity.BOTTOM) return; 37 | 38 | mTextPaint.getTextBounds(mNormalSizeText, 0, mNormalSizeText.length(), mTextViewRect); 39 | ds.getTextBounds(mText, 0, mText.length(), mTextRect); 40 | int mMainTextHeight = mTextViewRect.height(); 41 | 42 | int offset = mTextViewRect.bottom - mTextRect.bottom; 43 | switch (mGravity) { 44 | case SpecialGravity.TOP: 45 | mOffsetBaselineShift = mMainTextHeight - mTextRect.height() - offset; 46 | break; 47 | case SpecialGravity.CENTER: 48 | mOffsetBaselineShift = mMainTextHeight / 2 - mTextRect.height() / 2 - offset; 49 | break; 50 | } 51 | 52 | ds.baselineShift -= mOffsetBaselineShift; 53 | } 54 | 55 | @Override 56 | public void updateMeasureState(TextPaint ds) { 57 | super.updateMeasureState(ds); 58 | ds.baselineShift -= mOffsetBaselineShift; 59 | } 60 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/other/CustomLinkMovementMethod.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.other; 2 | 3 | import android.text.Layout; 4 | import android.text.Selection; 5 | import android.text.Spannable; 6 | import android.text.method.LinkMovementMethod; 7 | import android.view.MotionEvent; 8 | import android.widget.TextView; 9 | 10 | import cn.iwgang.simplifyspan.customspan.CustomClickableSpan; 11 | 12 | /** 13 | * Custom LinkMovementMethod 14 | * Created by iWgang on 15/12/8. 15 | * https://github.com/iwgang/SimplifySpan 16 | * Ref: http://stackoverflow.com/a/20905824 17 | */ 18 | public class CustomLinkMovementMethod extends LinkMovementMethod { 19 | private static CustomLinkMovementMethod sInstance; 20 | private CustomClickableSpan mCustomClickableSpan; 21 | 22 | public static CustomLinkMovementMethod getInstance() { 23 | if (sInstance == null) 24 | sInstance = new CustomLinkMovementMethod(); 25 | 26 | return sInstance; 27 | } 28 | 29 | @Override 30 | public boolean onTouchEvent(TextView textView, Spannable spannable, MotionEvent event) { 31 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 32 | mCustomClickableSpan = getPressedSpan(textView, spannable, event); 33 | if (mCustomClickableSpan != null) { 34 | mCustomClickableSpan.setPressed(true); 35 | Selection.setSelection(spannable, spannable.getSpanStart(mCustomClickableSpan), spannable.getSpanEnd(mCustomClickableSpan)); 36 | } 37 | } else if (event.getAction() == MotionEvent.ACTION_MOVE) { 38 | CustomClickableSpan touchedSpan = getPressedSpan(textView, spannable, event); 39 | if (mCustomClickableSpan != null && touchedSpan != mCustomClickableSpan) { 40 | mCustomClickableSpan.setPressed(false); 41 | mCustomClickableSpan = null; 42 | Selection.removeSelection(spannable); 43 | } 44 | } else { 45 | if (mCustomClickableSpan != null) { 46 | mCustomClickableSpan.setPressed(false); 47 | super.onTouchEvent(textView, spannable, event); 48 | } 49 | mCustomClickableSpan = null; 50 | Selection.removeSelection(spannable); 51 | } 52 | return true; 53 | } 54 | 55 | private CustomClickableSpan getPressedSpan(TextView textView, Spannable spannable, MotionEvent event) { 56 | int x = (int) event.getX(); 57 | int y = (int) event.getY(); 58 | 59 | x -= textView.getTotalPaddingLeft(); 60 | y -= textView.getTotalPaddingTop(); 61 | 62 | x += textView.getScrollX(); 63 | y += textView.getScrollY(); 64 | 65 | Layout layout = textView.getLayout(); 66 | int line = layout.getLineForVertical(y); 67 | int off = layout.getOffsetForHorizontal(line, x); 68 | 69 | CustomClickableSpan[] link = spannable.getSpans(off, off, CustomClickableSpan.class); 70 | CustomClickableSpan touchedSpan = null; 71 | if (link.length > 0) { 72 | touchedSpan = link[0]; 73 | } 74 | return touchedSpan; 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /library/build_push_maven.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | apply plugin: 'signing' 3 | 4 | def isReleaseBuild() { 5 | return VERSION_NAME.contains("SNAPSHOT") == false 6 | } 7 | 8 | def getReleaseRepositoryUrl() { 9 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 10 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 11 | } 12 | 13 | def getSnapshotRepositoryUrl() { 14 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 15 | : "https://oss.sonatype.org/content/repositories/snapshots/" 16 | } 17 | 18 | def getRepositoryUsername() { 19 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : '' 20 | } 21 | 22 | def getRepositoryPassword() { 23 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : '' 24 | } 25 | 26 | afterEvaluate { project -> 27 | uploadArchives { 28 | repositories { 29 | mavenDeployer { 30 | //为Pom文件做数字签名 31 | beforeDeployment { deployment -> signing.signPom(deployment) } 32 | 33 | pom.groupId = POM_GROUP_ID 34 | pom.artifactId = POM_ARTIFACT_ID 35 | pom.version = VERSION_NAME 36 | 37 | repository(url: getReleaseRepositoryUrl()) { 38 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 39 | } 40 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 41 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 42 | } 43 | 44 | pom.project { 45 | name project.name 46 | packaging 'aar' 47 | description POM_DESCRIPTION 48 | url POM_URL 49 | 50 | scm { 51 | url POM_SCM_URL 52 | connection POM_SCM_CONNECTION 53 | developerConnection POM_SCM_DEVELOPER_CONNECTION 54 | } 55 | 56 | licenses { 57 | license { 58 | name POM_LICENCE_NAME 59 | url POM_LICENCE_URL 60 | distribution POM_LICENCE_DISTRIBUTION 61 | } 62 | } 63 | 64 | developers { 65 | developer { 66 | id POM_DEVELOPER_ID 67 | name POM_DEVELOPER_NAME 68 | email POM_DEVELOPER_EMAIL 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | signing { 77 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 78 | sign configurations.archives 79 | } 80 | 81 | task androidJavadocs(type: Javadoc) { 82 | source = android.sourceSets.main.java.srcDirs 83 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 84 | } 85 | 86 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 87 | classifier = 'javadoc' 88 | from androidJavadocs.destinationDir 89 | } 90 | 91 | task androidSourcesJar(type: Jar) { 92 | classifier = 'sources' 93 | from android.sourceSets.main.java.srcDirs 94 | } 95 | 96 | artifacts { 97 | archives androidSourcesJar 98 | archives androidJavadocsJar 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/unit/SpecialClickableUnit.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.unit; 2 | 3 | import android.widget.TextView; 4 | 5 | import java.util.List; 6 | 7 | import cn.iwgang.simplifyspan.other.OnClickStateChangeListener; 8 | import cn.iwgang.simplifyspan.other.OnClickableSpanListener; 9 | 10 | /** 11 | * Clickable Special Unit 12 | * Created by iWgang on 15/12/7. 13 | * https://github.com/iwgang/SimplifySpan 14 | */ 15 | public class SpecialClickableUnit extends BaseSpecialUnit { 16 | private List onClickStateChangeListeners; 17 | private OnClickableSpanListener onClickListener; 18 | private TextView curTextView; 19 | private int normalBgColor; 20 | private int pressBgColor; 21 | // only text 22 | private boolean isShowUnderline; 23 | private int normalTextColor; 24 | private int pressTextColor; 25 | private Object tag; 26 | 27 | public SpecialClickableUnit(TextView curTextView, OnClickableSpanListener onClickListener) { 28 | super(null); 29 | this.curTextView = curTextView; 30 | this.onClickListener = onClickListener; 31 | } 32 | 33 | public SpecialClickableUnit showUnderline() { 34 | this.isShowUnderline = true; 35 | return this; 36 | } 37 | 38 | public SpecialClickableUnit setPressBgColor(int pressBgColor) { 39 | this.pressBgColor = pressBgColor; 40 | return this; 41 | } 42 | 43 | public SpecialClickableUnit setPressTextColor(int pressTextColor) { 44 | this.pressTextColor = pressTextColor; 45 | return this; 46 | } 47 | 48 | public SpecialClickableUnit setNormalBgColor(int normalBgColor) { 49 | this.normalBgColor = normalBgColor; 50 | return this; 51 | } 52 | 53 | public SpecialClickableUnit setNormalTextColor(int normalTextColor) { 54 | this.normalTextColor = normalTextColor; 55 | return this; 56 | } 57 | 58 | /** 59 | * Do not call !!! (Use only in SimplifySpanBuild) 60 | * 61 | * @param onClickStateChangeListeners OnClickStateChangeListener list 62 | */ 63 | public void setOnClickStateChangeListeners(List onClickStateChangeListeners) { 64 | this.onClickStateChangeListeners = onClickStateChangeListeners; 65 | } 66 | 67 | public OnClickableSpanListener getOnClickListener() { 68 | return onClickListener; 69 | } 70 | 71 | public int getPressTextColor() { 72 | return pressTextColor; 73 | } 74 | 75 | public int getPressBgColor() { 76 | return pressBgColor; 77 | } 78 | 79 | public int getNormalBgColor() { 80 | return normalBgColor; 81 | } 82 | 83 | public int getNormalTextColor() { 84 | return normalTextColor; 85 | } 86 | 87 | public TextView getCurTextView() { 88 | return curTextView; 89 | } 90 | 91 | public List getOnClickStateChangeListeners() { 92 | return onClickStateChangeListeners; 93 | } 94 | 95 | public boolean isShowUnderline() { 96 | return isShowUnderline; 97 | } 98 | 99 | /** 100 | * Do not call !!! (Use only in SimplifySpanBuild) 101 | * 102 | * @param text text 103 | */ 104 | public void setText(String text) { 105 | this.text = text; 106 | } 107 | 108 | public Object getTag() { 109 | return tag; 110 | } 111 | 112 | public SpecialClickableUnit setTag(Object tag) { 113 | this.tag = tag; 114 | return this; 115 | } 116 | } -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/customspan/CustomClickableSpan.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.customspan; 2 | 3 | import android.graphics.Color; 4 | import android.text.Spanned; 5 | import android.text.TextPaint; 6 | import android.text.style.ClickableSpan; 7 | import android.view.View; 8 | import android.widget.TextView; 9 | 10 | import java.util.List; 11 | 12 | import cn.iwgang.simplifyspan.other.OnClickStateChangeListener; 13 | import cn.iwgang.simplifyspan.other.OnClickableSpanListener; 14 | import cn.iwgang.simplifyspan.unit.SpecialClickableUnit; 15 | 16 | /** 17 | * Custom ClickableSpan 18 | * Created by iWgang on 15/12/8. 19 | * https://github.com/iwgang/SimplifySpan 20 | */ 21 | public class CustomClickableSpan extends ClickableSpan { 22 | private List mOnClickStateChangeListeners; 23 | private OnClickableSpanListener mOnClickableSpanListener; 24 | private boolean isPressed; 25 | private boolean isShowUnderline; 26 | private int mTextColorNor; 27 | private int mTextColorPre; 28 | private int mBgColorNor; 29 | private int mBgColorPre; 30 | private Object mTag; 31 | private String mClickText; 32 | private int mStartSpanIndex; 33 | private int mEndSpanIndex; 34 | 35 | public CustomClickableSpan(SpecialClickableUnit specialClickableUnit) { 36 | mTag = specialClickableUnit.getTag(); 37 | mTextColorNor = specialClickableUnit.getNormalTextColor(); 38 | mTextColorPre = specialClickableUnit.getPressTextColor(); 39 | mBgColorNor = specialClickableUnit.getNormalBgColor(); 40 | mBgColorPre = specialClickableUnit.getPressBgColor(); 41 | isShowUnderline = specialClickableUnit.isShowUnderline(); 42 | mOnClickableSpanListener = specialClickableUnit.getOnClickListener(); 43 | mOnClickStateChangeListeners = specialClickableUnit.getOnClickStateChangeListeners(); 44 | } 45 | 46 | @Override 47 | public void onClick(View widget) { 48 | if (null != mOnClickableSpanListener) { 49 | TextView tv = (TextView) widget; 50 | Spanned spanned = (Spanned) tv.getText(); 51 | mStartSpanIndex = spanned.getSpanStart(this); 52 | mEndSpanIndex = spanned.getSpanEnd(this); 53 | mClickText = spanned.subSequence(mStartSpanIndex, mEndSpanIndex).toString(); 54 | mOnClickableSpanListener.onClick(tv, this); 55 | } 56 | } 57 | 58 | public void setPressed(boolean isSelected) { 59 | if (null != mOnClickStateChangeListeners && !mOnClickStateChangeListeners.isEmpty()) { 60 | for (OnClickStateChangeListener csl : mOnClickStateChangeListeners) { 61 | csl.onStateChange(isSelected, mBgColorPre); 62 | } 63 | } 64 | isPressed = isSelected; 65 | } 66 | 67 | @Override 68 | public void updateDrawState(TextPaint ds) { 69 | super.updateDrawState(ds); 70 | 71 | // set text color And press status color 72 | if (mTextColorNor != 0) { 73 | if (mTextColorPre != 0) { 74 | ds.setColor(isPressed ? mTextColorPre : mTextColorNor); 75 | } else { 76 | ds.setColor(mTextColorNor); 77 | } 78 | } 79 | 80 | // set background color And press status color 81 | if (mBgColorPre != 0) { 82 | ds.bgColor = isPressed ? mBgColorPre : mBgColorNor == 0 ? Color.TRANSPARENT : mBgColorNor; 83 | } else if (mBgColorNor != 0) { 84 | ds.bgColor = mBgColorNor; 85 | } 86 | 87 | if (!isShowUnderline) { 88 | // clear underline 89 | ds.setUnderlineText(false); 90 | } 91 | } 92 | 93 | /** 94 | * get tag 95 | * @return tag object 96 | */ 97 | public Object getTag() { 98 | return mTag; 99 | } 100 | 101 | /** 102 | * get current click text 103 | * @return click text 104 | */ 105 | public String getClickText() { 106 | return mClickText; 107 | } 108 | 109 | /** 110 | * get current click text span start index in total 111 | * @return start span index 112 | */ 113 | public int getStartSpanIndex() { 114 | return mStartSpanIndex; 115 | } 116 | 117 | /** 118 | * get current click text span start index in end 119 | * @return end span index 120 | */ 121 | public int getEndSpanIndex() { 122 | return mEndSpanIndex; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/customspan/CustomImageSpan.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.customspan; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Rect; 6 | import android.graphics.drawable.Drawable; 7 | import android.text.style.ImageSpan; 8 | 9 | import java.lang.ref.WeakReference; 10 | 11 | import cn.iwgang.simplifyspan.other.OnClickStateChangeListener; 12 | import cn.iwgang.simplifyspan.other.SpecialGravity; 13 | import cn.iwgang.simplifyspan.unit.SpecialImageUnit; 14 | 15 | /** 16 | * Custom ImageSpan 17 | * Created by iWgang on 15/12/3. 18 | * https://github.com/iwgang/SimplifySpan 19 | */ 20 | public class CustomImageSpan extends ImageSpan implements OnClickStateChangeListener { 21 | private WeakReference mDrawableRef; 22 | private int gravity; 23 | private Rect mRect = new Rect(); 24 | private String mNormalSizeText; 25 | private int mBgColor; 26 | private boolean isSelected; 27 | private boolean isClickable; 28 | private int pressBgColor; 29 | 30 | private int mLineTextHeight; 31 | private int mLineTextBaselineOffset; 32 | 33 | public CustomImageSpan(String normalSizeText, SpecialImageUnit specialImageUnit) { 34 | super(specialImageUnit.getContext(), specialImageUnit.getBitmap(), ALIGN_BASELINE); 35 | this.gravity = specialImageUnit.getGravity(); 36 | this.mNormalSizeText = normalSizeText; 37 | this.mBgColor = specialImageUnit.getBgColor(); 38 | this.isClickable = specialImageUnit.isClickable(); 39 | } 40 | 41 | @Override 42 | public void onStateChange(boolean isSelected, int pressBgColor) { 43 | this.isSelected = isSelected; 44 | this.pressBgColor = pressBgColor; 45 | } 46 | 47 | @Override 48 | public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 49 | Drawable b = getCachedDrawable(); 50 | int drawableHeight = b.getIntrinsicHeight(); 51 | int drawableWidth = b.getIntrinsicWidth(); 52 | paint.getTextBounds(mNormalSizeText, 0, mNormalSizeText.length(), mRect); 53 | 54 | float finalUnitHeight = bottom - top; 55 | float bgTop = bottom - finalUnitHeight; 56 | 57 | if (isClickable && isSelected && pressBgColor != 0) { 58 | // click background 59 | paint.setColor(pressBgColor); 60 | canvas.drawRect(x, bgTop, x + drawableWidth, bgTop + finalUnitHeight, paint); 61 | } else { 62 | // normal background 63 | if (mBgColor != 0) { 64 | paint.setColor(mBgColor); 65 | canvas.drawRect(x, bgTop, x + drawableWidth, bgTop + finalUnitHeight, paint); 66 | } 67 | } 68 | 69 | int textHeight = mRect.height(); 70 | if (drawableHeight > textHeight) { 71 | super.draw(canvas, text, start, end, x, top, y, bottom, paint); 72 | return; 73 | } 74 | canvas.save(); 75 | 76 | if (mLineTextHeight <= 0) { 77 | Rect specialTextRect = new Rect(); 78 | paint.getTextBounds(mNormalSizeText, 0, mNormalSizeText.length(), specialTextRect); 79 | mLineTextHeight = specialTextRect.height(); 80 | mLineTextBaselineOffset = specialTextRect.bottom; 81 | } 82 | 83 | float newStartY = y; 84 | switch (gravity) { 85 | case SpecialGravity.TOP: { 86 | newStartY -= mLineTextHeight - mLineTextBaselineOffset; 87 | canvas.translate(x, newStartY); 88 | break; 89 | } 90 | case SpecialGravity.CENTER: { 91 | newStartY -= (mLineTextHeight / 2 + drawableHeight / 2 - mLineTextBaselineOffset); 92 | canvas.translate(x, newStartY); 93 | break; 94 | } 95 | case SpecialGravity.BOTTOM: { 96 | newStartY -= drawableHeight - mLineTextBaselineOffset; 97 | canvas.translate(x, newStartY); 98 | break; 99 | } 100 | } 101 | b.draw(canvas); 102 | canvas.restore(); 103 | } 104 | 105 | private Drawable getCachedDrawable() { 106 | Drawable drawable = null; 107 | 108 | if (mDrawableRef != null) { 109 | drawable = mDrawableRef.get(); 110 | } 111 | 112 | if (drawable == null) { 113 | drawable = getDrawable(); 114 | mDrawableRef = new WeakReference<>(drawable); 115 | } 116 | 117 | return drawable; 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/unit/SpecialImageUnit.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.unit; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | 6 | /** 7 | * Image Special Unit 8 | * Created by iWgang on 15/12/3. 9 | * https://github.com/iwgang/SimplifySpan 10 | */ 11 | public class SpecialImageUnit extends BaseSpecialUnit { 12 | private static final String DEF_IMG_PLACEHOLDER = "img"; 13 | 14 | private Context context; 15 | private Bitmap bitmap; 16 | private int bgColor; 17 | private boolean isClickable; 18 | private int width; // px 19 | private int height; // px 20 | 21 | /** 22 | * @param context Context 23 | * @param bitmap Bitmap 24 | */ 25 | public SpecialImageUnit(Context context, Bitmap bitmap) { 26 | this(context, DEF_IMG_PLACEHOLDER, bitmap); 27 | } 28 | 29 | /** 30 | * @param context Context 31 | * @param specialText Special Text 32 | * @param bitmap Bitmap 33 | */ 34 | public SpecialImageUnit(Context context, String specialText, Bitmap bitmap) { 35 | super(specialText); 36 | this.context = context; 37 | this.bitmap = bitmap; 38 | } 39 | 40 | /** 41 | * @param context Context 42 | * @param bitmap Bitmap 43 | * @param width Width 44 | * @param height Height 45 | */ 46 | public SpecialImageUnit(Context context, Bitmap bitmap, int width, int height) { 47 | this(context, DEF_IMG_PLACEHOLDER, bitmap, width, height); 48 | } 49 | 50 | /** 51 | * @param context Context 52 | * @param specialText Special Text 53 | * @param bitmap Bitmap 54 | * @param width Width 55 | * @param height Height 56 | */ 57 | public SpecialImageUnit(Context context, String specialText, Bitmap bitmap, int width, int height) { 58 | super(specialText); 59 | this.context = context; 60 | this.bitmap = bitmap; 61 | this.width = width; 62 | this.height = height; 63 | } 64 | 65 | /** 66 | * @param context Context 67 | * @param bitmap Bitmap 68 | * @param width Width 69 | * @param height Height 70 | * @param gravity Use SpecialGravity.xx 71 | */ 72 | public SpecialImageUnit(Context context, Bitmap bitmap, int width, int height, int gravity) { 73 | this(context, DEF_IMG_PLACEHOLDER, bitmap, width, height, gravity); 74 | } 75 | 76 | /** 77 | * @param context Context 78 | * @param specialText Special Text 79 | * @param bitmap Bitmap 80 | * @param width Width 81 | * @param height Height 82 | * @param gravity Use SpecialGravity.xx 83 | */ 84 | public SpecialImageUnit(Context context, String specialText, Bitmap bitmap, int width, int height, int gravity) { 85 | super(specialText); 86 | this.context = context; 87 | this.bitmap = bitmap; 88 | this.width = width; 89 | this.height = height; 90 | this.gravity = gravity; 91 | } 92 | 93 | /** 94 | * Set Background Color 95 | * @param bgColor background color 96 | * @return this 97 | */ 98 | public SpecialImageUnit setBgColor(int bgColor) { 99 | this.bgColor = bgColor; 100 | return this; 101 | } 102 | 103 | /** 104 | * Set Gravity 105 | * @param gravity use SpecialGravity.xx 106 | * @return this 107 | */ 108 | public SpecialImageUnit setGravity(int gravity) { 109 | this.gravity = gravity; 110 | return this; 111 | } 112 | 113 | /** 114 | * Set Convert Mode 115 | * @param convertMode use SpecialConvertMode.xx 116 | * @return this 117 | */ 118 | public SpecialImageUnit setConvertMode(int convertMode) { 119 | this.convertMode = convertMode; 120 | return this; 121 | } 122 | 123 | public boolean isClickable() { 124 | return isClickable; 125 | } 126 | 127 | /** 128 | * Use only in SimplifySpanBuild 129 | * @param clickable clickable 130 | */ 131 | public void setClickable(boolean clickable) { 132 | isClickable = clickable; 133 | } 134 | 135 | public void setBitmap(Bitmap bitmap) { 136 | this.bitmap = bitmap; 137 | } 138 | 139 | public Bitmap getBitmap() { 140 | return bitmap; 141 | } 142 | 143 | public int getWidth() { 144 | return width; 145 | } 146 | 147 | public int getHeight() { 148 | return height; 149 | } 150 | 151 | public int getBgColor() { 152 | return bgColor; 153 | } 154 | 155 | public Context getContext() { 156 | return context; 157 | } 158 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Android Gems](http://www.android-gems.com/badge/iwgang/SimplifySpan.svg?branch=master)](http://www.android-gems.com/lib/iwgang/SimplifySpan) 2 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-SimplifySpan-green.svg?style=true)](http://android-arsenal.com/details/1/2907) 3 | [![@iwgang](https://img.shields.io/badge/weibo-%40iwgang-blue.svg)](http://weibo.com/iwgang) 4 | 5 | # SimplifySpan 6 | A easy-to-use and powerful Spannable library 7 | 8 | ### screenshot 9 | ![](https://raw.githubusercontent.com/iwgang/SimplifySpan/master/screenshot/s1.png) 10 | ![](https://raw.githubusercontent.com/iwgang/SimplifySpan/master/screenshot/s3.gif) 11 | 12 | ### gradle 13 | implementation 'com.github.iwgang:simplifyspan:2.2' 14 | 15 | ### Support Units 16 | * SpecialTextUnit 17 | * **text** (Constructor | String) 18 | * **gravity** (setGravity(gravity | int)) SpecialGravity.TOP, SpecialGravity.CENTER, SpecialGravity.BOTTOM 19 | * **convertMode** (setConvertMode(convertMode | int)) SpecialConvertMode.ONLY_FIRST, SpecialConvertMode.ALL, SpecialConvertMode.ONLY_LAST 20 | * **textColor** (Constructor Or setTextColor(int color)) 21 | * **textSize** (Constructor Or setTextSize(float size)) ps 22 | * **backgroundColor** (setTextBackgroundColor(int color)) 23 | * **showUnderline** (showUnderline()) 24 | * **showStrikeThrough** (showStrikeThrough()) 25 | * **textBold** (useTextBold()) 26 | * **textItalic** (useTextItalic()) 27 | * **textStyle** (setTextStyle(Typeface style)) 28 | * **clickableUnit** (setClickableUnit(SpecialClickableUnit)) 29 | * SpecialLabelUnit 30 | * **text** (Constructor | String) 31 | * **gravity** (setGravity(gravity | int)) SpecialGravity.TOP, SpecialGravity.CENTER, SpecialGravity.BOTTOM 32 | * **convertMode** (setConvertMode(convertMode | int)) SpecialConvertMode.ONLY_FIRST, SpecialConvertMode.ALL, SpecialConvertMode.ONLY_LAST 33 | * **labelTextColor** (Constructor | int color) 34 | * **labelTextSize** (Constructor | int color) sp 35 | * **labelBgColor** (Constructor | int color) 36 | * **bitmap** (Constructor | Bitmap) 37 | * **labelBgRadius** (setLabelBgRadius(float radius)) Only support labelBgColor 38 | * **labelBgWidth** And **labelBgHeight** (Constructor Or setLabelBgSize(int width, int height)) px 39 | * **padding** (setPadding(int padding)) px 40 | * **paddingLeft** (setPaddingLeft(int padding)) px 41 | * **paddingRight** (setPaddingRight(int padding)) px 42 | * **labelBgBorderColor** And **borderSize** (showBorder(int labelBgBorderColor, float borderSize | px)) 43 | * **textBold** (useTextBold()) 44 | * **textItalic** (useTextItalic()) 45 | * **clickable** See SimplifySpanBuild.appendMultiClickable() Or SimplifySpanBuild.appendMultiClickableToFirst() 46 | * SpecialImageUnit 47 | * **text** (Constructor | String) 48 | * **gravity** (setGravity(gravity | int)) SpecialGravity.TOP, SpecialGravity.CENTER, SpecialGravity.BOTTOM 49 | * **convertMode** (setConvertMode(convertMode | int)) SpecialConvertMode.ONLY_FIRST, SpecialConvertMode.ALL, SpecialConvertMode.ONLY_LAST 50 | * **bitmap** (Constructor) 51 | * **width** And **height** (Constructor) px 52 | * **clickable** See SimplifySpanBuild.appendMultiClickable() Or SimplifySpanBuild.appendMultiClickableToFirst() 53 | * SpecialClickableUnit 54 | * **curTextView** (Constructor | TextView) 55 | * **onClickListener** (Constructor | OnClickableSpanListener) 56 | * **isShowUnderline** (showUnderline()) 57 | * **pressTextColor** (setPressTextColor(int color)) 58 | * **pressBgColor** (setPressBgColor(int color)) 59 | * **normalTextColor** (setNormalTextColor(int color)) 60 | * **normalBgColor** (setNormalBgColor(int color)) 61 | * SpecialRawSpanUnit 62 | * **text** (Constructor | String) 63 | * **spanObj** (Constructor) Spannable Object 64 | * **flags** (Constructor | int) Spannable flags 65 | 66 | ### Support Methods 67 | * SimplifySpanBuild 68 | * **append** (string | Units) 69 | * **appendToFirst** (string | Units) 70 | * **appendMultiClickable** () 71 | * **appendMultiClickableToFirst** () 72 | * **build** () 73 | 74 | ### how to use ? 75 | [Sample Code](https://github.com/iwgang/SimplifySpan/blob/master/app/src/main/java/cn/iwgang/simplifyspandemo/MainActivity.java) 76 | ``` 77 | // sample 1 78 | tvText.setText(new SimplifySpanBuild("距离您:").append(new SpecialTextUnit("385", Color.BLUE)).append(" 米").build()); 79 | 80 | // sample 2 81 | CharSequence spannableString = new SimplifySpanBuild(" 艾客优品雷霆Dock 2 雷电转USB3.0/火线/esata 扩展HUB") 82 | .appendToFirst(new SpecialLabelUnit("1212", Color.WHITE, sp2px(8), Color.RED, 70, 35).useTextBold().setGravity(SpecialGravity.CENTER)) 83 | .appendToFirst(new SpecialLabelUnit("天猫", Color.WHITE, sp2px(8), 0xFFFF5000, 60, 35).setGravity(SpecialGravity.CENTER)) 84 | .build(); 85 | tvText.setText(spannableString); 86 | ``` 87 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 20 | 21 | 30 | 31 | 39 | 40 | 46 | 47 | 56 | 57 | 65 | 66 | 74 | 75 | 84 | 85 | 93 | 94 | 103 | 104 | 114 | 115 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/unit/SpecialTextUnit.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.unit; 2 | 3 | import android.graphics.Typeface; 4 | import android.text.TextPaint; 5 | import android.widget.TextView; 6 | 7 | /** 8 | * Text Special Unit 9 | * Created by iWgang on 15/12/3. 10 | * https://github.com/iwgang/SimplifySpan 11 | */ 12 | public class SpecialTextUnit extends BaseSpecialUnit { 13 | private int textStyle = Typeface.NORMAL; 14 | private int textColor; 15 | private int textBackgroundColor; 16 | private float textSize; // sp 17 | private boolean isShowUnderline; 18 | private boolean isShowStrikeThrough; 19 | private boolean isTextBold; 20 | private boolean isTextItalic; 21 | private SpecialClickableUnit specialClickableUnit; 22 | private TextView curTextView; 23 | private TextPaint curTextPaint; 24 | 25 | /** 26 | * @param text text 27 | */ 28 | public SpecialTextUnit(String text) { 29 | super(text); 30 | } 31 | 32 | /** 33 | * @param text text 34 | * @param textColor Text Color 35 | */ 36 | public SpecialTextUnit(String text, int textColor) { 37 | this(text); 38 | this.textColor = textColor; 39 | } 40 | 41 | /** 42 | * @param text text 43 | * @param textColor text color 44 | * @param textSize text size (unit:sp) 45 | */ 46 | public SpecialTextUnit(String text, int textColor, float textSize) { 47 | this(text); 48 | this.textColor = textColor; 49 | this.textSize = textSize; 50 | } 51 | 52 | /** 53 | * @param text text 54 | * @param textColor text color 55 | * @param textSize text size (unit:sp) 56 | * @param gravity use SpecialGravity.xx 57 | * @param curTextView current TextView 58 | */ 59 | public SpecialTextUnit(String text, int textColor, float textSize, int gravity, TextView curTextView) { 60 | this(text); 61 | this.textColor = textColor; 62 | this.textSize = textSize; 63 | this.gravity = gravity; 64 | this.curTextView = curTextView; 65 | if (curTextView != null) { 66 | this.curTextPaint = curTextView.getPaint(); 67 | } 68 | } 69 | 70 | /** 71 | * Show StrikeThrough 72 | * 73 | * @return SpecialTextUnit 74 | */ 75 | public SpecialTextUnit showStrikeThrough() { 76 | isShowStrikeThrough = true; 77 | return this; 78 | } 79 | 80 | /** 81 | * Show Underline 82 | * 83 | * @return SpecialTextUnit 84 | */ 85 | public SpecialTextUnit showUnderline() { 86 | isShowUnderline = true; 87 | return this; 88 | } 89 | 90 | /** 91 | * Use Text Bold 92 | * 93 | * @return SpecialTextUnit 94 | */ 95 | public SpecialTextUnit useTextBold() { 96 | isTextBold = true; 97 | return this; 98 | } 99 | 100 | /** 101 | * Use Text Italic 102 | * 103 | * @return SpecialTextUnit 104 | */ 105 | public SpecialTextUnit useTextItalic() { 106 | isTextItalic = true; 107 | return this; 108 | } 109 | 110 | /** 111 | * Use TextView support textStyle 112 | * 113 | * @param textStyle please see {@link Typeface} 114 | * @return SpecialTextUnit 115 | */ 116 | public SpecialTextUnit setTextStyle(int textStyle) { 117 | this.textStyle = textStyle; 118 | return this; 119 | } 120 | 121 | /** 122 | * Set Background Color 123 | * 124 | * @param textBackgroundColor color 125 | * @return SpecialTextUnit 126 | */ 127 | public SpecialTextUnit setTextBackgroundColor(int textBackgroundColor) { 128 | this.textBackgroundColor = textBackgroundColor; 129 | return this; 130 | } 131 | 132 | /** 133 | * Set Special Text Color 134 | * 135 | * @param textColor color 136 | * @return SpecialTextUnit 137 | */ 138 | public SpecialTextUnit setTextColor(int textColor) { 139 | this.textColor = textColor; 140 | return this; 141 | } 142 | 143 | /** 144 | * Set Special Text Size 145 | * 146 | * @param textSize size (sp) 147 | * @return SpecialTextUnit 148 | */ 149 | public SpecialTextUnit setTextSize(float textSize) { 150 | this.textSize = textSize; 151 | return this; 152 | } 153 | 154 | /** 155 | * Set SpecialClickableUnit 156 | * 157 | * @param specialClickableUnit SpecialClickableUnit 158 | * @return SpecialTextUnit 159 | */ 160 | public SpecialTextUnit setClickableUnit(SpecialClickableUnit specialClickableUnit) { 161 | this.specialClickableUnit = specialClickableUnit; 162 | return this; 163 | } 164 | 165 | /** 166 | * Set Gravity 167 | * 168 | * @param curTextView current TextView 169 | * @param gravity use SpecialGravity.xx 170 | * @return SpecialTextUnit 171 | */ 172 | public SpecialTextUnit setGravity(TextView curTextView, int gravity) { 173 | this.curTextView = curTextView; 174 | if (curTextView != null) { 175 | this.curTextPaint = curTextView.getPaint(); 176 | } 177 | this.gravity = gravity; 178 | return this; 179 | } 180 | 181 | /** 182 | * Set Gravity 183 | * 184 | * @param curTextPaint current TextView.getTextPaint 185 | * @param gravity use SpecialGravity.xx 186 | * @return SpecialTextUnit 187 | */ 188 | public SpecialTextUnit setGravity(TextPaint curTextPaint, int gravity) { 189 | this.curTextPaint = curTextPaint; 190 | this.gravity = gravity; 191 | return this; 192 | } 193 | 194 | /** 195 | * Set Convert Mode 196 | * 197 | * @param convertMode use SpecialConvertMode.xx 198 | * @return SpecialTextUnit 199 | */ 200 | public SpecialTextUnit setConvertMode(int convertMode) { 201 | this.convertMode = convertMode; 202 | return this; 203 | } 204 | 205 | public int getTextColor() { 206 | return textColor; 207 | } 208 | 209 | public int getTextBackgroundColor() { 210 | return textBackgroundColor; 211 | } 212 | 213 | public float getTextSize() { 214 | return textSize; 215 | } 216 | 217 | public boolean isShowUnderline() { 218 | return isShowUnderline; 219 | } 220 | 221 | public boolean isShowStrikeThrough() { 222 | return isShowStrikeThrough; 223 | } 224 | 225 | public boolean isTextBold() { 226 | return isTextBold; 227 | } 228 | 229 | public boolean isTextItalic() { 230 | return isTextItalic; 231 | } 232 | 233 | public int getTextStyle() { 234 | return textStyle; 235 | } 236 | 237 | public TextView getCurTextView() { 238 | return curTextView; 239 | } 240 | 241 | public TextPaint getCurTextPaint() { 242 | return curTextPaint; 243 | } 244 | 245 | public SpecialClickableUnit getSpecialClickableUnit() { 246 | return specialClickableUnit; 247 | } 248 | 249 | } -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/unit/SpecialLabelUnit.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.unit; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Typeface; 5 | 6 | /** 7 | * Label Special Unit 8 | * Created by iWgang on 15/12/7. 9 | * https://github.com/iwgang/SimplifySpan 10 | */ 11 | public class SpecialLabelUnit extends BaseSpecialUnit { 12 | private int textStyle = Typeface.NORMAL; 13 | private int labelTextColor; 14 | private float labelTextSize; // sp 15 | private Bitmap mBitmap; 16 | private int labelBgColor; 17 | private float labelBgRadius; 18 | private int labelBgWidth; // px 19 | private int labelBgHeight; // px 20 | private int padding; // px 21 | private int paddingLeft; // px 22 | private int paddingRight; // px 23 | private float borderSize; // px 24 | private int labelBgBorderColor; 25 | private boolean isShowBorder; 26 | private boolean isTextBold; 27 | private boolean isTextItalic; 28 | private boolean isClickable; 29 | private int bgColor; 30 | 31 | /** 32 | * @param labelText label text 33 | * @param labelTextColor label text color 34 | * @param labelTextSize label text size (unit:xp) 35 | * @param labelBgColor label background color 36 | */ 37 | public SpecialLabelUnit(String labelText, int labelTextColor, float labelTextSize, int labelBgColor) { 38 | super(labelText); 39 | this.labelTextSize = labelTextSize; 40 | this.labelTextColor = labelTextColor; 41 | this.labelBgColor = labelBgColor; 42 | } 43 | 44 | /** 45 | * @param labelText label text 46 | * @param labelTextColor label text color 47 | * @param labelTextSize label text size (unit:xp) 48 | * @param labelBgColor label background color 49 | * @param labelBgWidth label background width 50 | * @param labelBgHeight label background height 51 | */ 52 | public SpecialLabelUnit(String labelText, int labelTextColor, float labelTextSize, int labelBgColor, int labelBgWidth, int labelBgHeight) { 53 | super(labelText); 54 | this.labelTextSize = labelTextSize; 55 | this.labelTextColor = labelTextColor; 56 | this.labelBgColor = labelBgColor; 57 | this.labelBgWidth = labelBgWidth; 58 | this.labelBgHeight = labelBgHeight; 59 | } 60 | 61 | /** 62 | * @param labelText label text 63 | * @param labelTextColor label text color 64 | * @param labelTextSize label text size (unit:xp) 65 | * @param bitmap label background image bitmap 66 | */ 67 | public SpecialLabelUnit(String labelText, int labelTextColor, float labelTextSize, Bitmap bitmap) { 68 | super(labelText); 69 | this.labelTextSize = labelTextSize; 70 | this.labelTextColor = labelTextColor; 71 | this.mBitmap = bitmap; 72 | } 73 | 74 | /** 75 | * @param labelText label text 76 | * @param labelTextColor label text color 77 | * @param labelTextSize label text size (unit:xp) 78 | * @param bitmap label background image bitmap 79 | * @param labelBgWidth label background width 80 | * @param labelBgHeight label background height 81 | */ 82 | public SpecialLabelUnit(String labelText, int labelTextColor, float labelTextSize, Bitmap bitmap, int labelBgWidth, int labelBgHeight) { 83 | super(labelText); 84 | this.labelTextSize = labelTextSize; 85 | this.labelTextColor = labelTextColor; 86 | this.mBitmap = bitmap; 87 | this.labelBgWidth = labelBgWidth; 88 | this.labelBgHeight = labelBgHeight; 89 | } 90 | 91 | public SpecialLabelUnit setLabelBgSize(int labelBgWidth, int labelBgHeight) { 92 | this.labelBgWidth = labelBgWidth; 93 | this.labelBgHeight = labelBgHeight; 94 | return this; 95 | } 96 | 97 | public SpecialLabelUnit setBitmap(Bitmap mBitmap) { 98 | this.mBitmap = mBitmap; 99 | return this; 100 | } 101 | 102 | public SpecialLabelUnit setPadding(int padding) { 103 | this.padding = padding; 104 | return this; 105 | } 106 | 107 | public SpecialLabelUnit setPaddingLeft(int paddingLeft) { 108 | this.paddingLeft = paddingLeft; 109 | return this; 110 | } 111 | 112 | public SpecialLabelUnit setPaddingRight(int paddingRight) { 113 | this.paddingRight = paddingRight; 114 | return this; 115 | } 116 | 117 | public SpecialLabelUnit setLabelBgRadius(float labelBgRadius) { 118 | this.labelBgRadius = labelBgRadius; 119 | return this; 120 | } 121 | 122 | public SpecialLabelUnit showBorder(int labelBgBorderColor, float borderSize) { 123 | isShowBorder = true; 124 | this.labelBgBorderColor = labelBgBorderColor; 125 | this.borderSize = borderSize; 126 | return this; 127 | } 128 | 129 | public SpecialLabelUnit useTextBold() { 130 | isTextBold = true; 131 | return this; 132 | } 133 | 134 | public SpecialLabelUnit useTextItalic() { 135 | isTextItalic = true; 136 | return this; 137 | } 138 | 139 | /** 140 | * Use TextView support textStyle 141 | * 142 | * @param textStyle please see {@link Typeface} 143 | * @return SpecialTextUnit 144 | */ 145 | public SpecialLabelUnit setTextStyle(int textStyle) { 146 | this.textStyle = textStyle; 147 | return this; 148 | } 149 | 150 | public SpecialLabelUnit setBgColor(int bgColor) { 151 | this.bgColor = bgColor; 152 | return this; 153 | } 154 | 155 | /** 156 | * Set Gravity 157 | * 158 | * @param gravity use SpecialGravity.xx 159 | * @return this 160 | */ 161 | public SpecialLabelUnit setGravity(int gravity) { 162 | this.gravity = gravity; 163 | return this; 164 | } 165 | 166 | /** 167 | * Set Convert Mode 168 | * 169 | * @param convertMode use SpecialConvertMode.xx 170 | * @return this 171 | */ 172 | public SpecialLabelUnit setConvertMode(int convertMode) { 173 | this.convertMode = convertMode; 174 | return this; 175 | } 176 | 177 | public int getLabelBgColor() { 178 | return labelBgColor; 179 | } 180 | 181 | public int getLabelBgHeight() { 182 | return labelBgHeight; 183 | } 184 | 185 | public int getLabelBgWidth() { 186 | return labelBgWidth; 187 | } 188 | 189 | public int getLabelTextColor() { 190 | return labelTextColor; 191 | } 192 | 193 | /** 194 | * label Text Size 195 | * 196 | * @return px 197 | */ 198 | public float getLabelTextSize() { 199 | return labelTextSize; 200 | } 201 | 202 | public int getPadding() { 203 | return padding; 204 | } 205 | 206 | public int getPaddingLeft() { 207 | return paddingLeft; 208 | } 209 | 210 | public int getPaddingRight() { 211 | return paddingRight; 212 | } 213 | 214 | public float getLabelBgRadius() { 215 | return labelBgRadius; 216 | } 217 | 218 | public boolean isShowBorder() { 219 | return isShowBorder; 220 | } 221 | 222 | /** 223 | * Use only in SimplifySpanBuild 224 | * 225 | * @param clickable set clickable 226 | */ 227 | public void setClickable(boolean clickable) { 228 | isClickable = clickable; 229 | } 230 | 231 | public float getBorderSize() { 232 | return borderSize; 233 | } 234 | 235 | public int getLabelBgBorderColor() { 236 | return labelBgBorderColor; 237 | } 238 | 239 | public boolean isTextBold() { 240 | return isTextBold; 241 | } 242 | 243 | public Bitmap getBitmap() { 244 | return mBitmap; 245 | } 246 | 247 | public boolean isClickable() { 248 | return isClickable; 249 | } 250 | 251 | public int getBgColor() { 252 | return bgColor; 253 | } 254 | 255 | public boolean isTextItalic() { 256 | return isTextItalic; 257 | } 258 | 259 | public int getTextStyle() { 260 | return textStyle; 261 | } 262 | 263 | 264 | } -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/customspan/CustomLabelSpan.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan.customspan; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Rect; 7 | import android.graphics.RectF; 8 | import android.graphics.Typeface; 9 | import android.media.ThumbnailUtils; 10 | import android.text.style.ReplacementSpan; 11 | 12 | import cn.iwgang.simplifyspan.other.OnClickStateChangeListener; 13 | import cn.iwgang.simplifyspan.other.SpecialGravity; 14 | import cn.iwgang.simplifyspan.unit.SpecialLabelUnit; 15 | 16 | /** 17 | * Custom Label Span 18 | * Created by iWgang on 15/12/3. 19 | * https://github.com/iwgang/SimplifySpan 20 | */ 21 | public class CustomLabelSpan extends ReplacementSpan implements OnClickStateChangeListener { 22 | private SpecialLabelUnit mSpecialLabelUnit; 23 | private String mSpecialText; 24 | private float mFinalWidth, mFinalHeight; 25 | private int mSpecialTextHeight = 0; 26 | private float mSpecialTextWidth = 0; 27 | private int mLineTextHeight = 0; 28 | private int mLineTextBaselineOffset = 0; 29 | private int mSpecialTextBaselineOffset = 0; 30 | private float mLabelBgRadius; 31 | private Bitmap mBitmap; 32 | private String mNormalSizeText; 33 | 34 | private int mPaddingTop = 0; 35 | private int mPaddingBottom = 0; 36 | private int mPaddingLeft = 0; 37 | private int mPaddingRight = 0; 38 | private boolean isLabelBgCenter = true; 39 | private boolean isDrawBitmap = false; 40 | private RectF bgRect; 41 | private boolean isInit = true; 42 | 43 | private int mBgColor; 44 | private boolean isSelected; 45 | private boolean isClickable; 46 | private int pressBgColor; 47 | 48 | public CustomLabelSpan(String normalSizeText, SpecialLabelUnit specialLabelUnit) { 49 | mSpecialLabelUnit = specialLabelUnit; 50 | mSpecialText = mSpecialLabelUnit.getText(); 51 | mNormalSizeText = normalSizeText; 52 | isClickable = mSpecialLabelUnit.isClickable(); 53 | mBgColor = mSpecialLabelUnit.getBgColor(); 54 | 55 | mBitmap = mSpecialLabelUnit.getBitmap(); 56 | if (null == mBitmap) { 57 | mLabelBgRadius = mSpecialLabelUnit.getLabelBgRadius(); 58 | if (mLabelBgRadius > 0) { 59 | bgRect = new RectF(); 60 | } 61 | } else { 62 | isDrawBitmap = true; 63 | } 64 | initPadding(); 65 | } 66 | 67 | private void initPadding() { 68 | if (mSpecialLabelUnit.getLabelBgHeight() > 0 || mSpecialLabelUnit.getLabelBgWidth() > 0) 69 | return; 70 | 71 | int allPadding = mSpecialLabelUnit.getPadding(); 72 | 73 | mPaddingTop = allPadding; 74 | mPaddingBottom = allPadding; 75 | 76 | int paddingLeft = mSpecialLabelUnit.getPaddingLeft(); 77 | if (paddingLeft > 0) { 78 | mPaddingLeft = paddingLeft; 79 | } else { 80 | mPaddingLeft = allPadding; 81 | } 82 | 83 | int paddingRight = mSpecialLabelUnit.getPaddingRight(); 84 | if (paddingRight > 0) { 85 | mPaddingRight = paddingRight; 86 | } else { 87 | mPaddingRight = allPadding; 88 | } 89 | 90 | if (mPaddingTop > 0 || mPaddingBottom > 0 || mPaddingLeft > 0 || mPaddingRight > 0) 91 | isLabelBgCenter = false; 92 | } 93 | 94 | @Override 95 | public void onStateChange(boolean isSelected, int pressBgColor) { 96 | this.isSelected = isSelected; 97 | this.pressBgColor = pressBgColor; 98 | } 99 | 100 | @Override 101 | public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { 102 | if (isInit) { 103 | isInit = false; 104 | initFinalHeight(paint); 105 | initFinalWidth(paint); 106 | 107 | // Re Create Bitmap 108 | if (isDrawBitmap) { 109 | Bitmap newBitmap = ThumbnailUtils.extractThumbnail(mBitmap, Math.round(mFinalWidth), Math.round(mFinalHeight)); 110 | if (null != newBitmap) { 111 | mBitmap.recycle(); 112 | mBitmap = newBitmap; 113 | } 114 | } 115 | } 116 | 117 | return Math.round(mFinalWidth); 118 | } 119 | 120 | @Override 121 | public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 122 | float finalUnitHeight = bottom - top; 123 | float bgTop = bottom - finalUnitHeight; 124 | if (isClickable && isSelected && pressBgColor != 0) { 125 | // click background 126 | paint.setColor(pressBgColor); 127 | canvas.drawRect(x, bgTop, x + mFinalWidth, bgTop + finalUnitHeight, paint); 128 | } else { 129 | // normal background 130 | if (mBgColor != 0) { 131 | paint.setColor(mBgColor); 132 | canvas.drawRect(x, bgTop, x + mFinalWidth, bgTop + finalUnitHeight, paint); 133 | } 134 | } 135 | 136 | float labelTextSize = mSpecialLabelUnit.getLabelTextSize(); 137 | if (labelTextSize > 0 && labelTextSize != paint.getTextSize()) { 138 | paint.setTextSize(labelTextSize); 139 | } 140 | 141 | float newStartY = y; 142 | int newTextY = y; 143 | 144 | switch (mSpecialLabelUnit.getGravity()) { 145 | case SpecialGravity.TOP: 146 | newStartY -= (mLineTextHeight - mLineTextBaselineOffset); 147 | newTextY -= (mLineTextHeight - mSpecialTextHeight - (mLineTextBaselineOffset - mSpecialTextBaselineOffset) - mPaddingTop); 148 | break; 149 | case SpecialGravity.CENTER: 150 | newStartY -= (mLineTextHeight / 2 + mFinalHeight / 2 - mLineTextBaselineOffset); 151 | newTextY -= (mLineTextHeight / 2 - mSpecialTextHeight / 2 - (mLineTextBaselineOffset - mSpecialTextBaselineOffset)); 152 | break; 153 | case SpecialGravity.BOTTOM: 154 | newStartY -= mFinalHeight - mLineTextBaselineOffset; 155 | newTextY -= mPaddingBottom - (mLineTextBaselineOffset - mSpecialTextBaselineOffset); 156 | break; 157 | } 158 | 159 | if (isDrawBitmap) { 160 | canvas.drawBitmap(mBitmap, x, newStartY, paint); 161 | } else { 162 | paint.setColor(mSpecialLabelUnit.getLabelBgColor()); 163 | if (mLabelBgRadius > 0) { 164 | bgRect.top = newStartY; 165 | bgRect.bottom = newStartY + mFinalHeight; 166 | bgRect.left = x; 167 | bgRect.right = x + mFinalWidth; 168 | if (mSpecialLabelUnit.isShowBorder()) { 169 | float borderSize = mSpecialLabelUnit.getBorderSize(); 170 | // draw background 171 | canvas.drawRect(x, newStartY, x + mFinalWidth, newStartY + mFinalHeight, paint); 172 | 173 | // draw border 174 | paint.setColor(mSpecialLabelUnit.getLabelBgBorderColor()); 175 | paint.setStyle(Paint.Style.STROKE); 176 | paint.setStrokeWidth(borderSize); 177 | canvas.drawRoundRect(bgRect, mLabelBgRadius, mLabelBgRadius, paint); 178 | 179 | // recover paint 180 | paint.setStyle(Paint.Style.FILL); 181 | } else { 182 | // draw background 183 | canvas.drawRoundRect(bgRect, mLabelBgRadius, mLabelBgRadius, paint); 184 | } 185 | } else { 186 | // draw background 187 | canvas.drawRect(x, newStartY, x + mFinalWidth, newStartY + mFinalHeight, paint); 188 | 189 | if (mSpecialLabelUnit.isShowBorder()) { 190 | // draw border 191 | paint.setColor(mSpecialLabelUnit.getLabelBgBorderColor()); 192 | paint.setStyle(Paint.Style.STROKE); 193 | paint.setStrokeWidth(mSpecialLabelUnit.getBorderSize()); 194 | canvas.drawRect(x, newStartY, x + mFinalWidth, newStartY + mFinalHeight, paint); 195 | 196 | // recover paint 197 | paint.setStyle(Paint.Style.FILL); 198 | } 199 | } 200 | } 201 | 202 | paint.setColor(mSpecialLabelUnit.getLabelTextColor()); 203 | float newTextX; 204 | if (isLabelBgCenter) { 205 | newTextX = x + Math.round(mFinalWidth / 2f - mSpecialTextWidth / 2f); 206 | } else { 207 | newTextX = x + mPaddingLeft; 208 | } 209 | 210 | if (mSpecialLabelUnit.isTextBold()) 211 | paint.setFakeBoldText(true); 212 | 213 | if (mSpecialLabelUnit.isTextItalic()) 214 | paint.setTypeface(Typeface.create((String) null, Typeface.ITALIC)); 215 | 216 | canvas.drawText(mSpecialText, newTextX, newTextY, paint); 217 | } 218 | 219 | private float initFinalWidth(Paint paint) { 220 | if (mFinalWidth <= 0) { 221 | float labelTextSize = mSpecialLabelUnit.getLabelTextSize(); 222 | if (labelTextSize > 0 && labelTextSize != paint.getTextSize()) { 223 | paint.setTextSize(labelTextSize); 224 | } 225 | 226 | int labelBgWidth = mSpecialLabelUnit.getLabelBgWidth(); 227 | mSpecialTextWidth = paint.measureText(mSpecialText, 0, mSpecialText.length()); 228 | if (labelBgWidth > 0 && labelBgWidth > mSpecialTextWidth) { 229 | mFinalWidth = labelBgWidth; 230 | } else { 231 | mFinalWidth = mSpecialTextWidth + mPaddingLeft + mPaddingRight; 232 | } 233 | } 234 | 235 | return mFinalWidth; 236 | } 237 | 238 | private float initFinalHeight(Paint paint) { 239 | if (mFinalHeight <= 0) { 240 | int labelBgHeight = mSpecialLabelUnit.getLabelBgHeight(); 241 | 242 | Rect specialTextRect = new Rect(); 243 | paint.getTextBounds(mNormalSizeText, 0, mNormalSizeText.length(), specialTextRect); 244 | mLineTextHeight = specialTextRect.height(); 245 | mLineTextBaselineOffset = specialTextRect.bottom; 246 | 247 | float labelTextSize = mSpecialLabelUnit.getLabelTextSize(); 248 | if (labelTextSize > 0 && labelTextSize != paint.getTextSize()) { 249 | paint.setTextSize(labelTextSize); 250 | } 251 | 252 | paint.getTextBounds(mSpecialText, 0, mSpecialText.length(), specialTextRect); 253 | mSpecialTextHeight = specialTextRect.height(); 254 | mSpecialTextBaselineOffset = specialTextRect.bottom; 255 | 256 | if (labelBgHeight > 0 && labelBgHeight > mSpecialTextHeight && labelBgHeight <= mLineTextHeight) { 257 | mFinalHeight = labelBgHeight; 258 | } else { 259 | mFinalHeight = mSpecialTextHeight + mPaddingTop + mPaddingBottom; 260 | } 261 | 262 | if (mFinalHeight > mLineTextHeight) { 263 | mFinalHeight = mLineTextHeight; 264 | } 265 | } 266 | 267 | return mFinalHeight; 268 | } 269 | 270 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/iwgang/simplifyspandemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspandemo; 2 | 3 | import android.graphics.BitmapFactory; 4 | import android.graphics.Color; 5 | import android.graphics.Typeface; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.widget.TextView; 9 | import android.widget.Toast; 10 | 11 | import cn.iwgang.simplifyspan.SimplifySpanBuild; 12 | import cn.iwgang.simplifyspan.customspan.CustomClickableSpan; 13 | import cn.iwgang.simplifyspan.other.OnClickableSpanListener; 14 | import cn.iwgang.simplifyspan.other.SpecialConvertMode; 15 | import cn.iwgang.simplifyspan.other.SpecialGravity; 16 | import cn.iwgang.simplifyspan.unit.SpecialClickableUnit; 17 | import cn.iwgang.simplifyspan.unit.SpecialImageUnit; 18 | import cn.iwgang.simplifyspan.unit.SpecialLabelUnit; 19 | import cn.iwgang.simplifyspan.unit.SpecialTextUnit; 20 | 21 | public class MainActivity extends AppCompatActivity implements OnClickableSpanListener { 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_main); 27 | 28 | TextView tvText1 = (TextView) findViewById(R.id.tv_text1); 29 | TextView tvText11 = (TextView) findViewById(R.id.tv_text11); 30 | TextView tvText2 = (TextView) findViewById(R.id.tv_text2); 31 | TextView tvText21 = (TextView) findViewById(R.id.tv_text21); 32 | TextView tvText3 = (TextView) findViewById(R.id.tv_text3); 33 | TextView tvText4 = (TextView) findViewById(R.id.tv_text4); 34 | TextView tvText41 = (TextView) findViewById(R.id.tv_text41); 35 | 36 | CharSequence tv1SpannableString = new SimplifySpanBuild(" 艾客优品雷霆Dock 2 雷电转USB3.0/火线/esata 扩展HUB") 37 | .appendToFirst(new SpecialLabelUnit("1212", Color.WHITE, sp2px(8), Color.RED, 70, 35).useTextBold().setGravity(SpecialGravity.CENTER)) 38 | .appendToFirst(new SpecialLabelUnit("天猫", Color.WHITE, sp2px(8), 0xFFFF5000, 60, 35).setGravity(SpecialGravity.CENTER)) 39 | .build(); 40 | tvText1.setText(tv1SpannableString); 41 | 42 | // tvText1.setText(new SimplifySpanBuild("距离您:").append(new SpecialTextUnit("385", Color.BLUE)).append(" 米").build()); 43 | 44 | SimplifySpanBuild simplifySpanBuild11 = new SimplifySpanBuild(); 45 | simplifySpanBuild11.append(new SpecialLabelUnit("火", Color.RED, sp2px(7), BitmapFactory.decodeResource(getResources(), R.drawable.label)).setPadding(15).setGravity(SpecialGravity.CENTER)) 46 | .append("正常") 47 | .append(new SpecialLabelUnit("火", Color.RED, sp2px(7), BitmapFactory.decodeResource(getResources(), R.drawable.label)).setPadding(15).setGravity(SpecialGravity.TOP)) 48 | .append("正常") 49 | .append(new SpecialLabelUnit("火", Color.RED, sp2px(7), BitmapFactory.decodeResource(getResources(), R.drawable.label)).setPadding(15).setGravity(SpecialGravity.BOTTOM)) 50 | .append("正常") 51 | .append(new SpecialLabelUnit("原创", Color.BLACK, sp2px(10), BitmapFactory.decodeResource(getResources(), R.drawable.tag)).setPadding(5).setPaddingLeft(15).setPaddingRight(30).setGravity(SpecialGravity.TOP)) 52 | .append("正常") 53 | .append(new SpecialLabelUnit("原创", Color.BLACK, sp2px(10), BitmapFactory.decodeResource(getResources(), R.drawable.tag)).setPadding(5).setPaddingLeft(15).setPaddingRight(30).setGravity(SpecialGravity.CENTER)) 54 | .append("正常") 55 | .append(new SpecialLabelUnit("原创", Color.BLACK, sp2px(10), BitmapFactory.decodeResource(getResources(), R.drawable.tag)).setPadding(5).setPaddingLeft(15).setPaddingRight(30).setGravity(SpecialGravity.BOTTOM)) 56 | .append("正常") 57 | .append(new SpecialLabelUnit("原创", Color.WHITE, sp2px(10), 0xFFFF5000).setLabelBgRadius(5).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.BOTTOM)) 58 | .append("正常") 59 | .append(new SpecialLabelUnit("原创", Color.WHITE, sp2px(10), 0xFFFF5000).setLabelBgRadius(5).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.TOP)) 60 | .append("正常") 61 | .append(new SpecialLabelUnit("原创", Color.WHITE, sp2px(10), 0xFFFF5000).setLabelBgRadius(5).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.CENTER)) 62 | .append("正常") 63 | .append(new SpecialLabelUnit("原创", Color.WHITE, sp2px(10), Color.GRAY).setLabelBgRadius(5).showBorder(Color.BLACK, 2).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.BOTTOM)) 64 | .append("正常") 65 | .append(new SpecialLabelUnit("原创", Color.WHITE, sp2px(10), Color.GRAY).setLabelBgRadius(5).showBorder(Color.BLACK, 2).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.TOP)) 66 | .append("正常") 67 | .append(new SpecialLabelUnit("原创", Color.WHITE, sp2px(10), Color.GRAY).setLabelBgRadius(5).showBorder(Color.BLACK, 2).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.CENTER)) 68 | .append("正常") 69 | .append(new SpecialLabelUnit("原创", Color.RED, sp2px(10), Color.TRANSPARENT).showBorder(Color.BLACK, 2).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.BOTTOM)) 70 | .append("正常") 71 | .append(new SpecialLabelUnit("原创", Color.RED, sp2px(10), Color.TRANSPARENT).showBorder(Color.BLACK, 2).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.TOP)) 72 | .append("正常") 73 | .append(new SpecialLabelUnit("原创", Color.RED, sp2px(10), Color.TRANSPARENT).showBorder(Color.BLACK, 2).setPadding(5).setPaddingLeft(10).setPaddingRight(10).setGravity(SpecialGravity.CENTER)); 74 | tvText11.setText(simplifySpanBuild11.build()); 75 | 76 | SimplifySpanBuild simplifySpanBuild2 = new SimplifySpanBuild("替换所有张字的颜色及字体大小并加粗,张歆艺、张馨予、张嘉倪、张涵予、张含韵、张韶涵、张嘉译、张佳宁、大张伟", new SpecialTextUnit("张").useTextBold().setTextSize(20).setTextColor(0xFFFFA500).setConvertMode(SpecialConvertMode.ALL)); 77 | tvText2.setText(simplifySpanBuild2.build()); 78 | 79 | SimplifySpanBuild simplifySpanBuild21 = new SimplifySpanBuild(); 80 | simplifySpanBuild21 81 | .append(new SpecialTextUnit("正常字体").setTextSize(18).setTextStyle(Typeface.NORMAL)) 82 | .append(new SpecialTextUnit("粗体").setTextSize(18).useTextBold()) 83 | .append(new SpecialTextUnit("斜体").setTextSize(18).useTextItalic()) 84 | .append(new SpecialTextUnit("粗斜体").setTextSize(18).useTextBold().useTextItalic()) 85 | .append(new SpecialTextUnit("黑体").setTextSize(18).setTextStyle(Typeface.DEFAULT_BOLD.getStyle())) 86 | .append(new SpecialTextUnit("等宽1字体").setTextSize(18).setTextStyle(Typeface.MONOSPACE.getStyle())) 87 | .append(new SpecialTextUnit("Sans Serif字体").setTextSize(18).setTextStyle(Typeface.SANS_SERIF.getStyle())) 88 | .append(new SpecialTextUnit("Serif字体").setTextSize(18).setTextStyle(Typeface.SERIF.getStyle())) 89 | .append("\n") 90 | .append(new SpecialTextUnit("居中").setTextSize(12).setGravity(tvText21.getPaint(), SpecialGravity.CENTER).setTextColor(Color.BLUE)) 91 | .append("正常") 92 | .append(new SpecialTextUnit("顶部").setTextSize(12).setGravity(tvText21, SpecialGravity.TOP).setTextColor(0xFFFF5000)) 93 | .append("正常") 94 | .append(new SpecialTextUnit("底部").setTextSize(12).setTextColor(0xFF8B658B)); 95 | tvText21.setText(simplifySpanBuild21.build()); 96 | 97 | SimplifySpanBuild simplifySpanBuild3 = new SimplifySpanBuild(); 98 | simplifySpanBuild3.append(new SpecialImageUnit(this, BitmapFactory.decodeResource(getResources(), R.drawable.ic_bulletin), 50, 50).setGravity(SpecialGravity.CENTER)) 99 | .append("正常") 100 | .append(new SpecialImageUnit(this, BitmapFactory.decodeResource(getResources(), R.drawable.test_img), 150, 150)) 101 | .append("正常") 102 | .append(new SpecialImageUnit(this, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher), 50, 50).setGravity(SpecialGravity.CENTER)) 103 | .append("正常") 104 | .append(new SpecialImageUnit(this, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher), 50, 50).setGravity(SpecialGravity.TOP)) 105 | .append("正常") 106 | .append(new SpecialImageUnit(this, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher), 50, 50).setGravity(SpecialGravity.BOTTOM)); 107 | tvText3.setText(simplifySpanBuild3.build()); 108 | 109 | 110 | int linkNorTextColor = 0xFF483D8B; 111 | int linkPressBgColor = 0xFF87CEFA; 112 | SimplifySpanBuild simplifySpanBuild4 = new SimplifySpanBuild(); 113 | simplifySpanBuild4.append(new SpecialTextUnit("@英雄联盟", linkNorTextColor).setClickableUnit(new SpecialClickableUnit(tvText4, this).setPressBgColor(linkPressBgColor))) 114 | .append(" : ") 115 | .append(new SpecialTextUnit("#LOG夜话#", linkNorTextColor).setClickableUnit(new SpecialClickableUnit(tvText4, this).setPressBgColor(linkPressBgColor))) 116 | .append(new SpecialTextUnit("#LOL明星召唤师#", linkNorTextColor).setClickableUnit(new SpecialClickableUnit(tvText4, this).setPressBgColor(linkPressBgColor))) 117 | .append("星光熠熠的各赛区阵容、精彩的1V1Solo赛、克隆/双人共玩等奇葩套路层出不穷的花样对抗,你最期待看到哪位选手、哪种模式的对决?"); 118 | simplifySpanBuild4.appendMultiClickable(new SpecialClickableUnit(tvText4, this).setNormalTextColor(linkNorTextColor).setPressBgColor(linkPressBgColor), 119 | " ", 120 | new SpecialImageUnit(this, BitmapFactory.decodeResource(getResources(), R.drawable.timeline_card_small_video), 35, 35).setGravity(SpecialGravity.CENTER), 121 | new SpecialTextUnit("LOL新大战闻声识英雄 ")); 122 | simplifySpanBuild4.append("完整文章见 "); 123 | simplifySpanBuild4.appendMultiClickable(new SpecialClickableUnit(tvText4, this).setNormalTextColor(linkNorTextColor).setPressBgColor(linkPressBgColor), 124 | new SpecialImageUnit(this, BitmapFactory.decodeResource(getResources(), R.drawable.timeline_card_small_article), 30, 30).setGravity(SpecialGravity.CENTER), 125 | new SpecialTextUnit("LOL超强攻略,不看绝对后悔 ").showUnderline()); 126 | simplifySpanBuild4.append(" 更多好玩的内容请点击 "); 127 | simplifySpanBuild4.appendMultiClickable(new SpecialClickableUnit(tvText4, this).setNormalTextColor(linkNorTextColor).setPressBgColor(linkPressBgColor), 128 | new SpecialImageUnit(this, BitmapFactory.decodeResource(getResources(), R.drawable.timeline_card_small_web), 42, 42).setGravity(SpecialGravity.CENTER), 129 | new SpecialTextUnit("网页链接")); 130 | simplifySpanBuild4.append(" 已收录 "); 131 | simplifySpanBuild4.appendMultiClickable(new SpecialClickableUnit(tvText4, this).setNormalTextColor(linkNorTextColor).setPressBgColor(linkPressBgColor), 132 | " ", 133 | new SpecialLabelUnit("酷玩", 0xFF483D8B, sp2px(8), Color.TRANSPARENT).showBorder(0xFF483D8B, 2).setLabelBgRadius(8).setPadding(5).setPaddingLeft(8).setPaddingRight(10).setGravity(SpecialGravity.CENTER), 134 | new SpecialTextUnit(" LOL新闻库 ")); 135 | simplifySpanBuild4.append(" 后面加的内容是为了凑字数的哈"); 136 | tvText4.setText(simplifySpanBuild4.build()); 137 | 138 | 139 | SimplifySpanBuild simplifySpanBuild41 = new SimplifySpanBuild(); 140 | simplifySpanBuild41.append("无默认背景") 141 | .append(new SpecialTextUnit("点我点我1").setClickableUnit(new SpecialClickableUnit(tvText41, this).setTag("1").setPressBgColor(0xFFFF5000)).setTextColor(Color.BLUE)) 142 | .append("无默认背景显示下划线") 143 | .append(new SpecialTextUnit("点我点我2").setClickableUnit(new SpecialClickableUnit(tvText41, this).setTag("2").showUnderline().setPressBgColor(0xFFFF5000).setPressTextColor(Color.WHITE)).setTextColor(0xFFFF5000)) 144 | .append("有默认背景") 145 | .append(new SpecialTextUnit("点我点我3").setClickableUnit(new SpecialClickableUnit(tvText41, this).setTag("3").setPressBgColor(Color.BLUE).setPressTextColor(Color.WHITE)).setTextColor(0xFFFF5000).setTextBackgroundColor(0xFF87CEEB)) 146 | .append("我只是个结尾"); 147 | tvText41.setText(simplifySpanBuild41.build()); 148 | } 149 | 150 | @Override 151 | public void onClick(TextView tv, CustomClickableSpan clickableSpan) { 152 | Toast.makeText(MainActivity.this, "Click Text: " + clickableSpan.getClickText() 153 | + " tag: " + clickableSpan.getTag() 154 | + " StartSpanIndex: " + clickableSpan.getStartSpanIndex() 155 | + " EndSpanIndex: " + clickableSpan.getEndSpanIndex() 156 | , Toast.LENGTH_SHORT).show(); 157 | } 158 | 159 | private float sp2px(float spValue) { 160 | final float scale = getResources().getDisplayMetrics().scaledDensity; 161 | return spValue * scale; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /library/src/main/java/cn/iwgang/simplifyspan/SimplifySpanBuild.java: -------------------------------------------------------------------------------- 1 | package cn.iwgang.simplifyspan; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Typeface; 5 | import android.media.ThumbnailUtils; 6 | import android.text.SpannableStringBuilder; 7 | import android.text.Spanned; 8 | import android.text.TextPaint; 9 | import android.text.TextUtils; 10 | import android.text.style.AbsoluteSizeSpan; 11 | import android.text.style.BackgroundColorSpan; 12 | import android.text.style.ForegroundColorSpan; 13 | import android.text.style.StrikethroughSpan; 14 | import android.text.style.StyleSpan; 15 | import android.text.style.UnderlineSpan; 16 | import android.widget.TextView; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | import cn.iwgang.simplifyspan.customspan.CustomAbsoluteSizeSpan; 25 | import cn.iwgang.simplifyspan.customspan.CustomClickableSpan; 26 | import cn.iwgang.simplifyspan.customspan.CustomImageSpan; 27 | import cn.iwgang.simplifyspan.customspan.CustomLabelSpan; 28 | import cn.iwgang.simplifyspan.other.CustomLinkMovementMethod; 29 | import cn.iwgang.simplifyspan.other.OnClickStateChangeListener; 30 | import cn.iwgang.simplifyspan.other.SpecialConvertMode; 31 | import cn.iwgang.simplifyspan.other.SpecialGravity; 32 | import cn.iwgang.simplifyspan.unit.BaseSpecialUnit; 33 | import cn.iwgang.simplifyspan.unit.SpecialClickableUnit; 34 | import cn.iwgang.simplifyspan.unit.SpecialImageUnit; 35 | import cn.iwgang.simplifyspan.unit.SpecialLabelUnit; 36 | import cn.iwgang.simplifyspan.unit.SpecialRawSpanUnit; 37 | import cn.iwgang.simplifyspan.unit.SpecialTextUnit; 38 | 39 | /** 40 | * SimplifySpan Build 41 | * Created by iWgang on 15/12/2. 42 | * https://github.com/iwgang/SimplifySpan 43 | */ 44 | public class SimplifySpanBuild { 45 | private List mFinalSpecialUnit; 46 | private List mBeforeSpecialUnit; 47 | private StringBuilder mStringBuilder; 48 | private StringBuilder mBeforeStringBuilder; 49 | private Map mBeforeCacheSpecialClickableUnitMap = new HashMap<>(); 50 | private Map mCacheSpecialClickableUnitMap = new HashMap<>(); 51 | 52 | private StringBuilder mNormalSizeText; 53 | 54 | public SimplifySpanBuild() { 55 | this(null); 56 | } 57 | 58 | public SimplifySpanBuild(String initializeNormalText) { 59 | init(initializeNormalText); 60 | } 61 | 62 | public SimplifySpanBuild(String initializeNormalText, BaseSpecialUnit... normalSpecialUnits) { 63 | init(initializeNormalText, normalSpecialUnits); 64 | } 65 | 66 | private void init(String initializeNormalText, BaseSpecialUnit... normalSpecialUnits) { 67 | mStringBuilder = new StringBuilder(TextUtils.isEmpty(initializeNormalText) ? "" : initializeNormalText); 68 | mBeforeStringBuilder = new StringBuilder(""); 69 | mNormalSizeText = new StringBuilder(""); 70 | mFinalSpecialUnit = new ArrayList<>(); 71 | mBeforeSpecialUnit = new ArrayList<>(); 72 | 73 | if (!TextUtils.isEmpty(initializeNormalText)) { 74 | if (null != normalSpecialUnits && normalSpecialUnits.length > 0) { 75 | buildNormalSpecialUnits(false, 0, initializeNormalText, normalSpecialUnits); 76 | } else { 77 | mNormalSizeText.append(initializeNormalText); 78 | } 79 | } 80 | } 81 | 82 | private void buildNormalSpecialUnits(boolean isAddToBeforeSpecialUnit, int initStartPos, String finalText, BaseSpecialUnit... normalSpecialUnits) { 83 | Map delTextTagMap = new HashMap<>(); 84 | List otherSpecialTextStartPos; 85 | for (BaseSpecialUnit st : normalSpecialUnits) { 86 | String specialText = st.getText(); 87 | 88 | if (TextUtils.isEmpty(specialText) || !finalText.contains(specialText)) continue; 89 | 90 | int specialTextLength = specialText.length(); 91 | switch (st.getConvertMode()) { 92 | case SpecialConvertMode.ONLY_FIRST: 93 | st.setStartPoss(new int[]{initStartPos + finalText.indexOf(specialText)}); 94 | break; 95 | case SpecialConvertMode.ONLY_LAST: 96 | st.setStartPoss(new int[]{initStartPos + finalText.lastIndexOf(specialText)}); 97 | break; 98 | case SpecialConvertMode.ALL: 99 | int firstSpecialTextStartPos = finalText.indexOf(specialText); 100 | otherSpecialTextStartPos = new ArrayList<>(); 101 | otherSpecialTextStartPos.add(firstSpecialTextStartPos); 102 | int previousSpecialTextStartPos = firstSpecialTextStartPos + specialTextLength; 103 | boolean hasNext = true; 104 | while (hasNext) { 105 | int nextSpecialTextStartPos = finalText.indexOf(specialText, previousSpecialTextStartPos); 106 | if (nextSpecialTextStartPos != -1) { 107 | otherSpecialTextStartPos.add(nextSpecialTextStartPos); 108 | previousSpecialTextStartPos = nextSpecialTextStartPos + specialTextLength; 109 | } else { 110 | hasNext = false; 111 | } 112 | } 113 | 114 | int[] startPos = new int[otherSpecialTextStartPos.size()]; 115 | for (int i = 0; i < otherSpecialTextStartPos.size(); i++) { 116 | startPos[i] = initStartPos + otherSpecialTextStartPos.get(i); 117 | } 118 | st.setStartPoss(startPos); 119 | break; 120 | } 121 | 122 | int[] startPossTemp = st.getStartPoss(); 123 | if (null == startPossTemp || startPossTemp.length == 0) continue; 124 | 125 | // excluding content 126 | if (st instanceof SpecialTextUnit) { 127 | SpecialTextUnit specialTextUnit = (SpecialTextUnit) st; 128 | 129 | if (specialTextUnit.getTextSize() > 0) { 130 | if (startPossTemp.length > 1) { 131 | delTextTagMap.put(specialText, true); 132 | } else { 133 | delTextTagMap.put(specialText, false); 134 | } 135 | } 136 | } else if (st instanceof SpecialImageUnit || st instanceof SpecialLabelUnit) { 137 | if (startPossTemp.length > 1) { 138 | delTextTagMap.put(specialText, true); 139 | } else { 140 | delTextTagMap.put(specialText, false); 141 | } 142 | } 143 | } 144 | 145 | if (!delTextTagMap.isEmpty()) { 146 | for (Map.Entry pm : delTextTagMap.entrySet()) { 147 | if (pm.getValue()) { 148 | // delete all 149 | finalText = finalText.replaceAll(pm.getKey(), ""); 150 | } else { 151 | // del first 152 | finalText = finalText.replace(pm.getKey(), ""); 153 | } 154 | } 155 | } 156 | 157 | 158 | if (isAddToBeforeSpecialUnit) { 159 | mNormalSizeText.insert(0, finalText); 160 | mBeforeSpecialUnit.addAll(Arrays.asList(normalSpecialUnits)); 161 | } else { 162 | mNormalSizeText.append(finalText); 163 | mFinalSpecialUnit.addAll(Arrays.asList(normalSpecialUnits)); 164 | } 165 | } 166 | 167 | /** 168 | * append SpecialUnit 169 | * 170 | * @param specialUnit SpecialUnit (Not support convertMode) 171 | * @return SimplifySpanBuild 172 | */ 173 | public SimplifySpanBuild append(BaseSpecialUnit specialUnit) { 174 | if (null == specialUnit) return this; 175 | 176 | String specialText = specialUnit.getText(); 177 | if (TextUtils.isEmpty(specialText)) return this; 178 | 179 | // process start pos 180 | specialUnit.setStartPoss(new int[]{mStringBuilder.length()}); 181 | mStringBuilder.append(specialText); 182 | mFinalSpecialUnit.add(specialUnit); 183 | return this; 184 | } 185 | 186 | /** 187 | * append normal text 188 | * 189 | * @param text normal text 190 | * @return SimplifySpanBuild 191 | */ 192 | public SimplifySpanBuild append(String text) { 193 | if (TextUtils.isEmpty(text)) return this; 194 | 195 | mNormalSizeText.append(text); 196 | mStringBuilder.append(text); 197 | return this; 198 | } 199 | 200 | /** 201 | * append SpecialUnit to first (Behind the existing BeforeContent) 202 | * 203 | * @param specialUnit (Not support convertMode) 204 | * @return SimplifySpanBuild 205 | */ 206 | public SimplifySpanBuild appendToFirst(BaseSpecialUnit specialUnit) { 207 | if (null == specialUnit) return this; 208 | 209 | String specialText = specialUnit.getText(); 210 | if (TextUtils.isEmpty(specialText)) return this; 211 | 212 | // process start pos 213 | int curBeforeFirstPos = mBeforeStringBuilder.length(); 214 | specialUnit.setStartPoss(new int[]{curBeforeFirstPos}); 215 | mBeforeStringBuilder.insert(curBeforeFirstPos, specialText); 216 | mBeforeSpecialUnit.add(specialUnit); 217 | return this; 218 | } 219 | 220 | /** 221 | * append normal text to first (Behind the existing BeforeContent) 222 | * 223 | * @param text normal text 224 | * @return SimplifySpanBuild 225 | */ 226 | public SimplifySpanBuild appendToFirst(String text) { 227 | if (TextUtils.isEmpty(text)) return this; 228 | 229 | mNormalSizeText.append(text); 230 | mBeforeStringBuilder.append(text); 231 | return this; 232 | } 233 | 234 | /** 235 | * append multi clickable SpecialUnit or String 236 | * 237 | * @param specialClickableUnit SpecialClickableUnit 238 | * @param specialUnitOrStrings Unit Or String 239 | * @return SimplifySpanBuild 240 | */ 241 | public SimplifySpanBuild appendMultiClickable(SpecialClickableUnit specialClickableUnit, Object... specialUnitOrStrings) { 242 | processMultiClickableSpecialUnit(false, specialClickableUnit, specialUnitOrStrings); 243 | return this; 244 | } 245 | 246 | /** 247 | * append multi clickable SpecialUnit or String to first (Behind the existing BeforeContent) 248 | * 249 | * @param specialClickableUnit SpecialClickableUnit 250 | * @param specialUnitOrStrings Unit Or String 251 | * @return SimplifySpanBuild 252 | */ 253 | public SimplifySpanBuild appendMultiClickableToFirst(SpecialClickableUnit specialClickableUnit, Object... specialUnitOrStrings) { 254 | processMultiClickableSpecialUnit(true, specialClickableUnit, specialUnitOrStrings); 255 | return this; 256 | } 257 | 258 | /** 259 | * Build 260 | * 261 | * @return SpannableStringBuilder 262 | */ 263 | public SpannableStringBuilder build() { 264 | if (mBeforeStringBuilder.length() > 0) { 265 | mStringBuilder.insert(0, mBeforeStringBuilder); 266 | 267 | // reset SpecialUnit start pos 268 | if (!mFinalSpecialUnit.isEmpty()) { 269 | for (BaseSpecialUnit specialUnit : mFinalSpecialUnit) { 270 | int[] tempStartPoss = specialUnit.getStartPoss(); 271 | 272 | if (null == tempStartPoss || tempStartPoss.length == 0) continue; 273 | 274 | for (int i = 0; i < tempStartPoss.length; i++) { 275 | int oldStartPos = tempStartPoss[i]; 276 | tempStartPoss[i] = oldStartPos + mBeforeStringBuilder.length(); 277 | } 278 | } 279 | } 280 | 281 | // reset Cache SpecialClickableUnit start pos 282 | if (!mCacheSpecialClickableUnitMap.isEmpty()) { 283 | for (Map.Entry cm : mCacheSpecialClickableUnitMap.entrySet()) { 284 | cm.getValue().startPos += mBeforeStringBuilder.length(); 285 | } 286 | } 287 | } 288 | 289 | if (!mBeforeCacheSpecialClickableUnitMap.isEmpty()) { 290 | mCacheSpecialClickableUnitMap.putAll(mBeforeCacheSpecialClickableUnitMap); 291 | } 292 | 293 | if (!mBeforeSpecialUnit.isEmpty()) { 294 | mFinalSpecialUnit.addAll(mBeforeSpecialUnit); 295 | } 296 | 297 | if (mStringBuilder.length() == 0) return null; 298 | if (mFinalSpecialUnit.isEmpty()) 299 | return new SpannableStringBuilder(mStringBuilder.toString()); 300 | 301 | if (mNormalSizeText.length() == 0) mNormalSizeText.append(mStringBuilder); 302 | 303 | String normalSizeText = mNormalSizeText.toString(); 304 | SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(mStringBuilder); 305 | boolean isInitClickListener = false; 306 | for (BaseSpecialUnit st : mFinalSpecialUnit) { 307 | String specialText = st.getText(); 308 | int[] startPoss = st.getStartPoss(); 309 | 310 | if (TextUtils.isEmpty(specialText) || null == startPoss || startPoss.length == 0) 311 | continue; 312 | 313 | int specialTextLength = specialText.length(); 314 | if (st instanceof SpecialTextUnit) { 315 | // text span 316 | SpecialTextUnit specialTextUnit = (SpecialTextUnit) st; 317 | 318 | final SpecialClickableUnit internalSpecialClickableUnit = specialTextUnit.getSpecialClickableUnit(); 319 | if (null != internalSpecialClickableUnit) { 320 | if (internalSpecialClickableUnit.getNormalTextColor() == 0) { 321 | internalSpecialClickableUnit.setNormalTextColor(specialTextUnit.getTextColor()); 322 | } 323 | if (internalSpecialClickableUnit.getNormalBgColor() == 0) { 324 | internalSpecialClickableUnit.setNormalBgColor(specialTextUnit.getTextBackgroundColor()); 325 | } 326 | } 327 | for (int startPos : startPoss) { 328 | // Set Text Color 329 | if (specialTextUnit.getTextColor() != 0) { 330 | spannableStringBuilder.setSpan(new ForegroundColorSpan(specialTextUnit.getTextColor()), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 331 | } 332 | 333 | // Set Text Background Color 334 | if (specialTextUnit.getTextBackgroundColor() != 0 && null == internalSpecialClickableUnit) { 335 | spannableStringBuilder.setSpan(new BackgroundColorSpan(specialTextUnit.getTextBackgroundColor()), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 336 | } 337 | 338 | // Add Underline 339 | if (specialTextUnit.isShowUnderline()) { 340 | spannableStringBuilder.setSpan(new UnderlineSpan(), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 341 | } 342 | 343 | // Add StrikeThrough 344 | if (specialTextUnit.isShowStrikeThrough()) { 345 | spannableStringBuilder.setSpan(new StrikethroughSpan(), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 346 | } 347 | 348 | // Set Text Bold 349 | if (specialTextUnit.isTextBold()) { 350 | spannableStringBuilder.setSpan(new StyleSpan(Typeface.BOLD), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 351 | } 352 | 353 | // Set Text Italic 354 | if (specialTextUnit.isTextItalic()) { 355 | spannableStringBuilder.setSpan(new StyleSpan(Typeface.ITALIC), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 356 | } 357 | 358 | // Set Text Style 359 | if (specialTextUnit.getTextStyle() != Typeface.NORMAL) { 360 | spannableStringBuilder.setSpan(new StyleSpan(specialTextUnit.getTextStyle()), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 361 | } 362 | 363 | // Set Text Size 364 | if (specialTextUnit.getTextSize() > 0) { 365 | TextPaint curTextPaint = specialTextUnit.getCurTextPaint(); 366 | int gravity = specialTextUnit.getGravity(); 367 | if (gravity != SpecialGravity.BOTTOM && null != curTextPaint) { 368 | spannableStringBuilder.setSpan(new CustomAbsoluteSizeSpan(normalSizeText, specialTextUnit.getText(), Math.round(specialTextUnit.getTextSize()), curTextPaint, gravity), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 369 | } else { 370 | spannableStringBuilder.setSpan(new AbsoluteSizeSpan(Math.round(specialTextUnit.getTextSize()), true), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 371 | } 372 | } 373 | 374 | // set clickable 375 | if (null != internalSpecialClickableUnit) { 376 | if (!isInitClickListener) { 377 | isInitClickListener = true; 378 | TextView curTextView = internalSpecialClickableUnit.getCurTextView(); 379 | if (null != curTextView) { 380 | curTextView.setMovementMethod(CustomLinkMovementMethod.getInstance()); 381 | } 382 | } 383 | spannableStringBuilder.setSpan(new CustomClickableSpan(internalSpecialClickableUnit), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 384 | } 385 | } 386 | } else if (st instanceof SpecialImageUnit) { 387 | // image Span 388 | SpecialImageUnit specialImageUnit = (SpecialImageUnit) st; 389 | Bitmap bitmap = specialImageUnit.getBitmap(); 390 | int imgWidth = specialImageUnit.getWidth(); 391 | int imgHeight = specialImageUnit.getHeight(); 392 | if (imgWidth > 0 && imgHeight > 0) { 393 | int bitWidth = bitmap.getWidth(); 394 | int bitHeight = bitmap.getHeight(); 395 | 396 | if (imgWidth < bitWidth && imgHeight < bitHeight) { 397 | Bitmap newBitmap = ThumbnailUtils.extractThumbnail(bitmap, imgWidth, imgHeight); 398 | if (null != newBitmap) { 399 | bitmap.recycle(); 400 | specialImageUnit.setBitmap(newBitmap); 401 | } 402 | } 403 | } 404 | 405 | for (int startPos : startPoss) { 406 | CustomImageSpan curCustomImageSpan = new CustomImageSpan(normalSizeText, specialImageUnit); 407 | spannableStringBuilder.setSpan(curCustomImageSpan, startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 408 | 409 | if (specialImageUnit.isClickable()) { 410 | addClickStateChangeListeners(startPos, startPos + specialTextLength, curCustomImageSpan); 411 | } 412 | } 413 | } else if (st instanceof SpecialLabelUnit) { 414 | // label span 415 | SpecialLabelUnit specialLabelUnit = (SpecialLabelUnit) st; 416 | for (int startPos : startPoss) { 417 | CustomLabelSpan curCustomLabelSpan = new CustomLabelSpan(normalSizeText, specialLabelUnit); 418 | spannableStringBuilder.setSpan(curCustomLabelSpan, startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 419 | 420 | if (specialLabelUnit.isClickable()) { 421 | addClickStateChangeListeners(startPos, startPos + specialTextLength, curCustomLabelSpan); 422 | } 423 | } 424 | } else if (st instanceof SpecialClickableUnit) { 425 | // clickable span 426 | SpecialClickableUnit specialClickableUnit = (SpecialClickableUnit) st; 427 | 428 | if (!isInitClickListener) { 429 | isInitClickListener = true; 430 | TextView curTextView = specialClickableUnit.getCurTextView(); 431 | if (null != curTextView) { 432 | curTextView.setMovementMethod(CustomLinkMovementMethod.getInstance()); 433 | } 434 | } 435 | 436 | int startPos = startPoss[0]; 437 | spannableStringBuilder.setSpan(new CustomClickableSpan(specialClickableUnit), startPos, startPos + specialTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 438 | } else if (st instanceof SpecialRawSpanUnit) { 439 | // raw span 440 | SpecialRawSpanUnit specialRawSpanUnit = (SpecialRawSpanUnit) st; 441 | 442 | int startPos = startPoss[0]; 443 | spannableStringBuilder.setSpan(specialRawSpanUnit.getSpanObj(), startPos, startPos + specialTextLength, specialRawSpanUnit.getFlags()); 444 | 445 | // Temporarily unable to support all 446 | } 447 | } 448 | 449 | return spannableStringBuilder; 450 | } 451 | 452 | private void processMultiClickableSpecialUnit(boolean isToFirst, SpecialClickableUnit specialClickableUnit, Object... specialUnitOrStrings) { 453 | if (null == specialClickableUnit || null == specialUnitOrStrings || specialUnitOrStrings.length == 0) 454 | return; 455 | 456 | StringBuilder tempStrBuild = new StringBuilder(); 457 | int baseStartPos; 458 | if (isToFirst) { 459 | baseStartPos = mBeforeStringBuilder.length(); 460 | } else { 461 | baseStartPos = mStringBuilder.length(); 462 | } 463 | for (Object su : specialUnitOrStrings) { 464 | if (su instanceof SpecialTextUnit) { 465 | SpecialTextUnit specialTextUnit = (SpecialTextUnit) su; 466 | 467 | String specialText = specialTextUnit.getText(); 468 | if (TextUtils.isEmpty(specialText)) continue; 469 | 470 | specialTextUnit.setClickableUnit(null); 471 | 472 | // process start pos 473 | specialTextUnit.setStartPoss(new int[]{baseStartPos + tempStrBuild.length()}); 474 | if (isToFirst) { 475 | mBeforeSpecialUnit.add(specialTextUnit); 476 | } else { 477 | mFinalSpecialUnit.add(specialTextUnit); 478 | } 479 | tempStrBuild.append(specialText); 480 | } else if (su instanceof SpecialImageUnit) { 481 | SpecialImageUnit specialImageUnit = (SpecialImageUnit) su; 482 | 483 | String specialText = specialImageUnit.getText(); 484 | if (TextUtils.isEmpty(specialText)) continue; 485 | 486 | specialImageUnit.setClickable(true); 487 | if (specialImageUnit.getBgColor() == 0 && specialClickableUnit.getNormalBgColor() != 0) { 488 | specialImageUnit.setBgColor(specialClickableUnit.getNormalBgColor()); 489 | } 490 | 491 | // process start pos 492 | specialImageUnit.setStartPoss(new int[]{baseStartPos + tempStrBuild.length()}); 493 | if (isToFirst) { 494 | mBeforeSpecialUnit.add(specialImageUnit); 495 | } else { 496 | mFinalSpecialUnit.add(specialImageUnit); 497 | } 498 | tempStrBuild.append(specialText); 499 | } else if (su instanceof SpecialLabelUnit) { 500 | SpecialLabelUnit specialLabelUnit = (SpecialLabelUnit) su; 501 | 502 | String specialText = specialLabelUnit.getText(); 503 | if (TextUtils.isEmpty(specialText)) continue; 504 | 505 | specialLabelUnit.setClickable(true); 506 | if (specialLabelUnit.getBgColor() == 0 && specialClickableUnit.getNormalBgColor() != 0) { 507 | specialLabelUnit.setBgColor(specialClickableUnit.getNormalBgColor()); 508 | } 509 | 510 | // process start pos 511 | specialLabelUnit.setStartPoss(new int[]{baseStartPos + tempStrBuild.length()}); 512 | if (isToFirst) { 513 | mBeforeSpecialUnit.add(specialLabelUnit); 514 | } else { 515 | mFinalSpecialUnit.add(specialLabelUnit); 516 | } 517 | tempStrBuild.append(specialText); 518 | } else if (su instanceof String) { 519 | tempStrBuild.append(su.toString()); 520 | } 521 | } 522 | 523 | String finalStr = tempStrBuild.toString(); 524 | specialClickableUnit.setText(finalStr); 525 | specialClickableUnit.setStartPoss(new int[]{baseStartPos}); 526 | PositionInfo positionInfo = new PositionInfo(baseStartPos, finalStr.length()); 527 | if (isToFirst) { 528 | mBeforeStringBuilder.insert(baseStartPos, finalStr); 529 | mBeforeSpecialUnit.add(specialClickableUnit); 530 | mBeforeCacheSpecialClickableUnitMap.put(specialClickableUnit, positionInfo); 531 | } else { 532 | mStringBuilder.append(finalStr); 533 | mFinalSpecialUnit.add(specialClickableUnit); 534 | mCacheSpecialClickableUnitMap.put(specialClickableUnit, positionInfo); 535 | } 536 | } 537 | 538 | private void addClickStateChangeListeners(int startPos, int endPos, OnClickStateChangeListener onClickStateChangeListener) { 539 | if (mCacheSpecialClickableUnitMap.isEmpty()) return; 540 | 541 | for (Map.Entry cm : mCacheSpecialClickableUnitMap.entrySet()) { 542 | PositionInfo curPositionInfo = cm.getValue(); 543 | int curStartPos = curPositionInfo.startPos; 544 | int curEndPos = curStartPos + curPositionInfo.textLength; 545 | if (startPos >= curStartPos && endPos <= curEndPos) { 546 | SpecialClickableUnit curCacheSpecialClickableUnit = cm.getKey(); 547 | List onClickStateChangeListeners = curCacheSpecialClickableUnit.getOnClickStateChangeListeners(); 548 | if (null == onClickStateChangeListeners) { 549 | onClickStateChangeListeners = new ArrayList<>(); 550 | curCacheSpecialClickableUnit.setOnClickStateChangeListeners(onClickStateChangeListeners); 551 | } 552 | onClickStateChangeListeners.add(onClickStateChangeListener); 553 | break; 554 | } 555 | } 556 | } 557 | 558 | static class PositionInfo { 559 | int startPos; 560 | int textLength; 561 | 562 | public PositionInfo(int startPos, int textLength) { 563 | this.startPos = startPos; 564 | this.textLength = textLength; 565 | } 566 | } 567 | 568 | } 569 | --------------------------------------------------------------------------------