├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ └── styles.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_face.jpg │ │ │ ├── ic_test.jpg │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── assets │ │ └── fonts │ │ │ └── Aileron-Light.otf │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── zrq │ │ └── spanbuilderdemo │ │ └── MainActivity.java ├── proguard-rules.pro └── build.gradle ├── spanbuilder ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── zrq │ │ └── spanbuilder │ │ ├── CustomTypefaceSpan.kt │ │ ├── Span.kt │ │ └── Spans.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── screenshots ├── all.jpg ├── common.jpg ├── qrcode.png └── app-debug.apk ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /spanbuilder/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':spanbuilder' 2 | -------------------------------------------------------------------------------- /screenshots/all.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/screenshots/all.jpg -------------------------------------------------------------------------------- /screenshots/common.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/screenshots/common.jpg -------------------------------------------------------------------------------- /screenshots/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/screenshots/qrcode.png -------------------------------------------------------------------------------- /screenshots/app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/screenshots/app-debug.apk -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SpanBuilderDemo 3 | 4 | -------------------------------------------------------------------------------- /spanbuilder/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SpanBuilder 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Aileron-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/app/src/main/assets/fonts/Aileron-Light.otf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_face.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_test.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrq1060/SpanBuilder/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /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 | #Fri Sep 25 16:21:01 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.6.1-all.zip 7 | -------------------------------------------------------------------------------- /spanbuilder/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\Android-SDK/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 | -------------------------------------------------------------------------------- /spanbuilder/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\Android-SDK/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 | -------------------------------------------------------------------------------- /spanbuilder/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: "com.vanniktech.maven.publish" 4 | 5 | android { 6 | compileSdkVersion 30 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 30 11 | versionCode 1 12 | versionName "1.0.0" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | 22 | lintOptions { 23 | abortOnError false 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 29 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.zrq.spanbuilderdemo" 7 | minSdkVersion 16 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation fileTree(include: ['*.jar'], dir: 'libs') 22 | implementation 'com.android.support:appcompat-v7:27.1.1' 23 | // implementation project(':spanbuilder') 24 | // implementation 'com.zrq:spans:1.1.0' 25 | implementation 'io.github.zrq1060:spans:1.1.0' 26 | } 27 | -------------------------------------------------------------------------------- /spanbuilder/src/main/java/com/zrq/spanbuilder/CustomTypefaceSpan.kt: -------------------------------------------------------------------------------- 1 | package com.zrq.spanbuilder 2 | 3 | import android.graphics.Paint 4 | import android.graphics.Typeface 5 | import android.text.TextPaint 6 | import android.text.style.MetricAffectingSpan 7 | 8 | /** 9 | * 描述:可设置自定义Typeface的span 10 | * 11 | * @author zhangrq 12 | * 2016/10/13 18:03 13 | */ 14 | class CustomTypefaceSpan(private val typeface: Typeface) : MetricAffectingSpan() { 15 | override fun updateDrawState(drawState: TextPaint) { 16 | apply(drawState) 17 | } 18 | 19 | override fun updateMeasureState(paint: TextPaint) { 20 | apply(paint) 21 | } 22 | 23 | private fun apply(paint: Paint) { 24 | val oldTypeface = paint.typeface 25 | val oldStyle = oldTypeface?.style ?: 0 26 | val fakeStyle = oldStyle and typeface.style.inv() 27 | if (fakeStyle and Typeface.BOLD != 0) { 28 | paint.isFakeBoldText = true 29 | } 30 | if (fakeStyle and Typeface.ITALIC != 0) { 31 | paint.textSkewX = -0.25f 32 | } 33 | paint.typeface = typeface 34 | } 35 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | GROUP=io.github.zrq1060 19 | POM_ARTIFACT_ID=spans 20 | VERSION_NAME=1.1.0 21 | 22 | POM_NAME=spans 23 | POM_DESCRIPTION=android span builder 24 | POM_INCEPTION_YEAR=2021 25 | POM_URL=https://github.com/zrq1060/SpanBuilder/ 26 | 27 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 28 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 29 | POM_LICENCE_DIST=repo 30 | 31 | POM_SCM_URL=https://github.com/zrq1060/SpanBuilder/ 32 | POM_SCM_CONNECTION=scm:git:git://github.com/zrq1060/SpanBuilder.git 33 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/zrq1060/SpanBuilder.git 34 | 35 | POM_DEVELOPER_ID=zrq1060 36 | POM_DEVELOPER_NAME=zrq1060 37 | POM_DEVELOPER_URL=https://github.com/zrq1060/ -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 19 | 20 | 27 | 28 | 35 | 36 | 44 | 45 | 53 | 54 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpanBuilder 2 | 3 | ## 特点 4 | 5 | * **内置大量常用样式** 6 | * **支持自定义样式** 7 | 8 | ## Demo 9 | 10 | ### 下载 11 | ![qrcode](https://github.com/zrq1060/SpanBuilder/blob/master/screenshots/qrcode.png) 12 | 13 | ### 项目演示 14 | 15 | #### 常用效果 16 | 17 | 18 | #### 全部效果 19 | 20 | 21 | ## 简单使用 22 | 23 | ### 引入 24 | 25 | project **build.gradle** 26 | 27 | ```groovy 28 | allprojects { 29 | repositories { 30 | mavenCentral() 31 | } 32 | } 33 | ``` 34 | 35 | app **build.gradle** 36 | 37 | ```groovy 38 | implementation 'io.github.zrq1060:spans:1.1.0' 39 | ``` 40 | 41 | ### 使用 42 | ```java 43 | textView.setText(Spans.builder() 44 | .text("8") 45 | .text(".88").size(28).color(Color.RED) 46 | .text("%").size(16).color(Color.BLACK) 47 | .build()); 48 | ``` 49 | 50 | ## 方法说明 51 | | 方法 | 说明 | 52 | | :-------- | :--------| 53 | | text() | 设置拼接内容,下面的方法全部作用于此text | 54 | | size() | 设置字体大小,单位sp | 55 | | sizePx() | 设置字体大小,单位px | 56 | | color() | 设置字体颜色 | 57 | | style() | 设置字体样式 (Typeface NORMAL:正常,BOLD:粗体,ITALIC:斜体,BOLD_ITALIC:粗斜体) | 58 | | backgroundColor() | 设置字体背景 | 59 | | typeface() | 设置自定义的ttf、otf字体 | 60 | | appearance() | 设置字体外貌 | 61 | | fontFamily() | 设置字体Family (可设置"monospace", "serif", and "sans-serif"等) | 62 | | strikeThrough() | 设置删除线 | 63 | | underLine() | 设置下划线 | 64 | | quote() | 设置引用线颜色 | 65 | | alignment() | 设置对齐方式(ALIGN_NORMAL、ALIGN_OPPOSITE、ALIGN_CENTER) | 66 | | relativeSize() | 设置X倍字体 | 67 | | scaleX() | 设置X轴缩放x倍 | 68 | | superscript() | 设置为上标 | 69 | | subscript() | 设置为下标 | 70 | | click() | 设置可点击 | 71 | | url() | 设置Url,点击会打开网页 | 72 | | drawableMargin() | 可设置Drawable,不会替换内容,顶部对齐,文字在图片右边 | 73 | | iconMargin() | 可设置Bitmap,不会替换内容,顶部对齐,文字在图片右边 | 74 | | dynamicDrawable() | 可设置Drawable,会替换内容,底部对齐,文字环绕嵌入型 | 75 | | image() | 可设置Drawable、Bitmap、Uri、resourceId,会替换内容,基线对齐,文字环绕嵌入型 | 76 | | bullet() | 设置子弹,可设置gapWidth、color | 77 | | leadingMargin() | 设置行前空隙 | 78 | | tabStop() | 设置制表符位置 | 79 | | blurMask() | 设置字体模糊 | 80 | | setSpanAll() | 设置自定义样式,作用于当前text()全部内容 | 81 | | setSpanPart() | 设置自定义样式,作用于当前text()部分内容 | 82 | | build() | | 83 | 84 | 85 | ## 联系我 86 | QQ:273902141 87 | 88 | 邮箱:zrq1060@163.com 89 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/zrq/spanbuilderdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zrq.spanbuilderdemo; 2 | 3 | import android.graphics.BitmapFactory; 4 | import android.graphics.BlurMaskFilter; 5 | import android.graphics.Color; 6 | import android.graphics.Typeface; 7 | import android.graphics.drawable.Drawable; 8 | import android.os.Bundle; 9 | import android.support.annotation.NonNull; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.text.Layout; 12 | import android.text.TextPaint; 13 | import android.text.style.ClickableSpan; 14 | import android.text.style.DynamicDrawableSpan; 15 | import android.text.style.URLSpan; 16 | import android.view.View; 17 | import android.widget.TextView; 18 | import android.widget.Toast; 19 | 20 | import com.zrq.spanbuilder.Spans; 21 | 22 | public class MainActivity extends AppCompatActivity { 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_main); 28 | TextView textView1 = findViewById(R.id.textView1); 29 | TextView textView2 = findViewById(R.id.textView2); 30 | TextView textView3 = findViewById(R.id.textView3); 31 | TextView textView4 = findViewById(R.id.textView4); 32 | TextView textView5 = findViewById(R.id.textView5); 33 | TextView textViewAll = findViewById(R.id.textViewAll); 34 | final Drawable drawable = getResources().getDrawable(R.mipmap.ic_test); 35 | // 获取颜色的方法: 36 | // 1.Color.RED; 37 | // 2.Color.parseColor("#123456"); 38 | // 3.ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary); 39 | // 4.getResources().getColor(R.color.colorPrimary) 40 | 41 | // Spans的常用样式 42 | textView1.setText(Spans.builder() 43 | .text("8") 44 | .text(".88").size(28).color(Color.RED) 45 | .text("%").size(16).color(Color.BLACK) 46 | .build()); 47 | 48 | textView2.setText(Spans.builder() 49 | .text("10").size(50).color(Color.RED).style(Typeface.BOLD) 50 | .text("元") 51 | .build()); 52 | 53 | textView3.setText(Spans.builder() 54 | .text("¥149").size(24).color(Color.RED) 55 | .text(".9 ").size(16).color(Color.RED) 56 | .text("¥259.00").size(20).color(Color.BLACK).strikeThrough() 57 | .text(" 4738").size(20).color(Color.RED).style(Typeface.BOLD_ITALIC) 58 | .text("件已售").size(20).color(Color.BLACK) 59 | .build()); 60 | 61 | textView4.setText(Spans.builder() 62 | .text("请阅读") 63 | .text("《隐私权政策》").click(textView4, new ClickableSpan() { 64 | @Override 65 | public void onClick(@NonNull View widget) { 66 | Toast.makeText(getApplicationContext(), "隐私权政策", Toast.LENGTH_SHORT).show(); 67 | } 68 | 69 | @Override 70 | public void updateDrawState(@NonNull TextPaint ds) { 71 | ds.setColor(Color.RED); 72 | ds.setUnderlineText(false); 73 | } 74 | }) 75 | .text("和") 76 | .text("《用户协议》").click(textView4, new ClickableSpan() { 77 | @Override 78 | public void onClick(@NonNull View widget) { 79 | Toast.makeText(getApplicationContext(), "用户协议", Toast.LENGTH_SHORT).show(); 80 | } 81 | 82 | @Override 83 | public void updateDrawState(@NonNull TextPaint ds) { 84 | ds.setColor(Color.RED); 85 | ds.setUnderlineText(false); 86 | } 87 | }) 88 | .text(" ") 89 | .build()); 90 | 91 | textView5.setText(Spans.builder() 92 | .text("表情前") 93 | .text("表情内容").image(getApplicationContext(), R.mipmap.ic_face) 94 | .text("表情后") 95 | .build()); 96 | 97 | // Spans的全部样式 98 | textViewAll.setText(Spans.builder() 99 | .text("--!!下面内容全部是在一个TextView上--").backgroundColor(Color.RED) 100 | .text("\n字体大小:").text("24SP大小 ").size(24).text("24PX大小").sizePx(24) 101 | .text("\n字体颜色:").text("颜色").color(Color.RED) 102 | .text("\n字体样式:").text("粗体 ").style(Typeface.BOLD).text("斜体 ").style(Typeface.ITALIC).text("粗斜体").style(Typeface.BOLD_ITALIC) 103 | .text("\n------------------------------------------------------------") 104 | .text("\n字体背景:").text("背景").backgroundColor(Color.RED) 105 | .text("\n自定义字体:Hello Word ").text("Hello Word").typeface(Typeface.createFromAsset(getAssets(), "fonts/Aileron-Light.otf")) 106 | .text("\n字体外貌:").text("外貌").appearance(this, android.R.style.TextAppearance_Small) 107 | .text("\n字体Family:").text("字体Family").fontFamily("monospace") 108 | .text("\n------------------------------------------------------------") 109 | .text("\n删除线:").text("删除线").strikeThrough() 110 | .text("\n下划线:").text("下划线").underLine() 111 | .text("\n引用线").quote(Color.BLUE) 112 | .text("\n------------------------------------------------------------") 113 | .text("\n对齐方式:") 114 | .text("\n正常对齐").alignment(Layout.Alignment.ALIGN_NORMAL) 115 | .text("\n反向对齐").alignment(Layout.Alignment.ALIGN_OPPOSITE) 116 | .text("\n居中对齐").alignment(Layout.Alignment.ALIGN_CENTER) 117 | .text("\n------------------------------------------------------------") 118 | .text("\nX倍字体:").text("2倍字体").relativeSize(2) 119 | .text("\nX轴缩放:").text("X轴缩0.5倍").scaleX(0.5f) 120 | .text("\n------------------------------------------------------------") 121 | .text("\n上标:").text("X").text("2").superscript() 122 | .text("\n下标:").text("X").text("2").subscript() 123 | .text("\n------------------------------------------------------------") 124 | .text("\n字体点击:").text("协议").click(textViewAll, new ClickableSpan() { 125 | @Override 126 | public void onClick(@NonNull View widget) { 127 | Toast.makeText(getApplicationContext(), "协议", Toast.LENGTH_SHORT).show(); 128 | } 129 | }).text(" ")// 点击协议后面也会触发点击事件,解决办法协议后面需有不可点击内容 130 | .text("\nURL点击:").text("百度").url(textViewAll, new URLSpan("http://www.baidu.com")).text(" ")// 点击百度后面也会触发点击事件,解决办法百度后面需有不可点击内容 131 | .text("\n------------------------------------------------------------") 132 | .text("\n设置图片:\n") 133 | .text("图片内容,").drawableMargin(drawable).text("可设置Drawable,不会替换内容,顶部对齐,文字在图片右边\n")// 设置 Drawable,不会替换内容 134 | .text("图片内容,").iconMargin(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_test)).text("可设置Bitmap,不会替换内容,顶部对齐,文字在图片右边\n")// 设置 Bitmap,不会替换内容 135 | .text("前前前前").text("图片内容").dynamicDrawable(new DynamicDrawableSpan(DynamicDrawableSpan.ALIGN_BOTTOM) { 136 | @Override 137 | public Drawable getDrawable() { 138 | drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 139 | return drawable;// 需要设置setBounds,会替换内容 140 | } 141 | }).text("后后后后,").text("可设置Drawable,会替换内容,底部对齐,文字环绕嵌入型\n") 142 | .text("前前前前").text("图片内容").image(drawable, DynamicDrawableSpan.ALIGN_BASELINE).text("后后后后,").text("可设置Drawable、Bitmap、Uri、resourceId,会替换内容,基线对齐,文字环绕嵌入型")// 设置 Bitmap,会替换内容 143 | .text("\n------------------------------------------------------------") 144 | .text("\n子弹:看不出效果,需前面无内容").bullet(2, Color.RED)// 前面不能设置内容 145 | .text("\n行前空隙:内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容").leadingMargin(100, 30) 146 | .text("\n制表符位置:内容\t内容内容内容内容内容内容内容内\t容内容内容内容内容内容内容").tabStop(900) 147 | .text("\n字体模糊:").text("容内容内容内容内容内容内容内容内容内容内容").blurMask(30, BlurMaskFilter.Blur.OUTER) 148 | .build()); 149 | 150 | // -------------------方法介绍:---------------------- 151 | // .text() // 设置拼接内容,下面的方法全部作用于此text 152 | // .size() // 设置字体大小,单位sp 153 | // .sizePx() // 设置字体大小,单位px 154 | // .color() // 设置字体颜色 155 | // .style() // 设置字体样式 (Typeface NORMAL:正常,BOLD:粗体,ITALIC:斜体,BOLD_ITALIC:粗斜体) 156 | 157 | // .backgroundColor() // 设置字体背景 158 | // .typeface() // 设置自定义的ttf、otf字体 159 | // .appearance() // 设置字体外貌 160 | // .fontFamily() // 设置字体Family (可设置"monospace", "serif", and "sans-serif"等) 161 | 162 | // .strikeThrough() // 设置删除线 163 | // .underLine() // 设置下划线 164 | // .quote() // 设置引用线颜色 165 | 166 | // .alignment() // 设置对齐方式(ALIGN_NORMAL、ALIGN_OPPOSITE、ALIGN_CENTER) 167 | 168 | // .relativeSize() // 设置X倍字体 169 | // .scaleX() // 设置X轴缩放x倍 170 | 171 | // .superscript() // 设置为上标 172 | // .subscript() // 设置为下标 173 | 174 | // .click() // 设置可点击 175 | // .url() // 设置Url,点击会打开网页 176 | 177 | // .drawableMargin() // 可设置Drawable,不会替换内容,顶部对齐,文字在图片右边 178 | // .iconMargin() // 可设置Bitmap,不会替换内容,顶部对齐,文字在图片右边 179 | // .dynamicDrawable() // 可设置Drawable,会替换内容,底部对齐,文字环绕嵌入型 180 | // .image() // 可设置Drawable、Bitmap、Uri、resourceId,会替换内容,基线对齐,文字环绕嵌入型 181 | 182 | // .bullet() // 设置子弹,可设置gapWidth、color 183 | // .leadingMargin() // 设置行前空隙 184 | // .tabStop() // 设置制表符位置 185 | // .blurMask() // 设置字体模糊 186 | 187 | 188 | // .setSpanAll() // 设置自定义样式,作用于当前text()全部内容 189 | // .setSpanPart() // 设置自定义样式,作用于当前text()部分内容 190 | // .build() 191 | 192 | 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /spanbuilder/src/main/java/com/zrq/spanbuilder/Span.kt: -------------------------------------------------------------------------------- 1 | package com.zrq.spanbuilder 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.BlurMaskFilter 6 | import android.graphics.Color 7 | import android.graphics.Typeface 8 | import android.graphics.drawable.Drawable 9 | import android.net.Uri 10 | import android.text.Layout 11 | import android.text.SpannableString 12 | import android.text.Spanned.* 13 | import android.text.method.LinkMovementMethod 14 | import android.text.style.* 15 | import android.widget.TextView 16 | 17 | /** 18 | * 描述: Span构建者,内部封装了常用的样式 19 | * 20 | * 21 | * 邮箱:zrq1060@163.com 22 | * 23 | * @author zhangrq 24 | * 2016/9/30 11:50 25 | */ 26 | class Span(source: CharSequence?) : SpannableString(source ?: "") { 27 | /** 28 | * 设置字体大小 29 | * 30 | * [size] 设置字体大小(单位sp) 31 | */ 32 | fun setTextSize(size: Int): Span { 33 | setOneSpanAll(AbsoluteSizeSpan(size, true)) 34 | return this 35 | } 36 | 37 | /** 38 | * 设置字体大小 39 | * 40 | * [size] 设置字体大小(单位px) 41 | */ 42 | fun setTextSizePx(size: Int): Span { 43 | setOneSpanAll(AbsoluteSizeSpan(size, false)) 44 | return this 45 | } 46 | 47 | /** 48 | * 设置字体颜色 49 | * 50 | * [color] 字体颜色 51 | */ 52 | fun setTextColor(color: Int): Span { 53 | setOneSpanAll(ForegroundColorSpan(color)) 54 | return this 55 | } 56 | 57 | /** 58 | * 设置背景颜色 59 | * 60 | * [color] 背景颜色 61 | */ 62 | fun setBackgroundColor(color: Int): Span { 63 | setOneSpanAll(BackgroundColorSpan(color)) 64 | return this 65 | } 66 | 67 | /** 68 | * 设置字体类型 69 | * 70 | * [style] 字体类型 [android.graphics.Typeface] NORMAL:正常,BOLD:粗体,ITALIC:斜体,BOLD_ITALIC:粗斜体 71 | */ 72 | fun setTextStyle(style: Int): Span { 73 | setOneSpanAll(StyleSpan(style)) 74 | return this 75 | } 76 | 77 | /** 78 | * 设置自定义的字体类型 79 | * 80 | * [typeface] 设置字体类型 81 | */ 82 | fun setTypeface(typeface: Typeface): Span { 83 | setOneSpanAll(CustomTypefaceSpan(typeface)) 84 | return this 85 | } 86 | 87 | /** 88 | * 设置字体style样式的外观 89 | * 90 | * [styleId] 字体style样式的id 91 | */ 92 | fun setTextAppearance(context: Context, styleId: Int): Span { 93 | setOneSpanAll(TextAppearanceSpan(context, styleId)) 94 | return this 95 | } 96 | 97 | /** 98 | * 设置系统自带字体类型 99 | * 100 | * [family] 设置字体 "monospace", "serif", and "sans-serif"等 101 | */ 102 | fun setFontFamily(family: String?): Span { 103 | setOneSpanAll(TypefaceSpan(family)) 104 | return this 105 | } 106 | 107 | /** 108 | * 设置点击 109 | * (默认是有下划线和字体颜色的,如需更改,则覆盖clickableSpan对象的updateDrawState方法 )设置如下:

