├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── example
├── build.gradle
├── debug.keystore
└── src
│ ├── instrumentTest
│ └── java
│ │ └── com
│ │ └── android
│ │ └── tests
│ │ └── basic
│ │ └── MainTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── bydavy
│ │ └── digitalclock
│ │ ├── Main.java
│ │ ├── MultipleClocks.java
│ │ ├── SimpleBigClock.java
│ │ └── SystemClockManager.java
│ └── res
│ ├── drawable
│ └── icon.png
│ ├── layout
│ ├── main.xml
│ ├── multiple.xml
│ └── simple_big_clock.xml
│ └── values
│ └── strings.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── bydavy
│ └── morpher
│ ├── ArrayHelper.java
│ ├── DigitalClockView.java
│ ├── DrawingHelper.java
│ ├── Font.java
│ └── font
│ └── DFont.java
├── local.properties
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | atlassian-ide-plugin.xml
2 | build
3 | *.iml
4 | .gradle
5 | .idea
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Davy Leggieri
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | android-morphing-number
2 | ========================
3 |
4 | This is an example of morphing effect implemented with Android UI toolkit. It is based on path and bezier curves. Some shortcuts have been taken. For instance, in the current implementation the Font is hardcoded but we can imagine loading it from svg files.
5 |
6 | ## Video
7 |
10 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:0.9.1'
7 | classpath 'net.ltgt.gradle:gradle-errorprone-plugin:latest.release'
8 | }
9 | }
10 |
11 | subprojects { project ->
12 | apply plugin: 'errorprone'
13 |
14 | repositories {
15 | mavenCentral()
16 | }
17 | }
--------------------------------------------------------------------------------
/example/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'android'
2 |
3 | dependencies {
4 | compile 'com.android.support:support-v4:13.0.0'
5 | compile project(':library')
6 | //compile group: 'com.google.guava', name: 'guava', version: '16.0.1'
7 | //debugCompile 'com.android.support:support-v13:13.0.0'
8 |
9 | //compile 'com.google.android.gms:play-services:3.1.36'
10 | }
11 |
12 | android {
13 | compileSdkVersion 15
14 | buildToolsVersion "19.0.1"
15 |
16 | testBuildType "debug"
17 |
18 | signingConfigs {
19 | myConfig {
20 | storeFile file("debug.keystore")
21 | storePassword "android"
22 | keyAlias "androiddebugkey"
23 | keyPassword "android"
24 | }
25 | }
26 |
27 | defaultConfig {
28 | versionCode 12
29 | versionName "2.0"
30 | minSdkVersion 16
31 | targetSdkVersion 16
32 |
33 | testInstrumentationRunner "android.test.InstrumentationTestRunner"
34 | testHandleProfiling false
35 |
36 | buildConfigField "boolean", "EXTRA_LOGS", "false"
37 |
38 | resConfig "en"
39 | resConfigs "nodpi", "hdpi"
40 | }
41 |
42 | buildTypes {
43 | debug {
44 | //packageNameSuffix ".debug"
45 | signingConfig signingConfigs.myConfig
46 |
47 | buildConfigField "boolean", "EXTRA_LOGS", "false"
48 | }
49 | }
50 |
51 | aaptOptions {
52 | noCompress 'txt'
53 | ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:.*:
_*:!CVS:!thumbs.db:!picasa.ini:!*~"
54 | }
55 |
56 | lintOptions {
57 | // if true, show all locations for an error, do not truncate lists, etc.
58 | showAll true
59 | // Fallback lint configuration (default severities, etc.)
60 | lintConfig file("default-lint.xml")
61 | // if true, generate a text report of issues (false by default)
62 | textReport true
63 | // location to write the output; can be a file or 'stdout'
64 | textOutput 'stdout'
65 | // if true, generate an XML report for use by for example Jenkins
66 | xmlReport true
67 | // file to write report to (if not specified, defaults to lint-results.xml)
68 | xmlOutput file("lint-report.xml")
69 | // if true, generate an HTML report (with issue explanations, sourcecode, etc)
70 | htmlReport true
71 | // optional path to report (default will be lint-results.html in the builddir)
72 | htmlOutput file("lint-report.html")
73 | }
74 | }
--------------------------------------------------------------------------------
/example/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bydavy/android-number-morphing/e755111a8086402b9f47425ae0f4b0c3ed7bed44/example/debug.keystore
--------------------------------------------------------------------------------
/example/src/instrumentTest/java/com/android/tests/basic/MainTest.java:
--------------------------------------------------------------------------------
1 | package com.android.tests.basic;
2 |
3 | import android.test.ActivityInstrumentationTestCase2;
4 | import android.test.suitebuilder.annotation.MediumTest;
5 | import android.widget.TextView;
6 | import com.bydavy.digitalclock.Main;
7 |
8 | public class MainTest extends ActivityInstrumentationTestCase2 {
9 |
10 | private TextView mTextView;
11 |
12 | /**
13 | * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
14 | */
15 | public MainTest() {
16 | super(Main.class);
17 | }
18 |
19 | @Override
20 | protected void setUp() throws Exception {
21 | super.setUp();
22 | final Main a = getActivity();
23 | // ensure a valid handle to the activity has been returned
24 | assertNotNull(a);
25 | mTextView = (TextView) a.findViewById(R.id.text);
26 | }
27 |
28 | /**
29 | * The name 'test preconditions' is a convention to signal that if this
30 | * test doesn't pass, the test case was not set up properly and it might
31 | * explain any and all failures in other tests. This is not guaranteed
32 | * to run before other tests, as junit uses reflection to find the tests.
33 | */
34 | @MediumTest
35 | public void testPreconditions() {
36 | assertNotNull(mTextView);
37 | }
38 |
39 | @MediumTest
40 | public void testBuildConfig() {
41 | assertEquals("bar", BuildConfig.FOO);
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/example/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
18 |
19 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/src/main/java/com/bydavy/digitalclock/Main.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.digitalclock;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.View;
7 |
8 | public class Main extends Activity {
9 |
10 | @Override
11 | public void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.main);
14 |
15 | findViewById(R.id.bigBlockButton).setOnClickListener(new View.OnClickListener() {
16 | @Override
17 | public void onClick(View v) {
18 | Intent intent = new Intent(Main.this, SimpleBigClock.class);
19 | startActivity(intent);
20 | }
21 | });
22 |
23 | findViewById(R.id.slowBigClockButton).setOnClickListener(new View.OnClickListener() {
24 | @Override
25 | public void onClick(View v) {
26 | Intent intent = new Intent(Main.this, SimpleBigClock.class);
27 | intent.putExtra(SimpleBigClock.EXTRA_MORPHING_DURATION, 3000);
28 | startActivity(intent);
29 | }
30 | });
31 |
32 | findViewById(R.id.multipleClocksButton).setOnClickListener(new View.OnClickListener() {
33 | @Override
34 | public void onClick(View v) {
35 | Intent intent = new Intent(Main.this, MultipleClocks.class);
36 | startActivity(intent);
37 | }
38 | });
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/example/src/main/java/com/bydavy/digitalclock/MultipleClocks.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.digitalclock;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import com.bydavy.morpher.DigitalClockView;
6 | import com.bydavy.morpher.font.DFont;
7 |
8 | import java.text.SimpleDateFormat;
9 |
10 | public class MultipleClocks extends Activity implements SystemClockManager.SystemClockListener {
11 |
12 | private DigitalClockView mDigitalClockView;
13 | private SystemClockManager mSystemClockManager;
14 | private SimpleDateFormat mSimpleDateFormat;
15 | private DigitalClockView mDigitalClockViewPadding;
16 | private DigitalClockView mDigitalClockViewBig;
17 | private SimpleDateFormat mShortDateFormat;
18 |
19 | @Override
20 | public void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.multiple);
23 |
24 | mDigitalClockView = (DigitalClockView) findViewById(R.id.digitalClock);
25 | mDigitalClockView.setFont(new DFont(120, 5));
26 |
27 | mDigitalClockViewPadding = (DigitalClockView) findViewById(R.id.digitalClockPadding);
28 | mDigitalClockViewPadding.setFont(new DFont(100, 10));
29 |
30 | mDigitalClockViewBig = (DigitalClockView) findViewById(R.id.digitalClockBig);
31 | mDigitalClockViewBig.setFont(new DFont(120, 12));
32 |
33 | mShortDateFormat = new SimpleDateFormat("hh:mm");
34 | mSimpleDateFormat = new SimpleDateFormat("hh:mm:ss");
35 | mSystemClockManager = new SystemClockManager(this);
36 | }
37 |
38 | @Override
39 | protected void onResume() {
40 | super.onResume();
41 | mSystemClockManager.start();
42 | }
43 |
44 | @Override
45 | protected void onPause() {
46 | super.onPause();
47 | mSystemClockManager.stop();
48 | }
49 |
50 | @Override
51 | public void onTimeChanged(long time) {
52 | String shortFormattedTime = mShortDateFormat.format(time);
53 | String formattedTime = mSimpleDateFormat.format(time);
54 |
55 | mDigitalClockView.setTimeNoAnimation(formattedTime);
56 | mDigitalClockViewPadding.setTime(formattedTime);
57 | mDigitalClockViewBig.setTime(formattedTime);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/example/src/main/java/com/bydavy/digitalclock/SimpleBigClock.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.digitalclock;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import com.bydavy.morpher.DigitalClockView;
6 | import com.bydavy.morpher.font.DFont;
7 |
8 | import java.text.SimpleDateFormat;
9 |
10 | public class SimpleBigClock extends Activity implements SystemClockManager.SystemClockListener {
11 |
12 | public static final String EXTRA_MORPHING_DURATION = "morphing_duration";
13 |
14 | private DigitalClockView mDigitalClockView;
15 | private SystemClockManager mSystemClockManager;
16 | private SimpleDateFormat mSimpleDateFormat;
17 |
18 | @Override
19 | public void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.simple_big_clock);
22 |
23 | mDigitalClockView = (DigitalClockView) findViewById(R.id.digitalClock);
24 | mDigitalClockView.setFont(new DFont(130, 10));
25 |
26 | int morphingDuration = getIntent().getIntExtra(EXTRA_MORPHING_DURATION, DigitalClockView.DEFAULT_MORPHING_DURATION);
27 | mDigitalClockView.setMorphingDuration(morphingDuration);
28 |
29 | mSimpleDateFormat = new SimpleDateFormat("hh:mm:ss");
30 | mSystemClockManager = new SystemClockManager(this);
31 | }
32 |
33 | @Override
34 | protected void onResume() {
35 | super.onResume();
36 | mSystemClockManager.start();
37 | }
38 |
39 | @Override
40 | protected void onPause() {
41 | super.onPause();
42 | mSystemClockManager.stop();
43 | }
44 |
45 | @Override
46 | public void onTimeChanged(long time) {
47 | String formattedTime = mSimpleDateFormat.format(time);
48 |
49 | // Useful when a long morphing duration is set otherwise we never see the destination number as it's always morphing
50 | if (!mDigitalClockView.isMorphingAnimationRunning()) {
51 | mDigitalClockView.setTime(formattedTime);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/example/src/main/java/com/bydavy/digitalclock/SystemClockManager.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.digitalclock;
2 |
3 | import android.os.Handler;
4 |
5 |
6 | public class SystemClockManager {
7 |
8 | public interface SystemClockListener {
9 | void onTimeChanged(long time);
10 | }
11 |
12 | private static final long ONE_SEC_IN_MS = 1000;
13 |
14 | private final SystemClockListener mListener;
15 | private final Handler mHandler;
16 | private final Runnable mNextTime;
17 |
18 | private boolean mStarted;
19 |
20 | public SystemClockManager(SystemClockListener listener) {
21 | mListener = listener;
22 | mHandler = new Handler();
23 |
24 | mNextTime = new Runnable() {
25 | @Override
26 | public void run() {
27 | mListener.onTimeChanged(System.currentTimeMillis());
28 | scheduleNextTime();
29 | }
30 | };
31 | }
32 |
33 | private void scheduleNextTime() {
34 | mHandler.postDelayed(mNextTime, ONE_SEC_IN_MS);
35 | }
36 |
37 | public void start() {
38 | if (!mStarted) {
39 | mNextTime.run();
40 | mStarted = true;
41 | }
42 | }
43 |
44 | public void stop() {
45 | if (mStarted) {
46 | mHandler.removeCallbacks(mNextTime);
47 | mStarted = false;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bydavy/android-number-morphing/e755111a8086402b9f47425ae0f4b0c3ed7bed44/example/src/main/res/drawable/icon.png
--------------------------------------------------------------------------------
/example/src/main/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
16 |
17 |
24 |
25 |
32 |
33 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/multiple.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
14 |
15 |
19 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/simple_big_clock.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/example/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Digital Clock Examples
4 | Digital Clock Fullscreen
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bydavy/android-number-morphing/e755111a8086402b9f47425ae0f4b0c3ed7bed44/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Feb 10 21:15:27 PST 2014
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-bin.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 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'android-library'
2 |
3 | android {
4 | compileSdkVersion 15
5 | buildToolsVersion "19.0.1"
6 |
7 | aaptOptions {
8 | noCompress 'txt'
9 | ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:.*:_*:!CVS:!thumbs.db:!picasa.ini:!*~"
10 | }
11 |
12 | lintOptions {
13 | // if true, show all locations for an error, do not truncate lists, etc.
14 | showAll true
15 | // Fallback lint configuration (default severities, etc.)
16 | lintConfig file("default-lint.xml")
17 | // if true, generate a text report of issues (false by default)
18 | textReport true
19 | // location to write the output; can be a file or 'stdout'
20 | textOutput 'stdout'
21 | // if true, generate an XML report for use by for example Jenkins
22 | xmlReport true
23 | // file to write report to (if not specified, defaults to lint-results.xml)
24 | xmlOutput file("lint-report.xml")
25 | // if true, generate an HTML report (with issue explanations, sourcecode, etc)
26 | htmlReport true
27 | // optional path to report (default will be lint-results.html in the builddir)
28 | htmlOutput file("lint-report.html")
29 | }
30 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/library/src/main/java/com/bydavy/morpher/ArrayHelper.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.morpher;
2 |
3 | public class ArrayHelper {
4 |
5 | private ArrayHelper() {
6 | throw new IllegalAccessError();
7 | }
8 |
9 | public static float[] expandIfRequired(float[] a, int newSize) {
10 | if (a == null || a.length != newSize) {
11 | a = new float[newSize];
12 | }
13 |
14 | return a;
15 | }
16 |
17 | public static float[][] expandIfRequired(float[][] a, int newSize) {
18 | if (a == null || a.length != newSize) {
19 | a = new float[newSize][];
20 | }
21 |
22 | return a;
23 | }
24 |
25 | public static float[][] expandIfRequired(float[][] a, int newSizeDim1, int newSizeDim2) {
26 | a = expandIfRequired(a, newSizeDim1);
27 |
28 | for (int i = 0; i < newSizeDim1; i++) {
29 | a[i] = expandIfRequired(a[i], newSizeDim2);
30 | }
31 |
32 | return a;
33 | }
34 |
35 | public static boolean[] expandIfRequired(boolean[] a, int newSize) {
36 | if (a == null || a.length != newSize) {
37 | a = new boolean[newSize];
38 | }
39 |
40 | return a;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/library/src/main/java/com/bydavy/morpher/DigitalClockView.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.morpher;
2 |
3 | import android.animation.ObjectAnimator;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.view.animation.LinearInterpolator;
9 | import com.bydavy.morpher.font.DFont;
10 |
11 | public class DigitalClockView extends View {
12 |
13 | public static int DEFAULT_MORPHING_DURATION = 300;
14 |
15 | public static final Font DEFAULT_FONT = new DFont(170, 10);
16 |
17 | private Font mFont;
18 | private int mMorphingDurationInMs;
19 |
20 | private float mMorphingPercent;
21 |
22 | // Local variables (opposed to mPreviousChars and mChars that hold immutable content "leaked" from the Font object)
23 | private float[][] mLocalChars;
24 | private float[][] mLocalWidth;
25 |
26 | // Store previous values (origin)
27 | private String mPreviousText;
28 | private float[][] mPreviousChars;
29 | private float[][] mPreviousWidth;
30 |
31 | // Store new values (destination)
32 | private String mText;
33 | private float[][] mChars;
34 | private float[][] mWidth;
35 |
36 | // FIXME The colon char doesn't fit in my current design
37 | private boolean[] mIsColonChar;
38 |
39 | private ObjectAnimator mMorphingAnimation;
40 |
41 | public DigitalClockView(Context context) {
42 | super(context);
43 | init();
44 | }
45 |
46 | public DigitalClockView(Context context, AttributeSet attrs) {
47 | super(context, attrs);
48 | // TODO Configure view with AttributeSet
49 | init();
50 | }
51 |
52 | public DigitalClockView(Context context, AttributeSet attrs, int defStyle) {
53 | super(context, attrs, defStyle);
54 | // TODO Configure view with AttributeSet
55 | init();
56 | }
57 |
58 | private void init() {
59 | mFont = DEFAULT_FONT;
60 | mMorphingDurationInMs = DEFAULT_MORPHING_DURATION;
61 | }
62 |
63 | public void setTime(String time) {
64 | setTime(time, true);
65 | }
66 |
67 | public void setTimeNoAnimation(String time) {
68 | setTime(time, false);
69 | }
70 |
71 | private void setTime(String time, boolean shouldMorph) {
72 | // Update only if time changed
73 | if (time == mText || (mText != null && mText.equals(time))) return;
74 |
75 | // Changing text length is not supported (at least not "morphed")
76 | if (mText == null || (mPreviousText != null && time != null && (mPreviousText.length() != time.length() || colonCharChangedPosition(mPreviousText, time)))) {
77 | shouldMorph = false;
78 | }
79 |
80 | int newSize = time.length();
81 | int pointsCount = mFont.getPointsCount();
82 |
83 | // Here we might reallocate memory (this view is not designed to change frequently)
84 | mChars = ArrayHelper.expandIfRequired(mChars, newSize);
85 | mWidth = ArrayHelper.expandIfRequired(mWidth, newSize);
86 |
87 | mPreviousChars = ArrayHelper.expandIfRequired(mPreviousChars, newSize);
88 | mPreviousWidth = ArrayHelper.expandIfRequired(mPreviousWidth, newSize);
89 |
90 | mLocalChars = ArrayHelper.expandIfRequired(mLocalChars, newSize, pointsCount);
91 | mLocalWidth = ArrayHelper.expandIfRequired(mLocalWidth, newSize, pointsCount);
92 |
93 | mIsColonChar = ArrayHelper.expandIfRequired(mIsColonChar, newSize);
94 |
95 | // Save current chars and width in order to generate a morphing animation
96 | if (shouldMorph) {
97 | float[][] originChars;
98 | float[][] originWidths;
99 |
100 | if (!isMorphingAnimationRunning()) {
101 | originChars = mChars;
102 | originWidths = mWidth;
103 | } else {
104 | // Save current morphing animation accordingly to the current state
105 | // FIXME Ideally we should not rely on the current mMorphingPercent but rather recompute the percentage
106 | // based on current time - start time / duration
107 | int size = mText.length();
108 | for (int i = 0; i < size; i++) {
109 | // Save result to mLocalChars and mLocalWidth. Both are local array that can be edited (not the case of
110 | // mPreviousChars, mPreviousWidth, mChars and mWidth).
111 | mFont.save(mPreviousChars[i], mChars[i], mMorphingPercent, mLocalChars[i]);
112 | mFont.saveWidth(mPreviousWidth[i], mWidth[i], mMorphingPercent, mLocalWidth[i]);
113 | }
114 |
115 | originChars = mLocalChars;
116 | originWidths = mLocalWidth;
117 | }
118 | System.arraycopy(originChars, 0, mPreviousChars, 0, mPreviousChars.length);
119 | System.arraycopy(originWidths, 0, mPreviousWidth, 0, mPreviousWidth.length);
120 | }
121 |
122 | fetchGlyphs(time, mChars, mWidth, mIsColonChar);
123 |
124 | mPreviousText = mText;
125 | mText = time;
126 |
127 | cancelMorphingAnimation();
128 |
129 | if (shouldMorph) {
130 | resetMorphingAnimation();
131 | startMorphingAnimation();
132 | }
133 |
134 | requestLayout();
135 | invalidate();
136 | }
137 |
138 | private void resetMorphingAnimation() {
139 | mMorphingPercent = 0;
140 | }
141 |
142 | private void startMorphingAnimation() {
143 | mMorphingAnimation = ObjectAnimator.ofFloat(this, "morphingPercent", 1);
144 | mMorphingAnimation.setInterpolator(new LinearInterpolator());
145 | mMorphingAnimation.setDuration(mMorphingDurationInMs);
146 | mMorphingAnimation.start();
147 | }
148 |
149 | private void cancelMorphingAnimation() {
150 | if (mMorphingAnimation != null) {
151 | mMorphingAnimation.cancel();
152 | mMorphingAnimation = null;
153 | }
154 | }
155 |
156 | public boolean isMorphingAnimationRunning() {
157 | return mMorphingAnimation != null && mMorphingAnimation.isRunning();
158 | }
159 |
160 | private void fetchGlyphs(String text, float[][] chars, float[][] widths, boolean[] isColumnChar) {
161 | int size = text.length();
162 | for (int i = 0; i < size; i++) {
163 | char c = text.charAt(i);
164 |
165 | // FIXME Column doesn't fit in my current impl
166 | boolean isColumn = c == ':';
167 | isColumnChar[i] = isColumn;
168 | if (isColumn) {
169 | continue;
170 | }
171 |
172 | if (!mFont.hasGlyph(c)) {
173 | throw new RuntimeException("Character not supported " + c);
174 | }
175 | chars[i] = mFont.getGlyphPoints(c);
176 | widths[i] = mFont.getGlyphBounds(c);
177 | }
178 | }
179 |
180 | public void setMorphingDuration(int durationInMs) {
181 | mMorphingDurationInMs = durationInMs;
182 | }
183 |
184 | public void setFont(Font font) {
185 | //if (mFont != font) {
186 | mFont = font;
187 | cancelMorphingAnimation();
188 |
189 | // Force text changed to repopulate internal arrays
190 | String time = mText;
191 | mText = null;
192 | setTime(time);
193 | //}
194 | }
195 |
196 | /*public void setFontColor(int color) {
197 | if (mColor != color) {
198 | mColor = color;
199 |
200 | invalidate();
201 | }
202 | }
203 |
204 | public void setFontSize(int sizeInPx) {
205 | if (mFontSize != sizeInPx) {
206 | mFontSize = sizeInPx;
207 |
208 | requestLayout();
209 | //invalidate();
210 | }
211 | }
212 |
213 | public void setFontThickness(int thicknessInPx) {
214 | if (mThickness != thicknessInPx) {
215 | mThickness = thicknessInPx;
216 |
217 | requestLayout();
218 | //invalidate();
219 | }
220 | }*/
221 |
222 | @Override
223 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
224 | setMinimumWidth(computeMinWidth());
225 | setMinimumHeight(computeMinHeight());
226 |
227 | int minW = getPaddingLeft() + getSuggestedMinimumWidth() + getPaddingRight();
228 | int w = resolveSizeAndState(minW, widthMeasureSpec, 1);
229 |
230 | int minH = getPaddingTop() + getSuggestedMinimumHeight() + getPaddingBottom();
231 | int h = resolveSizeAndState(minH, heightMeasureSpec, 0);
232 |
233 | setMeasuredDimension(w, h);
234 | }
235 |
236 | private int computeMinWidth() {
237 | float x = 0;
238 |
239 | if (mChars != null && mWidth != null) {
240 | float xSeparator = mFont.getGlyphSeparatorWidth();
241 |
242 | final int size = mChars.length;
243 | for (int i = 0; i < size; i++) {
244 | if (!mIsColonChar[i]) {
245 | if (!isMorphingAnimationRunning()) {
246 | x += mWidth[i][0];
247 | } else {
248 | x += mFont.computeWidth(mPreviousWidth[i], mWidth[i], mMorphingPercent);
249 | }
250 | } else {
251 | x += mFont.getColonWidth();
252 | }
253 |
254 | if (i < size - 1) {
255 | x += xSeparator;
256 | }
257 | }
258 | }
259 |
260 | return (int) x;
261 | }
262 |
263 | private int computeMinHeight() {
264 | // Only one line supported for now.
265 | return (int) mFont.getGlyphMaximalBounds()[1];
266 | }
267 |
268 | @Override
269 | protected void onDraw(Canvas canvas) {
270 | float xSeparator = mFont.getGlyphSeparatorWidth();
271 |
272 | canvas.save();
273 | canvas.translate(getPaddingLeft(), getPaddingTop());
274 | if (mChars != null) {
275 | float charWidth;
276 | final int size = mChars.length;
277 | for (int i = 0; i < size; i++) {
278 | if (!mIsColonChar[i]) {
279 | if (!isMorphingAnimationRunning()) {
280 | mFont.draw(canvas, mChars[i]);
281 | charWidth = mWidth[i][0];
282 | } else {
283 | mFont.draw(canvas, mPreviousChars[i], mChars[i], mMorphingPercent);
284 | charWidth = mFont.computeWidth(mPreviousWidth[i], mWidth[i], mMorphingPercent);
285 | }
286 | } else {
287 | mFont.drawColon(canvas);
288 | charWidth = mFont.getColonWidth();
289 | }
290 |
291 | if (i < size - 1) {
292 | canvas.translate(charWidth + xSeparator, 0);
293 | }
294 | }
295 |
296 | }
297 | canvas.restore();
298 | }
299 |
300 | @SuppressWarnings("unused")
301 | public void setMorphingPercent(float percent) {
302 | mMorphingPercent = percent;
303 |
304 | requestLayout();
305 | invalidate();
306 | }
307 |
308 | @SuppressWarnings("unused")
309 | public float getMorphingPercent() {
310 | return mMorphingPercent;
311 | }
312 |
313 | private static boolean colonCharChangedPosition(String previousTime, String time) {
314 | if (previousTime.length() != time.length()) {
315 | return true;
316 | }
317 |
318 | final int length = previousTime.length();
319 | for (int i = 0; i < length; i++) {
320 | char previousC = previousTime.charAt(i);
321 | if (previousC == ':' && previousC != time.charAt(i)) {
322 | return true;
323 | }
324 | }
325 |
326 | return false;
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/library/src/main/java/com/bydavy/morpher/DrawingHelper.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.morpher;
2 |
3 |
4 | public class DrawingHelper {
5 |
6 | private DrawingHelper() {
7 | throw new IllegalAccessError();
8 | }
9 |
10 | public static int addPoint(float[] array, int index, float x, float y) {
11 | array[index++] = x;
12 | array[index++] = y;
13 |
14 | return index;
15 | }
16 |
17 | public static int addBezierStraightLine(float[] array, int index, float x, float y) {
18 | float previousX = array[index - 2];
19 | float previousY = array[index - 1];
20 |
21 | // Generate intermediate points
22 |
23 | float sumX = previousX + x;
24 | float sumY = previousY + y;
25 |
26 | float x1 = (sumX + previousX) / 3;
27 | float y1 = (sumY + previousY) / 3;
28 |
29 | float x2= (sumX + x) / 3;
30 | float y2= (sumY + y) / 3;
31 |
32 | // Add control points
33 | index = addPoint(array, index, x1, y1);
34 | index = addPoint(array, index, x2, y2);
35 |
36 | // Add ending point
37 | index = addPoint(array, index, x, y);
38 |
39 | return index;
40 | }
41 |
42 | public static int addBezierCurve(float[] array, int index, float x1, float y1, float x2, float y2, float x3, float y3) {
43 | // Add control points
44 | index = addPoint(array, index, x1, y1);
45 | index = addPoint(array, index, x2, y2);
46 |
47 | // Add ending point
48 | index = addPoint(array, index, x3, y3);
49 |
50 | return index;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/library/src/main/java/com/bydavy/morpher/Font.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.morpher;
2 |
3 | import android.graphics.Canvas;
4 |
5 | /**
6 | * Font is an immutable object.
7 | * Be careful to not edit arrays that are exposed by this class (encapsulation is not respected
8 | * for performance reasons)
9 | */
10 | public interface Font {
11 |
12 | public static final float REFERENCE_HEIGHT = 1;
13 |
14 | /**
15 | * Test if the font supports the character
16 | *
17 | * @param c character
18 | * @return true if character supported, false otherwise
19 | */
20 | boolean hasGlyph(char c);
21 |
22 | /**
23 | * Returns the number of points defining each glyph. This is a constant per font
24 | * in order to simplify morphing.
25 | *
26 | * @return
27 | */
28 | int getPointsCount();
29 |
30 | /**
31 | * Returns an array of points to draw (x1, y1, x2, y2, etc).
32 | *
33 | * The first point is the starting point and then each point is prefixed by two control
34 | * points (Bezier curves).
35 | *
36 | * @param c character
37 | * @return array of points
38 | */
39 | float[] getGlyphPoints(char c);
40 |
41 | /**
42 | * Return the glyph's bounds
43 | *
44 | * @param c character
45 | * @return x, y
46 | */
47 | float[] getGlyphBounds(char c);
48 |
49 | /**
50 | * Return the biggest glyph's bounds
51 | *
52 | * @return x, y
53 | */
54 | float[] getGlyphMaximalBounds();
55 |
56 | /**
57 | * Return the space that should be added between each character
58 | *
59 | * In the current implementation the space in-between character is fixed.
60 | * Maybe in the future I will push it to the glyph definition.
61 | *
62 | *
63 | * @return
64 | */
65 | float getGlyphSeparatorWidth();
66 |
67 | void draw(Canvas canvas, float[] array);
68 |
69 | void draw(Canvas canvas, float[] a, float[] b, float percent);
70 |
71 | void save(float[] a, float[] b, float percent, float[] result);
72 |
73 | float computeWidth(float[] a, float[] b, float percent);
74 |
75 | void saveWidth(float[] floats, float[] mChar, float percent, float[] result);
76 |
77 |
78 | // FIXME Doesn't fit well in my current architecture
79 | void drawColon(Canvas canvas);
80 |
81 | float getColonWidth();
82 | }
83 |
--------------------------------------------------------------------------------
/library/src/main/java/com/bydavy/morpher/font/DFont.java:
--------------------------------------------------------------------------------
1 | package com.bydavy.morpher.font;
2 |
3 |
4 | import android.graphics.*;
5 | import com.bydavy.morpher.DrawingHelper;
6 | import com.bydavy.morpher.Font;
7 |
8 | public class DFont implements Font {
9 | private static final boolean DEBUG_DRAW_CONTROL_POINTS = false;
10 |
11 | private static final int POINTS_PER_GLYPH = 13 * 2;
12 | private static final int DIGITS = 10;
13 |
14 | private static final float KAPPA = (float) (4 * ((Math.sqrt(2) - 1) / 3));
15 |
16 | private final int mSize;
17 | private final int mThickness;
18 |
19 | private final Path mPath;
20 | private final Paint mPaint;
21 | // Used for debug only
22 | private final Paint mDebugControlPointsPaint;
23 |
24 | private static boolean isDigit(char c) {
25 | return c >= '0' && c <= '9';
26 | }
27 |
28 | private static boolean isColumn(char c) {
29 | return c == ':';
30 | }
31 |
32 | private static int getDigitIndex(char c) {
33 | return c - '0';
34 | }
35 |
36 | private final float[] mGlyphBounds;
37 | private final float[][] mDigitBounds;
38 | private final float[][] mDigits;
39 |
40 | private final float mColumnWidth;
41 |
42 | public DFont(int size, int thickness) {
43 | mPath = new Path();
44 |
45 | mPaint = new Paint();
46 | mPaint.setColor(Color.argb(255, 255, 255, 255));
47 | mPaint.setAntiAlias(true);
48 | mPaint.setStyle(Paint.Style.STROKE);
49 | mPaint.setStrokeJoin(Paint.Join.ROUND);
50 | mPaint.setStrokeCap(Paint.Cap.BUTT);
51 | mPaint.setPathEffect(new CornerPathEffect(thickness/5));
52 | mPaint.setStrokeWidth(thickness);
53 |
54 | mSize = size;
55 | mThickness = thickness;
56 |
57 | mGlyphBounds = new float[2];
58 | mGlyphBounds[0] = boundsX();
59 | mGlyphBounds[1] = boundsY();
60 |
61 | mDigitBounds = new float[DIGITS][];
62 | mDigitBounds[0] = getZeroBounds();
63 | mDigitBounds[1] = getOneBounds();
64 | mDigitBounds[2] = getTwoBounds();
65 | mDigitBounds[3] = getThreeBounds();
66 | mDigitBounds[4] = getFourBounds();
67 | mDigitBounds[5] = getFiveBounds();
68 | mDigitBounds[6] = getSixBounds();
69 | mDigitBounds[7] = getSevenBounds();
70 | mDigitBounds[8] = getEightBounds();
71 | mDigitBounds[9] = getNineBounds();
72 |
73 | mDigits = new float[DIGITS][];
74 | mDigits[0] = getZero();
75 | mDigits[1] = getOne();
76 | mDigits[2] = getTwo();
77 | mDigits[3] = getThree();
78 | mDigits[4] = getFour();
79 | mDigits[5] = getFive();
80 | mDigits[6] = getSix();
81 | mDigits[7] = getSeven();
82 | mDigits[8] = getEight();
83 | mDigits[9] = getNine();
84 |
85 | mColumnWidth = innerBoxWidth() / 2;
86 |
87 | if (!DEBUG_DRAW_CONTROL_POINTS) {
88 | mDebugControlPointsPaint = null;
89 | }else {
90 | mDebugControlPointsPaint = new Paint();
91 | mDebugControlPointsPaint.setStrokeWidth(4);
92 | }
93 | }
94 |
95 | @Override
96 | public int getPointsCount() {
97 | return POINTS_PER_GLYPH;
98 | }
99 |
100 | @Override
101 | public boolean hasGlyph(char c) {
102 | return isDigit(c) || isColumn(c);
103 | }
104 |
105 | @Override
106 | public float[] getGlyphPoints(char c) {
107 | float[] result = null;
108 |
109 | if (isDigit(c)) {
110 | result = mDigits[getDigitIndex(c)];
111 | }
112 |
113 | return result;
114 | }
115 |
116 | @Override
117 | public float[] getGlyphBounds(char c) {
118 | float[] result = null;
119 |
120 | if (isDigit(c)) {
121 | result = mDigitBounds[getDigitIndex(c)];
122 | }
123 |
124 | return result;
125 | }
126 |
127 | @Override
128 | public float[] getGlyphMaximalBounds() {
129 | return mGlyphBounds;
130 | }
131 |
132 | @Override
133 | public float getGlyphSeparatorWidth() {
134 | return mSize * .150f;
135 | }
136 |
137 | private float boundsX() {
138 | return mSize * .6f;
139 | }
140 |
141 | private float boundsY() {
142 | return mSize * Font.REFERENCE_HEIGHT;
143 | }
144 |
145 | private float borderThickness() {
146 | return mThickness / 2f;
147 | }
148 |
149 | private float innerBoxStartX() {
150 | return borderThickness();
151 | }
152 |
153 | private float innerBoxStartY() {
154 | return borderThickness();
155 | }
156 |
157 | private float innerBoxWidth() {
158 | return boundsX() - mThickness;
159 | }
160 |
161 | private float innerBoxHeight() {
162 | return boundsY() - mThickness;
163 | }
164 |
165 | private float innerBoxEndX() {
166 | return innerBoxStartX() + innerBoxWidth();
167 | }
168 |
169 | private float innerBoxEndY() {
170 | return innerBoxStartY() + innerBoxHeight();
171 | }
172 |
173 | private float[] createGlyphPointsArray() {
174 | return new float[getPointsCount()];
175 | }
176 |
177 | private float[] getZeroBounds() {
178 | return new float[]{getZeroInnerBoxWidth() + 2 * borderThickness(), boundsY()};
179 | }
180 |
181 | private float getZeroInnerBoxWidth() {
182 | return innerBoxWidth();
183 | }
184 |
185 | private float[] getZero() {
186 | float width = getZeroInnerBoxWidth();
187 |
188 | float halfHeight = innerBoxHeight() / 2;
189 | float halfWidth = width / 2;
190 |
191 | float controlPointX = halfWidth / 2 + halfWidth / 4;
192 | float controlPointY = halfHeight / 2;
193 |
194 | float p1x = innerBoxStartX();
195 | float p1y = innerBoxStartY() + halfHeight;
196 |
197 | float p2x = innerBoxStartX() + halfWidth;
198 | float p2y = innerBoxStartY();
199 |
200 | float p3x = innerBoxStartX() + width;
201 | float p3y = innerBoxStartY() + halfHeight;
202 |
203 | float p4x = innerBoxStartX() + halfWidth;
204 | float p4y = innerBoxStartY() + innerBoxHeight();
205 |
206 | float p5x = p1x;
207 | float p5y = p1y;
208 |
209 | final float[] points = createGlyphPointsArray();
210 |
211 | int index = 0;
212 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
213 | index = DrawingHelper.addBezierCurve(points, index, p1x, p1y - controlPointY, p2x - controlPointX, p2y, p2x, p2y);
214 | index = DrawingHelper.addBezierCurve(points, index, p2x + controlPointX, p2y, p3x, p3y - controlPointY, p3x, p3y);
215 | index = DrawingHelper.addBezierCurve(points, index, p3x, p3y + controlPointY, p4x + controlPointX, p4y, p4x, p4y);
216 | /*index =*/
217 | DrawingHelper.addBezierCurve(points, index, p4x - controlPointX, p4y, p5x, p5y + controlPointY, p5x, p5y);
218 |
219 | return points;
220 | }
221 |
222 | private float[] getOneBounds() {
223 | return new float[]{getOneInnerWidth() + 2 * borderThickness(), boundsY()};
224 | }
225 |
226 | private float getOneInnerWidth() {
227 | return innerBoxWidth() / 2;
228 | }
229 |
230 | private float[] getOne() {
231 | float width = getOneInnerWidth();
232 |
233 | float p1x = innerBoxStartX();
234 | float p1y = innerBoxStartY();
235 |
236 | float p2x = innerBoxStartX() + width;
237 | float p2y = p1y;
238 |
239 | float p3x = p2x;
240 | float p3y = innerBoxStartY() + innerBoxHeight() + borderThickness();
241 |
242 | float soft1x = p2x;
243 | float soft1y = (p2y + p3y) / 2 * (1f / 3);
244 |
245 | float soft2x = p2x;
246 | float soft2y = (p2y + p3y) / 2 * (2f / 3);
247 |
248 | final float[] points = createGlyphPointsArray();
249 |
250 | int index = 0;
251 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
252 | index = DrawingHelper.addBezierStraightLine(points, index, p2x, p2y);
253 | index = DrawingHelper.addBezierStraightLine(points, index, soft1x, soft1y);
254 | index = DrawingHelper.addBezierStraightLine(points, index, soft2x, soft2y);
255 | /*index =*/
256 | DrawingHelper.addBezierStraightLine(points, index, p3x, p3y);
257 |
258 | return points;
259 | }
260 |
261 | private float[] getTwoBounds() {
262 | return new float[]{getTwoInnerWidth() + 2 * borderThickness(), boundsY()};
263 | }
264 |
265 | private float getTwoInnerWidth() {
266 | return innerBoxWidth();
267 | }
268 |
269 | private float[] getTwo() {
270 | float width = getTwoInnerWidth();
271 | float radius = width / 2;
272 |
273 | float p1x = innerBoxStartX();
274 | float p1y = innerBoxStartY() + radius;
275 |
276 | float p2x = innerBoxStartX() + width;
277 | float p2y = p1y;
278 |
279 | float p3x = p2x;
280 | float p3y = p2y + width * (1f / 20);
281 |
282 | float p4x = innerBoxStartX();
283 | float p4y = innerBoxStartY() + innerBoxHeight();
284 |
285 | float p5x = innerBoxStartX() + width;
286 | float p5y = innerBoxStartY() + innerBoxHeight();
287 |
288 | float controlSegmentHalfCircle = 4 / 3f * radius;
289 |
290 | final float[] points = createGlyphPointsArray();
291 |
292 | int index = 0;
293 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
294 | index = DrawingHelper.addBezierCurve(points, index, p1x, p1y - controlSegmentHalfCircle, p2x, p2y - controlSegmentHalfCircle, p2x, p2y);
295 | index = DrawingHelper.addBezierStraightLine(points, index, p3x, p3y);
296 | index = DrawingHelper.addBezierStraightLine(points, index, p4x, p4y);
297 | /*index =*/
298 | DrawingHelper.addBezierStraightLine(points, index, p5x, p5y);
299 |
300 | return points;
301 | }
302 |
303 | private float[] getThreeBounds() {
304 | return new float[]{getThreeInnerWidth() + 2 * borderThickness(), boundsY()};
305 | }
306 |
307 | private float getThreeInnerWidth() {
308 | return innerBoxWidth();
309 | }
310 |
311 | private float[] getThree() {
312 | float width = getThreeInnerWidth();
313 |
314 | float radius = width / 2;
315 |
316 | float p1x = innerBoxStartX();
317 | float p1y = innerBoxStartY() + radius;
318 |
319 | float p2x = innerBoxStartX() + radius;
320 | float p2y = innerBoxStartY() + innerBoxHeight() / 2;
321 |
322 | float p3x = innerBoxStartX();
323 | float p3y = innerBoxStartY() + innerBoxHeight() - radius;
324 |
325 | // Between p1 and p2
326 | float soft1x = innerBoxStartX() + width;
327 | float soft1y = p1y;
328 |
329 | // Between p2 and p3
330 | float soft2x = innerBoxStartX() + width;
331 | float soft2y = p3y;
332 |
333 | float controlSegmentHalfCircle = 4 / 3f * radius;
334 | float controlSegmentQuadrant = KAPPA * radius;
335 |
336 | final float[] points = createGlyphPointsArray();
337 |
338 | int index = 0;
339 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
340 | index = DrawingHelper.addBezierCurve(points, index, p1x, p1y - controlSegmentHalfCircle, soft1x, soft1y - controlSegmentHalfCircle, soft1x, soft1y);
341 | index = DrawingHelper.addBezierCurve(points, index, soft1x, soft1y + controlSegmentQuadrant, p2x + controlSegmentQuadrant, p2y, p2x, p2y);
342 | index = DrawingHelper.addBezierCurve(points, index, p2x + controlSegmentQuadrant, p2y, soft2x, soft2y - controlSegmentQuadrant, soft2x, soft2y);
343 | /*index =*/
344 | DrawingHelper.addBezierCurve(points, index, soft2x, soft2y + controlSegmentHalfCircle, p3x, p3y + controlSegmentHalfCircle, p3x, p3y);
345 |
346 | return points;
347 | }
348 |
349 |
350 | private float[] getFourBounds() {
351 | return new float[]{getFiveInnerWidth() + 2 * borderThickness(), boundsY()};
352 | }
353 |
354 | private float getFourWidth() {
355 | return innerBoxWidth();
356 | }
357 |
358 | private float[] getFour() {
359 | float width = getFourWidth();
360 |
361 | float p1x = innerBoxStartX() + width + borderThickness();
362 | float p1y = innerBoxStartY() + innerBoxHeight() * (2 / 3f);
363 |
364 | float p2x = innerBoxStartX();
365 | float p2y = p1y;
366 |
367 | float p3x = innerBoxStartX() + width * (3 / 4f);
368 | float p3y = innerBoxStartY();
369 |
370 | float p4x = p3x;
371 | float p4y = innerBoxStartY() + innerBoxHeight() + borderThickness();
372 |
373 | // Between p3 and p4
374 | float soft1x = p3x;
375 | float soft1y = p1y;
376 |
377 | final float[] points = createGlyphPointsArray();
378 |
379 | int index = 0;
380 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
381 | index = DrawingHelper.addBezierStraightLine(points, index, p2x, p2y);
382 | index = DrawingHelper.addBezierStraightLine(points, index, p3x, p3y);
383 | index = DrawingHelper.addBezierStraightLine(points, index, soft1x, soft1y);
384 | /*index =*/
385 | DrawingHelper.addBezierStraightLine(points, index, p4x, p4y);
386 |
387 | return points;
388 | }
389 |
390 | private float[] getFiveBounds() {
391 | return new float[]{getFiveInnerWidth() + 2 * borderThickness(), boundsY()};
392 | }
393 |
394 | private float getFiveInnerWidth() {
395 | return innerBoxWidth();
396 | }
397 |
398 | private float[] getFive() {
399 | float width = getFiveInnerWidth();
400 |
401 | float diameter = width;
402 | float radius = width / 2;
403 |
404 | float p1x = innerBoxStartX() + width;
405 | float p1y = innerBoxStartY();
406 |
407 | float p2x = innerBoxStartX();
408 | float p2y = innerBoxStartY();
409 |
410 | float p3x = innerBoxStartX();
411 | float p3y = innerBoxStartY() + innerBoxHeight() - diameter;
412 |
413 | float p4x = innerBoxStartX();
414 | float p4y = innerBoxStartY() + innerBoxHeight();
415 |
416 | // Between p3 and p4
417 | float soft1x = innerBoxStartX() + width;
418 | float soft1y = p3y + radius;
419 |
420 | final float[] points = createGlyphPointsArray();
421 |
422 | int index = 0;
423 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
424 | index = DrawingHelper.addBezierStraightLine(points, index, p2x, p2y);
425 | index = DrawingHelper.addBezierStraightLine(points, index, p3x, p3y);
426 | index = DrawingHelper.addBezierCurve(points, index, p3x + radius, p3y, soft1x, soft1y - radius, soft1x, soft1y);
427 | /*index =*/
428 | DrawingHelper.addBezierCurve(points, index, soft1x, soft1y + radius, p4x + radius, p4y, p4x, p4y);
429 |
430 | return points;
431 | }
432 |
433 | private float[] getSixBounds() {
434 | return new float[]{getSixInnerWidth() + 2 * borderThickness(), boundsY()};
435 | }
436 |
437 | private float getSixInnerWidth() {
438 | return innerBoxWidth();
439 | }
440 |
441 | private float[] getSix() {
442 | float width = getSixInnerWidth();
443 |
444 | float diameter = width;
445 | float radius = diameter / 2;
446 | float halfRadius = radius / 2;
447 |
448 | float p1x = innerBoxStartX() + width;
449 | float p1y = innerBoxStartY();
450 |
451 | float p2x = innerBoxStartX();
452 | float p2y = innerBoxStartY() + innerBoxHeight() - radius;
453 |
454 | float p3x = p2x;
455 | float p3y = p2y;
456 |
457 | // Between p2 and soft2
458 | float soft1x = innerBoxStartX() + radius;
459 | float soft1y = innerBoxStartY() + innerBoxHeight();
460 |
461 | // Between soft2 and p3
462 | float soft2x = innerBoxStartX() + width;
463 | float soft2y = innerBoxStartY() + innerBoxHeight() - radius;
464 |
465 | final float[] points = createGlyphPointsArray();
466 |
467 | int index = 0;
468 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
469 | index = DrawingHelper.addBezierCurve(points, index, p1x / 2, 0, 0, p2y - (p2y / 2), p2x, p2y);
470 | index = DrawingHelper.addBezierCurve(points, index, p2x, p2y + halfRadius, soft1x - halfRadius, soft1y, soft1x, soft1y);
471 | index = DrawingHelper.addBezierCurve(points, index, soft1x + halfRadius, soft1y, soft2x, soft2y + halfRadius, soft2x, soft2y);
472 | // FIXME Not a perfect circle
473 | /*index =*/
474 | DrawingHelper.addBezierCurve(points, index, soft2x, soft2y - radius - halfRadius, p3x, p3y - radius - halfRadius, p3x, p3y);
475 |
476 | return points;
477 | }
478 |
479 | private float[] getSevenBounds() {
480 | return new float[]{getSevenInnerWidth() + 2 * borderThickness(), boundsY()};
481 | }
482 |
483 | private float getSevenInnerWidth() {
484 | return innerBoxWidth();
485 | }
486 |
487 | private float[] getSeven() {
488 | float width = getSevenInnerWidth();
489 |
490 | float p1x = innerBoxStartX();
491 | float p1y = innerBoxStartY();
492 |
493 | float p2x = innerBoxStartX() + width;
494 | float p2y = innerBoxStartY();
495 |
496 | float p3x = innerBoxStartX();
497 | float p3y = innerBoxStartY() + innerBoxHeight() + borderThickness();
498 |
499 | // Between p1 and p2
500 | float soft1x = (p1x + p2x) / 2;
501 | float soft1y = (p1y + p2y) / 2;
502 |
503 | // Between p2 and p3
504 | float soft2x = (p2x + p3x) / 2;
505 | float soft2y = (p2y + p3y) / 2;
506 |
507 | final float[] points = createGlyphPointsArray();
508 |
509 | int index = 0;
510 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
511 | index = DrawingHelper.addBezierStraightLine(points, index, soft1x, soft1y);
512 | index = DrawingHelper.addBezierStraightLine(points, index, p2x, p2y);
513 | index = DrawingHelper.addBezierStraightLine(points, index, soft2x, soft2y);
514 | /*index =*/
515 | DrawingHelper.addBezierStraightLine(points, index, p3x, p3y);
516 |
517 | return points;
518 | }
519 |
520 | private float[] getEightBounds() {
521 | return new float[]{getEightInnerWidth() + 2 * borderThickness(), boundsY()};
522 | }
523 |
524 | private float getEightInnerWidth() {
525 | return innerBoxWidth();
526 | }
527 |
528 | private float[] getEight() {
529 | float width = getEightInnerWidth();
530 | float halfWidth = width / 2;
531 |
532 | float leftControlPointX = innerBoxStartX() + halfWidth - 4 / 3f * halfWidth;
533 | float rightControlPointX = innerBoxStartX() + halfWidth + 4 / 3f * halfWidth;
534 |
535 | float p1x = innerBoxStartX() + halfWidth;
536 | float p1y = innerBoxStartY() + innerBoxHeight() / 2;
537 |
538 | float p2x = innerBoxStartX() + halfWidth;
539 | float p2y = innerBoxStartY();
540 |
541 | float p3x = p1x;
542 | float p3y = p1y;
543 |
544 | float p4x = innerBoxStartX() + halfWidth;
545 | float p4y = innerBoxStartY() + innerBoxHeight();
546 |
547 | float p5x = p1x;
548 | float p5y = p1y;
549 |
550 | final float[] points = createGlyphPointsArray();
551 |
552 | int index = 0;
553 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
554 | index = DrawingHelper.addBezierCurve(points, index, leftControlPointX, p1y, leftControlPointX, p2y, p2x, p2y);
555 | index = DrawingHelper.addBezierCurve(points, index, rightControlPointX, p2y, rightControlPointX, p3y, p3x, p3y);
556 | index = DrawingHelper.addBezierCurve(points, index, rightControlPointX, p3y, rightControlPointX, p4y, p4x, p4y);
557 | /*index =*/
558 | DrawingHelper.addBezierCurve(points, index, leftControlPointX, p4y, leftControlPointX, p5y, p5x, p5y);
559 |
560 | return points;
561 | }
562 |
563 | private float[] getNineBounds() {
564 | return new float[]{getNineInnerWidth() + 2 * borderThickness(), boundsY()};
565 | }
566 |
567 | private float getNineInnerWidth() {
568 | return innerBoxWidth();
569 | }
570 |
571 | private float[] getNine() {
572 | float width = getNineInnerWidth();
573 |
574 | float diameter = width;
575 | float radius = diameter / 2;
576 | float radiusControl = (4 / 3f) * radius;
577 |
578 | float p1x = innerBoxStartX() + width;
579 | float p1y = innerBoxStartY() + radius;
580 |
581 | float p2x = p1x;
582 | float p2y = p1y;
583 |
584 | float p3x = innerBoxStartX() + width;
585 | float p3y = innerBoxStartY() + diameter;
586 |
587 | float p4x = 0;
588 | float p4y = innerBoxStartY() + innerBoxHeight();
589 |
590 | // Between p1 and p2
591 | float soft1x = innerBoxStartX();
592 | float soft1y = innerBoxStartY() + radius;
593 |
594 | final float[] points = createGlyphPointsArray();
595 |
596 | int index = 0;
597 | index = DrawingHelper.addPoint(points, index, p1x, p1y);
598 | index = DrawingHelper.addBezierCurve(points, index, p1x, p1y + radiusControl, soft1x, soft1y + radiusControl, soft1x, soft1y);
599 | index = DrawingHelper.addBezierCurve(points, index, soft1x, soft1y - radiusControl, p2x, p2y - radiusControl, p2x, p2y);
600 | index = DrawingHelper.addBezierStraightLine(points, index, p3x, p3y);
601 | // FIXME Doesn't look good
602 | /*index =*/
603 | DrawingHelper.addBezierCurve(points, index, p3x, p3y + (diameter * (2 / 3f)), p4x + radius, p4y, p4x, p4y);
604 |
605 | return points;
606 | }
607 |
608 | @Override
609 | public void draw(Canvas canvas, float[] array) {
610 | if (array == null) return;
611 |
612 | mPath.reset();
613 |
614 | int i = 0;
615 | mPath.moveTo(array[i++], array[i++]);
616 |
617 | final int size = array.length;
618 | while (i < size - 5) {
619 | mPath.cubicTo(array[i++], array[i++], array[i++], array[i++], array[i++], array[i++]);
620 | }
621 |
622 | canvas.drawPath(mPath, mPaint);
623 |
624 | if (DEBUG_DRAW_CONTROL_POINTS) {
625 | drawDebug(canvas, array);
626 | }
627 | }
628 |
629 | private void drawDebug(Canvas canvas, float[] array) {
630 | if (array == null) return;
631 |
632 | int i = 0;
633 | float startX = array[i++];
634 | float startY = array[i++];
635 |
636 | final int size = array.length;
637 | while (i < size - 5) {
638 | float x1 = array[i++];
639 | float y1 = array[i++];
640 | float x2 = array[i++];
641 | float y2 = array[i++];
642 | float endX = array[i++];
643 | float endY = array[i++];
644 |
645 | mDebugControlPointsPaint.setColor(Color.BLUE);
646 | canvas.drawLine(startX, startY, x1, y1, mDebugControlPointsPaint);
647 | mDebugControlPointsPaint.setColor(Color.RED);
648 | canvas.drawLine(x1, y1, x2, y2, mDebugControlPointsPaint);
649 | mDebugControlPointsPaint.setColor(Color.BLUE);
650 | canvas.drawLine(x2, y2, endX, endY, mDebugControlPointsPaint);
651 |
652 | startX = endX;
653 | startY = endY;
654 | }
655 | }
656 |
657 | @Override
658 | public void draw(Canvas canvas, float[] a, float[] b, float percent) {
659 | if (a == null || b == null || a.length != b.length) return;
660 |
661 | mPath.reset();
662 |
663 | // It's intentionally duplicated (as creating a method will be time consuming due to method overhead)
664 | int i = 0;
665 | float x = a[i] * (1 - percent) + b[i] * percent;
666 | i++;
667 | float y = a[i] * (1 - percent) + b[i] * percent;
668 | i++;
669 | mPath.moveTo(x, y);
670 |
671 | final int size = a.length;
672 | while (i < size - 5) {
673 | float x1 = a[i] * (1 - percent) + b[i] * percent;
674 | i++;
675 | float y1 = a[i] * (1 - percent) + b[i] * percent;
676 | i++;
677 | float x2 = a[i] * (1 - percent) + b[i] * percent;
678 | i++;
679 | float y2 = a[i] * (1 - percent) + b[i] * percent;
680 | i++;
681 | float x3 = a[i] * (1 - percent) + b[i] * percent;
682 | i++;
683 | float y3 = a[i] * (1 - percent) + b[i] * percent;
684 | i++;
685 | mPath.cubicTo(x1, y1, x2, y2, x3, y3);
686 | }
687 |
688 | canvas.drawPath(mPath, mPaint);
689 |
690 | if (DEBUG_DRAW_CONTROL_POINTS) {
691 | drawDebug(canvas, a, b, percent);
692 | }
693 | }
694 |
695 | private void drawDebug(Canvas canvas, float[] a, float b[], float percent) {
696 | if (a == null || b == null || a.length != b.length) return;
697 |
698 | int i = 0;
699 | float startX = a[i] * (1 - percent) + b[i] * percent;
700 | i++;
701 | float startY = a[i] * (1 - percent) + b[i] * percent;
702 | i++;
703 |
704 | final int size = a.length;
705 | while (i < size - 5) {
706 | float x1 = a[i] * (1 - percent) + b[i] * percent;
707 | i++;
708 | float y1 = a[i] * (1 - percent) + b[i] * percent;
709 | i++;
710 | float x2 = a[i] * (1 - percent) + b[i] * percent;
711 | i++;
712 | float y2 = a[i] * (1 - percent) + b[i] * percent;
713 | i++;
714 | float endX = a[i] * (1 - percent) + b[i] * percent;
715 | i++;
716 | float endY = a[i] * (1 - percent) + b[i] * percent;
717 | i++;
718 | mDebugControlPointsPaint.setColor(Color.BLUE);
719 | canvas.drawLine(startX, startY, x1, y1, mDebugControlPointsPaint);
720 | mDebugControlPointsPaint.setColor(Color.RED);
721 | canvas.drawLine(x1, y1, x2, y2, mDebugControlPointsPaint);
722 | mDebugControlPointsPaint.setColor(Color.BLUE);
723 | canvas.drawLine(x2, y2, endX, endY, mDebugControlPointsPaint);
724 |
725 | startX = endX;
726 | startY = endY;
727 | }
728 | }
729 |
730 | @Override
731 | public void save(float[] a, float[] b, float percent, float[] result) {
732 | if (a == null || b == null || result == null || a.length != b.length || a.length > result.length) return;
733 |
734 | final int size = a.length;
735 | for (int i = 0; i < size; i++) {
736 | result[i] = a[i] * (1 - percent) + b[i] * percent;
737 | }
738 | }
739 |
740 | @Override
741 | public float computeWidth(float[] a, float[] b, float percent) {
742 | if (a == null || b == null) return 0;
743 |
744 | return a[0] * (1 - percent) + b[0] * percent;
745 | }
746 |
747 | @Override
748 | public void saveWidth(float[] a, float[] b, float percent, float[] result) {
749 | if (a == null || b == null || result == null) return;
750 |
751 | result[0] = a[0] * (1 - percent) + b[0] * percent;
752 | }
753 |
754 | @Override
755 | public void drawColon(Canvas canvas) {
756 | float halfHeight = innerBoxHeight() / 2f;
757 | float halfWidth = getColonWidth() / 2f;
758 | float radius = mThickness / 2f;
759 |
760 | canvas.drawCircle(halfWidth, halfHeight, radius, mPaint);
761 | canvas.drawCircle(halfWidth, innerBoxHeight() - radius, radius, mPaint);
762 | }
763 |
764 | @Override
765 | public float getColonWidth() {
766 | return mColumnWidth;
767 | }
768 | }
769 |
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file is automatically generated by Android Studio.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 | #
7 | # Location of the SDK. This is only used by Gradle.
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | #Sat Feb 08 16:26:33 PST 2014
11 | sdk.dir=/Users/dleggieri/Developments/android-sdk
12 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ":library"
2 | include ":example"
--------------------------------------------------------------------------------