├── .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 | IMAGE ALT TEXT HERE 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 |