110 | * public void updateDrawState(TextPaint ds) {

111 | * ds.setColor(ds.linkColor);

112 | * ds.setUnderlineText(false);

113 | * } 114 | * 115 | * [textView] 设置可点击的textView 116 | * [clickableSpan] 点击的span 117 | */ 118 | fun setClick(textView: TextView?, clickableSpan: ClickableSpan?): Span { 119 | if (textView != null) { 120 | textView.movementMethod = LinkMovementMethod.getInstance() 121 | textView.highlightColor = Color.TRANSPARENT 122 | } 123 | setOneSpanAll(clickableSpan) 124 | return this 125 | } 126 | 127 | /** 128 | * 设置删除线的样式 129 | */ 130 | fun setStrikeThrough(): Span { 131 | setOneSpanAll(StrikethroughSpan()) 132 | return this 133 | } 134 | 135 | /** 136 | * 设置下划线的样式 137 | */ 138 | fun setUnderLine(): Span { 139 | setOneSpanAll(UnderlineSpan()) 140 | return this 141 | } 142 | 143 | /** 144 | * 设置垂直的引用线 145 | * 146 | * [color] 设置垂直的引用线的颜色 147 | */ 148 | fun setQuote(color: Int): Span { 149 | setOneSpanAll(QuoteSpan(color)) 150 | return this 151 | } 152 | 153 | /** 154 | * 设置字体内容的对其方式 155 | * 156 | * [align] 设置对其方式 ALIGN_NORMAL, ALIGN_OPPOSITE, ALIGN_CENTER, 157 | */ 158 | fun setAlignment(align: Layout.Alignment): Span { 159 | setOneSpanAll(AlignmentSpan.Standard(align)) 160 | return this 161 | } 162 | 163 | /** 164 | * 设置字体的相对大小 165 | * 166 | * [proportion] 设置相对textSize的比例大小 167 | */ 168 | fun setRelativeSize(proportion: Float): Span { 169 | setOneSpanAll(RelativeSizeSpan(proportion)) 170 | return this 171 | } 172 | 173 | /** 174 | * 设置字体为上标的样式 175 | */ 176 | fun setSuperscript(): Span { 177 | setOneSpanAll(SuperscriptSpan()) 178 | return this 179 | } 180 | 181 | /** 182 | * 设置字体为下标的样式 183 | */ 184 | fun setSubscript(): Span { 185 | setOneSpanAll(SubscriptSpan()) 186 | return this 187 | } 188 | 189 | /** 190 | * 设置字体X轴缩放 191 | * 192 | * [proportion] 缩放的倍数 193 | */ 194 | fun setScaleX(proportion: Float): Span { 195 | setOneSpanAll(ScaleXSpan(proportion)) 196 | return this 197 | } 198 | 199 | /** 200 | * 设置URL,点击会打开浏览器展示 201 | * 202 | * [urlSpan] 如需修改字体颜色等,请参考[setClick]方法 203 | */ 204 | fun setURL(textView: TextView?, urlSpan: URLSpan): Span { 205 | if (textView != null) { 206 | textView.movementMethod = LinkMovementMethod.getInstance() 207 | textView.highlightColor = Color.TRANSPARENT 208 | } 209 | setOneSpanAll(urlSpan) 210 | return this 211 | } 212 | 213 | /** 214 | * 设置图片-Drawable,不需要设置setBounds,不会替换内容 215 | * 216 | * [drawable] 图片 217 | * [pad] 距离 218 | */ 219 | @JvmOverloads 220 | fun setDrawableMargin(drawable: Drawable, pad: Int = 0): Span { 221 | setOneSpanAll(DrawableMarginSpan(drawable, pad)) 222 | return this 223 | } 224 | 225 | /** 226 | * 设置图片-Bitmap,不会替换内容 227 | * 228 | * [bitmap] 图片 229 | * [pad] 距离 230 | */ 231 | @JvmOverloads 232 | fun setIconMargin(bitmap: Bitmap, pad: Int = 0): Span { 233 | setOneSpanAll(IconMarginSpan(bitmap, pad)) 234 | return this 235 | } 236 | 237 | /** 238 | * 设置图片-Drawable,需要设置setBounds,会替换内容 239 | * 240 | * [dynamicDrawableSpan] 提供Drawable的span 241 | */ 242 | fun setDynamicDrawable(dynamicDrawableSpan: DynamicDrawableSpan): Span { 243 | setOneSpanAll(dynamicDrawableSpan) 244 | return this 245 | } 246 | 247 | /** 248 | * 设置图片-Bitmap,会替换内容 249 | * 250 | * [verticalAlignment] 垂直对齐方式:ALIGN_BOTTOM、ALIGN_BASELINE 251 | */ 252 | @JvmOverloads 253 | fun setImage(context: Context, bitmap: Bitmap, verticalAlignment: Int = DynamicDrawableSpan.ALIGN_BOTTOM): Span { 254 | setOneSpanAll(ImageSpan(context, bitmap, verticalAlignment)) 255 | return this 256 | } 257 | 258 | /** 259 | * 设置图片-Drawable,会替换内容。Drawable需设置setBounds。 260 | * 261 | * [verticalAlignment] 垂直对齐方式:ALIGN_BOTTOM、ALIGN_BASELINE 262 | */ 263 | @JvmOverloads 264 | fun setImage(drawable: Drawable, source: String? = null, verticalAlignment: Int = DynamicDrawableSpan.ALIGN_BOTTOM): Span { 265 | setOneSpanAll(if (source == null) ImageSpan(drawable, verticalAlignment) else ImageSpan(drawable, source, verticalAlignment)) 266 | return this 267 | } 268 | 269 | /** 270 | * 解决上述方法JvmOverloads后无此参数情况 271 | */ 272 | fun setImage(drawable: Drawable, verticalAlignment: Int) = setImage(drawable, null, verticalAlignment) 273 | 274 | /** 275 | * 设置图片-Uri,会替换内容。 276 | * 277 | * [verticalAlignment] 垂直对齐方式:ALIGN_BOTTOM、ALIGN_BASELINE 278 | */ 279 | @JvmOverloads 280 | fun setImage(context: Context, uri: Uri, verticalAlignment: Int = DynamicDrawableSpan.ALIGN_BOTTOM): Span { 281 | setOneSpanAll(ImageSpan(context, uri, verticalAlignment)) 282 | return this 283 | } 284 | 285 | /** 286 | * 设置图片-resourceId,会替换内容。 287 | * 288 | * [verticalAlignment] 垂直对齐方式:ALIGN_BOTTOM、ALIGN_BASELINE 289 | */ 290 | @JvmOverloads 291 | fun setImage(context: Context, resourceId: Int, verticalAlignment: Int = DynamicDrawableSpan.ALIGN_BOTTOM): Span { 292 | setOneSpanAll(ImageSpan(context, resourceId, verticalAlignment)) 293 | return this 294 | } 295 | 296 | /** 297 | * 设置子弹 298 | */ 299 | @JvmOverloads 300 | fun setBullet(gapWidth: Int = BulletSpan.STANDARD_GAP_WIDTH, color: Int? = null): Span { 301 | setOneSpanAll(if (color == null) BulletSpan(gapWidth) else BulletSpan(gapWidth, color)) 302 | return this 303 | } 304 | 305 | /** 306 | * 设置行缩进 307 | * [first] 段落第一行的缩进距离 308 | * [rest] 段落其余行缩进距离 309 | */ 310 | @JvmOverloads 311 | fun setLeadingMargin(first: Int, rest: Int = first): Span { 312 | setOneSpanAll(LeadingMarginSpan.Standard(first, rest)) 313 | return this 314 | } 315 | 316 | /** 317 | * 设置一行上制表符位置 318 | * [where] 制表符与行前距离的偏移量 319 | */ 320 | fun setTabStop(where: Int): Span { 321 | setOneSpanAll(TabStopSpan.Standard(where)) 322 | return this 323 | } 324 | 325 | /** 326 | * 设置模糊 327 | * [radius] 模糊的半径 328 | * [style] 模糊的样式 329 | */ 330 | fun setBlurMask(radius: Float, style: BlurMaskFilter.Blur): Span { 331 | setOneSpanAll(MaskFilterSpan(BlurMaskFilter(radius, style))) 332 | return this 333 | } 334 | 335 | /** 336 | * 设置一个样式-作用于text全部 337 | * [span] 一个span样式 338 | */ 339 | private fun setOneSpanAll(span: Any?): Span { 340 | return setSpanPart(0, length, SPAN_EXCLUSIVE_EXCLUSIVE, span) 341 | } 342 | 343 | /** 344 | * 设置样式-作用于text全部 345 | * [flags] 默认:[SPAN_EXCLUSIVE_EXCLUSIVE]。常用:[SPAN_INCLUSIVE_INCLUSIVE]、[SPAN_EXCLUSIVE_EXCLUSIVE]、[SPAN_INCLUSIVE_EXCLUSIVE]、[SPAN_EXCLUSIVE_INCLUSIVE],详情请看:https://www.jianshu.com/p/1956e15c9a27 346 | * [spans] 多个span样式 347 | */ 348 | @JvmOverloads 349 | fun setSpanAll(flags: Int = SPAN_EXCLUSIVE_EXCLUSIVE, vararg spans: Any?): Span { 350 | return setSpanPart(0, length, flags, *spans) 351 | } 352 | 353 | /** 354 | * 设置样式-作用于text文本start到end的位置 355 | * 356 | * [start] 从此位置开始设置样式 357 | * [end] 从此位置结束设置样式 358 | * [flags] 默认:[SPAN_EXCLUSIVE_EXCLUSIVE]。常用:[SPAN_INCLUSIVE_INCLUSIVE]、[SPAN_EXCLUSIVE_EXCLUSIVE]、[SPAN_INCLUSIVE_EXCLUSIVE]、[SPAN_EXCLUSIVE_INCLUSIVE],详情请看:https://www.jianshu.com/p/1956e15c9a27 359 | * [spans] 多个span样式 360 | */ 361 | @JvmOverloads 362 | fun setSpanPart(start: Int, end: Int, flags: Int = SPAN_EXCLUSIVE_EXCLUSIVE, vararg spans: Any?): Span { 363 | if (start > end || spans.isEmpty()) return this 364 | for (span in spans) { 365 | if (span == null) continue 366 | super.setSpan(span, start, end, flags) 367 | } 368 | return this 369 | } 370 | } -------------------------------------------------------------------------------- /spanbuilder/src/main/java/com/zrq/spanbuilder/Spans.kt: -------------------------------------------------------------------------------- 1 | package com.zrq.spanbuilder 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.BlurMaskFilter 6 | import android.graphics.Typeface 7 | import android.graphics.drawable.Drawable 8 | import android.net.Uri 9 | import android.text.Layout 10 | import android.text.SpannableString.* 11 | import android.text.SpannableStringBuilder 12 | import android.text.style.* 13 | import android.widget.TextView 14 | 15 | /** 16 | * 描述: 17 | * 18 | * @author zhangrq 19 | * 2016/11/5 21:14 20 | */ 21 | class Spans : SpannableStringBuilder() { 22 | 23 | /** 24 | * 构建者模式拼接Spans 25 | */ 26 | class Builder { 27 | private var span = Span("") 28 | private var spans = Spans() 29 | 30 | /** 31 | * 设置文本内容 32 | * [text] 文本 33 | */ 34 | fun text(text: CharSequence?): Builder { 35 | // 新文本起始设置,拼接上一个,创建新的Span 36 | appendOld() // 拼接上一个 37 | span = Span(text) 38 | return this 39 | } 40 | 41 | /** 42 | * 生成拼接好的Spans 43 | */ 44 | fun build(): Spans { 45 | // 文本结束设置,拼接上一个,并返回结果 46 | appendOld() // 拼接上一个 47 | return spans 48 | } 49 | 50 | /** 51 | * 拼接上一个span 52 | */ 53 | private fun appendOld() { 54 | if (span.length != 0) { 55 | // 无内容即无样式,不增加;有内容,增加,增加的是前一个的样式 56 | spans.append(span) 57 | } 58 | } 59 | 60 | /** 61 | * 设置字体大小 62 | * 63 | * [textSize] 设置字体大小(单位sp) 64 | */ 65 | fun size(textSize: Int): Builder { 66 | span.setTextSize(textSize) 67 | return this 68 | } 69 | 70 | /** 71 | * 设置字体大小 72 | * 73 | * [textSize] 设置字体大小(单位Px) 74 | */ 75 | fun sizePx(textSize: Int): Builder { 76 | span.setTextSizePx(textSize) 77 | return this 78 | } 79 | 80 | /** 81 | * 设置字体颜色 82 | * 83 | * [textColor] 字体颜色 84 | */ 85 | fun color(textColor: Int): Builder { 86 | span.setTextColor(textColor) 87 | return this 88 | } 89 | 90 | /** 91 | * 设置背景颜色 92 | * 93 | * [color] 背景颜色 94 | */ 95 | fun backgroundColor(color: Int): Builder { 96 | span.setBackgroundColor(color) 97 | return this 98 | } 99 | 100 | /** 101 | * 设置字体类型 102 | * 103 | * [style] 字体类型 [android.graphics.Typeface] NORMAL:正常,BOLD:粗体,ITALIC:斜体,BOLD_ITALIC:粗斜体 104 | */ 105 | fun style(style: Int): Builder { 106 | span.setTextStyle(style) 107 | return this 108 | } 109 | 110 | /** 111 | * 设置自定义的字体类型 112 | * 113 | * [typeface] 设置字体类型 114 | */ 115 | fun typeface(typeface: Typeface): Builder { 116 | span.setTypeface(typeface) 117 | return this 118 | } 119 | 120 | /** 121 | * 设置字体style样式的外观 122 | * 123 | * [styleId] 字体style样式的id 124 | */ 125 | fun appearance(context: Context, styleId: Int): Builder { 126 | span.setTextAppearance(context, styleId) 127 | return this 128 | } 129 | 130 | /** 131 | * 设置系统自带字体类型 132 | * 133 | * [family] 设置字体 "monospace", "serif", and "sans-serif"等 134 | */ 135 | fun fontFamily(family: String?): Builder { 136 | span.setFontFamily(family) 137 | return this 138 | } 139 | 140 | /** 141 | * 设置点击 142 | * (默认是有下划线和字体颜色的,如需更改,则覆盖clickableSpan对象的updateDrawState方法 )设置如下:

143 | * public void updateDrawState(TextPaint ds) {

144 | * ds.setColor(ds.linkColor);

145 | * ds.setUnderlineText(false);

146 | * } 147 | * 148 | * [textView] 设置可点击的textView 149 | * [clickableSpan] 点击的span 150 | */ 151 | fun click(textView: TextView?, clickableSpan: ClickableSpan?): Builder { 152 | span.setClick(textView, clickableSpan) 153 | return this 154 | } 155 | 156 | /** 157 | * 设置删除线的样式 158 | */ 159 | fun strikeThrough(): Builder { 160 | span.setStrikeThrough() 161 | return this 162 | } 163 | 164 | /** 165 | * 设置下划线的样式 166 | */ 167 | fun underLine(): Builder { 168 | span.setUnderLine() 169 | return this 170 | } 171 | 172 | /** 173 | * 设置垂直的引用线 174 | * 175 | * [color] 设置垂直的引用线的颜色 176 | */ 177 | fun quote(color: Int): Builder { 178 | span.setQuote(color) 179 | return this 180 | } 181 | 182 | /** 183 | * 设置字体内容的对其方式 184 | * 185 | * [align] 设置对其方式 ALIGN_NORMAL, ALIGN_OPPOSITE, ALIGN_CENTER, 186 | */ 187 | fun alignment(align: Layout.Alignment): Builder { 188 | span.setAlignment(align) 189 | return this 190 | } 191 | 192 | /** 193 | * 设置字体的相对大小 194 | * 195 | * [proportion] 设置相对textSize的比例大小 196 | */ 197 | fun relativeSize(proportion: Float): Builder { 198 | span.setRelativeSize(proportion) 199 | return this 200 | } 201 | 202 | /** 203 | * 设置字体为上标的样式 204 | */ 205 | fun superscript(): Builder { 206 | span.setSuperscript() 207 | return this 208 | } 209 | 210 | /** 211 | * 设置字体为下标的样式 212 | */ 213 | fun subscript(): Builder { 214 | span.setSubscript() 215 | return this 216 | } 217 | 218 | /** 219 | * 设置字体X轴缩放 220 | * 221 | * [proportion] 缩放的倍数 222 | */ 223 | fun scaleX(proportion: Float): Builder { 224 | span.setScaleX(proportion) 225 | return this 226 | } 227 | 228 | /** 229 | * 设置URL,点击会打开浏览器展示 230 | * 231 | * [urlSpan] 如需修改字体颜色等,请参考[click]方法 232 | */ 233 | fun url(textView: TextView?, urlSpan: URLSpan): Builder { 234 | span.setURL(textView,urlSpan) 235 | return this 236 | } 237 | 238 | /** 239 | * 设置图片-Drawable,不需要设置setBounds,不会替换内容 240 | * 241 | * [drawable] 图片 242 | * [pad] 距离 243 | */ 244 | @JvmOverloads 245 | fun drawableMargin(drawable: Drawable, pad: Int = 0): Builder { 246 | span.setDrawableMargin(drawable, pad) 247 | return this 248 | } 249 | 250 | /** 251 | * 设置图片-Bitmap,不会替换内容 252 | * 253 | * [bitmap] 图片 254 | * [pad] 距离 255 | */ 256 | @JvmOverloads 257 | fun iconMargin(bitmap: Bitmap, pad: Int = 0): Builder { 258 | span.setIconMargin(bitmap, pad) 259 | return this 260 | } 261 | 262 | /** 263 | * 设置图片-Drawable,需要设置setBounds,会替换内容 264 | * 265 | * [dynamicDrawableSpan] 提供Drawable的span 266 | */ 267 | fun dynamicDrawable(dynamicDrawableSpan: DynamicDrawableSpan): Builder { 268 | span.setDynamicDrawable(dynamicDrawableSpan) 269 | return this 270 | } 271 | 272 | /** 273 | * 设置图片-Bitmap,会替换内容 274 | * 275 | * [verticalAlignment] 垂直对齐方式:ALIGN_BOTTOM、ALIGN_BASELINE 276 | */ 277 | @JvmOverloads 278 | fun image(context: Context, bitmap: Bitmap, verticalAlignment: Int = DynamicDrawableSpan.ALIGN_BOTTOM): Builder { 279 | span.setImage(context, bitmap, verticalAlignment) 280 | return this 281 | } 282 | 283 | /** 284 | * 设置图片-Drawable,会替换内容。Drawable需设置setBounds。 285 | * 286 | * [verticalAlignment] 垂直对齐方式:ALIGN_BOTTOM、ALIGN_BASELINE 287 | */ 288 | @JvmOverloads 289 | fun image(drawable: Drawable, source: String? = null, verticalAlignment: Int = DynamicDrawableSpan.ALIGN_BOTTOM): Builder { 290 | span.setImage(drawable, source, verticalAlignment) 291 | return this 292 | } 293 | 294 | /** 295 | * 解决上述方法JvmOverloads后无此参数情况 296 | */ 297 | fun image(drawable: Drawable, verticalAlignment: Int) = image(drawable, null, verticalAlignment) 298 | 299 | /** 300 | * 设置图片-Uri,会替换内容。 301 | * 302 | * [verticalAlignment] 垂直对齐方式:ALIGN_BOTTOM、ALIGN_BASELINE 303 | */ 304 | @JvmOverloads 305 | fun image(context: Context, uri: Uri, verticalAlignment: Int = DynamicDrawableSpan.ALIGN_BOTTOM): Builder { 306 | span.setImage(context, uri, verticalAlignment) 307 | return this 308 | } 309 | 310 | /** 311 | * 设置图片-resourceId,会替换内容。 312 | * 313 | * [verticalAlignment] 垂直对齐方式:ALIGN_BOTTOM、ALIGN_BASELINE 314 | */ 315 | @JvmOverloads 316 | fun image(context: Context, resourceId: Int, verticalAlignment: Int = DynamicDrawableSpan.ALIGN_BOTTOM): Builder { 317 | span.setImage(context, resourceId, verticalAlignment) 318 | return this 319 | } 320 | 321 | /** 322 | * 设置子弹 323 | * 324 | */ 325 | @JvmOverloads 326 | fun bullet(gapWidth: Int = BulletSpan.STANDARD_GAP_WIDTH, color: Int? = null): Builder { 327 | span.setBullet(gapWidth, color) 328 | return this 329 | } 330 | 331 | /** 332 | * 设置行缩进 333 | * [first] 段落第一行的缩进距离 334 | * [rest] 段落其余行缩进距离 335 | */ 336 | @JvmOverloads 337 | fun leadingMargin(first: Int, rest: Int = first): Builder { 338 | span.setLeadingMargin(first, rest) 339 | return this 340 | } 341 | 342 | /** 343 | * 设置一行上制表符位置 344 | * [where] 制表符与行前距离的偏移量 345 | */ 346 | fun tabStop(where: Int): Builder { 347 | span.setTabStop(where) 348 | return this 349 | } 350 | 351 | /** 352 | * 设置模糊 353 | * [radius] 模糊的半径 354 | * [style] 模糊的样式 355 | */ 356 | fun blurMask(radius: Float, style: BlurMaskFilter.Blur): Builder { 357 | span.setBlurMask(radius, style) 358 | return this 359 | } 360 | 361 | /** 362 | * 设置样式-作用于text全部 363 | * [flags] 默认:[SPAN_EXCLUSIVE_EXCLUSIVE]。常用:[SPAN_INCLUSIVE_INCLUSIVE]、[SPAN_EXCLUSIVE_EXCLUSIVE]、[SPAN_INCLUSIVE_EXCLUSIVE]、[SPAN_EXCLUSIVE_INCLUSIVE],详情请看:https://www.jianshu.com/p/1956e15c9a27 364 | * [spans] 多个span样式 365 | */ 366 | @JvmOverloads 367 | fun setSpanAll(flags: Int = SPAN_EXCLUSIVE_EXCLUSIVE, vararg spans: Any?): Builder { 368 | span.setSpanAll(flags, *spans) 369 | return this 370 | } 371 | 372 | /** 373 | * 设置样式-作用于text文本start到end的位置 374 | * 375 | * [start] 从此位置开始设置样式 376 | * [end] 从此位置结束设置样式 377 | * [flags] 默认:[SPAN_EXCLUSIVE_EXCLUSIVE]。常用:[SPAN_INCLUSIVE_INCLUSIVE]、[SPAN_EXCLUSIVE_EXCLUSIVE]、[SPAN_INCLUSIVE_EXCLUSIVE]、[SPAN_EXCLUSIVE_INCLUSIVE],详情请看:https://www.jianshu.com/p/1956e15c9a27 378 | * [spans] 多个span样式 379 | */ 380 | @JvmOverloads 381 | fun setSpanPart(start: Int, end: Int, flags: Int = SPAN_EXCLUSIVE_EXCLUSIVE, vararg spans: Any?): Builder { 382 | span.setSpanPart(start, end, flags, *spans) 383 | return this 384 | } 385 | } 386 | 387 | companion object { 388 | /** 389 | * 构建者模式拼接Spans 390 | */ 391 | @JvmStatic 392 | fun builder(): Builder { 393 | return Builder() 394 | } 395 | } 396 | } --------------------------------------------------------------------------------