SoFunction
Updated on 2025-05-07

Detailed explanation of the code to implement work management Gantt chart effects based on Android

Detailed explanation of the code to implement work management Gantt chart effects based on Android

Updated: May 7, 2025 09:13:08 Author: Katie.
In modern project management and team collaboration, Gantt Chart is one of the most intuitive progress visualization methods. On mobile, especially Android application scenarios, more and more applications such as schedules, attendance scheduling, etc. also need to display Gantt Charts in the App for mobile office or on-site management. Therefore, this article introduces the effect of implementing work management Gantt Charts based on Android.

1. Project introduction

1.1 Project background

In modern project management and team collaboration,Gantt ChartIt is one of the most intuitive methods of progress visualization. It splits the project into several tasks (tasks), uses the horizontal axis to represent time and the vertical axis to represent task sequences. It presents the beginning, end and duration of each task through a bar chart (Bar), helping managers to understand the project progress, resource allocation and key paths at a glance.

In mobile, especially Android application scenarios, more and more applications such as team management, schedule, attendance scheduling, production planning, etc. also need to display Gantt charts in the App for mobile office or on-site management. Since Android does not have Gantt Chart components natively, developers need to implement or integrate third-party libraries themselves. The goal of this project is to build a high-performance, flexible and customizable, support scrolling scaling and interaction without relying on heavyweight third-party libraries.Android native Gantt Chart Components, meet the following needs:

  • Taskbar visualization: Draw the bar representation of each task on the Gantt chart and support different colors and icon marks.

  • Timeline scale: The horizontal axis displays the date/hour scale and supports level switching (daily view/weekly view/monthly view).

  • Scroll vertically: When there are too many tasks, you can scroll up and down, and automatically reuse the row view to reduce memory usage.

  • Horizontal scrolling and zooming: When the time span is long, it can scroll left and right, and the timeline can be scaled through gestures (zoom in to view hourly details/zoom in to see the global monthly level).

  • Task interaction: Click on the task to pop up details, press and drag to adjust the start/end time.

  • Performance optimization:UsedRecyclerViewCanvasBatch drawing,ViewHolderTechnology such as multiplexing ensures high frame rate.

  • Configurability: Supports customization of various theme styles (light/dark), bar height, text size, line height, and time format.

  • MVVM architecture: The front and back ends are separated, and the data isViewModelManagement, UI only focuses on rendering and interaction.

  • Offline cache: The task data can be stored locallyRoomDatabase, realize offline display and incremental synchronization.

1.2 Functional design

Functional module illustrate
Timeline scale Supports four view modes: day/week/month/quarterly, and dynamically renders the scale according to the current zoom level
Task List Display task sequence vertically, usingRecyclerViewRealize rollable and reusable
Gantt strip rendering Calculate the X coordinates corresponding to the start/end time of the task, draw bars on Canvas, and support custom colors
Zoom and scroll CombinedScaleGestureDetectorandHorizontalScrollView, achieve smooth scaling and scrolling
Task interaction Click to pop upPopupWindowDisplay task details; support long press and drag to change the time (advanced functions are optional)
Data layer useRoomPersistence task data;ViewModelExposedLiveData<List<Task>>
Configuration and Theme existDefine customizable properties, such as Gantt bar height, color array, time format, etc.

1.3 Technical selection

  • language:Kotlin

  • UI:AndroidX、Material Components、ConstraintLayout

  • Graphic drawing: Canvas + Paint + Path + PorterDuff (for layer mixing, can be used for complex highlighting)

  • Gesture recognitionGestureDetector + ScaleGestureDetector

  • List reuseRecyclerView + LinearLayoutManager

  • Data persistence:Room + LiveData + ViewModel

  • Coroutine:Kotlin Coroutines + ViewModelScope

  • Dependency injection: Hilt (optional)

  • Date processing:ThreeTenABP ()

2. Related knowledge

2.1 Canvas drawing principle

  • / drawRoundRect: Draw task bars;

  • / drawText: Draw ticks and tick text;

  • layer (saveLayer/restore): Used when masking or blending mode is required;

2.2 RecyclerView Performance Optimization

  • ViewHolder Mode: Reuse task line layout;

  • ItemDecoration: Can be used to draw horizontal dividers or auxiliary grids;

  • DiffUtil: Efficiently calculate data changes and partial refresh;

