SoFunction
Updated on 2025-04-29

Detailed explanation of the code that implements the WordPad function based on Android

Detailed explanation of the code that implements the WordPad function based on Android

Updated: April 29, 2025 10:29:09 Author: Katie.
In many application scenarios, we need to allow users to freely draw or handwriting input, such as electronic contracts, express signing and confirmation, map/picture marking, document annotation, etc., so this article introduces how to implement a highly customizable writing board based on Android. Friends who need it can refer to it

1. Project introduction

1. Background and application scenarios

In many application scenarios, we need to allow users to freely draw or handwriting input, such as:

  • Signature confirmation: Electronic contract, express delivery signing

  • Drawing graffiti:Social App Share Hand-drawn Content

  • Apply and erase: Children's Educational Painting

  • Tag annotation: Map/picture marks, document annotations

This project will implement a highly customizable writing board that meets:

  • Free drawing: Supports multiple strokes, multiple colors, and multiple thicknesses

  • Undo redo: Revocable/redo operation

  • Clear screen save: Clear one click, save as picture with one click

  • Gesture optimization: Smooth curve, pressure sensing simulation (molarity simulation)

  • UI customizable: Color panel, pen width control, clear/undo/save buttons

  • Componentization: PackagingDrawingBoardView, easy to use in any layout

2. Function list

  1. Draw paths: Users touch the screen to draw continuous curves in real time

  2. Multi-color switching: Provide a color palette, support any color

  3. Adjustable pen width: Supports at least 3 stroke thicknesses

  4. Undo/redo: Can undo and redo each path

  5. Clear the canvas: Clear all drawn content with one click

  6. Save the picture: Save canvas content to local album or apply a private directory

  7. Export and share: You can directly share the drawn pictures

  8. Performance optimization: Supports hardware acceleration, path caching, and local refresh

2. Related knowledge

Before you start, you need to understand the following core technical points:

  1. Custom Views and Canvas

    • RewriteonDraw(Canvas),use(Path, Paint)Draw path

    • existonTouchEvent(MotionEvent)China basisACTION_DOWN/MOVE/UPBuildPath

  2. Data structure and undo/redo

    • useList<Path>Save the completed path and useStack<Path>Save the undoed path to support redoing

    • After each transaction is completed,currentPathjoin inpaths, clearredoStack

  3. Performance optimization

    • cachePathandPaintObjects, avoid frequent allocation

    • existinvalidate(Rect)Refresh the touch area locally to reduce full-screen redrawing

  4. Touch smooth

    • Use quadratic Bezier curves to smooth the trajectory:(prevX, prevY, (x+prevX)/2, (y+prevY)/2)

  5. File saving and sharing

    • WillBitmapExport: inDrawingBoardViewGenerated inBitmapandCanvasDraw the base map and all paths at once

    • useMediaStore(Android Q+) orFileOutputStreamSave to album

    • useFileProviderandIntent.ACTION_SENDShare pictures

  6. UI Components

    • useRecyclerVieworLinearLayoutBuild color panels and pen width panels

    • useMaterialButtonFloatingActionButtonSuch bearer undo, redo, clear, save operations

Ideas for realization

  1. PackagingDrawingBoardView

    • Public properties:setStrokeColor(int)setStrokeWidth(float)undo()redo()clear()exportBitmap()

    • Event handling:onTouchEventCollect and smoothly record the touch trajectory;

  2. Main interface layout

    • Top button area: Undo, redo, clear, save

    • CentralDrawingBoardViewFully screen

    • Bottom toolbar: color selection, pen width slider

  3. File storage and sharing

    • existMainActivityCalled in()GetBitmap, save or share

    • Use coroutines or background threads to process I/O to display progress prompts

  4. Status saving and recovery

    • existonSaveInstanceStateSavepathsandredoStackSerialized data

    • existonRestoreInstanceStateRestore the path to avoid loss of drawings on the screen rotation

  5. Modularization and multiplexing

    • Encapsulate all drawing logic in

    • Encapsulate the save and share functions in

4. Environment and dependence

// app/
apply plugin: ''
apply plugin: 'kotlin-android'
 
android {
  compileSdkVersion 34
  defaultConfig {
    applicationId ""
    minSdkVersion 21
    targetSdkVersion 34
  }
  buildFeatures { viewBinding true }
  kotlinOptions { jvmTarget = "1.8" }
}
 
dependencies {
  implementation ':appcompat:1.6.1'
  implementation ':core-ktx:1.10.1'
  implementation ':material:1.9.0'
  implementation ':lifecycle-runtime-ktx:2.6.1'
}

5. Integrate code

