├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── libraries │ ├── Gradle__android_arch_core_common_1_1_0_jar.xml │ ├── Gradle__android_arch_core_runtime_1_1_0.xml │ ├── Gradle__android_arch_lifecycle_common_1_1_0_jar.xml │ ├── Gradle__android_arch_lifecycle_livedata_core_1_1_0.xml │ ├── Gradle__android_arch_lifecycle_runtime_1_1_0.xml │ ├── Gradle__android_arch_lifecycle_viewmodel_1_1_0.xml │ ├── Gradle__com_android_support_animated_vector_drawable_27_1_1.xml │ ├── Gradle__com_android_support_appcompat_v7_27_1_1.xml │ ├── Gradle__com_android_support_support_annotations_27_1_1_jar.xml │ ├── Gradle__com_android_support_support_compat_27_1_1.xml │ ├── Gradle__com_android_support_support_core_ui_27_1_1.xml │ ├── Gradle__com_android_support_support_core_utils_27_1_1.xml │ ├── Gradle__com_android_support_support_fragment_27_1_1.xml │ ├── Gradle__com_android_support_support_vector_drawable_27_1_1.xml │ ├── Gradle__com_android_support_test_espresso_espresso_core_3_0_2.xml │ ├── Gradle__com_android_support_test_espresso_espresso_idling_resource_3_0_2.xml │ ├── Gradle__com_android_support_test_monitor_1_0_2.xml │ ├── Gradle__com_android_support_test_runner_1_0_2.xml │ ├── Gradle__com_fpliu_Android_CustomDimen_1_0_0.xml │ ├── Gradle__com_google_code_findbugs_jsr305_2_0_1_jar.xml │ ├── Gradle__com_google_code_gson_gson_2_8_0_jar.xml │ ├── Gradle__com_jakewharton_rxbinding2_rxbinding_2_0_0.xml │ ├── Gradle__com_squareup_javawriter_2_1_1_jar.xml │ ├── Gradle__io_reactivex_rxjava2_rxandroid_2_0_0.xml │ ├── Gradle__io_reactivex_rxjava2_rxjava_2_0_2_jar.xml │ ├── Gradle__javax_inject_javax_inject_1_jar.xml │ ├── Gradle__junit_junit_4_12_jar.xml │ ├── Gradle__net_sf_kxml_kxml2_2_3_0_jar.xml │ ├── Gradle__org_hamcrest_hamcrest_core_1_3_jar.xml │ ├── Gradle__org_hamcrest_hamcrest_integration_1_3_jar.xml │ ├── Gradle__org_hamcrest_hamcrest_library_1_3_jar.xml │ ├── Gradle__org_jetbrains_annotations_13_0_jar.xml │ ├── Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_2_41_jar.xml │ └── Gradle__org_reactivestreams_reactive_streams_1_0_0_jar.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── justdoit │ │ └── graffiti │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── justdoit │ │ │ └── graffiti │ │ │ ├── GraffitiDisplayView.kt │ │ │ ├── GraffitiDrawView.kt │ │ │ ├── GraffitiPosition.kt │ │ │ ├── GraffitiUtils.kt │ │ │ ├── GraffitiView.kt │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── send_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── view_graffiti_gift.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 │ │ ├── graffit_icon.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── icon_1.png │ │ └── icon_2.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── justdoit │ └── graffiti │ └── ExampleUnitTest.java ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew.bat ├── graffiti.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | # built application files 9 | *.apk 10 | *.ap_ 11 | app/parsed-config.txt 12 | app/src/main/java/com/hyzb/dao 13 | 14 | # files for the dex VM 15 | *.dex 16 | 17 | # Java class files 18 | *.class 19 | 20 | # Generated files 21 | bin/ 22 | gen/ 23 | 24 | # Eclipse 25 | proguard/ 26 | .classpath 27 | .project 28 | 29 | # Log files 30 | *.log 31 | 32 | # Temporary files 33 | *~ 34 | *.swp 35 | 36 | # Android studio auto generate 37 | .idea/ 38 | *.iws 39 | *.ipr 40 | 41 | # built native files 42 | #*.o 43 | #*.so 44 | 45 | # generated files 46 | product/ 47 | # Mac OS X clutter 48 | *.DS_Store 49 | 50 | app/libs/ 51 | build/kotlin/ 52 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__android_arch_core_common_1_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__android_arch_core_runtime_1_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__android_arch_lifecycle_common_1_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__android_arch_lifecycle_livedata_core_1_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__android_arch_lifecycle_runtime_1_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__android_arch_lifecycle_viewmodel_1_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_animated_vector_drawable_27_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_appcompat_v7_27_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_support_annotations_27_1_1_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_support_compat_27_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_support_core_ui_27_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_support_core_utils_27_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_support_fragment_27_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_support_vector_drawable_27_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_test_espresso_espresso_core_3_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_test_espresso_espresso_idling_resource_3_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_test_monitor_1_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_test_runner_1_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_fpliu_Android_CustomDimen_1_0_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_google_code_gson_gson_2_8_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_jakewharton_rxbinding2_rxbinding_2_0_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_squareup_javawriter_2_1_1_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__io_reactivex_rxjava2_rxandroid_2_0_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__io_reactivex_rxjava2_rxjava_2_0_2_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__javax_inject_javax_inject_1_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__junit_junit_4_12_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__net_sf_kxml_kxml2_2_3_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_jetbrains_annotations_13_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_2_41_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_reactivestreams_reactive_streams_1_0_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graffiti 2 | Android仿花椒直播涂鸦礼物,用图片当画笔涂鸦,kotlin实现 3 | ## 截图 4 | ![1](./graffiti.gif) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # built native files 12 | #*.o 13 | #*.so 14 | 15 | # generated files 16 | bin/ 17 | gen/ 18 | 19 | # Ignore gradle files 20 | .gradle/ 21 | build/ 22 | product/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Eclipse 28 | proguard/ 29 | .classpath 30 | .project 31 | .metadata/ 32 | 33 | # Log files 34 | *.log 35 | 36 | # Temporary files 37 | *~ 38 | *.swp 39 | 40 | # Mac OS X clutter 41 | *.DS_Store 42 | 43 | # Android studio auto generate 44 | .idea/ 45 | *.iml 46 | *.iws 47 | *.ipr 48 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | id("kotlin-android-extensions") 5 | id("kotlin-kapt") 6 | } 7 | 8 | android { 9 | compileSdkVersion(27) 10 | buildToolsVersion("27.0.3") 11 | defaultConfig { 12 | applicationId = "com.justdoit.graffiti" 13 | minSdkVersion(18) 14 | targetSdkVersion(22) 15 | versionCode = 1 16 | versionName = "1.0" 17 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | getByName("release") { 22 | isMinifyEnabled = false 23 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 30 | implementation("com.android.support:appcompat-v7:27.1.1") 31 | implementation("org.jetbrains.kotlin:kotlin-stdlib:1.2.41") 32 | implementation("com.jakewharton.rxbinding2:rxbinding:2.0.0") 33 | implementation("com.fpliu:Android-CustomDimen:1.0.0") 34 | implementation("com.google.code.gson:gson:2.8.0") 35 | 36 | testImplementation("junit:junit:4.12") 37 | androidTestImplementation("com.android.support.test:runner:1.0.2") 38 | androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.2") 39 | 40 | } 41 | -------------------------------------------------------------------------------- /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/androidTest/java/com/justdoit/graffiti/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.justdoit.graffiti; 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 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.justdoit.graffiti", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/justdoit/graffiti/GraffitiDisplayView.kt: -------------------------------------------------------------------------------- 1 | package com.justdoit.graffiti 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.Canvas 6 | import android.util.AttributeSet 7 | import android.view.View 8 | import io.reactivex.Observable 9 | import io.reactivex.schedulers.Schedulers 10 | import java.util.concurrent.TimeUnit 11 | 12 | /** 13 | * 展示涂鸦礼物 14 | * Created by jinxin on 2018/7/4 15 | */ 16 | class GraffitiDisplayView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attributeSet, defStyleAttr) { 17 | 18 | private var toDrawGraffitiPositions: ArrayList = ArrayList() 19 | 20 | private val bitmapCache = HashMap() 21 | 22 | fun display(originWidth: Int, originHeight: Int, path: List, finishCallback: (() -> Unit)? = null) { 23 | visibility = View.VISIBLE 24 | val pointCount = path.size.toLong() 25 | Observable 26 | .intervalRange(0, pointCount + 1, 0, 100, TimeUnit.MILLISECONDS) 27 | .subscribeOn(Schedulers.io()) 28 | .observeOn(Schedulers.io()) 29 | .subscribe { 30 | if (it == pointCount) { 31 | toDrawGraffitiPositions.clear() 32 | finishCallback?.invoke() 33 | } else { 34 | val ratio = width / originWidth.toFloat() 35 | val pointStr = path[it.toInt()] 36 | val values = pointStr.split(",") 37 | val x = values[0].toInt().times(ratio).toInt() 38 | val y = values[1].toInt().times(ratio).toInt() 39 | val picId = values[2] 40 | 41 | var bitmap = bitmapCache[picId] 42 | if (bitmap == null) { 43 | bitmap = if (picId == "1") { 44 | drawableToBitmap(context.resources.getDrawable(R.mipmap.icon_1)) 45 | }else{ 46 | drawableToBitmap(context.resources.getDrawable(R.mipmap.icon_2)) 47 | } 48 | bitmapCache[picId] = bitmap 49 | } 50 | toDrawGraffitiPositions.add(GraffitiPosition(x, y, picId, bitmap)) 51 | postInvalidate() 52 | } 53 | } 54 | } 55 | 56 | override fun onDraw(canvas: Canvas) { 57 | toDrawGraffitiPositions.apply { 58 | for (i in 0 until size) { 59 | this[i].apply { 60 | canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null) 61 | } 62 | } 63 | } 64 | } 65 | 66 | //清除缓存 67 | override fun onDetachedFromWindow() { 68 | super.onDetachedFromWindow() 69 | toDrawGraffitiPositions.clear() 70 | bitmapCache.keys.forEach { 71 | bitmapCache[it]?.run { 72 | if (!isRecycled) { 73 | recycle() 74 | } 75 | } 76 | } 77 | bitmapCache.clear() 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/justdoit/graffiti/GraffitiDrawView.kt: -------------------------------------------------------------------------------- 1 | package com.justdoit.graffiti 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.view.MotionEvent 7 | import android.view.View 8 | import android.widget.Toast 9 | 10 | /** 11 | * 绘制涂鸦礼物 12 | * Created by jinxin on 2018/7/4 13 | */ 14 | class GraffitiDrawView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { 15 | 16 | private var bitmap: Bitmap? = null 17 | 18 | private var lastX: Int = 0 19 | private var lastY: Int = 0 20 | 21 | private var cleared: Boolean = false 22 | 23 | private var positions = ArrayList() 24 | 25 | private var currentPic: String = "" 26 | 27 | private val maxCount: Int = 100 28 | 29 | var onPointsChanged: ((ArrayList) -> Unit)? = null 30 | 31 | fun clear() { 32 | cleared = true 33 | positions.clear() 34 | invalidate() 35 | onPointsChanged?.invoke(positions) 36 | } 37 | 38 | fun setPicId(pic: String) { 39 | currentPic = pic 40 | bitmap = if (pic == "1") { 41 | drawableToBitmap(context.resources.getDrawable(R.mipmap.icon_1)) 42 | } else { 43 | drawableToBitmap(context.resources.getDrawable(R.mipmap.icon_2)) 44 | } 45 | } 46 | 47 | override fun onDraw(canvas: Canvas) { 48 | if (cleared) { 49 | val paint = Paint().apply { 50 | color = Color.TRANSPARENT 51 | xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) 52 | } 53 | canvas.apply { 54 | val width = width.toFloat() 55 | val height = height.toFloat() 56 | val layerId = saveLayer(0f, 0f, width, height, null, Canvas.ALL_SAVE_FLAG) 57 | drawRect(0f, 0f, width, height, paint) 58 | restoreToCount(layerId) 59 | } 60 | cleared = false 61 | } else { 62 | if (positions.size >= maxCount) { 63 | Toast.makeText(context, "涂鸦最多发送${maxCount}个,超出最大限制", Toast.LENGTH_SHORT).show() 64 | } 65 | 66 | positions.forEach { 67 | canvas.drawBitmap(it.bitmap, it.x.toFloat(), it.y.toFloat(), null) 68 | } 69 | } 70 | } 71 | 72 | override fun onTouchEvent(event: MotionEvent?): Boolean { 73 | when (event?.action) { 74 | MotionEvent.ACTION_MOVE -> { 75 | val moveX = event.x.toInt() 76 | val moveY = event.y.toInt() 77 | bitmap?.let { 78 | val space = if (it.width < it.height) it.width else it.height 79 | if (Math.sqrt(((moveX - lastX) * (moveX - lastX) + (moveY - lastY) * (moveY - lastY)).toDouble()) >= space) { 80 | if (positions.size < maxCount) { 81 | 82 | positions.add(GraffitiPosition(moveX, moveY, currentPic, it)) 83 | 84 | invalidate() 85 | 86 | onPointsChanged?.invoke(positions) 87 | 88 | lastX = moveX 89 | lastY = moveY 90 | } 91 | } 92 | } 93 | } 94 | } 95 | return true 96 | } 97 | } -------------------------------------------------------------------------------- /app/src/main/java/com/justdoit/graffiti/GraffitiPosition.kt: -------------------------------------------------------------------------------- 1 | package com.justdoit.graffiti 2 | 3 | import android.graphics.Bitmap 4 | import android.os.Parcel 5 | import android.os.Parcelable 6 | 7 | data class GraffitiPosition( 8 | val x: Int, 9 | val y: Int, 10 | val picId: String, 11 | var bitmap: Bitmap 12 | 13 | 14 | ) : Parcelable { 15 | constructor(source: Parcel) : this( 16 | source.readInt(), 17 | source.readInt(), 18 | source.readString(), 19 | source.readParcelable(Bitmap::class.java.classLoader) 20 | ) 21 | 22 | override fun describeContents() = 0 23 | 24 | override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { 25 | writeInt(x) 26 | writeInt(y) 27 | writeString(picId) 28 | writeParcelable(bitmap, 0) 29 | } 30 | 31 | companion object { 32 | @JvmField 33 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 34 | override fun createFromParcel(source: Parcel): GraffitiPosition = GraffitiPosition(source) 35 | override fun newArray(size: Int): Array = arrayOfNulls(size) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/justdoit/graffiti/GraffitiUtils.kt: -------------------------------------------------------------------------------- 1 | package com.justdoit.graffiti 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.Canvas 6 | import android.graphics.PixelFormat 7 | import android.graphics.drawable.Drawable 8 | import android.text.TextUtils 9 | import android.util.DisplayMetrics 10 | import android.view.WindowManager 11 | import com.google.gson.Gson 12 | import com.google.gson.reflect.TypeToken 13 | 14 | /** 15 | * Created by jinxin on 2018/7/4 16 | */ 17 | 18 | fun drawableToBitmap(drawable: Drawable): Bitmap { 19 | // 取 drawable 的长宽 20 | val w = drawable.intrinsicWidth 21 | val h = drawable.intrinsicHeight 22 | 23 | // 取 drawable 的颜色格式 24 | val config = if (drawable.opacity != PixelFormat.OPAQUE) 25 | Bitmap.Config.ARGB_8888 26 | else 27 | Bitmap.Config.RGB_565 28 | // 建立对应 bitmap 29 | val bitmap = Bitmap.createBitmap(w, h, config) 30 | // 建立对应 bitmap 的画布 31 | val canvas = Canvas(bitmap) 32 | drawable.setBounds(0, 0, w, h) 33 | // 把 drawable 内容画到画布中 34 | drawable.draw(canvas) 35 | return bitmap 36 | } 37 | 38 | fun toList(JSONArrayStr: String?): List? { 39 | if (TextUtils.isEmpty(JSONArrayStr)) { 40 | return null 41 | } 42 | return try { 43 | val type = object : TypeToken>() {}.type 44 | Gson().fromJson(JSONArrayStr, type) as List? 45 | } catch (e: Exception) { 46 | null 47 | } 48 | } 49 | 50 | fun getScreenWidth(context: Context): Int { 51 | val metric = DisplayMetrics() 52 | val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager 53 | windowManager.defaultDisplay.getMetrics(metric) 54 | return metric.widthPixels 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/justdoit/graffiti/GraffitiView.kt: -------------------------------------------------------------------------------- 1 | package com.justdoit.graffiti 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.LinearLayout 7 | import com.jakewharton.rxbinding2.view.RxView 8 | import kotlinx.android.synthetic.main.view_graffiti_gift.view.* 9 | 10 | /** 11 | * 涂鸦绘制界面 12 | * Created by jinxin on 2018/7/4 13 | */ 14 | class GraffitiView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attributeSet, defStyleAttr) { 15 | 16 | var positions = ArrayList() 17 | private set 18 | 19 | val graffitiDrawViewHeight: Int 20 | get() = graffitiDrawView.height 21 | 22 | init { 23 | isClickable = true 24 | 25 | View.inflate(context, R.layout.view_graffiti_gift, this) 26 | 27 | RxView.clicks(tvClear).subscribe { resetGraffitiView() } 28 | 29 | (graffitiDrawView as GraffitiDrawView).onPointsChanged = { 30 | positions = it 31 | val size = it.size 32 | if (size > 0) { 33 | ivIcon.visibility = View.GONE 34 | tvTip.text = "共计${size}个涂鸦" 35 | } 36 | } 37 | } 38 | 39 | fun resetGraffitiView() { 40 | (graffitiDrawView as GraffitiDrawView).clear() 41 | ivIcon.visibility = View.VISIBLE 42 | tvTip.text = "至少需要涂鸦10个才能发送哟~" 43 | } 44 | 45 | fun setPicId(selectPic: String) { 46 | (graffitiDrawView as GraffitiDrawView).setPicId(selectPic) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/justdoit/graffiti/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.justdoit.graffiti 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.view.View 6 | import android.widget.Toast 7 | import com.jakewharton.rxbinding2.view.RxView 8 | import kotlinx.android.synthetic.main.activity_main.* 9 | import org.json.JSONArray 10 | 11 | /** 12 | * Created by jinxin on 2018/7/4 13 | */ 14 | class MainActivity : AppCompatActivity() { 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_main) 19 | 20 | RxView.clicks(ivSelectIcon1).subscribe { 21 | graffitiView.setPicId("1") 22 | } 23 | 24 | RxView.clicks(ivSelectIcon2).subscribe { 25 | graffitiView.setPicId("2") 26 | } 27 | 28 | RxView.clicks(btnSend).subscribe { 29 | val graffitiDrawViewHeight = graffitiView.graffitiDrawViewHeight 30 | val points = graffitiView.positions 31 | val count = points.size 32 | 33 | val jsonArray = JSONArray() 34 | val positionMap = HashMap>() 35 | var list: ArrayList 36 | 37 | for (i in 0 until points.size) { 38 | val position = points[i] 39 | jsonArray.put("${position.x},${position.y},${position.picId}") 40 | if (positionMap.containsKey(position.picId)) { 41 | list = positionMap[position.picId] as ArrayList 42 | list.add(position) 43 | } else { 44 | list = ArrayList() 45 | list.add(position) 46 | positionMap[position.picId] = list 47 | } 48 | } 49 | 50 | val graffitiPositions = toList(jsonArray.toString()) 51 | 52 | if (count >= 10) { 53 | if (graffitiPositions != null) { 54 | graffitiDisplayView?.display(getScreenWidth(this), graffitiDrawViewHeight, graffitiPositions) { 55 | graffitiDisplayView?.run { 56 | postDelayed({ 57 | visibility = View.INVISIBLE 58 | Toast.makeText(this@MainActivity, "展示完成~", Toast.LENGTH_SHORT).show() 59 | }, 2000) 60 | } 61 | } 62 | } 63 | graffitiView.resetGraffitiView() 64 | } else { 65 | Toast.makeText(this, "至少需要10个涂鸦才能发送哟~", Toast.LENGTH_SHORT).show() 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /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 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/send_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 20 | 21 | 27 | 28 | 34 | 35 | 41 | 42 | 48 | 49 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_graffiti_gift.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 21 | 22 | 31 | 32 | 33 | 34 | 38 | 39 | 49 | 50 | 54 | 55 | 56 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/graffit_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xxhdpi/graffit_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/icon_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xxhdpi/icon_1.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/icon_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xxhdpi/icon_2.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #FFFFFF 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Graffiti 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/justdoit/graffiti/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.justdoit.graffiti; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath ("com.android.tools.build:gradle:3.1.3") 10 | classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.50") 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | task("clean", Delete::class) { 22 | delete(rootProject.buildDir) 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jul 03 14:35:26 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /graffiti.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justdoitJX/Graffiti/3ae25ecc4dfc62e520b41734ec34c6c26fa58285/graffiti.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------