2.3 Gestures and View Zoom

  • ScaleGestureDetector: Listen to the pinch gesture of the two fingers to realize the zoom center as the focus of the finger;

  • GestureDetector: Listen to single finger scrolling, double-clicking, etc.;

  • Matrix: Available when Canvas pan and zoom;

2.4 Time and coordinate mapping

  • Timeline range: Calculate the total duration (milliseconds) based on the earliest start and latest end of the task;

  • Pixel Mappingx = ( - minTime) / timeSpan * totalWidth

  • Dynamic width: The total width is calculated based on the current zoom level and screen width;

2.5 Data layer and MVVM

  • Room entity@Entity data class Task(...);

  • DAO: Add, delete, modify and query task list;

  • ViewModel:useMutableLiveData<List<Task>>Manage tasks, coroutines are loaded asynchronously;

  • Activity/Fragment: Observe LiveData and submit the task list to the adapter;

Ideas for realization

  1. Overall framework

    • MainActivity(orGanttChartFragment) Initialize ViewModel, RecyclerView and timeline header;

    • The view is divided into two parts:Task list on the left + Gantt chart area on the right, the latter can scroll horizontally;

    • Using nestingRecyclerView: Use horizontal scrollingRecyclerView + LinearLayoutManager(HORIZONTAL)

    • Or lighter: place a custom one on the rightGanttChartView, outer coverHorizontalScrollView

  2. Core View: GanttChartView

    • InheritanceView,existonDraw()Complete the drawing of the timeline and task bars;

    • supportsetTasks(List<Task>)setScale(scaleFactor: Float)Interface;

    • MaintenanceminTimemaxTimetimeSpanviewWidthrowHeightbarHeightetc.

  3. Task line reuse

    • existofonBindViewHolder(), pass the task data toGanttChartViewHolder, the latter call(task)andinvalidate()

    • GanttChartViewHolderMaintain a single row height and index within to calculate the Y coordinates.

  4. Gesture zoom and scroll

    • existGanttChartViewInternal instantiation and registrationScaleGestureDetector,existonTouchEvent()Forward, updatescaleFactorAfter remeasurement of the widthinvalidate()

    • Outer layerHorizontalScrollViewResponsible for horizontal rolling;

  5. Click and drag(Advanced features, optional)

    • MonitorGestureDetectorofonSingleTapUp(event), calculate the time and task index of clicking X/Y, and a details dialog box pops up;

    • Press and start dragging, update the task start or end time in real time and repaint it.

  6. Time scale and view update

    • exist()First draw the top scale row, loopfor (i in 0..numTicks)

val x = leftPadding + i * (timeSpanPerTick / timeSpan) * viewWidth
(x, 0f, x, headerHeight, axisPaint)
(formatTime(minTime + i * timeSpanPerTick), x, textY, textPaint)
  • Below, draw the rectangular bars and the task name of each task row in turn.
  • Status management and refresh

    • whenscaleFactorOr when the task list is updated, call?.notifyDataSetChanged()

    • AvailableDiffUtilFine refresh;

4. Integrate code

The following integrates all core source files and layout files into the same code block, distinguishes files with comments, and attaches detailed comments.

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------/*
 plugins {
     id ''
     id 'kotlin-android'
     id 'kotlin-kapt'
 }
 android {
     compileSdkVersion 34
     defaultConfig {
         applicationId ""
         minSdkVersion 21
         targetSdkVersion 34
         versionCode 1
         versionName "1.0"
     }
     buildFeatures { viewBinding true }
 }
 dependencies {
     implementation ":core-ktx:1.10.1"
     implementation ":appcompat:1.7.0"
     implementation ":material:1.9.0"
     implementation ":constraintlayout:2.1.4"
     implementation ":recyclerview:1.3.1"
     implementation ":lifecycle-viewmodel-ktx:2.6.1"
     implementation ":lifecycle-livedata-ktx:2.6.1"
     implementation ":room-runtime:2.5.2"
     kapt ":room-compiler:2.5.2"
     implementation ":threetenbp:1.6.0" // or ThreeTenABP
     implementation ":threetenabp:1.4.4"
 }
 */
 
// ---------------- document:  ----------------package 
 
import 
import 
import 
import 
 
/**
  * Task: Room entity, representing a task in the Gantt chart
  */
