├── .gitignore ├── android-ui-toolkit ├── .gitignore ├── bintray.gradle ├── build.gradle ├── install.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── supernova │ │ └── uitoolkit │ │ └── ExampleInstrumentedTest.java │ └── main │ ├── AndroidManifest.xml │ └── java │ └── io │ └── supernova │ └── uitoolkit │ ├── animation │ └── ViewBackgroundProperties.java │ ├── drawable │ ├── GradientStop.java │ └── LinearGradientDrawable.java │ ├── recycler │ └── GridSpacingItemDecoration.java │ └── text │ ├── FontSpan.java │ ├── LetterSpacingSpan.java │ ├── LineHeightSpan.java │ └── RelativeLineHeightSpan.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── io │ │ └── supernova │ │ └── toolkit │ │ └── example │ │ ├── GridSpacingDecorationTestActivity.java │ │ ├── GridSpacingDecorationTestAdapter.java │ │ └── MainActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── font │ ├── font_lato_black.xml │ ├── font_lato_bold.xml │ ├── font_lato_light.xml │ ├── font_lato_regular.xml │ ├── font_sfnsdisplay.xml │ ├── lato_black.ttf │ ├── lato_bold.ttf │ ├── lato_light.ttf │ ├── lato_regular.ttf │ └── sfnsdisplay.ttf │ ├── layout │ ├── activity_grid_spacing_decoration_test.xml │ ├── activity_main.xml │ └── item_grid_spacing_decoration_test.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # IntelliJ 37 | *.iml 38 | .idea 39 | 40 | # Keystore files 41 | # Uncomment the following line if you do not want to check your keystore files in. 42 | #*.jks 43 | 44 | # External native build folder generated in Android Studio 2.2 and later 45 | .externalNativeBuild 46 | 47 | # Google Services (e.g. APIs or Firebase) 48 | google-services.json 49 | 50 | # Freeline 51 | freeline.py 52 | freeline/ 53 | freeline_project_description.json 54 | 55 | # fastlane 56 | fastlane/report.xml 57 | fastlane/Preview.html 58 | fastlane/screenshots 59 | fastlane/test_output 60 | fastlane/readme.md -------------------------------------------------------------------------------- /android-ui-toolkit/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android-ui-toolkit/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | 3 | version = libraryVersion 4 | 5 | /* 6 | * Comment the following part if you only want to distribute .aar files. 7 | * (For example, your source code is obfuscated by Proguard and is not shown to outsite developers) 8 | * Without source code .jar files, you only can publish on bintray respository but not jcenter. 9 | */ 10 | 11 | /*--------------------------------*/ 12 | if (project.hasProperty("android")) { 13 | task sourcesJar(type: Jar) { 14 | classifier = 'sources' 15 | from android.sourceSets.main.java.srcDirs 16 | } 17 | 18 | task javadoc(type: Javadoc) { 19 | failOnError false 20 | source = android.sourceSets.main.java.srcDirs 21 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 22 | } 23 | } else { // Java libraries 24 | task sourcesJar(type: Jar, dependsOn: classes) { 25 | classifier = 'sources' 26 | from sourceSets.main.allSource 27 | } 28 | } 29 | 30 | task javadocJar(type: Jar, dependsOn: javadoc) { 31 | classifier = 'javadoc' 32 | from javadoc.destinationDir 33 | } 34 | 35 | artifacts { 36 | archives javadocJar 37 | archives sourcesJar 38 | } 39 | /*--------------------------------*/ 40 | 41 | // Bintray 42 | Properties properties = new Properties() 43 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 44 | 45 | bintray { 46 | user = properties.getProperty("bintray.user") 47 | key = properties.getProperty("bintray.apikey") 48 | 49 | configurations = ['archives'] 50 | pkg { 51 | repo = bintrayRepo 52 | name = bintrayName 53 | desc = libraryDescription 54 | websiteUrl = siteUrl 55 | vcsUrl = gitUrl 56 | licenses = allLicenses 57 | publish = true 58 | publicDownloadNumbers = true 59 | version { 60 | desc = libraryDescription 61 | // gpg { 62 | // sign = true 63 | // passphrase = properties.getProperty("bintray.gpg.password") 64 | // } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /android-ui-toolkit/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | 6 | defaultConfig { 7 | minSdkVersion 16 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | 28 | implementation 'com.android.support:appcompat-v7:27.1.1' 29 | implementation 'com.android.support:recyclerview-v7:27.1.1' 30 | 31 | // testImplementation 'junit:junit:4.12' 32 | // androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | // androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | } 35 | 36 | ext { 37 | 38 | bintrayRepo = 'maven' 39 | bintrayName = 'android-ui-toolkit' 40 | 41 | publishedGroupId = 'io.supernova' 42 | libraryName = 'android-ui-toolkit' 43 | artifact = 'android-ui-toolkit' 44 | 45 | libraryDescription = 'Set of Android SDK extensions to make things where the SDK is not enough.' 46 | 47 | siteUrl = 'https://github.com/Supernova-Studio/android-ui-toolkit' 48 | gitUrl = 'https://github.com/Supernova-Studio/android-ui-toolkit.git' 49 | 50 | libraryVersion = '1.2.0' 51 | 52 | developerId = 'supernova-studio' 53 | developerName = 'Supernova Studio' 54 | developerEmail = 'support@supernova.io' 55 | 56 | licenseName = 'The Apache Software License, Version 2.0' 57 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 58 | allLicenses = ["Apache-2.0"] 59 | 60 | } 61 | 62 | apply from: 'install.gradle' 63 | apply from: 'bintray.gradle' -------------------------------------------------------------------------------- /android-ui-toolkit/install.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | 3 | group = publishedGroupId // Maven Group ID for the artifact 4 | 5 | install { 6 | repositories.mavenInstaller { 7 | // This generates POM.xml with proper parameters 8 | pom.project { 9 | packaging 'aar' 10 | groupId publishedGroupId 11 | artifactId artifact 12 | 13 | name libraryName 14 | description libraryDescription 15 | url siteUrl 16 | 17 | licenses { 18 | license { 19 | name licenseName 20 | url licenseUrl 21 | } 22 | } 23 | developers { 24 | developer { 25 | id developerId 26 | name developerName 27 | email developerEmail 28 | } 29 | } 30 | scm { 31 | connection gitUrl 32 | developerConnection gitUrl 33 | url siteUrl 34 | 35 | } 36 | } 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /android-ui-toolkit/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android-ui-toolkit/src/androidTest/java/io/supernova/uitoolkit/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("io.supernova.uitoolkit.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/java/io/supernova/uitoolkit/animation/ViewBackgroundProperties.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit.animation; 2 | 3 | import android.content.res.ColorStateList; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.Drawable; 6 | import android.graphics.drawable.GradientDrawable; 7 | import android.os.Build; 8 | import android.support.annotation.ColorInt; 9 | import android.util.Property; 10 | import android.view.View; 11 | 12 | import io.supernova.uitoolkit.drawable.LinearGradientDrawable; 13 | 14 | public interface ViewBackgroundProperties { 15 | 16 | /** 17 | * Allows animating corner radius on a View with an implementation of ValueAnimator or 18 | * ObjectAnimator. This will only make effect on a View target that has either {@link LinearGradientDrawable} 19 | * or {@link GradientDrawable} as its background. 20 | */ 21 | Property CORNER_RADIUS = new Property(Float.class, "cornerRadius") { 22 | 23 | @Override 24 | public Float get(View view) { 25 | Drawable drawable = view.getBackground(); 26 | 27 | if (drawable == null) { 28 | return 0f; 29 | } 30 | 31 | if (drawable instanceof LinearGradientDrawable) { 32 | return ((LinearGradientDrawable) drawable).getCornerRadius(); 33 | } else if (drawable instanceof GradientDrawable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 34 | return ((GradientDrawable) drawable).getCornerRadius(); 35 | } else { 36 | return 0f; 37 | } 38 | } 39 | 40 | @Override 41 | public void set(View view, Float value) { 42 | Drawable drawable = view.getBackground(); 43 | 44 | if (drawable == null) { 45 | return; 46 | } 47 | 48 | if (drawable instanceof GradientDrawable) { 49 | GradientDrawable gradientDrawable = (GradientDrawable) drawable; 50 | gradientDrawable.setCornerRadius(value); 51 | } else if (drawable instanceof LinearGradientDrawable) { 52 | LinearGradientDrawable linearGradientDrawable = (LinearGradientDrawable) drawable; 53 | linearGradientDrawable.setCornerRadius(value); 54 | } 55 | } 56 | }; 57 | 58 | 59 | /** 60 | * Allows animating stroke width on a View with an implementation of ValueAnimator or 61 | * ObjectAnimator. This will only make effect on a View target that has {@link LinearGradientDrawable} 62 | * as its background. 63 | */ 64 | Property STROKE_WIDTH = new Property(Float.class, "strokeWidth") { 65 | 66 | @Override 67 | public Float get(View view) { 68 | 69 | Drawable drawable = view.getBackground(); 70 | 71 | if (!(drawable instanceof LinearGradientDrawable)) { 72 | return 0f; 73 | } 74 | 75 | return ((LinearGradientDrawable) drawable).getStrokeWidth(); 76 | } 77 | 78 | @Override 79 | public void set(View view, Float value) { 80 | 81 | Drawable drawable = view.getBackground(); 82 | 83 | if (!(drawable instanceof LinearGradientDrawable)) { 84 | return; 85 | } 86 | 87 | ((LinearGradientDrawable) drawable).setStrokeWidth(value); 88 | } 89 | }; 90 | 91 | /** 92 | * Allows animating stroke color on a View with an implementation of ValueAnimator or 93 | * ObjectAnimator. This will only make effect on a View target that has {@link LinearGradientDrawable} 94 | * as its background. 95 | * 96 | * Animating colors requires setting {@link android.animation.ArgbEvaluator} to your instance of 97 | * {@link android.animation.ValueAnimator}. 98 | */ 99 | Property STROKE_COLOR = new Property(Integer.class, "strokeColor") { 100 | 101 | @Override 102 | @ColorInt 103 | public Integer get(View view) { 104 | 105 | Drawable drawable = view.getBackground(); 106 | 107 | if (!(drawable instanceof LinearGradientDrawable)) { 108 | return Color.TRANSPARENT; 109 | } 110 | 111 | return ((LinearGradientDrawable) drawable).getStrokeColor(); 112 | } 113 | 114 | @Override 115 | public void set(View view, @ColorInt Integer value) { 116 | 117 | Drawable drawable = view.getBackground(); 118 | 119 | if (!(drawable instanceof LinearGradientDrawable)) { 120 | return; 121 | } 122 | 123 | ((LinearGradientDrawable) drawable).setStrokeColor(value); 124 | } 125 | }; 126 | 127 | 128 | /** 129 | * Allows animating background on a View with an implementation of ValueAnimator or 130 | * ObjectAnimator. 131 | * 132 | * Animating colors requires setting {@link android.animation.ArgbEvaluator} to your instance of 133 | * {@link android.animation.ValueAnimator}. 134 | */ 135 | Property BACKGROUND_COLOR = new Property(Integer.class, "strokeColor") { 136 | 137 | @Override 138 | @ColorInt 139 | public Integer get(View view) { 140 | Drawable drawable = view.getBackground(); 141 | 142 | if (drawable instanceof GradientDrawable) { 143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 144 | ColorStateList colorStateList = ((GradientDrawable) drawable).getColor(); 145 | if (colorStateList != null) { 146 | return colorStateList.getDefaultColor(); 147 | } 148 | } 149 | } 150 | 151 | return Color.TRANSPARENT; 152 | } 153 | 154 | @Override 155 | public void set(View view, @ColorInt Integer value) { 156 | Drawable drawable = view.getBackground(); 157 | 158 | if (drawable instanceof GradientDrawable) { 159 | ((GradientDrawable) drawable).setColor(value); 160 | } else { 161 | view.setBackgroundColor(value); 162 | } 163 | } 164 | }; 165 | } 166 | -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/java/io/supernova/uitoolkit/drawable/GradientStop.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit.drawable; 2 | 3 | import android.support.annotation.ColorInt; 4 | import android.support.annotation.FloatRange; 5 | import android.support.annotation.NonNull; 6 | 7 | 8 | public class GradientStop implements Comparable { 9 | 10 | public final float position; 11 | @ColorInt 12 | public final int color; 13 | 14 | 15 | public GradientStop(@FloatRange(from = 0, to = 1) float position, int color) { 16 | 17 | if (position < 0 || position > 1) { 18 | throw new IllegalArgumentException("GradientStop position must be in inclusive range from 0 to 1"); 19 | } 20 | 21 | this.position = position; 22 | this.color = color; 23 | } 24 | 25 | 26 | @Override 27 | public int compareTo(@NonNull GradientStop o) { 28 | return Float.compare(this.position, o.position); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/java/io/supernova/uitoolkit/drawable/LinearGradientDrawable.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit.drawable; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.LinearGradient; 8 | import android.graphics.Outline; 9 | import android.graphics.Paint; 10 | import android.graphics.PixelFormat; 11 | import android.graphics.PointF; 12 | import android.graphics.Rect; 13 | import android.graphics.RectF; 14 | import android.graphics.Shader; 15 | import android.graphics.drawable.Drawable; 16 | import android.os.Build; 17 | import android.support.annotation.ColorInt; 18 | import android.support.annotation.ColorRes; 19 | import android.support.annotation.DimenRes; 20 | import android.support.annotation.Dimension; 21 | import android.support.annotation.NonNull; 22 | import android.support.annotation.Nullable; 23 | import android.support.annotation.RequiresApi; 24 | import android.support.v4.content.ContextCompat; 25 | import android.util.TypedValue; 26 | 27 | import java.util.ArrayList; 28 | import java.util.Arrays; 29 | import java.util.Collection; 30 | import java.util.List; 31 | 32 | 33 | public class LinearGradientDrawable extends Drawable { 34 | 35 | @NonNull 36 | private final PointF start; 37 | @NonNull 38 | private final PointF end; 39 | 40 | private final List stops = new ArrayList<>(); 41 | private float cornerRadius = 0f; 42 | 43 | private final RectF reusableRect = new RectF(0, 0, 0, 0); 44 | 45 | private final Paint gradientPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 46 | private final Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 47 | 48 | 49 | public LinearGradientDrawable(@NonNull PointF start, @NonNull PointF end, Collection stops) { 50 | this.start = start; 51 | this.end = end; 52 | 53 | this.stops.addAll(stops); 54 | 55 | this.init(); 56 | } 57 | 58 | 59 | public LinearGradientDrawable(@NonNull PointF start, @NonNull PointF end, GradientStop... stops) { 60 | this.start = start; 61 | this.end = end; 62 | 63 | this.stops.addAll(Arrays.asList(stops)); 64 | 65 | this.init(); 66 | } 67 | 68 | 69 | @Override 70 | protected void onBoundsChange(Rect bounds) { 71 | super.onBoundsChange(bounds); 72 | this.invalidateShader(); 73 | } 74 | 75 | 76 | @Override 77 | public void draw(@NonNull Canvas canvas) { 78 | 79 | RectF floatBounds = this.getFloatReusableBounds(); 80 | canvas.drawRoundRect(floatBounds, this.cornerRadius, this.cornerRadius, this.gradientPaint); 81 | 82 | if (this.hasStroke()) { 83 | canvas.drawRoundRect(floatBounds, this.cornerRadius, this.cornerRadius, this.strokePaint); 84 | } 85 | } 86 | 87 | 88 | @Override 89 | public void setAlpha(int alpha) { 90 | this.gradientPaint.setAlpha(alpha); 91 | this.strokePaint.setAlpha(alpha); 92 | } 93 | 94 | 95 | @Override 96 | public void setColorFilter(@Nullable ColorFilter colorFilter) { 97 | this.gradientPaint.setColorFilter(colorFilter); 98 | this.strokePaint.setColorFilter(colorFilter); 99 | } 100 | 101 | 102 | @Override 103 | public int getOpacity() { 104 | return PixelFormat.OPAQUE; 105 | } 106 | 107 | 108 | @Override 109 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 110 | public void getOutline(@NonNull Outline outline) { 111 | super.getOutline(outline); 112 | 113 | outline.setRoundRect(this.getBounds(), this.cornerRadius); 114 | 115 | // Outline accepts range 0f - 1f, while drawable alpha is 0 - 255 116 | outline.setAlpha(this.getAlpha() / 255f); 117 | } 118 | 119 | 120 | public void addStop(GradientStop stop) { 121 | this.stops.add(stop); 122 | this.invalidateShader(); 123 | } 124 | 125 | 126 | public float getCornerRadius() { 127 | return this.cornerRadius; 128 | } 129 | 130 | 131 | public void setCornerRadius(float cornerRadius) { 132 | this.cornerRadius = cornerRadius; 133 | invalidateSelf(); 134 | } 135 | 136 | 137 | public float getStrokeWidth() { 138 | return this.strokePaint.getStrokeWidth(); 139 | } 140 | 141 | 142 | public void setStrokeWidth(float width) { 143 | this.strokePaint.setStrokeWidth(width); 144 | invalidateSelf(); 145 | } 146 | 147 | 148 | @ColorInt 149 | public int getStrokeColor() { 150 | return this.strokePaint.getColor(); 151 | } 152 | 153 | 154 | public void setStrokeColor(@ColorInt int color) { 155 | this.strokePaint.setColor(color); 156 | invalidateSelf(); 157 | } 158 | 159 | 160 | private void init() { 161 | this.strokePaint.setStyle(Paint.Style.STROKE); 162 | } 163 | 164 | 165 | private void invalidateShader() { 166 | if(this.stops.isEmpty()) { 167 | this.setDefaultColorShader(); 168 | } else if(this.stops.size() == 1) { 169 | this.setSolidColorPaint(this.stops.get(0).color); 170 | } else { 171 | this.setLinearGradientShaderPaint(); 172 | } 173 | 174 | invalidateSelf(); 175 | } 176 | 177 | 178 | private void setDefaultColorShader() { 179 | this.setSolidColorPaint(Color.TRANSPARENT); 180 | } 181 | 182 | 183 | private void setSolidColorPaint(@ColorInt int color) { 184 | this.gradientPaint.setShader(null); 185 | this.gradientPaint.setColor(color); 186 | } 187 | 188 | 189 | private void setLinearGradientShaderPaint() { 190 | 191 | Rect rect = getBounds(); 192 | 193 | float width = rect.width(); 194 | float height = rect.height(); 195 | 196 | float gradientStartX = width * start.x; 197 | float gradientStartY = height * start.y; 198 | 199 | float gradientEndX = width * end.x; 200 | float gradientEndY = height * end.y; 201 | 202 | float[] fractions = new float[this.stops.size()]; 203 | int[] colors = new int[this.stops.size()]; 204 | 205 | for(int i = 0; i < stops.size(); i++) { 206 | GradientStop stop = stops.get(i); 207 | fractions[i] = stop.position; 208 | colors[i] = stop.color; 209 | } 210 | 211 | LinearGradient gradient = new LinearGradient(gradientStartX, gradientStartY, 212 | gradientEndX, gradientEndY, 213 | colors, fractions, Shader.TileMode.CLAMP); 214 | 215 | gradientPaint.setShader(gradient); 216 | } 217 | 218 | 219 | private RectF getFloatReusableBounds() { 220 | Rect bounds = this.getBounds(); 221 | 222 | this.reusableRect.bottom = bounds.bottom; 223 | this.reusableRect.left = bounds.left; 224 | this.reusableRect.right = bounds.right; 225 | this.reusableRect.top = bounds.top; 226 | 227 | return this.reusableRect; 228 | } 229 | 230 | 231 | private boolean hasStroke() { 232 | return this.strokePaint.getStrokeWidth() != 0; 233 | } 234 | 235 | 236 | public static class Builder { 237 | 238 | @NonNull 239 | private Context context; 240 | 241 | private final PointF start; 242 | private final PointF end; 243 | 244 | private final List stops = new ArrayList<>(); 245 | 246 | private float cornerRadius = 0f; 247 | private float strokeWidth = 0f; 248 | @ColorInt 249 | private int strokeColor = Color.TRANSPARENT; 250 | 251 | 252 | public Builder(@NonNull Context context, @NonNull PointF start, @NonNull PointF end) { 253 | this.context = context; 254 | this.start = start; 255 | this.end = end; 256 | } 257 | 258 | 259 | /* STOPS */ 260 | 261 | public Builder addStop(GradientStop stop) { 262 | this.stops.add(stop); 263 | return this; 264 | } 265 | 266 | public Builder addStop(float position, @ColorInt int color) { 267 | return this.addStop(new GradientStop(position, color)); 268 | } 269 | 270 | public Builder addStopWithResource(float position, @ColorRes int color) { 271 | return this.addStop(new GradientStop(position, ContextCompat.getColor(context, color))); 272 | } 273 | 274 | 275 | /* RAW VALUES */ 276 | 277 | public Builder cornerRadiusPx(@Dimension float cornerRadius) { 278 | this.cornerRadius = cornerRadius; 279 | return this; 280 | } 281 | 282 | public Builder strokeWidthPx(@Dimension float width) { 283 | this.strokeWidth = width; 284 | return this; 285 | } 286 | 287 | public Builder cornerRadiusDp(@Dimension float radius) { 288 | this.cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, radius, this.context.getResources().getDisplayMetrics()); 289 | return this; 290 | } 291 | 292 | public Builder strokeWidthDp(@Dimension float width) { 293 | this.strokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, this.context.getResources().getDisplayMetrics()); 294 | return this; 295 | } 296 | 297 | public Builder strokeColor(@ColorInt int color) { 298 | this.strokeColor = color; 299 | return this; 300 | } 301 | 302 | 303 | /* RESOURCE VALUES */ 304 | 305 | public Builder cornerRadius(@DimenRes int cornerRadiusRes) { 306 | this.cornerRadius = this.context.getResources().getDimension(cornerRadiusRes); 307 | return this; 308 | } 309 | 310 | public Builder strokeWidth(@DimenRes int strokeWidthRes) { 311 | this.strokeWidth = this.context.getResources().getDimension(strokeWidthRes); 312 | return this; 313 | } 314 | 315 | public Builder strokeColorRes(@ColorRes int strokeColorRes) { 316 | this.strokeColor = ContextCompat.getColor(this.context, strokeColorRes); 317 | return this; 318 | } 319 | 320 | 321 | /* BUILD */ 322 | 323 | public LinearGradientDrawable build() { 324 | LinearGradientDrawable gradient = new LinearGradientDrawable(this.start, this.end, this.stops); 325 | 326 | gradient.setStrokeColor(this.strokeColor); 327 | gradient.setStrokeWidth(this.strokeWidth); 328 | gradient.setCornerRadius(this.cornerRadius); 329 | 330 | return gradient; 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/java/io/supernova/uitoolkit/recycler/GridSpacingItemDecoration.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit.recycler; 2 | 3 | 4 | import android.graphics.Rect; 5 | import android.support.v7.widget.GridLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.Pair; 8 | import android.view.View; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | 15 | /** 16 | * Implementation of RecyclerView.ItemDecoration that adds consistent spacings between items in RecyclerView 17 | * with GridLayoutManager preserving equal size of all children across non-scrolling axis. 18 | */ 19 | public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { 20 | 21 | private final int spacing; 22 | 23 | private List> calculatedSpacings; 24 | private int parentSizeBase; 25 | private int spanCountBase; 26 | 27 | 28 | /** 29 | * Creates spacing decoration without applying screen density to the spacing argument. 30 | * 31 | * @param spacing - space between items in RecyclerView 32 | */ 33 | public GridSpacingItemDecoration(float spacing) { 34 | this.spacing = Math.round(spacing); 35 | } 36 | 37 | 38 | @Override 39 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 40 | 41 | if (!(parent.getLayoutManager() instanceof GridLayoutManager)) { 42 | throw new UnsupportedOperationException("GridSpacingItemDecoration can only be used in combination with GridLayoutManager."); 43 | } 44 | 45 | GridLayoutManager manager = ((GridLayoutManager) parent.getLayoutManager()); 46 | 47 | switch(manager.getOrientation()) { 48 | case GridLayoutManager.VERTICAL: 49 | this.getItemOffsetVertical(outRect, parent, view); 50 | break; 51 | 52 | case GridLayoutManager.HORIZONTAL: 53 | this.getItemOffsetHorizontal(outRect, parent, view); 54 | break; 55 | } 56 | } 57 | 58 | private void getItemOffsetVertical(Rect outRect, RecyclerView parent, View child) { 59 | 60 | int parentSize = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(); 61 | GridLayoutManager manager = ((GridLayoutManager) parent.getLayoutManager()); 62 | GridLayoutManager.LayoutParams layoutParams = ((GridLayoutManager.LayoutParams) child.getLayoutParams()); 63 | int childAdapterPosition = parent.getChildAdapterPosition(child); 64 | 65 | List> calculatedSpacings = this.getCalculatedSpacings(parentSize, manager.getSpanCount()); 66 | Pair spacing = calculatedSpacings.get(layoutParams.getSpanIndex()); 67 | 68 | // Assign left spacing 69 | outRect.left = spacing.first; 70 | 71 | // Find the end span of this item 72 | if (layoutParams.getSpanSize() > 1) { 73 | // This item is larger than 1 span, find the spacing for the last span this view occupies 74 | Pair trailingSpacing = calculatedSpacings.get(layoutParams.getSpanIndex() + layoutParams.getSpanSize() - 1); 75 | outRect.right = trailingSpacing.second; 76 | } else { 77 | // This item has span size 1, assign right spacing from the same container 78 | outRect.right = spacing.second; 79 | } 80 | 81 | if (childAdapterPosition >= manager.getSpanCount()) { 82 | // Add vertical spacing in addition to horizontal spacings 83 | outRect.top = this.spacing; 84 | } 85 | } 86 | 87 | private void getItemOffsetHorizontal(Rect outRect, RecyclerView parent, View child) { 88 | 89 | int parentSize = parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(); 90 | GridLayoutManager manager = ((GridLayoutManager) parent.getLayoutManager()); 91 | GridLayoutManager.LayoutParams layoutParams = ((GridLayoutManager.LayoutParams) child.getLayoutParams()); 92 | int childAdapterPosition = parent.getChildAdapterPosition(child); 93 | 94 | List> calculatedSpacings = this.getCalculatedSpacings(parentSize, manager.getSpanCount()); 95 | Pair spacing = calculatedSpacings.get(layoutParams.getSpanIndex()); 96 | 97 | // Assign left spacing 98 | outRect.top = spacing.first; 99 | 100 | // Find the end span of this item 101 | if (layoutParams.getSpanSize() > 1) { 102 | // This item is larger than 1 span, find the spacing for the last span this view occupies 103 | Pair trailingSpacing = calculatedSpacings.get(layoutParams.getSpanIndex() + layoutParams.getSpanSize() - 1); 104 | outRect.bottom = trailingSpacing.second; 105 | } else { 106 | // This item has span size 1, assign right spacing from the same container 107 | outRect.bottom = spacing.second; 108 | } 109 | 110 | if (childAdapterPosition >= manager.getSpanCount()) { 111 | // Add horizontal spacing in addition to vertical spacings 112 | outRect.left = this.spacing; 113 | } 114 | } 115 | 116 | private List> getCalculatedSpacings(int parentSize, int spanCount) { 117 | 118 | if (this.parentSizeBase == parentSize && this.spanCountBase == spanCount) { 119 | // Cached value is valid here 120 | return this.calculatedSpacings; 121 | } else { 122 | // Recalculation needs to happen 123 | List> calculatedSpacings = this.calculateSpacings(parentSize, spanCount); 124 | this.parentSizeBase = parentSize; 125 | this.spanCountBase = spanCount; 126 | this.calculatedSpacings = calculatedSpacings; 127 | return calculatedSpacings; 128 | } 129 | } 130 | 131 | private List> calculateSpacings(float parentSize, int spanCount) { 132 | 133 | // We need at least 2 children next to each other to place spacing between them 134 | if (spanCount < 2) { 135 | return Collections.singletonList(Pair.create(0, 0)); 136 | } 137 | 138 | List> newSpacings = new ArrayList<>(); 139 | 140 | float normalSpanSize = parentSize / spanCount; 141 | float spacedSpanSize = (parentSize - (spanCount - 1) * this.spacing) / spanCount; 142 | 143 | int firstItemSpacing = Math.round(normalSpanSize - spacedSpanSize); 144 | 145 | // First item in a row will have right spacing only 146 | newSpacings.add(Pair.create(0, firstItemSpacing)); 147 | 148 | // Remember how much spacing left to consume 149 | int remainingSpacingFromLastPass = this.spacing - firstItemSpacing; 150 | 151 | for (int i = 1; i < spanCount; i++) { 152 | 153 | // Calculate spacing for this span position 154 | int left = remainingSpacingFromLastPass; 155 | int right = Math.round(normalSpanSize - left - spacedSpanSize); 156 | newSpacings.add(Pair.create(left, right)); 157 | 158 | // Write remaining spacing 159 | remainingSpacingFromLastPass = this.spacing - right; 160 | } 161 | 162 | return newSpacings; 163 | } 164 | } -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/java/io/supernova/uitoolkit/text/FontSpan.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit.text; 2 | 3 | import android.graphics.Typeface; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.text.TextPaint; 7 | import android.text.style.MetricAffectingSpan; 8 | 9 | public class FontSpan extends MetricAffectingSpan { 10 | 11 | @Nullable 12 | private final Typeface typeface; 13 | 14 | public FontSpan(@Nullable final Typeface typeface) { 15 | this.typeface = typeface; 16 | } 17 | 18 | @Override 19 | public void updateMeasureState(@NonNull TextPaint textPaint) { 20 | update(textPaint); 21 | } 22 | 23 | @Override 24 | public void updateDrawState(TextPaint textPaint) { 25 | update(textPaint); 26 | } 27 | 28 | private void update(TextPaint textPaint) { 29 | Typeface old = textPaint.getTypeface(); 30 | int oldStyle = old != null ? old.getStyle() : Typeface.NORMAL; 31 | Typeface font = Typeface.create(this.typeface, oldStyle); 32 | textPaint.setTypeface(font); 33 | } 34 | } -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/java/io/supernova/uitoolkit/text/LetterSpacingSpan.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit.text; 2 | 3 | import android.os.Build; 4 | import android.support.annotation.RequiresApi; 5 | import android.text.TextPaint; 6 | import android.text.style.MetricAffectingSpan; 7 | 8 | 9 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 10 | public class LetterSpacingSpan extends MetricAffectingSpan { 11 | 12 | private float letterSpacing; 13 | 14 | 15 | public LetterSpacingSpan(float letterSpacing) { 16 | this.letterSpacing = letterSpacing; 17 | } 18 | 19 | 20 | 21 | @Override 22 | public void updateMeasureState(TextPaint p) { 23 | p.setLetterSpacing(this.letterSpacing); 24 | } 25 | 26 | 27 | @Override 28 | public void updateDrawState(TextPaint tp) { 29 | tp.setLetterSpacing(this.letterSpacing); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/java/io/supernova/uitoolkit/text/LineHeightSpan.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit.text; 2 | 3 | import android.graphics.Paint; 4 | import android.support.annotation.Nullable; 5 | import android.text.TextPaint; 6 | 7 | 8 | public class LineHeightSpan implements android.text.style.LineHeightSpan.WithDensity { 9 | 10 | private final int height; 11 | 12 | 13 | public LineHeightSpan(int height) { 14 | this.height = height; 15 | } 16 | 17 | 18 | @Override 19 | public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) { 20 | // Should not get called, at least not by StaticLayout. 21 | this.chooseHeight(text, start, end, spanstartv, v, fm, null); 22 | } 23 | 24 | @Override 25 | public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm, @Nullable TextPaint paint) { 26 | int size = height; 27 | 28 | // Apply density if provided. 29 | if (paint != null) { 30 | size *= paint.density; 31 | } 32 | 33 | this.applyLineHeight(size, fm); 34 | } 35 | 36 | private void applyLineHeight(int newHeight, Paint.FontMetricsInt fm) { 37 | fm.top = fm.bottom - newHeight; 38 | fm.ascent = fm.descent - newHeight; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android-ui-toolkit/src/main/java/io/supernova/uitoolkit/text/RelativeLineHeightSpan.java: -------------------------------------------------------------------------------- 1 | package io.supernova.uitoolkit.text; 2 | 3 | import android.graphics.Paint; 4 | import android.support.annotation.Nullable; 5 | import android.text.TextPaint; 6 | import android.text.style.LineHeightSpan; 7 | 8 | 9 | public class RelativeLineHeightSpan implements LineHeightSpan.WithDensity { 10 | 11 | private final float lineHeightMultiplier; 12 | 13 | 14 | public RelativeLineHeightSpan(float lineHeightMultiplier) { 15 | this.lineHeightMultiplier = lineHeightMultiplier; 16 | } 17 | 18 | 19 | @Override 20 | public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) { 21 | // Should not get called, at least not by StaticLayout. 22 | this.chooseHeight(text, start, end, spanstartv, v, fm, null); 23 | } 24 | 25 | @Override 26 | public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm, @Nullable TextPaint paint) { 27 | 28 | // Calculate new line height. 29 | float currentHeight = this.getLineHeightFromMetrics(fm); 30 | float newHeight = currentHeight * lineHeightMultiplier; 31 | 32 | this.applyLineHeight(((int) newHeight), fm); 33 | } 34 | 35 | 36 | private int getLineHeightFromMetrics(Paint.FontMetricsInt fm) { 37 | return fm.descent - fm.ascent; 38 | } 39 | 40 | private void applyLineHeight(int newHeight, Paint.FontMetricsInt fm) { 41 | fm.top = fm.bottom - newHeight; 42 | fm.ascent = fm.descent - newHeight; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "io.supernova.supernovauitoolkit" 7 | minSdkVersion 21 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:27.1.1' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | 29 | implementation project(':android-ui-toolkit') 30 | implementation 'com.android.support:recyclerview-v7:27.1.1' 31 | } 32 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/io/supernova/toolkit/example/GridSpacingDecorationTestActivity.java: -------------------------------------------------------------------------------- 1 | package io.supernova.toolkit.example; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.support.v7.widget.GridLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | 8 | import io.supernova.supernovauitoolkit.R; 9 | import io.supernova.uitoolkit.recycler.GridSpacingItemDecoration; 10 | 11 | 12 | public class GridSpacingDecorationTestActivity extends AppCompatActivity { 13 | 14 | private RecyclerView recyclerView; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_grid_spacing_decoration_test); 20 | 21 | this.recyclerView = findViewById(R.id.recycler_view); 22 | this.initRecyclerView(); 23 | } 24 | 25 | private void initRecyclerView() { 26 | GridLayoutManager manager = new GridLayoutManager(this, 5, GridLayoutManager.HORIZONTAL, false); 27 | manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 28 | @Override 29 | public int getSpanSize(int position) { 30 | if (position == 8 || position == 14) { 31 | return 2; 32 | } else { 33 | return 1; 34 | } 35 | } 36 | }); 37 | this.recyclerView.setLayoutManager(manager); 38 | this.recyclerView.setAdapter(new GridSpacingDecorationTestAdapter()); 39 | this.recyclerView.addItemDecoration(new GridSpacingItemDecoration((int) this.getResources().getDimension(R.dimen.test_recycler_view_spacing))); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/io/supernova/toolkit/example/GridSpacingDecorationTestAdapter.java: -------------------------------------------------------------------------------- 1 | package io.supernova.toolkit.example; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.ViewGroup; 7 | 8 | import io.supernova.supernovauitoolkit.R; 9 | 10 | 11 | public class GridSpacingDecorationTestAdapter extends RecyclerView.Adapter { 12 | 13 | @NonNull 14 | @Override 15 | public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 16 | return new TestViewHolder(parent); 17 | } 18 | 19 | 20 | @Override 21 | public void onBindViewHolder(@NonNull TestViewHolder holder, int position) { 22 | 23 | } 24 | 25 | 26 | @Override 27 | public int getItemCount() { 28 | return 30; 29 | } 30 | 31 | 32 | static class TestViewHolder extends RecyclerView.ViewHolder { 33 | 34 | TestViewHolder(ViewGroup parent) { 35 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid_spacing_decoration_test, parent, false)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/io/supernova/toolkit/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package io.supernova.toolkit.example; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.PointF; 5 | import android.os.Bundle; 6 | import android.support.v4.content.res.ResourcesCompat; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.text.SpannableString; 9 | import android.text.Spanned; 10 | import android.view.View; 11 | import android.widget.TextView; 12 | 13 | import io.supernova.supernovauitoolkit.R; 14 | import io.supernova.uitoolkit.drawable.LinearGradientDrawable; 15 | import io.supernova.uitoolkit.text.FontSpan; 16 | import io.supernova.uitoolkit.text.LetterSpacingSpan; 17 | import io.supernova.uitoolkit.text.LineHeightSpan; 18 | import io.supernova.uitoolkit.text.RelativeLineHeightSpan; 19 | 20 | 21 | public class MainActivity extends AppCompatActivity { 22 | 23 | private TextView exampleTextView; 24 | private TextView exampleTextView2; 25 | 26 | private View gradientView; 27 | 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_main); 33 | 34 | this.init(); 35 | } 36 | 37 | 38 | private void init() { 39 | this.exampleTextView = findViewById(R.id.exampleTextView); 40 | this.exampleTextView2 = findViewById(R.id.exampleTextView2); 41 | this.gradientView = findViewById(R.id.gradientView); 42 | 43 | this.setupLineSpacingExample(); 44 | this.setupLetterSpacingExample(); 45 | this.setupGradient(); 46 | } 47 | 48 | 49 | private void setupLineSpacingExample() { 50 | 51 | SpannableString spannableString = new SpannableString("Example String\nShowcasing line spacing span\nIt's awesome, trust me"); 52 | 53 | // Setup line height span 54 | LineHeightSpan lineHeightSpan = new LineHeightSpan(40); 55 | spannableString.setSpan(lineHeightSpan, 15, 42, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 56 | 57 | // Setup relative line height span 58 | RelativeLineHeightSpan relativeLineHeightSpan = new RelativeLineHeightSpan(0.7f); 59 | spannableString.setSpan(relativeLineHeightSpan, 44, spannableString.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 60 | 61 | // Pass spannable text to the view 62 | this.exampleTextView.setText(spannableString); 63 | } 64 | 65 | 66 | private void setupLetterSpacingExample() { 67 | 68 | SpannableString spannableString = new SpannableString("Example String\nShowcasing letter spacing span\nIt's awesome, trust me"); 69 | 70 | // Setup stretch line height span 71 | LetterSpacingSpan stretchingSpan = new LetterSpacingSpan(0.2f); 72 | spannableString.setSpan(stretchingSpan, 0, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 73 | 74 | // Setup shrink letter spacing span 75 | LetterSpacingSpan shrinkingSpan = new LetterSpacingSpan(-0.1f); 76 | spannableString.setSpan(shrinkingSpan, 30, spannableString.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 77 | 78 | FontSpan span = new FontSpan(ResourcesCompat.getFont(this, R.font.font_lato_light)); 79 | spannableString.setSpan(span, 5, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 80 | 81 | // Pass spannable text to the view 82 | this.exampleTextView2.setText(spannableString); 83 | } 84 | 85 | 86 | private void setupGradient() { 87 | 88 | // Create gradient and pass it as the background to the view 89 | // 90 | // LinearGradientDrawable.Builder requires context to convert resource references and DP dimensions to pixels and 91 | // start and end points that define the gradient orientation. 92 | this.gradientView.setBackground(new LinearGradientDrawable.Builder(this, new PointF(0, 0), new PointF(1, 1)) 93 | // Stops 94 | .addStop(0, Color.BLUE) 95 | .addStop(0.3f, Color.BLACK) 96 | .addStop(0.6f, Color.WHITE) 97 | .addStopWithResource(1, R.color.colorPrimaryDark) 98 | 99 | // Corner radius resource 100 | .cornerRadius(R.dimen.test_gradient_corner_radius) 101 | 102 | // Stroke width converted from DPs 103 | .strokeWidthDp(2) 104 | 105 | // Stroke color resource 106 | .strokeColorRes(R.color.colorPrimary) 107 | 108 | .build()); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/font/font_lato_black.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/font_lato_bold.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/font_lato_light.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/font_lato_regular.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/font_sfnsdisplay.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/lato_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Supernova-Studio/android-ui-toolkit/13ec53160efffdd18d76ee639365e3eb5bbabf17/app/src/main/res/font/lato_black.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/lato_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Supernova-Studio/android-ui-toolkit/13ec53160efffdd18d76ee639365e3eb5bbabf17/app/src/main/res/font/lato_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/lato_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Supernova-Studio/android-ui-toolkit/13ec53160efffdd18d76ee639365e3eb5bbabf17/app/src/main/res/font/lato_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/lato_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Supernova-Studio/android-ui-toolkit/13ec53160efffdd18d76ee639365e3eb5bbabf17/app/src/main/res/font/lato_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/sfnsdisplay.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Supernova-Studio/android-ui-toolkit/13ec53160efffdd18d76ee639365e3eb5bbabf17/app/src/main/res/font/sfnsdisplay.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_grid_spacing_decoration_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 26 | 27 | 28 | 43 | 44 | 45 |