So I want to make a simple touch screen guitar tablature creator for android but I really do not know what method to use. I am fairly new to writing apps but I am comfortable with java.
The guitar fret board would look something like this. http://upload.wikimedia.org/wikipedia/commons/3/37/Guitar_Fretboard_Open_Strings_Diagram.png
1) At first I was leaning towards making buttons for each string and fret but I thought that may be a little difficult considering there are 24 frets on a guitar plus 6 strings (144 buttons) and each time the button was pushed it would update and array that has 6 rows and x amount of columns. I would make place the buttons on the image at the frets and make them invisible, but having the buttons line up may be tricky and they may not scale the way I would like.
An array would be pretty handy to hold the values because tablature is written like so...
e string -------
b string ----2--
g string ------3
d string 0-0----
a string -------
E string -------
the fret numbers are written in.
I am not even sure where to begin with this method. Should I have a separate method for each button because they all have different values?
To get a value from the button to the array I would need to put something like
strArray[count,n]
where count is a variable out side the method and n is the variable that is returned when the button is clicked. Count would be increased or decreased with a next/previous button so it can traverse the array
2) I was looking at this method http://blahti.wordpress.com/2012/06/26/images-with-clickable-areas/ and it looks like it could work but the developer was using colors to tell the different hot spot, and I was thinking that would probably not be feasible either considering I would need 144 different colors.
Is there an easier way that I could implement this instead?
Sorry if I haven't explained very well, I am new to developing on android and I have a lot of questions.
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);
}
}
Related
i am developing an android app, about joining dots and making picture.
so far i can't find a way to extract exact x and y coordinates of black dots . so I've hard coded x and y locations to draw point on exact black dot of image. It worked wonderful on 1152*720 size ,but problem occurs when i tested it on 480*600 size , dots misplaced from there exact location , now
My Question is if I've written something like :
x = 100 ,
y = 200
(on screen 1152*720)
what will be x and y values in different screen sizes like 480*600 and how to calculate it ? i know it is silly question but i am new to this stuff.
Answering your question as you have asked it...
int oldScreenX // The current x coord
int newScreenX // The new x coord
...
float oldScreenSizeX = 1152.0f;
float newScreenSizeX = 600.0f;
newScreenX = (int)(oldScreenX / oldScreenSizeX) * newScreenSizeX; // Remember to cast the result back to an int
Do the same for y.
Additional:
Perhaps you should reconsider your approach.
The real problem is how do you put the dot in the same location on the Image, if the Image is being drawn at a different size. So forget about measuring the screen size. Measure the Image size instead.
For example, if you are showing your image in an ImageView, you could write a general scaling method like the following:
public int scaleCoordinate(int unscaledImageSize, int scaledImageSize, int unscaledCoordinate) {
scaledCoordinate = (int)(unscaledCoordinate / unscaledImageSize) * scaledImageSize; // Remember to cast the result back to an int
return scaledCoordinate;
}
Then you can use it in your code, something like:
ImageView imageView = (ImageView)findViewById(R.id.my_imageview);
Drawable drawable = image.getDrawable();
// Get the original size of the bitmap
int unscaledSizeX = drawable.getIntrinsicWidth();
// Get the current size it is being drawn at on the screen
int scaledSizeX = imageView.getWidth();
int scaledCoordinateX = scaleCoordinate(unscaledSizeX, scaledSizeX, unscaledCoordinateX);
NOTE:
The ImageView needs to be measured and laid out by the system before calling the above code. If you call it too early imageView.getWidth() will return 0.
It would be best to call the above code once the ImageView is actually displayed on the screen (from your onResume() or later).
I was done this, while studying java may be it will be helpful for you.
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
public class DrawLineOnGivenLocations extends Applet implements MouseListener, MouseMotionListener{
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
public void init() {
addMouseListener(this);
addMouseMotionListener(this);
}
public void mouseMoved(MouseEvent me) {
// show status
showStatus("Moving mouse at " + me.getX() + ", " + me.getY());
}
public void mouseClicked(MouseEvent me) {
// save coordinates
x1 = me.getX();
y1 = me.getY();
x2 = me.getX();
y2 = me.getY();
repaint();
}
public void paint(Graphics g){
g.drawLine(x1,y1 ,x2, y2);
}
public void mouseEntered(MouseEvent arg0) {}
public void mouseDragged(MouseEvent arg0) {}
public void mouseExited(MouseEvent arg0) {}
public void mousePressed(MouseEvent arg0) {
//repaint();
}
public void mouseReleased(MouseEvent arg0) {}
}
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I need to create a series of concentric ellipses (rings), and need to place user icons on the circumference of these ellipses. See the image below.
Till now I've drawn 3 elliptical concentric circles on canvas & placed user icons.
I need user icons to be draggable across the rings.
Please suggest ways to implement this.
Since it appears you're already placing icons on the circumference of the rings, I assume you know how to do the math [but see edit] to determine the points along the circumference and are asking about the drag-and-drop.
You'll probably want to implement the icon movement using a drag-and-drop approach. Assuming that you keep the rings as a single image, then you will only have a single drop destination. You will then need to analyze the drop point mathematically [see edit] (by ascertaining its pixel color) to determine which ring the icon was dropped into. If you create separate views for the rings, then each one can be its own drop point. (You'll probably need to eventually work out how to redistribute the icons within each ring, but that's a different question.)
Here's some code that shows a minimal way to handle the drag-and-drop using a single image icon on a single view group (where you'd display your rings image).
MainActivity.java
package com.example.dragexample;
import android.app.Activity;
import android.content.ClipData;
import android.os.Bundle;
import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnDragListener;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
public class MainActivity extends Activity {
static final String TAG = "DragActivity";
ImageView icon = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.rings).setOnDragListener(new OnDragListener() {
#Override
public boolean onDrag(View vw, DragEvent event) {
if (event.getAction() == DragEvent.ACTION_DROP) {
// Drop the icon and redisplay it:
icon.setX(event.getX());
icon.setY(event.getY());
icon.setVisibility(View.VISIBLE);
// Analyze the drop point mathematically (or perhaps get its pixel color)
// to determine which ring the icon has been dragged into and then take
// appropriate action.
int destRing = determineDestinationRing(event.getX(), event.getY());
}
return true;
}
});
icon = (ImageView) findViewById(R.id.icon);
icon.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View vw, MotionEvent event) {
Log.v(TAG, "Touch event " + event.getAction());
if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
Log.v(TAG, "Starting drag");
// Set up clip data (empty) and drag shadow objects and start dragging:
ClipData cd = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(vw);
vw.startDrag(cd, shadowBuilder, vw, 0);
vw.setVisibility(View.INVISIBLE);
}
return true;
}
});
}
public void resetImage(View vw) {
Log.v(TAG, "Resetting image position");
icon.setX(0f);
icon.setY(0f);
icon.setVisibility(View.VISIBLE);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rings"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="resetImage" >
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_launcher" />
</FrameLayout>
Edit 1:
To mathematically determine which ring the icon is dropped into, you could use something like the following, which loops through an implementation of the standard equation for an ellipse using hard-coded sizes for the axes. Note that a zero will be returned if an icon is dropped within the innermost 'me' ring. Also, this approach will be a bit more challenging in practice due to the fact that the on-screen ring sizes will likely be adjusted when the layout is rendered. In that case, the final sizes of the axes will need to be determined at run time.
// Axis values must be ascending order; ring 0 is 'me';
float a[] = {50, 100, 150, 200};
float b[] = {100, 200, 300, 400};
public int determineDestinationRing(float x, float y) {
// Check for inclusion within each ring:
for (int i = 0; i < a.length; i++) {
if (((x * x) / (a[i] * a[i]) + (y * y) / (b[i] * b[i])) <= 1)
return i;
}
return -1;
}
To solve the problem, you need to use the equation of ellipse:
(x/a)2 + (y/a)2 = 1 ,
where:
x,y are coordinates of any point on ellipse circumference
a,b are radius on x and y axis respectively.
When the user drags and drops the icon, if the center co-ordinates of the icon intersect with the ellipse circumference, then above formula should hold good. In that case you can place the icon on the ellipse. Since you have 3 ellipses, you will have to do this check couple of times, once each for the other 2 ellipses.
public class CustomView extends View {
// Other methods
public boolean onTouchEvent(MotionEvent e) {
int index = e.getActionIndex();
float x = e.getX(index);
float y = e.getY(index);
int a = this.getWidth()/2;
int b = this.getHeight()/2;
// x-a instead of x and y-b instead of y, to get the offset from centre of ellipse.
double result = Math.pow(((x-a)/a), 2) + Math.pow(((y-b)/b), 2);
Log.v(TAG, "(" + (x-a) + "/" + a + ")2 + (" + (y-b) + "/" + b + ")2 = " + result);
return true;
}
}
I would do it like this. First of all we need to detect which icon is touched on TOUCH_DOWN event. This can be simply done by comparing touch point and icons coordinates. Once the closest icon is found, we should also know which ellipse this icon belongs to, meaning we know both the horizontal and the vertical radiuses of this ellipse. We also know the coordinates of the center of the ellipses.
Then, here is the idea of what we are going to do. We will calculate an angle between the line drawn through the touch point and the center of the ellipses and the horizontal line drawn through the center.
o <- touch point
/
/ \ <- angle
center-> o----- <- horizontal line
Now, after knowing the angle, we will use equation of a circle adjusted to ellipse, to recalculate new icon's coordinates to stick our icon exactly to that ellipse. Let's go to the algorithm.
To detect touch angle, we will use atan2 function.
double atan2Angle = Math.atan2(touchY - centerY, touchX - centerX);
This will give us following values depending on the angle.
-π
-2*π -0
o <- center
2*π 0
π
To be able to use this angle in equation of a circle we need to convert it into more traditional presentation like the one below.
270°
180° o 0°/360°
90°
It can be done like this.
float angleFactor = (float) (atan2Angle / 2 * Math.PI);
if (angleFactor < 0) {
angleFactor += 1f;
}
float touchAngle = angleFactor * 360f;
if (angle < 0) {
angle += 360f;
}
Now, after we've got our touch angle in degree, we can calculate new coordinates for our icon using equation of an ellipse.
double touchAngleRad = Math.toRadians(touchAngle);
float iconX = centerX + (float) (radiusX * Math.cos(touchAngleRad));
float iconY = centerY + (float) (radiusY * Math.sin(touchAngleRad));
// centerX, centerY - coordinates of the center of ellipses
// radiusX, radiusY - horizontal and vertical radiuses of the ellipse, to which
// the touched icon belongs
// iconX, iconY - new coordinates of the icon lying on that ellipse
If you recalculate icon position according to this algorithm on every TOUCH_MOVE and invalidate the view, then your icon will move across its ellipse.
The code can be further optimized by using radians instead of degrees, but I thought degrees are better suitable for explanation. Hope this help. Post your code, if you experience any issues with the implementation.
I would like to make a simple game about a boy at the bottom of the screen trying to catch the falling apples. The apples are falling straight down at random x coordinates, and the boy at the bottom can just scroll right or left.
So far the apples now can be generated randomly and correctly animated, and the boy can scroll properly.
However, I am having difficulties in the game core condition: When apple is in the boy's width, the apple is considered successful caught.
The apples are generated and stored as follows:
private final Queue<Button> spots = new ConcurrentLinkedQueue<Button>();
and when generating new apples to the view:
spots.add(spot);
and when the apples are disappeared from the view:
spots.remove(spot);
Checking condition:
I use the below code to check for the condition:
(BOY_X is the x-coordinate of the boy)
public void Check_all_Spot(int BOY_X)
{
int [] apple_x;
int [] apple_y;
int apple_shown_length =spots.size();
Button [] apples = null;
String text_x ="";
String text_y = "";
if (!spots.isEmpty())
{
apples = spots.toArray(new Button[apple_shown_length]);
}
apple_x = new int [apple_shown_length];
apple_y = new int [apple_shown_length];
for (int w = 0; w < apple_shown_length ; ++w)
{
apple_x[w] = (int) apples[w].getX();
apple_y[w] = (int) apples[w].getY();
text_x = text_x+ "\n" +apple_x[w];
text_y = text_y+ "\n" +apple_y[w];
if ( (apple_x[w]+SPOT_DIAMETER)/2 >= BOY_X ||
(apple_x[w]+SPOT_DIAMETER)/2 <= (BOY_X+BOY_WIDTH) ||
(apple_y[w]+SPOT_DIAMETER) >= (viewHeight - BOY_HEIGHT) ||
(apple_y[w]+SPOT_DIAMETER) <= (viewHeight - (BOY_HEIGHT)/2) )
// check text
{
}
}
Toast.makeText(getContext(), "X=" +text_x+"\nY=" +text_y, Toast.LENGTH_LONG).show();
}
Question:
In this way of converting the spots queue to a button array apple, the toast reports the x and y coordinates for all the apples in the view.
My question is, though now I know about which apple is satisfying the checking condition, how could it be instructing to the spots queue to remove the apple??
Or should I use a game thread and counting the time elasped?
Or I should not convert the ConcurrentLinkedQueue to button array?
I would like to obtain some of your advice. Many thanks!
After getting the calculator application to work I decided to try to create pong. There is a box in the center and two paddles on both ends. The phone is horizontal. I have the box bouncing off the walls and the paddle moves with me moving my finger down. My problem is i want to make it two player and i want to have multiple finger input for the game. I want one finger to move paddle 1 and the other to move paddle 2. So far this is my input code
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
float p1y = ev.getY();
if(ev.getX()<300)
{
player1y = p1y;
}
if(ev.getX()>300)
{
player2y = p1y;
}
//player1y = p1y;
invalidate();
break;
}
}
return true;
}
it resides in my surfaceview class. How can i modify the input method or completely get rid of it and change it to accomplish my goal? Also sorry about my variables. Eclipse crashes a lot on me and my laptops touch panel tends to move my cursor so shorter variables seemed viable. p1y is the y of the touch. and player1y and player2y is the y positions of the player1 and player2 paddle.
A MotionEvent can hold multiple pointers. Use getPointerCount() to see how many pointers are touching the screen in the current event. There are alternate versions of getX and getY that take an index from 0-getPointerCount() - 1.
In a more complex app you would want to track fingers by pointer ID, but for something this simple where you are using a cutoff point on the screen you could do something like this in your ACTION_MOVE case:
int pointerCount = ev.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
float x = ev.getX(i);
float y = ev.getY(i);
if (x < 300) {
player1y = y;
} else if (x > 300) {
player2y = y;
}
}
This post from the Android Developers Blog might help if you'd like more information: http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
I want to know how I can detect child views if I move a view from one ViewGroup to another ViewGroup, particularly when doing a touch event. Is there a method I can call that will let me know which views i'm "hovering" over?
What I'm doing right now is when I detect an ACTION_MOVE event on my view i'm raising it to the top level parent so that it can move and be drawn within the entire window ( and not just inside it's original parent bounds ), then I want to move the view across to a different ViewGroup and on ACTION_UP attach the view to that ViewGroup.
Inspired by Ami's response, but discovering that MotionEvent#getX()/getY() along with View#getTop()/etc return coordinates wrt the parent View, I ended up doing the following below to operate in screen coordinates, allowing me to work across ViewGroups:
private boolean inRegion(float x, float y, View v) {
v.getLocationOnScreen(mCoordBuffer);
return mCoordBuffer[0] + v.getWidth() > x && // right edge
mCoordBuffer[1] + v.getHeight() > y && // bottom edge
mCoordBuffer[0] < x && // left edge
mCoordBuffer[1] < y; // top edge
}
whose usage inside an OnTouchListener is e.g.:
boolean inside = inRegion(event.getRawX(), event.getRawY(), targetView);
I think I found a simpler way to do this.
Create an ArrayList of possible targets
Call this method from your touch event, supplying your targets list and the coords
private View findView(float x, float y, ArrayList<View> targets)
{
final int count = targets.size();
for (int i = 0; i < count; i++) {
final View target = targets.get(i);
if (target.getRight() > x && target.getTop() < y
&& target.getBottom() > y && target.getLeft() < x) {
return target;
}
}
return null;
}
I found Sebastian Roth's answer very helpful with resources, but since it wasn't really an answer to my question, I thought I'd share what I came up with.
Here is the code I use to detect views ( only views that will accept a drop that is ) given a coordinate on the screen.
private DropView findDropTarget( int x, int y, int[] dropCoordinates ){
final Rect r = mRectTemp;
final ArrayList<DropView> dropTargets = ((main) context).getBoardDropTargets();
final int count = dropTargets.size();
for (int i=count-1; i>=0; i--) {
final DropView target = dropTargets.get(i);
target.getHitRect(r);
target.getLocationOnScreen(dropCoordinates);
r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
if (r.contains(x, y)) {
dropCoordinates[0] = x - dropCoordinates[0];
dropCoordinates[1] = y - dropCoordinates[1];
return target;
}
}
}
Ok, first off mRectTemp is just an allocated Rectangle so you don't have to keep creating new ones ( I.E. final Rect r = new Rect() )
The next line dropTargets is a list of views that will accept a drop in my app.
Next I loop through each view.
I then use getHitRect(r) to return the screen coordiantes of the view.
I then offset the coordiantes to account for the notification bar or any other view that could displace the coordiantes.
finally I see if x and y are inside the coordinates of the given rectangle r ( x and y are the event.rawX() and event.rawY() ).
It actually turned out to be simpler then expected and works very well.
Read this:
http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)
http://developer.android.com/reference/android/view/View.html#onTouchEvent(android.view.MotionEvent)
I had implemented a Drag and Drop using that method.
I also highly recommend a read of the HomeScreen sourcecode, which contains this thing (kind of):
https://android.googlesource.com/platform/packages/apps/Launcher2