@Entity(tableName = "tasks")
data class Task(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val name: String,
    val startTime: Long, // millisecond timestamp    val endTime: Long,
    val color: Int // ARGB color)
 
// ---------------- document:  ----------------package 
 
import 
import .*
 
/**
  * TaskDao: Task addition, deletion, modification and search interface
  */
@Dao
interface TaskDao {
    @Query("SELECT * FROM tasks ORDER BY startTime")
    fun getAllTasks(): LiveData&lt;List&lt;Task&gt;&gt;
 
    @Insert(onConflict = )
    suspend fun insert(task: Task)
 
    @Delete
    suspend fun delete(task: Task)
}
 
// ---------------- document:  ----------------package 
 
import 
import 
 
/**
  * AppDatabase: Room database
  */
@Database(entities = [Task::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun taskDao(): TaskDao
}
 
// ---------------- document:  ----------------package 
 
import 
import .*
import 
import 
import 
import 
 
/**
  * GanttViewModel: Holds a task list, provides addition, deletion, modification and check
  */
class GanttViewModel(application: Application) : AndroidViewModel(application) {
    private val db = (application, AppDatabase::, "").build()
    private val dao = ()
 
    val tasks: LiveData&lt;List&lt;Task&gt;&gt; = ()
 
    fun addTask(task: Task) =  {
        (task)
    }
 
    fun deleteTask(task: Task) =  {
        (task)
    }
}
 
// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&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;!-- List of task names on the left --&gt;
    &lt;
        android:
        android:layout_width="120dp" android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"/&gt;
 
    &lt;!-- Gantt chart area on the right,Roll horizontally --&gt;
    &lt;HorizontalScrollView
        android:
        android:layout_width="0dp" android:layout_height="0dp"
        android:scrollbars="none"
        app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/rvTasks" app:layout_constraintEnd_toEndOf="parent"&gt;
 
        &lt;
            android:
            android:layout_width="wrap_content" android:layout_height="match_parent"/&gt;
 
    &lt;/HorizontalScrollView&gt;
 
    &lt;!-- Add task button --&gt;
    &lt;
        android:
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        app:srcCompat="@android:drawable/ic_input_add"
        app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
        android:layout_margin="16dp"/&gt;
&lt;/&gt;
 
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;TextView xmlns:andro
    android:
    android:layout_width="match_parent" android:layout_height="48dp"
    android:gravity="center_vertical"
    android:paddingStart="8dp"
    android:textSize="16sp"
    android:textColor="#333"/&gt;
 
// ---------------- document:  ----------------package 
 
import 
import 
import 
import 
import 
import 
import 
 
/**
  * TaskNameAdapter: List of task names on the left
  */
class TaskNameAdapter : ListAdapter&lt;Task, &gt;(DIFF) {
    companion object {
        val DIFF = object : &lt;Task&gt;() {
            override fun areItemsTheSame(old: Task, new: Task) =  == 
            override fun areContentsTheSame(old: Task, new: Task) = old == new
        }
    }
    inner class NameVH(val binding: ItemTaskNameBinding) : ()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        NameVH(((), parent, false))
    override fun onBindViewHolder(holder: NameVH, position: Int) {
         = getItem(position).name
    }
}
 
// ---------------- document:  ----------------package 
 
import 
import .*
import 
import .*
import 
import 
import 
import 
import 
import 
 
/**
  * GanttChartView: Custom Gantt Chart View
  */
class GanttChartView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
): View(context, attrs), ,  {
 
    // data    private var tasks: List&lt;Task&gt; = emptyList()
    private var minTime = Long.MAX_VALUE
    private var maxTime = 0L
    private var timeSpan = 1L // ms
 
    // Paintbrush    private val barPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val axisPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = ; strokeWidth=2f }
    private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = ; textSize = 24f }
 
    // Layout parameters    private val rowHeight = 80f
    private val headerHeight = 80f
    private var scaleFactor = 1f
    private var offsetX = 0f
 
    // Gestures    private val scroller = OverScroller(context)
    private val gestureDetector = GestureDetector(context, this)
    private val scaleDetector = ScaleGestureDetector(context, this)
 
    init {
         = 
    }
 
    /** Set tasks externally and recalculate the range */
    fun setTasks(list: List&lt;Task&gt;) {
        tasks = list
        if (()) {
            minTime =  {  }
            maxTime =  {  }
            timeSpan = maxTime - minTime
        }
        invalidate()
    }
 
    override fun onDraw(canvas: Canvas) {
        (canvas)
        if (()) return
 
        // 1. Draw the timeline        val totalWidth = () * scaleFactor
        val tickCount = 6
        for (i in 0..tickCount) {
            val x = offsetX + i / () * totalWidth
            (x, 0f, x, headerHeight, axisPaint)
            val time = minTime + i / () * timeSpan
            val label = (time)
                .atZone(()).toLocalDate().toString()
            (label, x + 10, headerHeight - 20, textPaint)
        }
 
        // 2. Draw each row of task bars         { idx, task -&gt;
            val top = headerHeight + idx * rowHeight
            val bottom = top + rowHeight * 0.6f
            // Calculate left and right            val left = offsetX + ( - minTime) / () * totalWidth
            val right = offsetX + ( - minTime) / () * totalWidth
             = 
            (left, top + 10, right, bottom, barPaint)
        }
    }
 
    // ================== Gestures and zoom =======================    override fun onTouchEvent(event: MotionEvent): Boolean {
        (event)
        if (!) {
            (event)
        }
        return true
    }
    override fun onScale(detector: ScaleGestureDetector): Boolean {
        scaleFactor *= 
        scaleFactor = (0.5f, 3f)
        invalidate()
        return true
    }
    override fun onScaleBegin(detector: ScaleGestureDetector) = true
    override fun onScaleEnd(detector: ScaleGestureDetector) {}
    override fun onDown(e: MotionEvent) = true
    override fun onShowPress(e: MotionEvent) {}
    override fun onSingleTapUp(e: MotionEvent) = false
    override fun onScroll(e1: MotionEvent, e2: MotionEvent, dx: Float, dy: Float): Boolean {
        offsetX -= dx
        offsetX = (-(), () * scaleFactor)
        invalidate()
        return true
    }
    override fun onLongPress(e: MotionEvent) {}
    override fun onFling(e1: MotionEvent, e2: MotionEvent, vx: Float, vy: Float): Boolean {
        (
            (), 0,
            (), 0,
            (-width).toInt(), (width * scaleFactor).toInt(),
            0, 0
        )
        postInvalidateOnAnimation()
        return true
    }
    override fun computeScroll() {
        if (()) {
            offsetX = ()
            invalidate()
        }
    }
}
 
