Cannot get the invalidate statement to work - java

package com.game.crazyeights;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.View;
import com.game.crazyeights.R;
public class TitleView extends View {
private Bitmap titleGraphic;
private Bitmap playButtonUp;
private Bitmap playButtonDown;
private boolean playButtonPressed;
private int screenH;
private int screenW;
public TitleView(Context context) {
super(context);
titleGraphic = BitmapFactory.decodeResource(getResources(), R.drawable.titlegraphic);
playButtonUp = BitmapFactory.decodeResource(getResources(), R.drawable.play_button_up);
playButtonDown = BitmapFactory.decodeResource(getResources(), R.drawable.play_button_down);
}
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
screenW = w;
screenH = h;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(titleGraphic, (screenW-titleGraphic.getWidth())/2 , 0, null);
if (playButtonPressed) {
canvas.drawBitmap(playButtonDown, (screenW-playButtonUp.getWidth())/2, (int)(screenH*0.7), null);
} else {
canvas.drawBitmap(playButtonUp, (screenW-playButtonUp.getWidth())/2, (int)(screenH*0.7), null);
}
}
public boolean onTouchedEvent(MotionEvent event) {
int eventaction = event.getAction();
int X = (int)event.getX();
int Y = (int)event.getY();
switch (eventaction) {
case MotionEvent.ACTION_DOWN:
if (X > (screenW-playButtonUp.getWidth())/2 && X < (screenW-playButtonUp.getWidth())/2 + playButtonUp.getWidth() && Y > (int) (screenH*0.7) && Y < (int) (screenH*0.7) + playButtonUp.getHeight()) {
playButtonPressed = true;
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
playButtonPressed = false;
break;
}
invalidate();
return true;
}
}
Basically, I tried to put a invalidate at the end to redraw the screen to update the buttons state. What I mean by that is when the button is pressed down on it has a picture of it in a different state I wanna update to but, it doesn't do that when I use the invalidate. And when the user lets go of the button it updates back to the regular button state image..
If you didn't understand what I said above ill try to simplify it. I want the button to update its image when pressed down on, and to go back to its previous image when you stop pressing the button. I tried using invalidate but, it didn't work. Any idea here?

You should not place invalidate() at the end of the method. OnTouchedEvent() is fired every time you touch or move within that View.
Make sure that case within MotionEvent.ACTION_DOWN is called. I mean those boolean expressions are valid. Then call invalidate() at the end of that case.
Also I this this method is cumbersome and unnecessary. Instead you can use ImageView.
Good luck!

Related

How to bring floating action button outside of the Android app? [closed]

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 2 years ago.
Improve this question
I want to create an android app that can translate any text on the screen of the user. It should work throughout the Android Phone on all apps. I want to bring a floating action button outside the app and make it run as a visible, moveable, and clickable background services.
The floating button will take the copied text and then translate it using google translate API.
Please help me, I need some guidance and resource.
Please check below Java and XML file for floating action button
JAVA
package com.example;
import android.content.Context;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
public class MovableFloatingActionButton extends FloatingActionButton implements View.OnTouchListener {
private final static float CLICK_DRAG_TOLERANCE = 10; // Often, there will be a slight, unintentional, drag when the user taps the FAB, so we need to account for this.
private float downRawX, downRawY;
private float dX, dY;
public MovableFloatingActionButton(Context context) {
super(context);
init();
}
public MovableFloatingActionButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MovableFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOnTouchListener(this);
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent){
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)view.getLayoutParams();
int action = motionEvent.getAction();
if (action == MotionEvent.ACTION_DOWN) {
downRawX = motionEvent.getRawX();
downRawY = motionEvent.getRawY();
dX = view.getX() - downRawX;
dY = view.getY() - downRawY;
return true; // Consumed
}
else if (action == MotionEvent.ACTION_MOVE) {
int viewWidth = view.getWidth();
int viewHeight = view.getHeight();
View viewParent = (View)view.getParent();
int parentWidth = viewParent.getWidth();
int parentHeight = viewParent.getHeight();
float newX = motionEvent.getRawX() + dX;
newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent
newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent
float newY = motionEvent.getRawY() + dY;
newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent
newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent
view.animate()
.x(newX)
.y(newY)
.setDuration(0)
.start();
return true; // Consumed
}
else if (action == MotionEvent.ACTION_UP) {
float upRawX = motionEvent.getRawX();
float upRawY = motionEvent.getRawY();
float upDX = upRawX - downRawX;
float upDY = upRawY - downRawY;
if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { // A click
return performClick();
}
else { // A drag
return true; // Consumed
}
}
else {
return super.onTouchEvent(motionEvent);
}
}
}
XML
<com.example.MovableFloatingActionButton
android:id="#+id/btnFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_navigate_next_white_24dp"/>