// =======================================================
// File: res/layout/activity_main.xml// Description: Main interface layout, including toolbar, DrawingBoardView, color/pen width tools// =======================================================
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;
    xmlns:andro
    xmlns:app="/apk/res-auto"
    android:layout_width="match_parent" android:layout_height="match_parent"&gt;
 
  &lt;!-- Top Action Bar --&gt;
  &lt;
      android:
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:theme="@style/"
      app:title="Writing Board"/&gt;
 
  &lt;!-- Draw the panel --&gt;
  &lt;
      android:
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_marginTop="?attr/actionBarSize"
      android:background="#FFFFFF"/&gt;
 
  &lt;!-- Bottom toolbar --&gt;
  &lt;LinearLayout
      android:
      android:orientation="horizontal"
      android:gravity="center_vertical"
      android:padding="8dp"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom"
      android:background="#CCFFFFFF"&gt;
 
    &lt;!-- Color panel --&gt;
    &lt;HorizontalScrollView
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"&gt;
      &lt;LinearLayout
          android:
          android:orientation="horizontal"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"/&gt;
    &lt;/HorizontalScrollView&gt;
 
    &lt;!-- Pen width slider --&gt;
    &lt;SeekBar
        android:
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:max="50"
        android:progress="10"
        android:layout_marginStart="16dp"/&gt;
  &lt;/LinearLayout&gt;
 
  &lt;!-- Floating operation button --&gt;
  &lt;
      android:
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_margin="16dp"
      android:src="@drawable/ic_baseline_clear_24"
      app:layout_anchorGravity="bottom|end"
      app:layout_anchor="@id/drawingBoard"/&gt;
 
  &lt;
      android:
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_margin="16dp"
      android:src="@drawable/ic_baseline_undo_24"
      app:layout_anchorGravity="bottom|start"
      app:layout_anchor="@id/drawingBoard"/&gt;
 
  &lt;
      android:
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_margin="16dp"
      android:src="@drawable/ic_baseline_redo_24"
      app:layout_anchorGravity="bottom|start"
      app:layout_anchor="@id/btnUndo"/&gt;
 
  &lt;
      android:
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_margin="16dp"
      android:src="@drawable/ic_baseline_save_24"
      app:layout_anchorGravity="bottom|end"
      app:layout_anchor="@id/btnClear"/&gt;
 
&lt;/&gt;
 
// =======================================================
// document:// Description: Custom drawing board, supports drawing, undo, redo, clear, and export// =======================================================
package 
 
import 
import .*
import 
import 
import 
import .*
 
class DrawingBoardView @JvmOverloads constructor(
  context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {
 
  // Collection of brushes and paths  private var paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = ; strokeWidth = 10f
    style = ; strokeCap = 
    strokeJoin = 
  }
  private var currentPath = Path()
  private val paths = mutableListOf&lt;Pair&lt;Path, Paint&gt;&gt;()
  private val redoStack = Stack&lt;Pair&lt;Path, Paint&gt;&gt;()
 
  // Touch the previous point  private var prevX = 0f; private var prevY = 0f
 
  /** Set the brush color */
  fun setStrokeColor(color: Int) {  = color }
 
  /** Set the brush thickness */
  fun setStrokeWidth(width: Float) {  = width }
 
  /** Undo */
  fun undo() {
    if (()) (())
    invalidate()
  }
 
  /** Redo */
  fun redo() {
    if (()) paths += ()
    invalidate()
  }
 
  /** Clear */
  fun clear() {
    (); ()
    invalidate()
  }
 
  /** Export Bitmap */
  fun exportBitmap(): Bitmap {
    val bmp = (width, height, .ARGB_8888)
    val canvas = Canvas(bmp)
    ()
    for ((p, paint) in paths) (p, paint)
    return bmp
  }
 
  override fun onTouchEvent(e: MotionEvent): Boolean {
    val x = ; val y = 
    when () {
      MotionEvent.ACTION_DOWN -&gt; {
        currentPath = Path().apply { moveTo(x, y) }
        prevX = x; prevY = y
        // Clear the redo stack with new operation        ()
      }
      MotionEvent.ACTION_MOVE -&gt; {
        val mx = (x + prevX) / 2
        val my = (y + prevY) / 2
        (prevX, prevY, mx, my)
        prevX = x; prevY = y
      }
      MotionEvent.ACTION_UP -&gt; {
        // Complete a stroke and store the path and its brush attributes        val p = Path(currentPath)
        val paintCopy = Paint(paint)
        paths += Pair(p, paintCopy)
      }
    }
    invalidate()
    return true
  }
 
  override fun onDraw(canvas: Canvas) {
    (canvas)
    // Draw historical paths in turn    for ((p, paint) in paths) (p, paint)
    // Draw the current path    (currentPath, paint)
  }
}
 