// ---------------- document:  ----------------package 
 
import 
import 
import 
import 
import 
import 
import 
import 
 
/**
  * MainActivity: Sample Gantt Chart Display
  */
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val vm: GanttViewModel by viewModels()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        (savedInstanceState)
        binding = (layoutInflater)
        setContentView()
 
        // List of task names on the left        val nameAdapter = TaskNameAdapter()
         {
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = nameAdapter
        }
 
        // Observe task data        (this) { list -&gt;
            (list)
            (list)
        }
 
        // Added sample tasks         {
            val now = ()
            val task = Task(
                name = "Task${now%100}",
                startTime = now,
                endTime = now + 3600_000 * (1 + (now%5).toInt()),
                color = (((now/1000)%255).toInt(),120,150)
            )
            (task)
        }
    }
}
 
// ---------------- document: activity_main.xml (ViewBinding) ----------------&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;layout  xmlns:andro
         xmlns:app="/apk/res-auto"&gt;
  &lt;data/&gt;
  &lt;
      android:layout_width="match_parent" android:layout_height="match_parent"&gt;
 
    &lt;
        android:
        android:layout_width="120dp" android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"/&gt;
 
    &lt;HorizontalScrollView
        android:
        android:layout_width="0dp" android:layout_height="0dp"
        android:scrollbars="none"
        app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/rvTasks" app:layout_constraintEnd_toEndOf="parent"&gt;
 
      &lt;
          android:
          android:layout_width="2000dp" android:layout_height="match_parent"/&gt;
    &lt;/HorizontalScrollView&gt;
 
    &lt;
        android:
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        app:srcCompat="@android:drawable/ic_input_add"
        app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
        android:layout_margin="16dp"/&gt;
  &lt;/&gt;
&lt;/layout&gt;

V. Method description

  • (): Get the task list asynchronously and useLiveDataForm exposure, automatically monitor data changes;

  • ()/deleteTask():existViewModelScopeExecute Room operations in  to ensure that the UI thread is not blocked;

  • TaskNameAdapter: The vertical list on the left, only responsible for displaying the task name;

  • (list): Accept the task list, calculateminTimemaxTimetimeSpanand refresh the view;

  • (): First draw the top time scale, then traverse the task list to draw each task bar;

  • and: Respond to single-finger scrolling to pan the view and double-finger zoom to adjustscaleFactor

  • OverScroller:existonFling()Initiate inertial sliding and incomputeScroll()Continuous updateoffsetX ;

  • MainActivity

    1. BindRecyclerViewandGanttChartView

    2. observe, submit data in both directions;

    3. ClickfabAddRandomly added new task demonstration effect.