onProgressChanged being run even when there is no progress change

I have an android app with two seekbars, and I've set them up so that the app doesn't respond unless both of them are pressed. My problem is that when they are both pressed down, the method onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) starts continuously running in an infinite loop. This happens even though I am holding my fingers completely still on the bars (which means that the progress is not changing on either of the bars, and therefore the onProgressChanged method shouldn't be being called). Does anyone have any insights as to why this could be happening?
Here is some relevant code:
void setupLRSpeedBars(final int UIID, final Bool bar1, final Bool bar2) {
SeekBar sb = (SeekBar) findViewById(UIID);
sb.setProgress(9);
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (bar1.isTrue()) {
progress -= 9;
transmit((UIID == R.id.leftSpeedBar ? "L" : "R") +
(progress >= 0 ? "+" : "") +
Integer.toString(progress));
}
}
public void onStartTrackingTouch(SeekBar seekBar) {
bar2.set(true);
}
public void onStopTrackingTouch(SeekBar seekBar) {
bar2.set(false);
seekBar.setProgress(9);
//transmit((UIID == R.id.leftSpeedBar ? "L" : "R") + "+0");
transmit("s");
}
});
}
Also note that I've used a custom Bool class instead of a primitive boolean, in order for me to get around the restriction that all variables outside of the OnSeekBarChangeListener be marked final. (I need to send information between the onSeekBarChangeListeners in order to work out whether the user is holding their fingers down on both seekbars at the same time)
I have a hunch that this might be the cause of my problems, but I don't see how.
class Bool {
private boolean bool;
public Bool () {}
public Bool(boolean bool) { this.bool = bool; }
public void set (boolean bool) { this.bool = bool; }
public boolean get () { return bool; }
public boolean isTrue () { return bool; }
}
EDIT:
I'll also note that I'm using a custom, vertical seekbar. Here is the code:
package android.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class VerticalSeekBar extends SeekBar {
private OnSeekBarChangeListener myListener;
public VerticalSeekBar(Context context) {
super(context);
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public VerticalSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
#Override
public void setOnSeekBarChangeListener(OnSeekBarChangeListener mListener){
this.myListener = mListener;
}
#Override
public synchronized void setProgress(int progress){
super.setProgress(progress);
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(myListener!=null)
myListener.onStartTrackingTouch(this);
break;
case MotionEvent.ACTION_MOVE:
setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
onSizeChanged(getWidth(), getHeight(), 0, 0);
myListener.onProgressChanged(this, getMax() - (int) (getMax() * event.getY() / getHeight()), true);
break;
case MotionEvent.ACTION_UP:
myListener.onStopTrackingTouch(this);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
}
I think that the problem is in the onTouchEvent implementation of your VerticalSeekBar because you are processing every MotionEvent.ACTION_MOVE received.
From the documentation:
A new onTouchEvent() is triggered with an ACTION_MOVE event whenever the current touch contact position, pressure, or size changes. As described in Detecting Common Gestures, all of these events are recorded in the MotionEvent parameter of onTouchEvent().
Because finger-based touch isn't always the most precise form of interaction, detecting touch events is often based more on movement than on simple contact. To help apps distinguish between movement-based gestures (such as a swipe) and non-movement gestures (such as a single tap), Android includes the notion of "touch slop." Touch slop refers to the distance in pixels a user's touch can wander before the gesture is interpreted as a movement-based gesture. For more discussion of this topic, see Managing Touch Events in a ViewGroup.
That is, you think that your fingers are completely still but your seek bars are receiving ACTION_MOVE events.
In your case, the "touch slop" approximation is now a good idea because the calculated touch slop is huge for your purposes, as touch slop is defined as:
"Touch slop" refers to the distance in pixels a user's touch can wander before the gesture is interpreted as scrolling. Touch slop is typically used to prevent accidental scrolling when the user is performing some other touch operation, such as touching on-screen elements.
To solve your problem you can calculate the distance between the last managed position and the current one to trigger your onProgressChanged:
private static final float MOVE_PRECISION = 5; // You may want to tune this parameter
private float lastY;
// ...
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = event.getY();
if (myListener != null)
myListener.onStartTrackingTouch(this);
break;
case MotionEvent.ACTION_MOVE:
if (calculateDistanceY(event) > MOVE_PRECISION) {
setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
onSizeChanged(getWidth(), getHeight(), 0, 0);
myListener.onProgressChanged(this, getMax() - (int) (getMax() * event.getY() / getHeight()), true);
lastY = event.getY();
}
break;
case MotionEvent.ACTION_UP:
myListener.onStopTrackingTouch(this);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
private float calculateDistanceY (MotionEvent event) {
return Math.abs(event.getY() - lastY);
}

How to tell if an X and Y coordinate are inside my button?

I have managed, with great difficulty, to make a bitmap overlay the screen. I can also get touch input, however it gets touch input for EVERYWHERE on the screen.
I want to know how I would be able to check if the touch was on my bitmap, which is visible on the screen.
The service and view class is below. I have thought and thought, but I couldn't think of a way to do it :(
package <package>;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Toast;
public class MyService extends Service {
ButtonView mView;
Bitmap bit;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
bit = BitmapFactory.decodeResource(getResources(), R.drawable.button);
NotificationCompat.Builder builder = new NotificationCompat.Builder(
this);
builder.setContentTitle("Ingress Tools Running");
builder.setContentText("Click to stop Ingress Tools");
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(
this, StopActivity.class), 0));
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1, builder.build());
mView = new ButtonView(this, bit);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.FILL_PARENT,
WindowManager.LayoutParams.FILL_PARENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.RIGHT;
params.setTitle("Load Average");
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.addView(mView, params);
}
#Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(getBaseContext(), "onDestroy", Toast.LENGTH_LONG).show();
if (mView != null) {
((WindowManager) getSystemService(WINDOW_SERVICE))
.removeView(mView);
mView = null;
}
}
}
class ButtonView extends ViewGroup {
private Paint mLoadPaint;
private Rect r;
private Bitmap bit;
public ButtonView(Context context, Bitmap bit) {
super(context);
Toast.makeText(context, "HUDView", Toast.LENGTH_LONG).show();
mLoadPaint = new Paint();
mLoadPaint.setAntiAlias(true);
mLoadPaint.setTextSize(10);
mLoadPaint.setARGB(255, 255, 0, 0);
r = new Rect();
r.set(380, 134, 468, 213);
this.bit = bit;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bit, 100, 100, null);
}
#Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int area = bit.getWidth() * bit.getHeight();
//if (event.getY() <= maxY && event.getX() <= maxX) {
Toast.makeText(getContext(), "Open tools: ", Toast.LENGTH_LONG)
.show();
//}
return true;
}
}
this works for any view
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (inViewInBounds(myButtonView, (int) event.getRawX(), (int) event.getRawY())) {
// User moved outside bounds
Log.e("dispatchTouchEvent", "you touched inside button");
} else {
Log.e("dispatchTouchEvent", "you touched outside button");
}
}
return super.dispatchTouchEvent(event);
}
Rect outRect = new Rect();
int[] location = new int[2];
private boolean inViewInBounds(View view, int x, int y) {
view.getDrawingRect(outRect);
view.getLocationOnScreen(location);
outRect.offset(location[0], location[1]);
return outRect.contains(x, y);
}
Consider using FrameLayout (or any other subclass of ViewGroup) instead of ViewGroup directly. Because your current implementation of onLayout method is not correct, which will lead you to problems with displaying of child views.
Now, closer to your question. You should ininitialize Rect and just store left, top, right and bottom position of your Bitmap. As I can see, currently you're initialized r variable, but not using it anywhere.
So, you can initialize it like this:
r = new Rect(100, 100, 100 + bit.getWidth(), 100 + bit.getHeight());
Now in onTouchEvent you can just check:
r.contains((int) event.getX(), (int) event.getY());
Rect rect = new Rect();
getHitRect(rect);
if (rect.contains((int) event.getX(), (int) event.getY())) {}
you can use getHitRect(Rect). it returns the Hit rectangle in parent's coordinates. Here the documentation
Use if statement with method if(r.contains(x, y)) on that button which you want to check. This method will return true, when x and y point is inside rectangle r. You can also make public method within that class, so you can access it outside ButtonView class with button object reference.
// We assume MotionEvent is from the direct View parent so we are in the same co-ordindate space
fun View.isWithinBounds(event: MotionEvent): Boolean {
val rect = Rect(x.roundToInt(), y.roundToInt(), (x + width).roundToInt(), (y + height).roundToInt())
return rect.contains(event.x.roundToInt(), event.y.roundToInt())
}
When the "touch event" happens, it's going through all view's tree.
F.e. if you have Linear layout and ImageView on it and user touchs the screen on ImageView, then touch event intercepts and firstly it'll be handled at LinearLayour and then at the ImageView.
If you want to block event f.e. on the bitmap, then you should override onTouchEvent for Bitmap and return true value. This will mean that you handled this event and it won't be available for LinearLayout.
image.setOnTouchListener( new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return true; // attentively read documentation for onTouc interface
}
});

