├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── build.gradle ├── canvasview ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── abhishekkumar │ │ └── canvasview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── abhishekkumar │ │ │ └── canvasview │ │ │ └── CanvasView.kt │ └── res │ │ └── values │ │ ├── colors.xml │ │ └── strings.xml │ └── test │ └── java │ └── dev │ └── abhishekkumar │ └── canvasview │ └── ExampleUnitTest.kt ├── example ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── abhishekkumar │ │ └── canvasview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── abhishekkumar │ │ │ └── canvasview │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.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 │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── dev │ └── abhishekkumar │ └── canvasview │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | CanvasView -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CanvasView 2 | Android library that lets you draw on it. 3 | 4 | 5 | ## Add library to project: 6 | 7 | ### Step 1. Add the JitPack repository to your build file 8 | 9 | 10 | Add it in your root build.gradle at the end of repositories: 11 | 12 | allprojects { 13 | repositories { 14 | ... 15 | maven { url 'https://jitpack.io' } 16 | } 17 | } 18 | ### Step 2. Add the dependency 19 | 20 | dependencies { 21 | implementation 'com.github.imabhishekkumar:CanvasView:1.0' 22 | } 23 | 24 | ## Implementation 25 | 26 | ### In XML layout 27 | 28 | 32 | 33 | ### Set background color, marker color, and stroke width 34 | 35 | val canvasView = findViewById(R.id.canvasView) 36 | canvasView.setColorBackground(R.color.colorPrimary) 37 | canvasView.setColorMarker(R.color.colorAccent) 38 | canvasView.setStrokeWidth(12f) 39 | 40 | ### Get/Set Bitmap from/to view 41 | 42 | canvasView.getBitmap() 43 | canvasView.setBitmap(bitmap) 44 | 45 | ### Clear view 46 | canvasView.clearView() 47 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.50' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /canvasview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /canvasview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | android { 5 | compileSdkVersion 28 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 23 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles 'consumer-rules.pro' 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 30 | implementation 'androidx.appcompat:appcompat:1.1.0' 31 | implementation 'androidx.core:core-ktx:1.1.0' 32 | testImplementation 'junit:junit:4.12' 33 | androidTestImplementation 'androidx.test:runner:1.2.0' 34 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 35 | } 36 | -------------------------------------------------------------------------------- /canvasview/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imabhishekkumar/CanvasView/96d7c729317e8d23aa1547720dca33beff726a2d/canvasview/consumer-rules.pro -------------------------------------------------------------------------------- /canvasview/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 | -------------------------------------------------------------------------------- /canvasview/src/androidTest/java/dev/abhishekkumar/canvasview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.abhishekkumar.canvasview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("dev.abhishekkumar.canvasview.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /canvasview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /canvasview/src/main/java/dev/abhishekkumar/canvasview/CanvasView.kt: -------------------------------------------------------------------------------- 1 | package dev.abhishekkumar.canvasview 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.view.ViewConfiguration 9 | import androidx.core.content.res.ResourcesCompat 10 | import kotlin.math.abs 11 | import android.graphics.Bitmap 12 | import java.io.FileOutputStream 13 | import java.io.IOException 14 | import java.io.File 15 | 16 | 17 | private var STROKE_WIDTH = 12f 18 | 19 | class CanvasView : View { 20 | private lateinit var extraCanvas: Canvas 21 | private lateinit var extraBitmap: Bitmap 22 | 23 | 24 | constructor(context: Context) : super(context) {} 25 | 26 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} 27 | 28 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( 29 | context, 30 | attrs, 31 | defStyleAttr 32 | ) 33 | 34 | private var colorBackground = ResourcesCompat.getColor(resources, R.color.colorBackground, null) 35 | 36 | fun setColorBackground(color: Int) { 37 | colorBackground = ResourcesCompat.getColor(resources, color, null) 38 | } 39 | 40 | private var drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null) 41 | 42 | fun setColorMarker(color: Int) { 43 | drawColor = ResourcesCompat.getColor(resources, color, null) 44 | setPaint() 45 | } 46 | 47 | fun setStrokeWidth(width: Float) { 48 | STROKE_WIDTH = width 49 | setPaint() 50 | } 51 | 52 | fun getBitmap(): Bitmap { 53 | return extraBitmap 54 | } 55 | 56 | fun setBitmap(bitmap: Bitmap) { 57 | extraBitmap = bitmap 58 | } 59 | 60 | fun saveAs(format: Bitmap.CompressFormat, quality: Int, destination: File) { 61 | try { 62 | val fileOutputStream = FileOutputStream(destination) 63 | fileOutputStream.use { out -> 64 | extraBitmap.compress(format, quality, out) 65 | 66 | } 67 | } catch (e: IOException) { 68 | e.printStackTrace() 69 | } 70 | 71 | } 72 | 73 | fun clearView() { 74 | extraCanvas.drawColor(colorBackground) 75 | } 76 | 77 | private fun setPaint(): Paint { 78 | return Paint().apply { 79 | color = drawColor 80 | isAntiAlias = true 81 | isDither = true 82 | style = Paint.Style.STROKE 83 | strokeJoin = Paint.Join.ROUND 84 | strokeCap = Paint.Cap.ROUND 85 | strokeWidth = STROKE_WIDTH 86 | } 87 | } 88 | 89 | private var path = Path() 90 | private var motionTouchEventX = 0f 91 | private var motionTouchEventY = 0f 92 | private var currentX = 0f 93 | private var currentY = 0f 94 | 95 | private fun touchStart() { 96 | path.reset() 97 | path.moveTo(motionTouchEventX, motionTouchEventY) 98 | currentX = motionTouchEventX 99 | currentY = motionTouchEventY 100 | } 101 | 102 | private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop 103 | 104 | private fun touchMove() { 105 | val dx = abs(motionTouchEventX - currentX) 106 | val dy = abs(motionTouchEventY - currentY) 107 | if (dx >= touchTolerance || dy >= touchTolerance) { 108 | path.quadTo( 109 | currentX, 110 | currentY, 111 | (motionTouchEventX + currentX) / 2, 112 | (motionTouchEventY + currentY) / 2 113 | ) 114 | currentX = motionTouchEventX 115 | currentY = motionTouchEventY 116 | extraCanvas.drawPath(path, setPaint()) 117 | } 118 | invalidate() 119 | } 120 | 121 | private fun touchUp() { 122 | path.reset() 123 | } 124 | 125 | override fun onTouchEvent(event: MotionEvent?): Boolean { 126 | motionTouchEventX = event!!.x 127 | motionTouchEventY = event.y 128 | 129 | when (event.action) { 130 | MotionEvent.ACTION_DOWN -> touchStart() 131 | MotionEvent.ACTION_MOVE -> touchMove() 132 | MotionEvent.ACTION_UP -> touchUp() 133 | } 134 | return true 135 | 136 | } 137 | 138 | override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { 139 | super.onSizeChanged(width, height, oldWidth, oldHeight) 140 | if (::extraBitmap.isInitialized) extraBitmap.recycle() 141 | extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) 142 | extraCanvas = Canvas(extraBitmap) 143 | extraCanvas.drawColor(colorBackground) 144 | } 145 | 146 | override fun onDraw(canvas: Canvas?) { 147 | super.onDraw(canvas) 148 | canvas!!.drawBitmap(extraBitmap, 0f, 0f, null) 149 | } 150 | 151 | 152 | } 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /canvasview/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #000000 5 | 6 | -------------------------------------------------------------------------------- /canvasview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CanvasView 3 | 4 | -------------------------------------------------------------------------------- /canvasview/src/test/java/dev/abhishekkumar/canvasview/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.abhishekkumar.canvasview 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "dev.abhishekkumar.canvasview" 11 | minSdkVersion 23 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation 'androidx.appcompat:appcompat:1.1.0' 29 | implementation 'androidx.core:core-ktx:1.1.0' 30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 31 | implementation project(':canvasview') 32 | testImplementation 'junit:junit:4.12' 33 | androidTestImplementation 'androidx.test:runner:1.2.0' 34 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 35 | } 36 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/src/androidTest/java/dev/abhishekkumar/canvasview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.abhishekkumar.canvasview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("dev.abhishekkumar.canvasview", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/src/main/java/dev/abhishekkumar/canvasview/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.abhishekkumar.canvasview 2 | 3 | import android.graphics.Bitmap 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import android.util.Log 7 | import kotlinx.android.synthetic.main.activity_main.* 8 | import android.os.Environment.getExternalStorageDirectory 9 | import androidx.core.app.ComponentActivity.ExtraData 10 | import androidx.core.content.ContextCompat.getSystemService 11 | import android.icu.lang.UCharacter.GraphemeClusterBreak.T 12 | import android.os.Environment 13 | import java.io.File 14 | 15 | 16 | class MainActivity : AppCompatActivity() { 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_main) 21 | val canvasView = findViewById(R.id.canvasView) 22 | canvasView.setColorBackground(R.color.colorPrimary) 23 | canvasView.setColorMarker(R.color.colorAccent) 24 | canvasView.setStrokeWidth(12f) 25 | clear.setOnClickListener { 26 | canvasView.clearView() 27 | } 28 | saveBtn.setOnClickListener { 29 | val filename = "test.png" 30 | val sd = getExternalStorageDirectory() 31 | val dest = File(sd, filename) 32 | canvasView.saveAs(Bitmap.CompressFormat.PNG, 90, dest) 33 | } 34 | 35 | setImage.setOnClickListener { 36 | imageView.setImageBitmap(canvasView.getBitmap()) 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 |