Using invisible buttons over the top of a larger background would be conceptually easy, but would have the scalability issues that you mention. It also won't handle recognizing multiple simultaneous touches very well. the The core of the second approach, using touch, is probably the way you want to go. The MotionEvent in the onTouch() method will provide you with the location and action of the touches. Then it just becomes an exercise in translating the coordinate of the spot touched/moved/lifted with the appropriate fret or string.
There are many decent tutorials available on doing touch recognition. Here's one.
Edit:
There are a couple of ways to translate between touch position and the desired string & fret. One way is to just do the calculations strng = x / (width / NUM_STRINGS)
and fret = y / (height / NUM_FRETS)
for each touch event. (Assuming the strings are drawn lengthwise on the display.)
Another straightforward way is to build and use lookup tables to do the translation. This does cost a few thousand bytes to store the ints, but allows for a couple of fast array lookups to determine the string and fret.
Here's a sample activity that implements the latter approach. It contains some assumptions and shortcuts, but the basic functionality should is fairly sound. Only the down event shows the translation; you'll want to do something appropriate for the up and move events as well.
MainActivity.java
package com.example.guitar;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class MainActivity extends Activity
implements OnTouchListener, ViewTreeObserver.OnGlobalLayoutListener {
final static String TAG = "MainActivity";
final static int NUM_STRINGS = 6;
final static int NUM_FRETS = 12;
ImageView img = null;
LinearLayout layout = null;
int width = 0;
int height = 0;
int touchToString[] = null;
int touchToFret[] = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout = (LinearLayout) findViewById(R.id.layout);
layout.setOnTouchListener(this);
layout.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
public boolean onTouch(View v, MotionEvent event) {
// Handle the touch event:
int idx = event.getActionIndex();
int id = event.getPointerId(idx);
int x = (int) event.getX(idx);
int y = (int) event.getY(idx);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
Log.d(TAG, String.format("DOWN event for pointer %d at %d, %d", id, x, y));
// If touch is within the bounds of the layout:
if (x > 0 && x < width && y > 0 && y < height)
Log.i(TAG, String.format("Pressed string %d at fret position %d",
touchToString[x], touchToFret[y]));
break;
case MotionEvent.ACTION_MOVE:
for (int ptr = 0; ptr < event.getPointerCount(); ptr++)
Log.d(TAG, String.format("MOVE event for pointer %d at %d, %d",
event.getPointerId(ptr), (int) event.getX(ptr), (int) event.getY(ptr)));
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, String.format("UP event for pointer %d at %d, %d", id, x, y));
break;
}
return true;
}
public void onGlobalLayout() {
// Get the current width and height of the layout:
width = layout.getMeasuredWidth();
height = layout.getMeasuredHeight();
Log.i(TAG, String.format("The layout is now (%d x %d)", width , height));
// (Re)build the string position translation array:
touchToString = new int[width];
for (int x = 0; x < width; x++)
touchToString[x] = x / (width / NUM_STRINGS);
// (Re)build the fret position translation array:
touchToFret = new int[height];
for (int y = 0; y < height; y++)
touchToFret[y] = y / (height / NUM_FRETS);
}
}