// =======================================================
// document:// Description: Image saving and sharing tool// =======================================================
package 
 
import 
import 
import 
import 
import 
import .*
 
object ImageUtil {
  /** Save to album and return to Uri */
  fun saveBitmapToGallery(ctx: Context, bmp: Bitmap, name: String = "draw_${()}"): Uri? {
    return if (.SDK_INT &gt;= Build.VERSION_CODES.Q) {
      val values = ContentValues().apply {
        put(.DISPLAY_NAME, "$")
        put(.MIME_TYPE, "image/png")
        put(.RELATIVE_PATH, "Pictures/DrawingBoard")
        put(.IS_PENDING, 1)
      }
      val uri = (.EXTERNAL_CONTENT_URI, values)
      uri?.let {
        (it)?.use { os -&gt; (, 100, os) }
        (); (.IS_PENDING, 0)
        (it, values, null, null)
      }
      uri
    } else {
      val dir = File((null), "DrawingBoard")
      if (!()) ()
      val file = File(dir, "$")
      FileOutputStream(file).use { fos -&gt; (, 100, fos) }
      (file)
    }
  }
}
 
// =======================================================
// document:// Description: Main interface logic: Initialize artboard, tool binding, save and share// =======================================================
package 
 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import .*
 
class MainActivity : AppCompatActivity() {
  private lateinit var binding: ActivityMainBinding
  private val scope = CoroutineScope( + Job())
 
  // Temporary Uri after sharing  private var savedImageUri: Uri? = null
 
  // Share authorization  private val shareLauncher = registerForActivityResult(
    ()
  ) { /* nothing */ }
 
  override fun onCreate(savedInstanceState: Bundle?) {
    (savedInstanceState)
    binding = (layoutInflater)
    setContentView()
 
    // Initialize the color panel    initColorPalette()
 
    // Pen width control    (object: SimpleSeekListener(){
      override fun onProgressChanged(sb: , p: Int, u: Boolean) {
        (())
      }
    })
 
    // Top button binding     { () }
      { () }
      { () }
      { saveDrawing() }
  }
 
  private fun initColorPalette() {
    val colors = listOf(, , , , )
    for (c in colors) {
      val btn = ImageButton(this).apply {
        val size = (.color_btn_size)
        layoutParams = (size, size).apply {
          marginEnd = 16
        }
        setBackgroundColor(c)
        setOnClickListener { (c) }
      }
      (btn)
    }
  }
 
  private fun saveDrawing() {
    // Save and share asynchronously     {
      val bmp = withContext() { () }
      savedImageUri = (this@MainActivity, bmp)
      if (savedImageUri != null) {
        shareImage(savedImageUri!!)
      } else {
        (this@MainActivity, "Save failed", Toast.LENGTH_SHORT).show()
      }
    }
  }
 
  private fun shareImage(uri: Uri) {
    val contentUri = if ( == "file") {
      (this, "$", (!!).toFile())
    } else uri
    val intent = Intent(Intent.ACTION_SEND).apply {
      type = "image/png"
      putExtra(Intent.EXTRA_STREAM, contentUri)
      addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    ((intent, "Share drawing"))
  }
 
  override fun onDestroy() {
    ()
    ()
  }
}
 
// =======================================================
// document:// Description: Simple SeekBar listening, omit callback implementation// =======================================================
package 
 
import 
 
abstract class SimpleSeekListener:  {
  override fun onStartTrackingTouch(p0: SeekBar?) {}
  override fun onStopTrackingTouch(p0: SeekBar?) {}
}

6. Code interpretation

  1. DrawingBoardView

    • Data structurepaths: List<Pair<Path,Paint>>Save each stroke trajectory and the corresponding brush;

    • Touch processing:usequadToSmooth drawing; inACTION_UP Deep copy path and brush entry paths

    • Undo/redoundo()frompathsMove out the last transactionredoStackredo()Then reverse operation;

    • Clear and exportclear()Clear everything,exportBitmap()Generate white backgroundBitmapAnd redraw all paths.

  2. ImageUtil

    • Compatible with Android Q+ and the following versions, use them separatelyMediaStoreor file stream saving;

    • Save inPictures/DrawingBoardorgetExternalFilesDir, and returnUriEasy to share.

  3. MainActivity

    • UI bindingcolorPaletteDynamically generate color buttons,seekStrokeDynamically control pen width;

    • Operation button: Clear, undo, and redo buttons to directly call the corresponding API;

    • Save and share: Coroutine asynchronous exportBitmap→Save →GetUri→ByIntent.ACTION_SENDshare;

  4. Permissions and URIs

    • useFileProviderAdapt to Android 7.0+ file access restrictions;

    • existandprovider_paths.xmlCorrect configuration;

