├── .gitignore
├── README.md
├── build.gradle
├── design
└── output.gif
├── gradlew
├── gradlew.bat
├── library
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── deange
│ └── numberview
│ ├── NumberView.java
│ ├── NumberViewGroup.java
│ ├── PaintProvider.java
│ └── digits
│ ├── Digit.java
│ ├── Digits.java
│ ├── Eight.java
│ ├── Empty.java
│ ├── Five.java
│ ├── Four.java
│ ├── Nine.java
│ ├── One.java
│ ├── Seven.java
│ ├── Six.java
│ ├── Three.java
│ ├── Two.java
│ └── Zero.java
├── sample
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── deange
│ │ └── numberview
│ │ └── sample
│ │ ├── MainActivity.java
│ │ ├── NumberActivity.java
│ │ └── NumberGroupActivity.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_launcher.png
│ ├── drawable-mdpi
│ └── ic_launcher.png
│ ├── drawable-xhdpi
│ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ └── ic_launcher.png
│ ├── drawable-xxxhdpi
│ └── ic_launcher.png
│ ├── layout
│ ├── activity_number.xml
│ └── activity_number_group.xml
│ ├── values-v11
│ └── styles.xml
│ ├── values-v21
│ └── styles.xml
│ └── values
│ ├── strings.xml
│ └── styles.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Eclipse project files
19 | .classpath
20 | .project
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Intellij project files
26 | *.iml
27 | *.ipr
28 | *.iws
29 | .idea/
30 | .gradle/
31 | gradle/
32 | build/
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NumberView
2 |
3 | A simple number tweener.
4 |
5 | 
6 |
7 | ---
8 | ### What is it?
9 | NumberView is mean to be a take on [Timely][]'s beautiful and tasteful number tweening animations. Despite looking like it draws actual numbers in it, the numbers drawn are represented as Bèzier curve paths, meticulously calculated with control points and anchors.
10 |
11 | ---
12 | ### Usage
13 | Using [NumberView][] to show a single digit in your project is simple. You can drop it into your XML files as a regular custom view:
14 |
15 | Layout file:
16 | ```xml
17 |
20 | ```
21 |
22 | And in your Java file:
23 | ```java
24 | final NumberView view = findViewById(...);
25 | view.advance(1);
26 | postDelayed(() -> view.advance(2), 1000);
27 | postDelayed(() -> view.advance(), 2000); // Displays "3"
28 | ```
29 |
30 | However, typically you'll want to show more than one digit at a time. For that you can use [NumberViewGroup][], which automatically takes care of adding new digits as you need them.
31 |
32 | Layout file:
33 | ```xml
34 |
37 |
38 | ```
39 |
40 | And in your Java file:
41 | ```java
42 | final NumberViewGroup view = findViewById(...);
43 | view.advance(1);
44 | postDelayed(() -> view.advance(20), 1000);
45 | postDelayed(() -> view.advance(), 2000); // Displays "21"
46 | ```
47 |
48 | You can always view the sample application code for more usage demos.
49 |
50 | ---
51 | ### Dependencies
52 | No dependencies. Works all the way back to API level 14.
53 |
54 | ---
55 | ### Download
56 |
57 | ```groovy
58 | repositories {
59 | maven {
60 | url "https://jitpack.io"
61 | }
62 | }
63 |
64 | dependencies {
65 | compile 'com.github.cdeange:NumberView:1.1.0'
66 | }
67 | ```
68 |
69 | ---
70 | ### Developed By
71 | - Christian De Angelis -
72 |
73 | ---
74 | ### License
75 |
76 | ```
77 | Copyright 2017 Christian De Angelis
78 |
79 | Licensed under the Apache License, Version 2.0 (the "License");
80 | you may not use this file except in compliance with the License.
81 | You may obtain a copy of the License at
82 |
83 | http://www.apache.org/licenses/LICENSE-2.0
84 |
85 | Unless required by applicable law or agreed to in writing, software
86 | distributed under the License is distributed on an "AS IS" BASIS,
87 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
88 | See the License for the specific language governing permissions and
89 | limitations under the License.
90 | ```
91 |
92 |
93 | [Timely]: https://play.google.com/store/apps/details?id=ch.bitspin.timely
94 | [NumberView]: https://github.com/cdeange/NumberView/blob/master/library/src/main/java/com/deange/numberview/NumberView.java
95 | [NumberViewGroup]: https://github.com/cdeange/NumberView/blob/master/library/src/main/java/com/deange/numberview/NumberViewGroup.java
96 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | mavenCentral()
5 | }
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:2.3.0-alpha1'
8 | }
9 | }
--------------------------------------------------------------------------------
/design/output.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/NumberView/68f9cd9cd87a302501c30c70768e9344788c7cc6/design/output.gif
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion '25.0.0'
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 24
10 | versionCode 1
11 | versionName '1.1.0'
12 | }
13 |
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/NumberView.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.ObjectAnimator;
6 | import android.animation.ValueAnimator;
7 | import android.annotation.TargetApi;
8 | import android.content.Context;
9 | import android.graphics.Canvas;
10 | import android.graphics.Color;
11 | import android.graphics.Paint;
12 | import android.graphics.Path;
13 | import android.os.Build;
14 | import android.os.Parcel;
15 | import android.os.Parcelable;
16 | import android.util.AttributeSet;
17 | import android.util.Property;
18 | import android.util.TypedValue;
19 | import android.view.View;
20 | import android.view.ViewGroup;
21 | import android.view.animation.AccelerateDecelerateInterpolator;
22 | import android.view.animation.Interpolator;
23 | import android.view.animation.LinearInterpolator;
24 |
25 | import com.deange.numberview.digits.Digit;
26 | import com.deange.numberview.digits.Digits;
27 |
28 | public class NumberView extends View {
29 |
30 | private static final boolean DEBUG = true;
31 |
32 | public static final long DEFAULT_ANIMATION_DURATION = 500L;
33 | public static final float DEFAULT_WIDTH = 140f;
34 | public static final float DEFAULT_HEIGHT = 200f;
35 | public static final float ASPECT_RATIO = DEFAULT_WIDTH / DEFAULT_HEIGHT;
36 |
37 | // "8" is used since it constitutes the widest number drawn
38 | private static final String MEASURING_TEXT = "8";
39 |
40 | private static final Property FACTOR =
41 | new Property(Float.class, "factor") {
42 | @Override
43 | public Float get(final NumberView object) {
44 | return object.mFactor;
45 | }
46 |
47 | @Override
48 | public void set(final NumberView object, final Float value) {
49 | object.mFactor = value;
50 | object.invalidate();
51 | }
52 | };
53 |
54 | private final NumberViewPaint mPaint = new NumberViewPaint();
55 | private final Path mPath = new Path();
56 |
57 | private Digit mNext = Digits.empty();
58 | private Digit mCurrent = Digits.empty();
59 | private boolean mFirstLayout = true;
60 |
61 | private float mWidth;
62 | private float mHeight;
63 | private float mScale;
64 | private float mFactor;
65 | private ValueAnimator mAnimator;
66 |
67 | public NumberView(final Context context) {
68 | super(context);
69 | init();
70 | }
71 |
72 | public NumberView(final Context context, final AttributeSet attrs) {
73 | super(context, attrs);
74 | init();
75 | }
76 |
77 | public NumberView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
78 | super(context, attrs, defStyleAttr);
79 | init();
80 | }
81 |
82 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
83 | public NumberView(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
84 | super(context, attrs, defStyleAttr, defStyleRes);
85 | init();
86 | }
87 |
88 | private void init() {
89 | setWillNotDraw(false);
90 |
91 | // A new paint with the style as stroke
92 | mPaint.setAntiAlias(true);
93 | mPaint.setColor(Color.BLACK);
94 | mPaint.setStrokeWidth(2f);
95 | mPaint.setStyle(Paint.Style.STROKE);
96 |
97 | // Set up size values
98 | mScale = 1;
99 | mWidth = DEFAULT_WIDTH;
100 | mHeight = DEFAULT_HEIGHT;
101 |
102 | measureTextSize(mWidth);
103 |
104 | mAnimator = ObjectAnimator.ofFloat(this, FACTOR, 0f, 1f);
105 | mAnimator.setDuration(DEFAULT_ANIMATION_DURATION);
106 | mAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
107 | mAnimator.addListener(new AnimatorListenerAdapter() {
108 | @Override
109 | public void onAnimationEnd(final Animator animation) {
110 | // End of the current number animation
111 | // Begin setting values for the next number in the sequence
112 | mCurrent = mNext;
113 | }
114 | });
115 | mAnimator.start();
116 | }
117 |
118 | private void measureTextSize(final float targetMaxWidth) {
119 | // Calculate the right scale for the text size
120 | int sp = 0;
121 | float px = 0;
122 | float validPx;
123 | do {
124 | sp++;
125 | validPx = px;
126 | px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
127 | mPaint.setTextSizeInternal(px);
128 | } while (mPaint.measureText(MEASURING_TEXT) < targetMaxWidth);
129 |
130 | setTextSize(validPx);
131 | }
132 |
133 | public void setAnimationDuration(final long duration) {
134 | mAnimator.setDuration(duration);
135 | }
136 |
137 | public void setInterpolator(final Interpolator interpolator) {
138 | mAnimator.setInterpolator((interpolator == null) ? new LinearInterpolator() : interpolator);
139 | }
140 |
141 | public void setPaint(final Paint paint) {
142 | mPaint.set(paint);
143 | }
144 |
145 | public Paint getPaint() {
146 | return mPaint;
147 | }
148 |
149 | public void setTextSize(final int sizeUnit, final float textSize) {
150 | final float pixelSize = TypedValue.applyDimension(sizeUnit, textSize, getResources().getDisplayMetrics());
151 | setTextSize(pixelSize);
152 | }
153 |
154 | public void setTextSize(final float textSize) {
155 | mPaint.setTextSize(textSize);
156 | }
157 |
158 | public float getTextSize() {
159 | return getPaint().getTextSize();
160 | }
161 |
162 | public Digit getDigit() {
163 | return mNext;
164 | }
165 |
166 | public void hide() {
167 | show(Digits.empty());
168 | }
169 |
170 | public void hideNow() {
171 | showNow(Digits.empty());
172 | }
173 |
174 | public void show(final Digit digit) {
175 | if (digit == null) {
176 | throw new IllegalArgumentException("digit cannot be null");
177 | }
178 | mNext = digit;
179 | mAnimator.start();
180 | }
181 |
182 | public void showNow(final Digit digit) {
183 | if (digit == null) {
184 | throw new IllegalArgumentException("digit cannot be null");
185 | }
186 | mNext = digit;
187 | mCurrent = digit;
188 | mAnimator.start();
189 | }
190 |
191 | private void setScale(float scale) {
192 | if (scale == 0) {
193 | throw new IllegalArgumentException("Scale cannot be 0");
194 | }
195 |
196 | scale = Math.abs(scale);
197 |
198 | if (mScale == scale) return;
199 |
200 | final float inverseFactor = (scale / mScale);
201 | mWidth *= inverseFactor;
202 | mHeight *= inverseFactor;
203 |
204 | mScale = scale;
205 |
206 | requestLayout();
207 | invalidate();
208 | }
209 |
210 | private boolean isAnimating() {
211 | return mAnimator.isRunning();
212 | }
213 |
214 | private float lerp(float v0, float v1, float t) {
215 | return (1 - t) * v0 + t * v1;
216 | }
217 |
218 | private boolean fequals(final float f0, final float f1) {
219 | final float ulp0 = Math.ulp(f0);
220 | final float ulp1 = Math.ulp(f1);
221 | return Math.abs(f0 - f1) <= Math.max(ulp0, ulp1);
222 | }
223 |
224 | @Override
225 | protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
226 | final int minWidth = getSuggestedMinimumWidth();
227 | final int minHeight = getSuggestedMinimumHeight();
228 | int width, height;
229 |
230 | if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
231 | if (!isAnimating()) {
232 | mWidth = mScale * mCurrent.getWidth();
233 | }
234 | width = (int) Math.max(minWidth, mWidth);
235 | } else {
236 | width = MeasureSpec.getSize(widthMeasureSpec);
237 | }
238 |
239 | if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
240 | height = (int) Math.max(minHeight, mHeight);
241 | } else {
242 | height = MeasureSpec.getSize(heightMeasureSpec);
243 | }
244 |
245 | if (height * ASPECT_RATIO < width) {
246 | height = (int) (width / ASPECT_RATIO);
247 | }
248 |
249 | setMeasuredDimension(width, height);
250 | }
251 |
252 | @Override
253 | protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
254 | super.onLayout(changed, l, t, r, b);
255 |
256 | // Handles the case of an absolute dimension specified in the layout params
257 | if (mFirstLayout) {
258 | mFirstLayout = false;
259 | final ViewGroup.LayoutParams params = getLayoutParams();
260 | if (params != null && params.width > 0) {
261 | measureTextSize(params.width);
262 | }
263 | }
264 | }
265 |
266 | @Override
267 | public void onDraw(final Canvas canvas) {
268 | super.onDraw(canvas);
269 |
270 | final Digit thisNumber = mCurrent;
271 | final Digit nextNumber = mNext;
272 |
273 | final float[][] current = thisNumber.getPoints();
274 | final float[][] next = nextNumber.getPoints();
275 | final float[][] curr1 = thisNumber.getControlPoints1();
276 | final float[][] next1 = nextNumber.getControlPoints1();
277 | final float[][] curr2 = thisNumber.getControlPoints2();
278 | final float[][] next2 = nextNumber.getControlPoints2();
279 |
280 | // A factor of the difference between current and next frame based on interpolation
281 | // If we ourselves did not specifically request drawing, then draw our previous state
282 | final float factor = mFactor;
283 |
284 | final float thisWidth = mScale * thisNumber.getWidth();
285 | final float nextWidth = mScale * nextNumber.getWidth();
286 | final float interpolatedWidth = lerp(thisWidth, nextWidth, factor);
287 | if (!fequals(thisWidth, nextWidth) || !fequals(mWidth, interpolatedWidth)) {
288 | mWidth = Math.max(interpolatedWidth, 1f);
289 | requestLayout();
290 | }
291 |
292 | final float translateX = ((float) getMeasuredWidth() - mWidth) / 2f;
293 | final float translateY = ((float) getMeasuredHeight() - mHeight) / 2f;
294 |
295 | // Reset the path
296 | mPath.reset();
297 |
298 | // Draw the first point
299 | mPath.moveTo(
300 | mScale * (lerp(current[0][0], next[0][0], factor) + translateX),
301 | mScale * (lerp(current[0][1], next[0][1], factor) + translateY));
302 |
303 | // Connect the rest of the points as a bezier curve
304 | for (int i = 0; i < 4; i++) {
305 | mPath.cubicTo(
306 | mScale * (lerp(curr1[i][0], next1[i][0], factor) + translateX),
307 | mScale * (lerp(curr1[i][1], next1[i][1], factor) + translateY),
308 | mScale * (lerp(curr2[i][0], next2[i][0], factor) + translateX),
309 | mScale * (lerp(curr2[i][1], next2[i][1], factor) + translateY),
310 | mScale * (lerp(current[i + 1][0], next[i + 1][0], factor) + translateX),
311 | mScale * (lerp(current[i + 1][1], next[i + 1][1], factor) + translateY));
312 | }
313 |
314 | // Draw the path
315 | canvas.drawPath(mPath, mPaint);
316 |
317 | if (DEBUG) {
318 | canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
319 | }
320 | }
321 |
322 | @Override
323 | public Parcelable onSaveInstanceState() {
324 | final SavedState ss = new SavedState(super.onSaveInstanceState());
325 |
326 | // If we are animating while saving state, skip to the end by saving mCurrent as mNext
327 | ss.next = mNext;
328 | ss.current = isAnimating() ? mNext : mCurrent;
329 |
330 | return ss;
331 | }
332 |
333 | @Override
334 | public void onRestoreInstanceState(Parcelable state) {
335 | if (!(state instanceof SavedState)) {
336 | super.onRestoreInstanceState(state);
337 | return;
338 | }
339 |
340 | final SavedState ss = (SavedState) state;
341 | super.onRestoreInstanceState(ss.getSuperState());
342 |
343 | mNext = ss.next;
344 | mCurrent = ss.current;
345 | }
346 |
347 | private static class SavedState extends BaseSavedState {
348 | public Digit next;
349 | public Digit current;
350 |
351 | private SavedState(Parcelable superState) {
352 | super(superState);
353 | }
354 |
355 | private SavedState(Parcel in) {
356 | super(in);
357 | next = (Digit) in.readSerializable();
358 | current = (Digit) in.readSerializable();
359 | }
360 |
361 | @Override
362 | public void writeToParcel(Parcel out, int flags) {
363 | super.writeToParcel(out, flags);
364 | out.writeSerializable(next);
365 | out.writeSerializable(current);
366 | }
367 |
368 | public static final Parcelable.Creator CREATOR =
369 | new Parcelable.Creator() {
370 | public SavedState createFromParcel(Parcel in) {
371 | return new SavedState(in);
372 | }
373 |
374 | public SavedState[] newArray(int size) {
375 | return new SavedState[size];
376 | }
377 | };
378 | }
379 |
380 | private class NumberViewPaint extends Paint {
381 | @Override
382 | public void setTextSize(final float textSize) {
383 | super.setTextSize(textSize);
384 | setScale(measureText(MEASURING_TEXT) / DEFAULT_WIDTH);
385 | }
386 |
387 | @Override
388 | public void set(final Paint src) {
389 | super.set(src);
390 | setScale(measureText(MEASURING_TEXT) / DEFAULT_WIDTH);
391 | }
392 |
393 | protected void setTextSizeInternal(final float textSize) {
394 | super.setTextSize(textSize);
395 | }
396 | }
397 |
398 | }
399 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/NumberViewGroup.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.os.Build;
6 | import android.util.AttributeSet;
7 | import android.view.Gravity;
8 | import android.widget.LinearLayout;
9 |
10 | import com.deange.numberview.digits.Digit;
11 | import com.deange.numberview.digits.Digits;
12 |
13 | public class NumberViewGroup extends LinearLayout {
14 |
15 | private boolean mPerformNow;
16 | private int mMinShown;
17 | private int mNumber;
18 | private boolean mHide;
19 |
20 | private PaintProvider mPaintProvider;
21 |
22 | public NumberViewGroup(final Context context) {
23 | super(context);
24 | init();
25 | }
26 |
27 | public NumberViewGroup(final Context context, final AttributeSet attrs) {
28 | super(context, attrs);
29 | init();
30 | }
31 |
32 | public NumberViewGroup(final Context context, final AttributeSet attrs, final int defStyleAttr) {
33 | super(context, attrs, defStyleAttr);
34 | init();
35 | }
36 |
37 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
38 | public NumberViewGroup(
39 | final Context context,
40 | final AttributeSet attrs,
41 | final int defStyleAttr,
42 | final int defStyleRes) {
43 | super(context, attrs, defStyleAttr, defStyleRes);
44 | init();
45 | }
46 |
47 | private void init() {
48 | setOrientation(HORIZONTAL);
49 | setGravity(Gravity.CENTER);
50 | }
51 |
52 | protected NumberView addNewChild() {
53 | final NumberView child = new NumberView(getContext());
54 | if (mPaintProvider != null) {
55 | mPaintProvider.mutate(child.getPaint(), getChildCount());
56 | }
57 |
58 | child.hideNow();
59 | addView(child, 0);
60 |
61 | return child;
62 | }
63 |
64 | private Digit resolveDigit(int number, int digit) {
65 | return (mHide) ? Digits.empty() : Digits.forInt((int) ((number / Math.pow(10, digit)) % 10));
66 | }
67 |
68 | private int getIntLength(final int number) {
69 | // Yeah it's ugly as sin but it works, and it works fast.
70 | final int n = (number == Integer.MIN_VALUE) ? Integer.MAX_VALUE : Math.abs(number);
71 | return n < 100000
72 | ? n < 100 ? n < 10 ? 1 : 2 : n < 1000 ? 3 : n < 10000 ? 4 : 5
73 | : n < 10000000 ? n < 1000000 ? 6 : 7 : n < 100000000 ? 8 : n < 1000000000 ? 9 : 10;
74 | }
75 |
76 | private int getRequiredChildCount() {
77 | return Math.max(mMinShown, mHide ? 0 : getIntLength(mNumber));
78 | }
79 |
80 | private void bindViews() {
81 |
82 | final int size = getRequiredChildCount();
83 |
84 | for (int i = 0; i < size; i++) {
85 |
86 | while (i >= getChildCount()) {
87 | addNewChild();
88 | }
89 |
90 | final NumberView child = getDigitAt(i);
91 | final Digit d = resolveDigit(mNumber, i);
92 |
93 | if (mPerformNow) {
94 | child.showNow(d);
95 | } else {
96 | child.show(d);
97 | }
98 | }
99 |
100 | for (int i = size; i < getChildCount(); i++) {
101 | // Unused children :'(
102 | final NumberView child = getDigitAt(i);
103 | if (mPerformNow) {
104 | child.hideNow();
105 | } else {
106 | child.hide();
107 | }
108 | }
109 |
110 | requestLayout();
111 | invalidate();
112 | }
113 |
114 | public NumberView getDigitAt(final int index) {
115 | // Reverse the indexing order of the children
116 | return (NumberView) getChildAt(getChildCount() - index - 1);
117 | }
118 |
119 | public NumberView[] getDigits() {
120 | // Returns views in order from LSB to MSB
121 | final NumberView[] views = new NumberView[getChildCount()];
122 | for (int i = 0; i < getChildCount(); i++) {
123 | views[i] = getDigitAt(i);
124 | }
125 | return views;
126 | }
127 |
128 | public void show(final int number) {
129 | mHide = false;
130 | mNumber = number;
131 | mPerformNow = false;
132 | bindViews();
133 | }
134 |
135 | public void showNow(final int number) {
136 | mHide = false;
137 | mNumber = number;
138 | mPerformNow = true;
139 | bindViews();
140 | }
141 |
142 | public void hide() {
143 | mHide = true;
144 | mPerformNow = false;
145 | bindViews();
146 | }
147 |
148 | public void hideNow() {
149 | mHide = true;
150 | mPerformNow = true;
151 | bindViews();
152 | }
153 |
154 | public void setMinimumNumbersShown(final int minimum) {
155 | mMinShown = minimum;
156 |
157 | while (getChildCount() < mMinShown) {
158 | addNewChild();
159 | }
160 | }
161 |
162 | public void setPaintProvider(final PaintProvider paintProvider) {
163 | mPaintProvider = paintProvider;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/PaintProvider.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview;
2 |
3 | import android.graphics.Paint;
4 |
5 | public interface PaintProvider {
6 | void mutate(final Paint paint, final int digit);
7 | }
8 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Digit.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import java.io.Serializable;
4 |
5 | public interface Digit extends Serializable {
6 |
7 | float[][] getPoints();
8 |
9 | float[][] getControlPoints1();
10 |
11 | float[][] getControlPoints2();
12 |
13 | float getWidth();
14 |
15 | char getChar();
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Digits.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import android.util.Log;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | public final class Digits {
9 |
10 | private static final String TAG = "Digits";
11 |
12 | private static final Map DIGITS = new HashMap<>();
13 |
14 | static {
15 | register(new Zero());
16 | register(new One());
17 | register(new Two());
18 | register(new Three());
19 | register(new Four());
20 | register(new Five());
21 | register(new Six());
22 | register(new Seven());
23 | register(new Eight());
24 | register(new Nine());
25 | register(new Empty());
26 | }
27 |
28 | private Digits() {
29 | throw new AssertionError();
30 | }
31 |
32 | public static void register(final Digit digit) {
33 | final char character = digit.getChar();
34 | final Digit oldValue = DIGITS.put(character, digit);
35 | if (oldValue != null) {
36 | Log.w(TAG, "Replacing existing digit " + oldValue + " for character '" + character + "'");
37 | }
38 | }
39 |
40 | public static Digit empty() {
41 | return forChar('\0');
42 | }
43 |
44 | public static Digit forChar(final char character) {
45 | return DIGITS.get(character);
46 | }
47 |
48 | public static Digit forInt(final int digit) {
49 | if (digit < 0 || digit > 9) {
50 | throw new IllegalArgumentException("Digit must be between 0 and 9");
51 | }
52 | return forChar((char) ('0' + digit));
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Eight.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Eight implements Digit {
6 |
7 | private final float[][] POINTS = { { 71, 96 }, { 71, 19 }, { 71, 96 }, { 71, 179 }, { 71, 96 } };
8 | private final float[][] CONTROLS1 = { { 14, 95 }, { 124, 19 }, { 14, 96 }, { 124, 179 } };
9 | private final float[][] CONTROLS2 = { { 14, 19 }, { 124, 96 }, { 6, 179 }, { 124, 96 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '8';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Empty.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import com.deange.numberview.NumberView;
4 |
5 | /* package */ class Empty implements Digit {
6 |
7 | private static final float[] F = { NumberView.DEFAULT_WIDTH / 8f, NumberView.DEFAULT_HEIGHT / 2f };
8 | private static final float[][] POINTS = new float[][]{ F.clone(), F.clone(), F.clone(), F.clone(), F.clone() };
9 | private static final float[][] CONTROLS1 = new float[][]{ F.clone(), F.clone(), F.clone(), F.clone() };
10 | private static final float[][] CONTROLS2 = new float[][]{ F.clone(), F.clone(), F.clone(), F.clone() };
11 |
12 | @Override
13 | public float[][] getPoints() {
14 | return POINTS;
15 | }
16 |
17 | @Override
18 | public float[][] getControlPoints1() {
19 | return CONTROLS1;
20 | }
21 |
22 | @Override
23 | public float[][] getControlPoints2() {
24 | return CONTROLS2;
25 | }
26 |
27 | @Override
28 | public float getWidth() {
29 | return 1f;
30 | }
31 |
32 | @Override
33 | public char getChar() {
34 | return '\0';
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Five.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Five implements Digit {
6 |
7 | private final float[][] POINTS = { { 116, 20 }, { 61, 20 }, { 42, 78 }, { 115, 129 }, { 15, 154 } };
8 | private final float[][] CONTROLS1 = { { 61, 20 }, { 42, 78 }, { 67, 66 }, { 110, 183 } };
9 | private final float[][] CONTROLS2 = { { 61, 20 }, { 42, 78 }, { 115, 85 }, { 38, 198 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '5';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Four.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Four implements Digit {
6 |
7 | private final float[][] POINTS = { { 125, 146 }, { 13, 146 }, { 99, 25 }, { 99, 146 }, { 99, 179 } };
8 | private final float[][] CONTROLS1 = { { 125, 146 }, { 13, 146 }, { 99, 25 }, { 99, 146 } };
9 | private final float[][] CONTROLS2 = { { 13, 146 }, { 99, 25 }, { 99, 146 }, { 99, 179 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '4';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Nine.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Nine implements Digit {
6 |
7 | private final float[][] POINTS = { { 117, 100 }, { 17, 74 }, { 124, 74 }, { 60, 180 }, { 60, 180 } };
8 | private final float[][] CONTROLS1 = { { 94, 136 }, { 12, 8 }, { 122, 108 }, { 60, 180 } };
9 | private final float[][] CONTROLS2 = { { 24, 134 }, { 118, -8 }, { 99, 121 }, { 60, 180 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '9';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/One.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class One implements Digit {
6 |
7 | private final float[][] POINTS = { { 15, 20.5f }, { 42.5f, 20.5f }, { 42.5f, 181 }, { 42.5f, 181 }, { 42.5f, 181 } };
8 | private final float[][] CONTROLS1 = { { 15, 20.5f }, { 42.5f, 20.5f }, { 42.5f, 181 }, { 42.5f, 181 } };
9 | private final float[][] CONTROLS2 = { { 15, 20.5f }, { 42.5f, 20.5f }, { 42.5f, 181 }, { 42.5f, 181 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH / 2f;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '1';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Seven.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Seven implements Digit {
6 |
7 | private final float[][] POINTS = { { 17, 21 }, { 128, 21 }, { 90.67f, 73.34f }, { 53.34f, 126.67f }, { 16, 181 } };
8 | private final float[][] CONTROLS1 = { { 17, 21 }, { 128, 21 }, { 90.67f, 73.34f }, { 53.34f, 126.67f } };
9 | private final float[][] CONTROLS2 = { { 128, 21 }, { 90.67f, 73.34f }, { 53.34f, 126.67f }, { 16, 181 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '7';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Six.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Six implements Digit {
6 |
7 | private final float[][] POINTS = { { 80, 20 }, { 80, 20 }, { 16, 126 }, { 123, 126 }, { 23, 100 } };
8 | private final float[][] CONTROLS1 = { { 80, 20 }, { 41, 79 }, { 22, 208 }, { 116, 66 } };
9 | private final float[][] CONTROLS2 = { { 80, 20 }, { 18, 92 }, { 128, 192 }, { 46, 64 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '6';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Three.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Three implements Digit {
6 |
7 | private final float[][] POINTS = { { 33.25f, 54 }, { 69.5f, 18 }, { 69.5f, 96 }, { 70, 180 }, { 26.5f, 143 } };
8 | private final float[][] CONTROLS1 = { { 33, 27 }, { 126, 18 }, { 128, 96 }, { 24, 180 } };
9 | private final float[][] CONTROLS2 = { { 56, 18 }, { 116, 96 }, { 120, 180 }, { 26, 150 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '3';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Two.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Two implements Digit {
6 |
7 | private final float[][] POINTS = { { 26, 60 }, { 114.5f, 61 }, { 78, 122 }, { 27, 177 }, { 117, 177 } };
8 | private final float[][] CONTROLS1 = { { 29, 2 }, { 114.5f, 78 }, { 64, 138 }, { 27, 177 } };
9 | private final float[][] CONTROLS2 = { { 113, 4 }, { 100, 98 }, { 44, 155 }, { 117, 177 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '2';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/deange/numberview/digits/Zero.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.digits;
2 |
3 | import static com.deange.numberview.NumberView.DEFAULT_WIDTH;
4 |
5 | /* package */ class Zero implements Digit {
6 |
7 | private final float[][] POINTS = { { 14.5f, 100 }, { 70, 18 }, { 126, 100 }, { 70, 180 }, { 14.5f, 100 } };
8 | private final float[][] CONTROLS1 = { { 14.5f, 60 }, { 103, 18 }, { 126, 140 }, { 37, 180 } };
9 | private final float[][] CONTROLS2 = { { 37, 18 }, { 126, 60 }, { 103, 180 }, { 14.5f, 140 } };
10 |
11 | @Override
12 | public float[][] getPoints() {
13 | return POINTS;
14 | }
15 |
16 | @Override
17 | public float[][] getControlPoints1() {
18 | return CONTROLS1;
19 | }
20 |
21 | @Override
22 | public float[][] getControlPoints2() {
23 | return CONTROLS2;
24 | }
25 |
26 | @Override
27 | public float getWidth() {
28 | return DEFAULT_WIDTH;
29 | }
30 |
31 | @Override
32 | public char getChar() {
33 | return '0';
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | repositories {
4 | mavenCentral()
5 | }
6 |
7 | android {
8 | compileSdkVersion 24
9 | buildToolsVersion '25.0.0'
10 |
11 | defaultConfig {
12 | applicationId 'com.deange.numberview.sample'
13 | minSdkVersion 14
14 | targetSdkVersion 24
15 | versionCode 1
16 | versionName '1.1.0'
17 | }
18 | }
19 |
20 | dependencies {
21 | compile project(':library')
22 | }
23 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/deange/numberview/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.sample;
2 |
3 | import android.app.ListActivity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.widget.ArrayAdapter;
8 | import android.widget.ListView;
9 |
10 | public class MainActivity extends ListActivity {
11 |
12 | private static final String[] TYPES = {
13 | "NumberView",
14 | "NumberViewGroup",
15 | };
16 |
17 | private static final Class[] ACTIVITIES = new Class[]{
18 | NumberActivity.class,
19 | NumberGroupActivity.class,
20 | };
21 |
22 | @Override
23 | protected void onCreate(final Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setListAdapter(new TypesAdapter());
26 | }
27 |
28 | @Override
29 | protected void onListItemClick(final ListView l, final View v, final int position, final long id) {
30 | startActivity(new Intent(this, ACTIVITIES[position]));
31 | }
32 |
33 | private class TypesAdapter extends ArrayAdapter {
34 | public TypesAdapter() {
35 | super(MainActivity.this, android.R.layout.simple_list_item_1, TYPES);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/deange/numberview/sample/NumberActivity.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.sample;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import android.view.View;
8 | import android.widget.Button;
9 |
10 | import com.deange.numberview.NumberView;
11 | import com.deange.numberview.digits.Digits;
12 |
13 | import java.util.Timer;
14 | import java.util.TimerTask;
15 |
16 | public class NumberActivity extends Activity implements View.OnClickListener {
17 |
18 | private static final String KEY_TIME = "time";
19 |
20 | private Timer mTimer = new Timer();
21 | private Handler mMainHandler = new Handler(Looper.getMainLooper());
22 |
23 | private NumberView mMinuteTensView;
24 | private NumberView mMinuteOnesView;
25 | private NumberView mSecondTensView;
26 | private NumberView mSecondOnesView;
27 |
28 | private Button mResetButton;
29 | private Button mStartStopButton;
30 |
31 | private int mTime = 0;
32 |
33 | private boolean mStarted = false;
34 |
35 | @Override
36 | protected void onCreate(final Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | setContentView(R.layout.activity_number);
39 |
40 | mResetButton = (Button) findViewById(R.id.button_reset);
41 | mStartStopButton = (Button) findViewById(R.id.button_start_stop);
42 |
43 | mResetButton.setOnClickListener(this);
44 | mStartStopButton.setOnClickListener(this);
45 |
46 | mSecondTensView = (NumberView) findViewById(R.id.number_second_tens_position);
47 | mSecondOnesView = (NumberView) findViewById(R.id.number_second_ones_position);
48 | mMinuteTensView = (NumberView) findViewById(R.id.number_minute_tens_position);
49 | mMinuteOnesView = (NumberView) findViewById(R.id.number_minute_ones_position);
50 |
51 | mMinuteTensView.getPaint().setStrokeWidth(5f);
52 | mMinuteOnesView.getPaint().setStrokeWidth(5f);
53 |
54 | mTime = savedInstanceState == null ? 0 : savedInstanceState.getInt(KEY_TIME);
55 | }
56 |
57 | @Override
58 | protected void onResume() {
59 | handleStartStop();
60 | super.onResume();
61 | }
62 |
63 | @Override
64 | protected void onPause() {
65 | handleStartStop();
66 | super.onPause();
67 | }
68 |
69 | @Override
70 | protected void onSaveInstanceState(final Bundle outState) {
71 | outState.putInt(KEY_TIME, mTime);
72 | super.onSaveInstanceState(outState);
73 | }
74 |
75 | private void updateUi() {
76 | mSecondOnesView.show(Digits.forInt(mTime % 10));
77 | mSecondTensView.show(Digits.forInt((mTime / 10) % 6));
78 | mMinuteOnesView.show(Digits.forInt((mTime / 60) % 10));
79 | mMinuteTensView.show(Digits.forInt((mTime / 600) % 6));
80 |
81 | mTime++;
82 | }
83 |
84 | private void startTimer() {
85 | mTimer.scheduleAtFixedRate(new UpdateTask(), 0, 1000);
86 | }
87 |
88 | private void handleStartStop() {
89 | if (mStarted) {
90 | mTimer.cancel();
91 | mStartStopButton.setText(R.string.button_start);
92 |
93 | } else {
94 | mTimer = new Timer();
95 | startTimer();
96 | mStartStopButton.setText(R.string.button_stop);
97 | }
98 |
99 | mStarted = !mStarted;
100 | }
101 |
102 | private void handleReset() {
103 | mTime = 0;
104 | mSecondOnesView.hideNow();
105 | mSecondTensView.hideNow();
106 | mMinuteOnesView.hideNow();
107 | mMinuteTensView.hideNow();
108 | }
109 |
110 | @Override
111 | public void onClick(View v) {
112 | switch (v.getId()) {
113 |
114 | case R.id.button_reset:
115 | handleReset();
116 | break;
117 |
118 | case R.id.button_start_stop:
119 | handleStartStop();
120 | break;
121 |
122 | }
123 | }
124 |
125 | private class UpdateTask extends TimerTask {
126 | @Override
127 | public void run() {
128 | mMainHandler.post(new Runnable() {
129 | @Override
130 | public void run() {
131 | updateUi();
132 | }
133 | });
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/deange/numberview/sample/NumberGroupActivity.java:
--------------------------------------------------------------------------------
1 | package com.deange.numberview.sample;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import android.view.View;
8 | import android.widget.Button;
9 |
10 | import com.deange.numberview.NumberViewGroup;
11 | import com.deange.numberview.digits.Digits;
12 |
13 | import java.util.Timer;
14 | import java.util.TimerTask;
15 |
16 | public class NumberGroupActivity extends Activity implements View.OnClickListener {
17 |
18 | private static final String KEY_TIME = "time";
19 |
20 | private Timer mTimer = new Timer();
21 | private Handler mMainHandler = new Handler(Looper.getMainLooper());
22 |
23 | private NumberViewGroup mNumberViewGroup;
24 |
25 | private Button mResetButton;
26 | private Button mStartStopButton;
27 |
28 | private boolean mStarted = false;
29 | private int mTime;
30 |
31 | @Override
32 | protected void onCreate(final Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_number_group);
35 |
36 | mResetButton = (Button) findViewById(R.id.button_reset);
37 | mStartStopButton = (Button) findViewById(R.id.button_start_stop);
38 |
39 | mResetButton.setOnClickListener(this);
40 | mStartStopButton.setOnClickListener(this);
41 |
42 | mNumberViewGroup = (NumberViewGroup) findViewById(R.id.number_group);
43 | mNumberViewGroup.hideNow();
44 |
45 | mTime = savedInstanceState == null ? 0 : savedInstanceState.getInt(KEY_TIME);
46 | }
47 |
48 | @Override
49 | protected void onResume() {
50 | handleStartStop();
51 | super.onResume();
52 | }
53 |
54 | @Override
55 | protected void onPause() {
56 | handleStartStop();
57 | super.onPause();
58 | }
59 |
60 | @Override
61 | protected void onSaveInstanceState(final Bundle outState) {
62 | outState.putInt(KEY_TIME, mTime);
63 | super.onSaveInstanceState(outState);
64 | }
65 |
66 | private void updateUi() {
67 | mNumberViewGroup.show(mTime);
68 | mTime++;
69 | }
70 |
71 | private void startTimer() {
72 | mTimer.scheduleAtFixedRate(new UpdateTask(), 0, 1000);
73 | }
74 |
75 | private void handleStartStop() {
76 | if (mStarted) {
77 | mTimer.cancel();
78 | mStartStopButton.setText(R.string.button_start);
79 |
80 | } else {
81 | mTimer = new Timer();
82 | startTimer();
83 | mStartStopButton.setText(R.string.button_stop);
84 | }
85 |
86 | mStarted = !mStarted;
87 | }
88 |
89 | private void handleReset() {
90 | mNumberViewGroup.hideNow();
91 | mTime = 0;
92 | }
93 |
94 | @Override
95 | public void onClick(View v) {
96 | switch (v.getId()) {
97 |
98 | case R.id.button_reset:
99 | handleReset();
100 | break;
101 |
102 | case R.id.button_start_stop:
103 | handleStartStop();
104 | break;
105 |
106 | }
107 | }
108 |
109 | private class UpdateTask extends TimerTask {
110 | @Override
111 | public void run() {
112 | mMainHandler.post(new Runnable() {
113 | @Override
114 | public void run() {
115 | updateUi();
116 | }
117 | });
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/NumberView/68f9cd9cd87a302501c30c70768e9344788c7cc6/sample/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/NumberView/68f9cd9cd87a302501c30c70768e9344788c7cc6/sample/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/NumberView/68f9cd9cd87a302501c30c70768e9344788c7cc6/sample/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/NumberView/68f9cd9cd87a302501c30c70768e9344788c7cc6/sample/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/NumberView/68f9cd9cd87a302501c30c70768e9344788c7cc6/sample/src/main/res/drawable-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_number.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
18 |
19 |
23 |
24 |
28 |
29 |
33 |
34 |
35 |
36 |
43 |
44 |
52 |
53 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_number_group.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
20 |
21 |
29 |
30 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | NumberView
4 | RESET
5 | START
6 | STOP
7 |
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample', ':library'
2 |
--------------------------------------------------------------------------------