6. Project Summary

6.1 Results Review

  • Completed a native Android Gantt Chart component, supporting vertical task lists, horizontal timelines, automatic calculation of coordinates and adaptive scaling;

  • AdoptRecyclerViewand PureViewCombining drawing to achieve high-performance rendering and interaction;

  • Supports gesture scaling, scrolling and inertial sliding, making the user experience smooth;

  • The data layer is based on Room + LiveData + ViewModel, which realizes offline storage and real-time refresh.

6.2 Technology gains

  • A deep understanding of Canvas coordinate mapping, time → pixel conversion and custom View drawing mechanism;

  • MasteredGestureDetectorScaleGestureDetectorOverScrollerEqual gestures and inertial sliding API;

  • Learn to integrate Room database and UI components in the MVVM architecture;

  • Learned how to implement configurable, high-performance large data visualization components on Android.

6.3 Follow-up optimization

  • Dynamic loading: For tasks with large time spans, load time scales and task bars as needed to avoid drawing too many elements at once;

  • Task interaction: Add task drag to change the start and end time, slide to adjust the duration, and press to pop up the context menu;

  • View linkage: The task list is linked to the Gantt chart, click on the task name to highlight the Gantt bar, click on the Gantt bar to scroll the list;

  • Theme and style: Support dark mode, customizable line height, bar height, scale font, interval color, etc.;

  • Performance detection: Use Systrace to analyze drawing and gesture responses to further optimize frame rates;

  • Accessible: Added to Gantt bars and time scalescontentDescription, improve the A11Y experience;

  • Unit testing and UI automation testing: Focus on testing time mapping, scaling logic and sliding boundaries.

The above is the detailed explanation of the code for implementing the effect of work management Gantt charts based on Android. For more information about Android's work management Gantt charts, please pay attention to my other related articles!

  • Android
  • Work Management
  • Gantt Chart

Related Articles

  • Android application development uses graphic and text usage methods for calling c++ jni through java

    This article mainly introduces
    2013-11-11
  • Android programming implements the masking, capturing and modifying methods of Home keys

    This article mainly introduces Android programming to implement the masking, capture and modifying methods of Home keys. It analyzes the relevant skills of using onAttachedToWindow to capture Home keys. Friends who need it can refer to it.
    2016-06-06
  • Detailed explanation of jump, animation and transmission of Flutter routes (easiest)

    This article mainly introduces you to the relevant information about the jump, animation and parameter transmission of Flutter routing. 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, please learn with the editor below.
    2019-01-01
  • Android implements top navigation indicators that imitate NetEase News

    This article mainly introduces the relevant information about Android's top navigation indicator imitating NetEase News. It is very good and has reference value. Friends who need it can refer to it.
    2016-08-08
  • Android advanced animation chapter SVG vector animation example

    Vector animation is an animation that uses mathematical equations to describe complex curves on the screen in a computer, and uses abstract motion characteristics of graphics to record changing picture information. This article will take you to understand vector animation in Android.
    2021-11-11
  • Overview of the Android App's operating environment and Android system architecture

    This article mainly introduces the Android App's operating environment and Android system architecture overview, and introduces knowledge points such as the application process isolation mechanism. Friends who need it can refer to it
    2016-03-03
  • Android programming method to realize the sliding effect of novel readers

    This article mainly introduces the method of Android programming to realize the sliding effect of novel readers, and involves relevant implementation techniques for the sliding effect of onTouch event. It has certain reference value. Friends who need it can refer to it.
    2015-10-10
  • Source code details the usage of () in Android

    This article uses the source code to analyze the usage of () in Android, the common problems and solutions, and learn them together.
    2017-12-12
  • Solve the problem that Android virtual keys obscures the page content

    Today, the editor will share with you an article to solve the problem that Android virtual buttons obscures the content of the page, which is of great reference value. I hope it will be helpful to everyone. Let's take a look with the editor
    2018-07-07
  • Android platform HttpGet and HttpPost request instance

    The Android platform, which comes from the Internet search engine giant, needs no need to say more about its support for the Internet. Apache's HttpClient module has been integrated into the Android SDK. Using the HttpClient module, we can use the HTTP protocol to connect to the network
    2014-05-05

Latest Comments