7. Performance and Optimization

  1. Partial refresh

    • Available inonTouchEventRecord the change area in the  useinvalidate(left, top, right, bottom)Replace global refresh;

  2. Object reuse

    • Avoid creating new ones every time you touchPaintorPathObjects, can maintain pooling strategies;

  3. Memory management

    • For large canvases or long-term drawing, pay attention to Bitmap memory and use it if necessaryinBitmapReuse;

  4. Multi-touch

    • Extended to support multiple finger drawing at the same time, one per fingerPath

8. Project Summary and Expansion

  • This article fully implements a complete writing board component, covering the entire process of free drawing, undoing and redoing, clearing, saving and sharing.

  • Through component encapsulation, the business layer only needs to reference it in the layout.DrawingBoardViewAnd bind the button to quickly integrate.

Expand direction

  1. Pen pressure sensing: Dynamically adjust the width or transparency of the pen in combination with the pressure of the stylus;

  2. Graphic marking: Supports various annotation modes such as straight lines, rectangles, circles, and text;

  3. Cloud synchronization: Upload the drawing data to the server in vector format to achieve cross-end synchronization;

  4. Animation playback: Record drawing timestamps, supporting playback of the drawing process;

  5. Jetpack Compose Refactoring:useCanvasandImplement the Compose version of the writing board.

9. FAQ

  1. Q: How to save multi-page canvas?
    A: Can be found inpathsAdd to page index, generate multiple photos according to page numbers when exporting.BitmapAnd pack.

  2. Q: What should I do if the image is too large after Bitmap is exported?
    A: When savingBitmapCompress, or scale to the appropriate size first.

  3. Q: How to make the undo support some handwriting?
    A: Currently, it is cancelled by the whole piece. If you need to cancel it in detail, you can use each paragraph.quadToSplit into smaller paths and record.

  4. Q: How to keep drawing after rotating the screen?
    A: InonSaveInstanceStateSerializationpathsData, after rotationonRestoreInstanceStateRecovery.

  5. Q: How to support the graffiti eraser function?
    A: You can switch in graffiti mode = PorterDuffXfermode()To erase the track.

The above is the detailed explanation of the code that implements the WordPad function based on Android. For more information about the Android WordPad function, please pay attention to my other related articles!

  • Android
  • Writing board

Related Articles

  • Detailed tutorial on the implementation of Android's open-screen page countdown function

    This article mainly introduces a detailed tutorial on the implementation of the countdown function of Android for opening the screen page, which has certain reference value. Interested friends can refer to it.
    2017-06-06
  • Android ViewPager and radiogroup implementation association example

    This article mainly introduces the examples of the implementation association of Android ViewPager and radiogroup, which has certain reference value. If you are interested, you can learn about it.
    2017-03-03
  • Android implements the function of adding two numbers

    This article mainly introduces the implementation of two numbers addition function for Android. The sample code in the article is introduced in detail and has a certain reference value. Interested friends can refer to it.
    2020-03-03
  • How to use Snackbar in Android and tips

    This article mainly introduces relevant materials on how to use Snackbar in Android and the tips. The example code is introduced in this article in detail, which has certain reference learning value for everyone's study or work. Friends who need it, let's study together.
    2018-03-03
  • Detailed explanation of how to implement list slip removal by Android programming

    This article mainly introduces the method of Android programming to implement list slip removal. Combined with the example form, it analyzes the principles and specific implementation techniques of Android list slip removal function. The comments include detailed instructions. Friends who need it can refer to it.
    2018-01-01
  • Detailed explanation of Android teaches you how to create an efficient picture loading framework

    This article mainly introduces a detailed explanation of Android and teaches you how to create an efficient picture loading framework. The editor thinks it is quite good. Now I will share it with you and give you a reference. Let's take a look with the editor
    2016-12-12
  • Code for parsing JSON data in Android

    This article mainly introduces the relevant information on the JSON data analysis instance code in Android. Friends who need it can refer to it
    2017-03-03
  • Android programming development method of seekBar using handler message processing operation

    This article mainly introduces the method of seekBar using handler message processing operations in Android programming development. It analyzes the relevant skills of Android to implement progress bar functions based on examples. Friends who need it can refer to it.
    2015-12-12
  • Android imitation WeChat voice recording function

    This article mainly introduces the function of recording voice in Android imitation WeChat. The sample code in the article is introduced in detail and has certain reference value. Interested friends can refer to it.
    2019-11-11
  • Android Animation Practical Battle PopupWindow pops up at the bottom of the screen

    This article mainly introduces Android Animation animation practical project. PopupWindow pops up at the bottom of the screen. How to implement it? The sample code in the article is introduced in detail and has certain reference value. Interested friends can refer to it.
    2016-01-01

Latest Comments