├── .gitignore ├── .idea ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── ccheng.xml ├── inspectionProfiles │ └── profiles_settings.xml └── scopes │ └── scope_settings.xml ├── MIT License.txt ├── README.md ├── build.gradle ├── en.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── justifytext-library ├── .gitignore ├── build.gradle ├── gradle.properties ├── library_justifytext │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.txt │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── me │ │ └── biubiubiu │ │ └── justifytext │ │ └── library │ │ └── JustifyTextView.java ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── me │ └── biubiubiu │ └── justifytext │ └── library │ └── JustifyTextView.java ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── 1.txt │ └── 1en.txt │ ├── java │ └── me │ │ └── biubiubiu │ │ └── justifytext │ │ ├── DemoActivity.java │ │ ├── MyScrollView.java │ │ └── TextActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_demo.xml │ ├── activity_main.xml │ └── activity_text.xml │ ├── menu │ └── main.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── settings.gradle ├── texttest.iml └── zh.png /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/* 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | .DS_Store 6 | **/*.iml 7 | .idea 8 | /sample/sample.apk 9 | /.idea/misc.xml 10 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/ccheng.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /MIT License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2023 Chao Cheng and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # JustifiedTextView 3 | 4 | Implement justified textview base on the native TextView. Let text displays fill the screen width without extra blanks in the end of line. 5 | 6 | ### English 7 | ![截了个图](./en.png) 8 | 9 | ### Chinese 10 | ![截了个图](./zh.png) 11 | 12 | ## Usage 13 | 14 | You can import in build.gradle like this 15 | 16 | compile 'me.biubiubiu.justifytext:library:1.1' 17 | 18 | If you use maven, add this to pom.xml. 19 | 20 | 21 | me.biubiubiu.justifytext 22 | library 23 | 1.1 24 | aar 25 | 26 | 27 | Then add put this into layout file. 28 | 29 | ```xml 30 | 35 | ``` 36 | 37 | ## demo 38 | 39 | [directly download](http://pan.baidu.com/s/1bnq2rk7) 40 | 41 | 42 | Get it on Google Play 44 | 45 | 46 | ## Change list 47 | 48 | - **1.1** Display ok when using custom font. 49 | 50 | 51 | ---------------------------------- 52 | 53 | MIT License 54 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.0.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | mavenCentral() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufo22940268/android-justifiedtextview/9329c7f68a79f78c0eabed35f6dc8d030b322d0d/en.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Settings specified in this file will override any Gradle settings 5 | # configured through the IDE. 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 19 | 20 | VERSION_NAME=1.1 21 | VERSION_CODE=2 22 | GROUP=me.biubiubiu.justifytext 23 | 24 | POM_DESCRIPTION=android justified textview 25 | POM_URL=https://github.com/ufo22940268/Android-Flipperview 26 | POM_SCM_URL=https://github.com/ufo22940268/Android-Flipperview 27 | POM_SCM_CONNECTION=scm:git@github.com:ufo22940268/Android-Flipperview 28 | POM_SCM_DEV_CONNECTION=scm:git@github.com:ufo22940268/Android-Flipperview 29 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 30 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 31 | POM_LICENCE_DIST=repo 32 | POM_DEVELOPER_ID=ufo22940268 33 | POM_DEVELOPER_NAME=frank cheng 34 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufo22940268/android-justifiedtextview/9329c7f68a79f78c0eabed35f6dc8d030b322d0d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 24 14:24:39 CST 2015 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-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /justifytext-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /justifytext-library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 19 5 | buildToolsVersion "19.1.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 8 9 | targetSdkVersion 19 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | compile fileTree(dir: 'libs', include: ['*.jar']) 22 | } 23 | 24 | apply from: 'https://raw.githubusercontent.com/daimajia/AndroidImageSlider/master/library/gradle-mvn-push.gradle' 25 | -------------------------------------------------------------------------------- /justifytext-library/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Settings specified in this file will override any Gradle settings 5 | # configured through the IDE. 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 19 | 20 | POM_NAME=Android-JustifiedTextView Library 21 | POM_ARTIFACT_ID=library 22 | POM_PACKAGING=aar 23 | -------------------------------------------------------------------------------- /justifytext-library/library_justifytext/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /justifytext-library/library_justifytext/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android-library' 2 | 3 | android { 4 | compileSdkVersion 19 5 | buildToolsVersion "19.1.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 8 9 | targetSdkVersion 19 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | runProguard false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | } 24 | -------------------------------------------------------------------------------- /justifytext-library/library_justifytext/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/ccheng/program/adt-bundle-mac-x86_64-20130219/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 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 | #} -------------------------------------------------------------------------------- /justifytext-library/library_justifytext/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /justifytext-library/library_justifytext/src/main/java/me/biubiubiu/justifytext/library/JustifyTextView.java: -------------------------------------------------------------------------------- 1 | package me.biubiubiu.justifytext.library; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.text.Layout; 7 | import android.text.StaticLayout; 8 | import android.text.TextPaint; 9 | import android.util.AttributeSet; 10 | import android.widget.TextView; 11 | 12 | /** 13 | * @author ccheng 14 | * @Date 3/18/14 15 | */ 16 | public class JustifyTextView extends TextView { 17 | 18 | private int mLineY; 19 | private int mViewWidth; 20 | public static final String TWO_CHINESE_BLANK = " "; 21 | 22 | public JustifyTextView(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | } 25 | 26 | @Override 27 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 28 | super.onLayout(changed, left, top, right, bottom); 29 | } 30 | 31 | @Override 32 | protected void onDraw(Canvas canvas) { 33 | TextPaint paint = getPaint(); 34 | paint.setColor(getCurrentTextColor()); 35 | paint.drawableState = getDrawableState(); 36 | mViewWidth = getMeasuredWidth(); 37 | String text = getText().toString(); 38 | mLineY = 0; 39 | mLineY += getTextSize(); 40 | Layout layout = getLayout(); 41 | 42 | // layout.getLayout()在4.4.3出现NullPointerException 43 | if (layout == null) { 44 | return; 45 | } 46 | 47 | Paint.FontMetrics fm = paint.getFontMetrics(); 48 | 49 | int textHeight = (int) (Math.ceil(fm.descent - fm.ascent)); 50 | textHeight = (int) (textHeight * layout.getSpacingMultiplier() + layout.getSpacingAdd()); 51 | 52 | for (int i = 0; i < layout.getLineCount(); i++) { 53 | int lineStart = layout.getLineStart(i); 54 | int lineEnd = layout.getLineEnd(i); 55 | float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint()); 56 | String line = text.substring(lineStart, lineEnd); 57 | if (needScale(line) && i < layout.getLineCount() -1) { 58 | drawScaledText(canvas, lineStart, line, width); 59 | } else { 60 | canvas.drawText(line, 0, mLineY, paint); 61 | } 62 | mLineY += textHeight; 63 | } 64 | } 65 | 66 | private void drawScaledText(Canvas canvas, int lineStart, String line, float lineWidth) { 67 | float x = 0; 68 | if (isFirstLineOfParagraph(lineStart, line)) { 69 | String blanks = " "; 70 | canvas.drawText(blanks, x, mLineY, getPaint()); 71 | float bw = StaticLayout.getDesiredWidth(blanks, getPaint()); 72 | x += bw; 73 | 74 | line = line.substring(3); 75 | } 76 | 77 | int gapCount = line.length() - 1; 78 | int i = 0; 79 | if (line.length() > 2 && line.charAt(0) == 12288 && line.charAt(1) == 12288) { 80 | String substring = line.substring(0, 2); 81 | float cw = StaticLayout.getDesiredWidth(substring, getPaint()); 82 | canvas.drawText(substring, x, mLineY, getPaint()); 83 | x += cw; 84 | i += 2; 85 | } 86 | 87 | float d = (mViewWidth - lineWidth) / gapCount; 88 | for (; i < line.length(); i++) { 89 | String c = String.valueOf(line.charAt(i)); 90 | float cw = StaticLayout.getDesiredWidth(c, getPaint()); 91 | canvas.drawText(c, x, mLineY, getPaint()); 92 | x += cw + d; 93 | } 94 | } 95 | 96 | private boolean isFirstLineOfParagraph(int lineStart, String line) { 97 | return line.length() > 3 && line.charAt(0) == ' ' && line.charAt(1) == ' '; 98 | } 99 | 100 | private boolean needScale(String line) { 101 | if (line == null || line.length() == 0) { 102 | return false; 103 | } else { 104 | return line.charAt(line.length() - 1) != '\n'; 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /justifytext-library/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/ccheng/program/adt-bundle-mac-x86_64-20130219/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 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 | #} -------------------------------------------------------------------------------- /justifytext-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /justifytext-library/src/main/java/me/biubiubiu/justifytext/library/JustifyTextView.java: -------------------------------------------------------------------------------- 1 | package me.biubiubiu.justifytext.library; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.text.Layout; 6 | import android.text.StaticLayout; 7 | import android.text.TextPaint; 8 | import android.util.AttributeSet; 9 | import android.widget.TextView; 10 | 11 | /** 12 | * Created by ccheng on 3/18/14. 13 | */ 14 | public class JustifyTextView extends TextView { 15 | 16 | private int mLineY; 17 | private int mViewWidth; 18 | 19 | public JustifyTextView(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | } 22 | 23 | @Override 24 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 25 | super.onLayout(changed, left, top, right, bottom); 26 | } 27 | 28 | @Override 29 | protected void onDraw(Canvas canvas) { 30 | TextPaint paint = getPaint(); 31 | paint.setColor(getCurrentTextColor()); 32 | paint.drawableState = getDrawableState(); 33 | mViewWidth = getMeasuredWidth(); 34 | String text = (String) getText(); 35 | mLineY = 0; 36 | mLineY += getTextSize() * 1.5; 37 | Layout layout = getLayout(); 38 | for (int i = 0; i < layout.getLineCount(); i++) { 39 | int lineStart = layout.getLineStart(i); 40 | int lineEnd = layout.getLineEnd(i); 41 | String line = text.substring(lineStart, lineEnd); 42 | 43 | float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint()); 44 | if (needScale(line) && i < layout.getLineCount() -1) { 45 | drawScaledText(canvas, lineStart, line, width); 46 | } else { 47 | canvas.drawText(line, 0, mLineY, paint); 48 | } 49 | 50 | mLineY += getLineHeight(); 51 | } 52 | } 53 | 54 | private void drawScaledText(Canvas canvas, int lineStart, String line, float lineWidth) { 55 | float x = 0; 56 | if (isFirstLineOfParagraph(lineStart, line)) { 57 | String blanks = " "; 58 | canvas.drawText(blanks, x, mLineY, getPaint()); 59 | float bw = StaticLayout.getDesiredWidth(blanks, getPaint()); 60 | x += bw; 61 | 62 | line = line.substring(3); 63 | } 64 | 65 | float d = (mViewWidth - lineWidth) / line.length() - 1; 66 | for (int i = 0; i < line.length(); i++) { 67 | String c = String.valueOf(line.charAt(i)); 68 | float cw = StaticLayout.getDesiredWidth(c, getPaint()); 69 | canvas.drawText(c, x, mLineY, getPaint()); 70 | x += cw + d; 71 | } 72 | } 73 | 74 | private boolean isFirstLineOfParagraph(int lineStart, String line) { 75 | return line.length() > 3 && line.charAt(0) == ' ' && line.charAt(1) == ' '; 76 | } 77 | 78 | private boolean needScale(String line) { 79 | if (line.length() == 0) { 80 | return false; 81 | } else { 82 | return line.charAt(line.length() - 1) != '\n'; 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "19.1.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 8 9 | targetSdkVersion 21 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | 14 | /*signingConfigs {*/ 15 | /*release {*/ 16 | /*storeFile file("/Applications/Android Studio.app/bin/me-biubiubiu")*/ 17 | /*storePassword "12345678"*/ 18 | /*keyAlias "me-biubiubiu-alias"*/ 19 | /*keyPassword "12345678"*/ 20 | /*}*/ 21 | /*}*/ 22 | 23 | lintOptions { 24 | checkReleaseBuilds false 25 | // Or, if you prefer, you can continue to check for errors in release builds, 26 | // but continue the build even when errors are found: 27 | abortOnError false 28 | } 29 | 30 | buildTypes { 31 | release { 32 | minifyEnabled true 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 34 | /*signingConfig signingConfigs.release*/ 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | compile 'me.biubiubiu.justifytext:library:1.1' 41 | compile fileTree(dir: 'libs', include: ['*.jar']) 42 | compile 'com.android.support:appcompat-v7:21.0.3' 43 | } 44 | -------------------------------------------------------------------------------- /sample/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/ccheng/program/adt-bundle-mac-x86_64-20130219/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 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 | #} -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /sample/src/main/assets/1.txt: -------------------------------------------------------------------------------- 1 | 太阳穴那里却又是一阵钻心的疼,宿醉后的反应让她有些懵了,低头看着自己一、丝、不、挂、吻痕遍及的身子,再掀开被子看了看自己身下的那抹刺眼的血红。 2 | ‘轰’——爆炸的声音在脑海里响起,然后她的大脑一片空白,几乎是如机械似的穿上已经整齐叠放在身边的衣服,然后拉开门想要立刻离开这里。 3 | 刚打开门却看到一个人站在门口,带着惊讶的表情看着她,“小小小、小姐,你要的醒酒汤。” 4 | 服务员端着一碗刚刚熬制出来的醒酒汤,手间颤了一下,似乎是被她过激的动作吓到。 5 | 叶一涵根本来不及顾及那些,猛地上前揪住那个服务员的衣领,脸色阴鸷的问他,“我问你,开这间房的人是谁?” 6 | 服务员没有想到她直接过来揪住他,手中的醒酒汤也来不及。 7 | -------------------------------------------------------------------------------- /sample/src/main/assets/1en.txt: -------------------------------------------------------------------------------- 1 | Via NASA: “This movie shows a coronal mass ejection (CME) on the sun from July 22, 2012 at 10:00 p.m. EDT until 2 a.m. on July 23 as captured by NASA’s Solar Terrestrial RElations Observatory-Ahead (STEREO-A). Because the CME headed in STEREO-A’s direction, it appears like a giant halo around the sun. NOTE: This video loops 3 times.” Credit: NASA/STEREO 2 | Fortunately, the blast site of the CMEs was not directed at Earth. Had this event occurred a week earlier when the point of eruption was Earth-facing, a potentially disastrous outcome would have unfolded. 3 | “I have come away from our recent studies more convinced than ever that Earth and its inhabitants were incredibly fortunate that the 2012 eruption happened when it did,” Baker tells NASA. “If the eruption had occurred only one week earlier, Earth would have been in the line of fire.” -------------------------------------------------------------------------------- /sample/src/main/java/me/biubiubiu/justifytext/DemoActivity.java: -------------------------------------------------------------------------------- 1 | package me.biubiubiu.justifytext; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | /** 9 | * Created by ccheng on 7/24/14. 10 | */ 11 | public class DemoActivity extends Activity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_demo); 17 | } 18 | 19 | public void onDisplayChinese(View view) { 20 | Intent intent = new Intent(this, TextActivity.class); 21 | intent.putExtra(TextActivity.KEY_FILE_NAME, "1.txt"); 22 | startActivity(intent); 23 | } 24 | 25 | public void onDisplayEnglish(View view) { 26 | Intent intent = new Intent(this, TextActivity.class); 27 | intent.putExtra(TextActivity.KEY_FILE_NAME, "1en.txt"); 28 | startActivity(intent); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/src/main/java/me/biubiubiu/justifytext/MyScrollView.java: -------------------------------------------------------------------------------- 1 | package me.biubiubiu.justifytext; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ScrollView; 6 | 7 | /** 8 | * Created by ccheng on 7/24/14. 9 | */ 10 | public class MyScrollView extends ScrollView { 11 | 12 | private ScrollView mAlternativeScrollView; 13 | 14 | public MyScrollView(Context context, AttributeSet attrs) { 15 | super(context, attrs); 16 | } 17 | 18 | 19 | public void setAlternativeScrollView(ScrollView alternativeScrollView) { 20 | mAlternativeScrollView = alternativeScrollView; 21 | } 22 | 23 | @Override 24 | protected void onScrollChanged(int l, int t, int oldl, int oldt) { 25 | super.onScrollChanged(l, t, oldl, oldt); 26 | if (mAlternativeScrollView != null) { 27 | mAlternativeScrollView.scrollTo(l, t); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/src/main/java/me/biubiubiu/justifytext/TextActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.biubiubiu.justifytext; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | import android.widget.TextView; 22 | 23 | import java.io.BufferedReader; 24 | import java.io.IOException; 25 | import java.io.InputStreamReader; 26 | 27 | public class TextActivity extends Activity { 28 | 29 | public static final String KEY_FILE_NAME = "KEY_FILE_NAME"; 30 | private TextView mJustifiedText; 31 | private TextView mText; 32 | private MyScrollView mScroller; 33 | private MyScrollView mJustifiedScroller; 34 | 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_text); 38 | 39 | mText = (TextView)findViewById(R.id.text); 40 | mJustifiedText = (TextView)findViewById(R.id.justified_text); 41 | String text = ""; 42 | 43 | BufferedReader br = null; 44 | try { 45 | String fileName = getIntent().getStringExtra(KEY_FILE_NAME); 46 | br = new BufferedReader(new InputStreamReader(getAssets().open(fileName))); 47 | String line; 48 | StringBuffer sb = new StringBuffer(); 49 | while ((line = br.readLine()) != null) { 50 | sb.append(line + "\n"); 51 | } 52 | text = sb.toString(); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } finally { 56 | try { 57 | br.close(); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | mJustifiedText.setText(text); 64 | mText.setText(text); 65 | 66 | mScroller = (MyScrollView)findViewById(R.id.scroller); 67 | mJustifiedScroller = (MyScrollView)findViewById(R.id.justified_scroller); 68 | mScroller.setAlternativeScrollView(mJustifiedScroller); 69 | mJustifiedScroller.setAlternativeScrollView(mScroller); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufo22940268/android-justifiedtextview/9329c7f68a79f78c0eabed35f6dc8d030b322d0d/sample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufo22940268/android-justifiedtextview/9329c7f68a79f78c0eabed35f6dc8d030b322d0d/sample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufo22940268/android-justifiedtextview/9329c7f68a79f78c0eabed35f6dc8d030b322d0d/sample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufo22940268/android-justifiedtextview/9329c7f68a79f78c0eabed35f6dc8d030b322d0d/sample/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 |