Not able to Edit a cropped photo in Android

I'm building an Android app where I have to edit a picture which has been picked from the phone gallery and cropped.
So in my layout, I have an ImageView where I am placing the cropped image
xml file
<ImageView
android:id="#+id/ivEditPhoto"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:layout_weight=".90"
android:src="#drawable/app_icon_big" />
Placing the cropped image in ImaveView
bitmapPic = (Bitmap) getIntent().getParcelableExtra(
"CroppedBitmapImage");
picView = (ImageView) findViewById(R.id.ivEditPhoto);
picView.setImageBitmap(bitmapPic);
The image is getting placed correctly. But the problem is when I try to edit it.
I have an edit button and on click of that I do the following which includes registering of On Touch Listener.
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int height = displaymetrics.heightPixels;
int width = displaymetrics.widthPixels;
Bitmap alteredPastedBitmap = Bitmap.createBitmap(bitmapPic.getWidth(),
bitmapPic.getHeight(), bitmapPic.getConfig());
pasteCanvas = new Canvas(alteredPastedBitmap);
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(5);
matrix = new Matrix();
pasteCanvas.drawBitmap(bitmapPic, matrix, paint);
picView.setOnTouchListener(this);
Then the following
public boolean onTouch(View v, MotionEvent event) {
Toast.makeText(this, v.getId(), Toast.LENGTH_SHORT).show();
if (v.getId() == R.id.ivEditPhoto) {
Toast.makeText(this, "onTouch", Toast.LENGTH_SHORT).show();
int action = event.getAction();
x = event.getX();
y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
sX = event.getX();
sY = event.getY();
break;
case MotionEvent.ACTION_UP:
if (skewedBitmap == null) {
int resID = 0;
if (imageId == 0)
resID = R.drawable.green_arrow;
else
resID = imageId;
bitmapToPaste = BitmapFactory.decodeResource(
getResources(), resID);
} else {
bitmapToPaste = skewedBitmap;
skewedBitmap = null;
}
pasteCanvas.drawBitmap(bitmapToPaste, sX- (bitmapToPaste.getWidth() / 2),sY- (bitmapToPaste.getHeight() / 2), null);
picView.invalidate();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
}
return true;
}
The issue is:
the bitmapPic.getWidth() and bitmapPic.getHeight() is 160*160 and the onTouch event.getX(); and event.getY(); is beyond the co-ordinates (For example: 150.33 & 500.89) although the image seems to be occupying the whole screen and the touch is on the image. So onTouch doesn't place the bitmap and returns a false.
Could any of you please guide me on this?
I had the same issue in one of my applications. I need to see more of your code but this could be related to the onInterceptTouch() method you need to implement.
If that is not the case, then maybe you can look into this code for help on how to move, modify and rotate bitmaps:
http://adblogcat.com/custom-view-to-draw-rotate-erase-and-convert-images-to-black-and-white/
I wrote this tutorial btw and this is my website and I think is easier for you to follow that tutorial than just paste it here.

How do i handle touch events properly in android?

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.

Categories

Resources