Piano Trainer: Android Progress

Looks a little different now:


I am going to give a little walk through on how I have accomplished everything so far. The simplest way to accomplish the piano roll was to extend the ScrollView class. Here are the major methods that needed writing with some short explanations:

protected void onDraw(Canvas canvas) { // "paint"
 super.onDraw(canvas); 
 // All of our other drawing methods here...  
}

public boolean onTouchEvent(MotionEvent event) {
 // If touched for first time, save the position
 if (event.getAction() == MotionEvent.ACTION_DOWN) {   
  event_start_Y = event.getRawY();
  event_start_Time = current_time;
  return true;
 } 
 // If it is being dragged, find distance and scroll
 else if (event.getAction() == MotionEvent.ACTION_MOVE) {   
  float y = event.getRawY();    
  current_time = ((y - event_start_Y) * 20) + event_start_Time;
  if( current_time > song_duration) current_time = song_duration;
  slider.setProgress( (int) current_time);
  percentage.setText( (int)(100*current_time/song_duration) + "%");
  this.invalidate();    
  return true;   
 }
 // We don't handle anything else
 else {
  return false;
 }
}

The ScrollView was the simplest way to go, but it did not allow for quick redrawing via a thread. Because of the way invalidate() works, you are not guaranteed that the component will be redrawn quickly. Quoting the Android API, "If the view is visible, onDraw(Canvas) will be called at some point in the future."

Instead, what most articles I have read have recommended extending the SurfaceView class and implementing a SurfaceHolder for callbacks. Your thread must then have a reference to the SurfaceHolder of your SurfaceView. From there, you can acquire and lock a canvas, draw to it, and then unlock and repost it to your view. Here are the simplified methods I used:
/* Inside our custom SurfaceView */
public void repaint() {
 // When I want to manually tell it to refresh for use in events
 // from buttons and the slider
 Canvas c = this.getHolder().lockCanvas();
 draw(c);
 this.getHolder().unlockCanvasAndPost(c);
}

class MidiThread extends Thread {  
  
 SurfaceHolder holder; // The holder where we get our canvas
 MidiView view;  // The actual view, which has our drawing methods
  
 public MidiThread(SurfaceHolder h, MidiView2 m) {
  this.holder = h; // Store the holder
  this.view = m; // Store the view

  // Note: you could also just pass the view, 
  // on use view.getHolder() to get the holder
 }
  
 public void run() {
   
  Canvas c = null;   
  long start_time = System.currentTimeMillis();      
    
  do  { 
   try {   
    // Update the time elapsed
    current_time = (double) System.currentTimeMillis() - start_time;
    // Get the canvas to draw to
    c = holder.lockCanvas();
                          
    // Call our custom paint method
    view.onDraw(c);
     
   } catch (Exception e) {
    // In case we can't get the canvas lock
    e.printStackTrace();
   } finally {
    // Give up the canvas and post it to the view
    holder.unlockCanvasAndPost(c);
    // Note: We want to break out here so we don't keep the lock after we are done
    if (!is_playing) break;
   }     
    
  } while ( current_time  < song_duration );  
 }  
}

I found a vertical SeekBar widget from stack overflow which can be found here. All credit to that guy!!! (You rock!) Then here is a simplified version of how my xml file looks with the slider and buttons ( Note: I put underscores in front of the elements because blogger was actually interpreting them as what they were. ):

<_ data-blogger-escaped-encoding="utf-8" data-blogger-escaped-version="1.0" data-blogger-escaped-xml="">
<_linearlayout data-blogger-escaped-br="" data-blogger-escaped-xmlns:android="http://schemas.android.com/apk/res/android">    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/main"
    >
        
    <_tablelayout data-blogger-escaped-br="">     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:stretchColumns="1">     
     <_tablerow>
      <_linearlayout data-blogger-escaped-br="">       android:id="@+id/view_holder"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
   />
      <_linearlayout data-blogger-escaped-br="">       android:id="@+id/menu_holder"
       android:layout_width="50px" 
       android:layout_height="fill_parent"
       android:orientation="vertical">
       
           <_button data-blogger-escaped-android:id="@+id/play" data-blogger-escaped-br="">              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:text="Play" 
          />
          <_button data-blogger-escaped-android:id="@+id/load" data-blogger-escaped-br="">              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:text="Load" 
          />
          <_button data-blogger-escaped-android:id="@+id/percentage" data-blogger-escaped-br="">              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:text="100%" 
          />
          
           <_com data-blogger-escaped-.midi.miditrainer.verticalslider="" data-blogger-escaped-br="">           android:id="@+id/slider"
           android:layout_width="fill_parent"
           android:layout_height="400px"           
                  android:layout_weight="1"
           />         
      <_ data-blogger-escaped-inearlayout="">
     <_ data-blogger-escaped-ablerow="">   
    <_ data-blogger-escaped-ablelayout=""> 
<_ data-blogger-escaped-inearlayout="">
Anyways, thanks for reading!