So we want to make a game in which we move a balloon with an Android phone's accelerometer. There will be obstacles probably created using ImageView's that cause the balloon to pop when they collide. So far, we got the ImageView balloon to move, but it seems as though the drawable (called yellow_balloon) is only able to be seen INSIDE the ImageView balloon's layout.
The java code:
package com.ec327.ballon;
import android.os.Bundle;
import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.util.Log;
public class GameScreen extends Activity implements SensorEventListener {
private ImageView balloon=null;
final String tag = "AccLogger";
SensorManager sensore=null;
int xa=0;
int ya=0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game_screen);
sensore = (SensorManager) getSystemService(SENSOR_SERVICE);
balloon = (ImageView)findViewById(R.id.balloon);
balloon.scrollTo(xa, ya);
//balloon.offsetLeftAndRight(50);
//balloon.offsetTopAndBottom(50);
//balloon.setImageResource(R.drawable.yellow_balloon);
}
public void onSensorChanged(SensorEvent event){
Sensor sensor = event.sensor;
float [] values = event.values;
synchronized (this) {
Log.d(tag, "onSensorChanged: " + sensor + ", x: " +
values[0] + ", y: " + values[1] + ", z: " + values[2]);
if (sensor.getType() == Sensor.TYPE_ACCELEROMETER ) {
xa=(int)values[0];// this part of code is only test to see int x and y on Activity
ya=(int)values[1];
}
}
balloon.scrollBy(xa, ya);
balloon.invalidate();
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.d(tag,"onAccuracyChanged: " + sensor + ", accuracy: " + accuracy);
}
protected void onResume() {
super.onResume();
Sensor Accel = sensore.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// register this class as a listener for the orientation and accelerometer sensors
sensore.registerListener((SensorEventListener) this, Accel, SensorManager.SENSOR_DELAY_FASTEST);
}
}
The XML code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#drawable/blank_screen">
<ImageView
android:id="#+id/balloon"
android:layout_width="41dp"
android:layout_height="142dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:src="#drawable/yellow_balloon" />
</RelativeLayout>
In the xml file, ImageView balloon has a layout_width=41dp and layout_height=142dp. When the program runs, the yellow_balloon moves around, but once it moves outside the layout dimensions of ImageView balloon, it can't be seen anymore.
Does scrollBy() only move the drawable yellow_balloon and not the actual balloon layout's position? We've tried using that as well as layout.setMargins, but neither are working. We've also tried using setOffsetTopAndBottom and setOffsetLeftAndRight, but that does not move the ImageView of the balloon. What's causing this problem and how do we fix it?
I am not entirely sure using imageViews is the easiest or most efficient way of doing what you are doing, especially if you want to create obstacles for the balloon to collide into and pop.
My suggestion would be that your game is done entirely inside of a SurfaceView. From here you would be able to add in "Sprites" which would be all of your objects (your Balloon, your obstacles) and you can have it check for sprite collisions (your cases where the balloon would pop).
The problem with imageviews is that you are going to need to move the entire image and once you are introducing multiple images, unless the box around the image is entirely full with the picture you will begin to get picture overlaps or collisions before the actual collision occurs.
Related
I am creating an app in which a user clicks on a Button to input a selected image to an ImageView. Initially, the image is correctly cropped at 50% from the right, as shown in below:
Then, the user can drag the slider to the left to reveal less of the image, or right to reveal more of the image. However, when the user moves the slider, the app crashes, returning the following Null object error on the line of code where I set the ImageView's background to the newly cropped image based on the slider's progress (singlePreviewBox.setBackground(mClipDrawable);).
java.lang.NullPointerException: Attempt to invoke virtual method 'int
android.graphics.drawable.Drawable.getOpacity()' on a null object
reference at
android.graphics.drawable.ClipDrawable.getOpacity(ClipDrawable.java:159)
at android.view.View.computeOpaqueFlags(View.java:15698) at
android.view.View.setBackgroundDrawable(View.java:20502) at
android.view.View.setBackground(View.java:20395) at
com.example.changingimageviewwidth.MainActivity$1.onProgressChanged(MainActivity.java:49)
This seems strange, considering that the code performing the initial 50% crop from the right works, and I used that same code in my slider listener.
Below is my code:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/transition_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<Button
android:id="#+id/photo_input_1"
android:onClick="chooseFile"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:fontFamily="sans-serif"
android:text="No Photo"
android:textAllCaps="false"
android:textSize="16sp"
android:background="#f3f3f3"
android:elevation="4dp"
android:layout_margin="4dp"
android:minHeight="40dp"/>
<SeekBar
android:id="#+id/image_reveal_slider"
style="#style/Widget.AppCompat.SeekBar.Discrete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="10"
android:progress="5" />
<ImageView
android:id="#+id/image_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="370dp"
android:adjustViewBounds="true"/>
</LinearLayout>
MainActivity.java:
package com.example.changingimageviewwidth;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Toast;
import java.io.InputStream;
public class MainActivity extends Activity
{
// Request code used when reading in a file
private Integer READ_IN_FILE = 0;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SeekBar imageRevealSlider = (SeekBar) findViewById(R.id.image_reveal_slider);
// perform seek bar change listener event used for getting the progress value
imageRevealSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
int progressChangedValue = 0;
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
progressChangedValue = progress;
ImageView singlePreviewBox = findViewById(R.id.image_container);
singlePreviewBox.invalidate();
ClipDrawable mClipDrawable = new ClipDrawable(singlePreviewBox.getDrawable(), 11,
ClipDrawable.HORIZONTAL);
mClipDrawable.setLevel((progress / 10) * 10000);
singlePreviewBox.setImageResource(0);
singlePreviewBox.setBackground(mClipDrawable);
}
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
public void onStopTrackingTouch(SeekBar seekBar) {
Toast.makeText(MainActivity.this, "Seek bar progress is :" + progressChangedValue,
Toast.LENGTH_SHORT).show();
}
});
}
public void chooseFile(View v) {
// Specify that only photos are allowed as inputs (.jpg, .png).
Intent photoInputSpecs = new Intent(Intent.ACTION_GET_CONTENT);
photoInputSpecs.setType("image/*");
Intent photoInputHandler = Intent.createChooser(photoInputSpecs, "Choose a file");
startActivityForResult(photoInputHandler, READ_IN_FILE);
}
// Processes the results of Activities.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
// Processes the photos that the user selected
if (requestCode == READ_IN_FILE) {
// Retrieve the photo's location
Uri photoUri = data.getData();
InputStream photoInputStream = getContentResolver().openInputStream(photoUri);
System.out.println("[photoInputStream.available()] = " + photoInputStream.available());
Bitmap photoBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(),
photoUri);
// Display the selected image in the preview box
displayPhoto(photoUri);
}
}
catch (Exception ex) {
System.out.println("onActivityResult error: " + ex.toString());
}
}
// Displays a specified photo in the split and single screen preview boxes.
private void displayPhoto(Uri photoUri) {
ImageView singlePreviewBox = findViewById(R.id.image_container);
singlePreviewBox.invalidate();
singlePreviewBox.setImageURI(photoUri);
ClipDrawable mClipDrawable = new ClipDrawable(singlePreviewBox.getDrawable(), 11,
ClipDrawable.HORIZONTAL);
mClipDrawable.setLevel(5000);
singlePreviewBox.setImageResource(0);
singlePreviewBox.setBackground(mClipDrawable);
}
}
I noticed several problems with your code.
Null Pointer Exception
If your ImageView has no image, your app will crash. This is something that you need to check beforehand.
Math
mClipDrawable.setLevel((progress / 10) * 10000); Division between integers might causes a faction loss. So you either have to cast one of them or both; like this one.
mClipDrawable.setLevel((int)((progress / 10F) * 10000F));
The Use of Methods
singlePreviewBox.setImageResource(0); You're setting an image to your ImageView, soon after you're setting it with a different method. You don't even need this method by the way. Even the use of this method is incorrect. It supposed to look like this:
singlePreviewBox.setImageResource(R.drawable.myImage);
I am creating an app in Android where I have a button that displays silly phrases when pressed. I figured out how to get it to say two phrases but I can't figure out how to make it do more. I also want to add in a feature to have the button pick phrases at random instead of going in the order I set. I am thinking of using an arraylist for this. How do I set the button to cycle through different methods in Andrido Studio?
I also want you to know that I am a beginner so please don't be too hard on me.
This is my XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/funny_imageview"
android:scaleType="fitCenter" />
<TextView
android:id="#+id/funny_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text=""/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/funny_sayings_button"
android:text="Click Me!"
android:layout_marginTop="8dp"
android:onClick="changeFunnySayingsButton"/>
</LinearLayout>
This is my Java:
package com.example.android.funnysayingsbutton;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
boolean clicked = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* This method changes the funny text and image on the screen when the button is clicked.
*/
public void changeFunnySayingsButton (View view){
if (clicked){
funnySayingOne();
clicked = false;
}
else {
funnySayingTwo();
clicked = true;
}
}
/**
* This method is for the first funny expression.
*/
public void funnySayingOne(){
displayAnswer("Time to Monkey Around");
displayImage(R.drawable.monkey);
}
/**
* This method is for the second funny expression.
*/
public void funnySayingTwo(){
displayAnswer("Penguin Party Time");
displayImage(R.drawable.penguin);
}
/**
* This method is for the third funny expression.
*/
public void funnySayingThree(){
displayAnswer("It's Rabbit Season.");
displayImage(R.drawable.rabbit);
}
/**
* This method displays the funny text on the screen.
*/
private void displayAnswer(String answer) {
TextView questionTextView = (TextView) findViewById(R.id.funny_textview);
questionTextView.setText(answer);
}
/**
* This method displays the funny image on the screen.
*/
private void displayImage(int picture) {
ImageView questionTextView = (ImageView) findViewById(R.id.funny_imageview);
questionTextView.setImageResource(picture);
}
}
I think that you're on the right track thinking about using a data structure like an ArrayList.
I'd start by making a class to represent each funny thing, e.g.
public class FunnyThing {
private String phrase; // I'd use a resource ID here
private int drawableResourceId;
}
Then it's a matter of initializing the set of FunnyThing objects and storing them in your data structure. You could do this in the onCreate method of your activity, but you may want to consider writing a factory method somewhere else (e.g. the FunnyThing class) that your activity just calls to keep the code from getting cluttered when there are more than 2-3 funny things.
Finally, on each button press you can choose a random index in your collection of FunnyThings and display that, e.g.
private void displayRandomFunnyThing() {
// assumes you have a java.util.Random instance vairable named mRandom
FunnyThing ft = mFunnyThings.get( mRandom.nextInt( mFunnyThings.size());
displayAnswer( ft.phrase );
displayImage( ft.drawableResourceId);
}
Of course you'll need to add an onClickListener to your button, but I'm assuming that you left that code out for brevity.
You'll also have the problem that the phrase selection is random, so you may get the same phrase many times before you see all of them. If you want to prevent duplicates, well, that will take more code.
First of all, where is your button onClick listener? This is where you should add your methods.
Button button = (Button)this.findViewById(R.id.funny_sayings_button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
changeFunnySayingsButton (v);
}
});
To continue with you could create an ArrayList of Strings. You add all of your strings and then simply pick one at random from the list. Hint:
//will choose a random number between 0 and the length of the list - 1
int i = rand.nextInt(yourList.size());
String yourFunnyString=yourList.get(i);
If you also want to display an image alongside with the String you should also create a map. Use the String as a key. When you select from yourList then use this key to select the value (your image) from the map.
It should be fairly straightforward to do.
I record a video from Samsung Galaxy S4 (1080wx1920h) from FRONT CAMERA.
The resulting video is rotated 90° and upside down. (See the picture)
Then I take the video (final resolution 320wx240h) and I display it to TextureView with:
textureView.setRotation(90.0f);
textureView.setScaleX(-1);
and I set the layout parameters of the textureView to:
ViewGroup.LayoutParams params = textureView.getLayoutParams();
params.height = 1440;
params.width = 1080;
textureView.setLayoutParams(params);
The result looks like:
After several retries I figgured that if I set layout to:
params.height = 810;
params.width = 1080;
The dimensions ration remains correct:
Finally I would like to display the video as it was recorded in RecordingActivity (1080wx1440h):
Any thoughts on how to accomplish this?
Or is there a way how to record the video from front camera in correct rotation?
Full activity code:
import android.app.Activity;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.TextureView;
import android.view.ViewGroup;
public class ReplayActivity extends Activity implements TextureView.SurfaceTextureListener {
private String pathToVideo;
private TextureView textureView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_replay);
textureView = (TextureView)findViewById(R.id.texture_view);
textureView.setSurfaceTextureListener(this);
textureView.setRotation(90.0f);
textureView.setScaleX(-1);
pathToVideo = getIntent().getStringExtra("path");
}
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setSurface(new Surface(surface));
try {
mediaPlayer.setDataSource(pathToVideo);
mediaPlayer.prepare();
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);//x = 1080, y = 1920
Point videoDimensions = new Point(mediaPlayer.getVideoWidth(),mediaPlayer.getVideoHeight());//x = 320, y = 240
Point resultParams = VideoHelpers.getScaledDimension(new Point(videoDimensions.y * 1000, videoDimensions.x * 1000), size);//x = 1080, y = 1440
ViewGroup.LayoutParams params = textureView.getLayoutParams();
params.height = resultParams.y;//1440
params.width = resultParams.x;//1080
textureView.setLayoutParams(params);
mediaPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {return false;}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}
}
XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000">
<TextureView
android:id="#+id/texture_view"
android:layout_width="wrap_content"
android:background="#000000"
android:layout_height="wrap_content" />
</RelativeLayout>
Playing with the camera, the previews, the textures, adn the matrix can be tricky.
Have in mind that the natural orientation of the camera is landscape, so if you dont do anything else, the resulting video will be in that orientation. Also, the preview frames will be landscape.
when you do textureView.setRotation(90.0f); and textureView.setScaleX(-1); you are only modifying the internal transformation matrix of the texture, this is, when something is drawed inside, a transformation occurs, and what you see is different of the actual content. This is fine, but actually the camera dont know anything about this rotation and neither the mediarecorder.
If you are using Mediarecorder You should take a look at
MediaRecorder.html#setOrientationHint(int)
Sets the orientation hint for output video playback. This method
should be called before prepare(). This method will not trigger the
source video frame to rotate during video recording, but to add a
composition matrix containing the rotation angle in the output video
if the output format is OutputFormat.THREE_GPP or OutputFormat.MPEG_4
so that a video player can choose the proper orientation for playback.
Note that some video players may choose to ignore the compostion
matrix in a video during playback.
Parameters
degrees the angle to be rotated clockwise in degrees. The
supported angles are 0, 90, 180, and 270 degrees.
If you are using other recording approach, probably you shold take a look at
Camera.Parameters.html#setRotation(int)
or
Camera.html#setDisplayOrientation(int)
I am using Motorola Xoom with WiFi for application development purpose.I have tried to implement code to create speedometer using accelerometer but the only problem I am facing is that JAVA compiler is deprecating "Sensorlistener". I need some help to fix it. My using following codes for JAVA and .xml layout
JAVA
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Context;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;
public class Speedometer extends Activity {
Handler handler = new Handler();
SensorManager sensorManager;
TextView myTextView;
float appliedAcceleration = 0;
float currentAcceleration = 0;
float velocity = 0;
Date lastUpdate;
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
myTextView = (TextView)findViewById(R.id.myTextView);
lastUpdate = new Date(System.currentTimeMillis());
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
sensorManager.registerListener(sensorListener, SensorManager.SENSOR_ACCELEROMETER, SensorManager.SENSOR_DELAY_FASTEST);
Timer updateTimer = new Timer("velocityUpdate");
updateTimer.scheduleAtFixedRate(new TimerTask() {
public void run() {
updateGUI();
}
}, 0, 1000);
}
private void updateGUI() {
// Convert from meters per second to miles per hour.
final double mph = (Math.round(100*velocity / 1.6 * 3.6))/100;
// Update the GUI
handler.post(new Runnable() {
public void run() {
myTextView.setText(String.valueOf(mph) + "mph");
}
});
}
private void updateVelocity() {
// Calculate how long this acceleration has been applied.
Date timeNow = new Date(System.currentTimeMillis());
long timeDelta = timeNow.getTime()-lastUpdate.getTime();
lastUpdate.setTime(timeNow.getTime());
// Calculate the change in velocity at the
// current acceleration since the last update.
float deltaVelocity = appliedAcceleration * (timeDelta/1000);
appliedAcceleration = currentAcceleration;
// Add the velocity change to the current velocity.
velocity += deltaVelocity;
}
private final SensorListener sensorListener = new SensorListener() {
double calibration = Double.NaN;
public void onSensorChanged(int sensor, float[] values) {
double x = values[SensorManager.DATA_X];
double y = values[SensorManager.DATA_Y];
double z = values[SensorManager.DATA_Z];
double a = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));
if (calibration == Double.NaN)
calibration = a;
else {
updateVelocity();
currentAcceleration = (float)a;
}
}
public void onAccuracyChanged(int sensor, int accuracy) {}
};
}
.XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="#+id/myTextView"
android:gravity="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textStyle="bold"
android:textSize="40sp"
android:text="CENTER"
android:editable="false"
android:singleLine="true"
android:layout_margin="10px"/>
/>
</LinearLayout>
I will be grateful for helpful suggestions.
The documentation spells it out, just use SensorEventListener instead.
In your xml file you wrote twice /> for closing the TextView tag
android:layout_margin="10px"/>
/>
</LinearLayout>
just delete one of them.
Scope of the project
When a user touches the Android screen with two fingers, draw a "Frame" at each touch location with a "cursor" for each frame. Each frame is a custom slider that the cursor will move up and down. All the way up will be 100%, middle will be 0% and all the way down will be -100%. This will be used to control small motors, similar to tank turning, each touch controls a separate motor (sending signals over bluetooth). After a two touch and everything is drawn, I want to be able to lift off either finger, BUT keep the cursor at what ever location it was last at, while the other finger is free to move its cursor. When the last finger is lifted off, everything "hides" and resets to 0%.
Functionality Wanted
On two finger touch, draw separate .pngs under the touch location
After the frames and cursors are drawn, keep track of where they are relative to the frame to determine the percentage.
If a finger is lifted off, keep that fingers cursor at last known location, but the other finger can move it's cursor. Also if the finger is put back down it should be able to move its cursor again.
If both fingers are lifted off of the screen, hide everything and reset percentages to 0%
Functionality Obtained
I can draw the frames and cursors on multitouch
Positions and percentages work fine
Cursors do move properly
What doesn't work
I am unsure if I should have one custom class that handles both touch event or if i should have 2 instances of the custom class each handling their own touch events (I have tried both, the only way i get any "real" functionality is with 1 custom class handling both touch events, the other way doesn't work as intended)
When I only have 1 custom class, It works great, but I have it "hide" everything if both fingers are not on the screen, and sometimes android registers that I have lifted a finger off the screen and this causes me a lot of issues when the frames hide then re appear in a different location
When I use 2 custom classes I touch each custom class would have its own touch event, and i wouldn't have to worry about multitouch if i split the classes evenly between the screen. This was not the case, still need to deal with multitouch
Can someone explain to me how android handles their touch events. from what I have done, it seems if i lay down finger 1, the finger 2, the first finger will register a "ACTION_DOWN" and the second will register a "ACTION_POINTER_2_DOWN", BUT if i life off my first finger, my second finger is "demoted" and now all of the events my second finger registers does not related to "ACTION_POINTER_2" and instead will be "ACTION_DOWN, ACTION_UP, etc". Is this correct?
TouchUI.java
package com.robota.android;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ImageView;
public class TouchUI extends ImageView {
public static final String LEFT_TOUCHUI = "com.robota.android:id/leftTouchUI";
public static final String RIGHT_TOUCHUI = "com.robota.android:id/rightTouchUI";
private String whoAmI = new String();
private MyPoints framePts = new MyPoints();
private MyPoints cursorPts = new MyPoints();
private Bitmap frame;
private Bitmap cursor;
private int frameWidth;
private int frameHeight;
private int cursorHeight;
private boolean pointerDown = false;
private int dy;
public TouchUI(final Context context, final AttributeSet as){
super(context, as);
Log.d("TouchUI", getResources().getResourceName(this.getId()));
whoAmI = new String(getResources().getResourceName(this.getId()));
if(whoAmI.equals(LEFT_TOUCHUI)){
frame = BitmapFactory.decodeResource(getResources(), R.drawable.tank_left);
}else if(whoAmI.equals(RIGHT_TOUCHUI)){
frame = BitmapFactory.decodeResource(getResources(), R.drawable.tank_right);
}
cursor = BitmapFactory.decodeResource(getResources(), R.drawable.cursor);
frameWidth = frame.getWidth();
frameHeight = frame.getHeight();
cursorHeight = cursor.getHeight();
}
public void determinePointers(int x, int y){
framePts.setOrigin(x-frameWidth/2, y-frameHeight/2);
cursorPts.setOrigin(x-frameWidth/2, y-frameHeight/2);
}
#Override
public boolean onTouchEvent(MotionEvent e){
int x = 0;
int y = 0;
Log.d("TouchUI", ">>>>> " + whoAmI);
if(e.getAction() == MotionEvent.ACTION_DOWN){
determinePointers(x,y);
pointerDown = true;
}else if(e.getAction() == MotionEvent.ACTION_UP){
pointerDown = false;
}else if(e.getAction() == MotionEvent.ACTION_MOVE){
dy = (int)e.getY()-framePts.getY();
if(dy <= 0){
dy=0;
}else if(dy+cursorHeight/2 >= frameHeight){
dy=frameHeight;
}
sendMotorSpeed(dy);
}
return true;
}
public void sendMotorSpeed(int dy){
float motor = dy;
motor-=frameHeight;
motor*=-1;
motor = (motor/frameHeight)*255;
PacketController.updateMotorSpeeds(whoAmI, (int)motor);
}
public void onDraw(Canvas canvas){
if(pointerDown){//twoDown){
canvas.drawBitmap(frame, framePts.getX(), framePts.getY(), null);
canvas.drawBitmap(cursor, cursorPts.getX(), (cursorPts.getY()+dy), null);
}
invalidate();
}
private class MyPoints{
private int x = -100;
private int y = -100;
private int deltaY = 0;;
public MyPoints(){
this.x = 0;
this.y = 0;
}
public int getX(){
return this.x;
}
public int getY(){
return this.y;
}
public void setOrigin(int x, int y){
this.x = x;
this.y = y;
}
public int getDeltaY(){
return deltaY;
}
public void setDeltaY(int newY){
deltaY = (newY-y);
Log.d("TouchUI", "DY: " + deltaY);
}
}
}
Main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/parentLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.robota.android.TouchUI xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/leftTouchUI"
android:background="#0000"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_weight="1">
</com.robota.android.TouchUI>
<com.robota.android.TouchUI xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rightTouchUI"
android:background="#0000"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_weight="1">
</com.robota.android.TouchUI>
</LinearLayout>
RobotController.java (Main Activity Class)
package com.robota.android;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
public class RobotController extends Activity {
// Tag used to keep track of class in the Log
private static final String TAG = "robotController_new";
// Boolean to debugging
private static final boolean D = true;
// Intent request codes
private static final int DISCONNECT_DEVICE = 1;
private static final int CONNECT_DEVICE = 2;
private static final int REQUEST_ENABLE_BT = 3;
// Handler Codes
public static final int MESSAGE_READ = 1;
public static final int MESSAGE_WRITE = 2;
// Local Bluetooth Adapter
private BluetoothAdapter bluetoothAdapter = null;
// Bluetooth Discovery and Datahandler
private BluetoothComm btComm = null;
// Debug's TextView, this is where strings will be written to display
private TextView tv;
private ScrollView sv;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
if(D) Log.d(TAG, "++ON CREATE++");
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(bluetoothAdapter == null){
if(D) Log.d(TAG, "NO BLUETOOTH DEVICE");
Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_SHORT).show();
finish();
return;
}
PacketController.controller = this;
}
public void onStart(){
super.onStart();
if(D) Log.d(TAG, "++ON START++");
if(!bluetoothAdapter.isEnabled()){
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}else{
// Start BluetoothComm
if(btComm == null){
setupComm();
}
}
}
/**
* Creates new Bluetooth Communication
*/
private void setupComm(){
if(D) Log.d(TAG, "+++setupComm+++");
btComm = new BluetoothComm(this, handler);
}
private void connectDevice(Intent data){
if(D) Log.d(TAG, "+++connectDevice+++");
String addr = data.getExtras()
.getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(addr);
if(D) Log.d(TAG,"REMOTE ADDR: "+ addr);
btComm.connect(device);
}
private void disconnectDevice(){
if(D) Log.d(TAG, "---disconnectDevice---");
if(btComm.getState() == btComm.STATE_CONNECTED){
btComm.disconnect();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
//super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent serverIntent = null;
switch(item.getItemId()){
case R.id.insecure_connect_scan:
// Launch the DeviceListActivity to see devices and do scan
serverIntent = new Intent(this, DeviceListActivity.class);
try{
startActivityForResult(serverIntent, CONNECT_DEVICE);
}catch(ActivityNotFoundException activityNotFound){
Log.e(TAG, "Could not start DeviceListActivity(Insecure)");
}
return true;
}
return false;
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
switch(requestCode){
case CONNECT_DEVICE:
if(resultCode == Activity.RESULT_OK){
connectDevice(data);
}
break;
case DISCONNECT_DEVICE:
if(resultCode == Activity.RESULT_OK){
disconnectDevice();
}
break;
}
}
public Handler getHandler(){
return this.handler;
}
public BluetoothComm getBtComm(){
return this.btComm;
}
// The Handler that gets information back from the BluetoothChatService
private final Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
if(D) Log.d(TAG, "check message");
switch (msg.what) {
case MESSAGE_READ:
if(D) Log.d(TAG, "trying to read message");
byte[] readBuf = (byte[]) msg.obj;
// construct a string from the valid bytes in the buffer
String readMessage = new String(readBuf, 0, msg.arg1);
if(D) Log.d(TAG, "bytes: " + readBuf + " arg1: " + msg.arg1 + " Message: " + readMessage);
tv.append(readMessage);
break;
case MESSAGE_WRITE:
if(D) Log.d(TAG, "trying to send message");
String sendMessage = new String(String.valueOf(msg.obj));
}
}
};
}
Any other classes not listed I didn't believe needed to be, but if they are needed please let me know.
Any help is much appreciated
You're going to need to save the pointerId's of each point and compare them to the new Id's given with each MotionEvent. It's slightly tricky to explain, so I'll point you to this ADB Post that explains it much better than I could. Long story short? Multitouch can be tricksy, but it's not as bad as it looks at first glance.