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!