How can I create a click-drag-to-edge button? - android

I've been messing around with android for about two weeks and need to make an application where you essentially click-and-drag-to-edge a view to make it like sliding button. I'm not sure the term, so probably there is an answer out there that I couldn't find.
What I've come up on my own is the following:
public class MyButton extends View implements View.OnTouchListener {
float defaultX;
int defaultWidth;
public MyButton(Context context, int width) {
super(context);
defaultX = getX();
defaultWidth = width;
setOnTouchListener(this);
}
public boolean onTouch(View view, MotionEvent event) {
int X = (int) event.getRawX();
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
setX(X);
break;
case MotionEvent.ACTION_UP:
setX(defaultX);
break;
}
// Check if view is past width of screen
if (getX()+getWidth() > defaultWidth) {
callOnClick();
setX(defaultX);
}
return true;
}
}
It basically works except for when click is called, it seems to get called multiple times. That screws up when on my next activity after I call finish(), the screen pops back up again because it thinks click is being called over and over - usually about 4 or 5 times. I think this is due to a queue of messages of the drag event being stored for the activity.
Is there a better or 'normal' way of implementing this? The closest thing I can think of similar to what I'm trying to make is like the slide-to-unlock button on the iPhone.

Related

How to get the coordinates(X,Y) of the pattern lock component in Android Studio

I'm trying to get the coordinates of X and Y of the pattern lock component. The code that i'm using to create the Pattern Lock component is from aritraroy/PatternLockView which can be found on Github. Firstly, i set the root view on a view variable like this:
and then i set the OnTouchListener on root view.
Finally i create the the PatterLockViewListener:
final PatternLockViewListener mPatternLockViewListener = new PatternLockViewListener() {
//do whatever
}
What i want is by clicking the PatternLock component to get the coordinates(X,Y) in correlation to the root view. Instead i get the coordinates in all other components except from the PatternLock (by clicking the PatternLock component the OnTouchListener doesnt trigger and as a result, the code is not working/running)
The solution that finally worked for me after searching for 3, 4 days is this:
public class Pattern extends AppCompatActivity
{
PatternLockView pattern_lock;
//x,y coordinates for when the finger touches the screen
//x,y coordinates for when the finger moves around the screen
#Override
protected void onCreate(Bundle savedInstanceState)
{
pattern_lock = (PatternLockView)findViewById(R.id.pattern_screen);
pattern_lock.setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
//code
return Pattern.super.onTouchEvent(event);
case MotionEvent.ACTION_MOVE:
//code
return Pattern.super.onTouchEvent(event);
case MotionEvent.ACTION_UP:
//code
return Pattern.super.onTouchEvent(event);
}
return true;
}
});
}
}
I'm not really sure why to return that (return Pattern.super.onTouchEvent(event);) and not true as most posts say about onTouch.

Android custom horizontal drag tremble

I created a class that should be used to allow user to horizontally relocate a view by sliding his finger on a bar, the code is this
public class Dragger implements View.OnTouchListener {
private View toDrag;
private Point startDragPoint;
private int offset;
public Dragger (View bar, View toDragg){
toDrag = toDragg;
dragging = false;
bar.setOnTouchListener(this);
}
public boolean onTouch(View v, MotionEvent evt) {
switch (evt.getAction()) {
case MotionEvent.ACTION_DOWN:
startDragPoint = new Point((int) evt.getX(), (int) evt.getY());
offset = startDragPoint.x - (int) toDrag.getX();
break;
case MotionEvent.ACTION_MOVE:
int currentX = (int)evt.getX();
Log.d("LOG","currentX=" + currentX);
toDrag.setX(currentX-offset);
break;
case MotionEvent.ACTION_UP:
int difference = (startDragPoint.x-offset)-(int)toDrag.getX();
if(difference<-125){
((ViewManager) toDrag.getParent()).removeView(toDrag);
}else{
toDrag.setX(startDragPoint.x-offset);
}
dragging = false;
}
return true;
}
}
In the oncreate of the main activity i just use it by calling his constructor like this:
new Dragger(barItem,itemToDrag);
It apparently work, but it seems to tremble a lot while dragging... I tried to understand why does it work so strangely, so i added a Log instruction in the ACTION_MOVE, and the result is that... even if i only move to right pixel by pixel sometimes the currentX instead of increasing by 1 it decrease of a variable number, sometimes 10, sometimes 18 and when i go to the next pixel it turn back to normal by increasing of 1...
That really doesen't make sense...
Some ideas on how to fix it?
First guess: There are other fingers on the device which causes move to work erratically
Second guess: Right now you seem to be using the setX method more like a moveX and specifying the increment by which the function moves. This might not be what it was designed for
Third Point:
Sometimes the move function acts finicky. It is always run (as in whenever there is a pointer on the screen) and depends on the position of the finger. I would recommend changing the function to make it like a step
Suggestion
Make the action move like a step function that takes one value
case MotionEvent.ACTION_MOVE:
int currentX = (int)evt.getX();
if(abs(currentX)>10)currentX=(int) currentX/5;
toDrag.setX(currentX-offset);

OnTouchListener MotionEvent.ACTION_MOVE: gets only first view

I have a grid of buttons in my activity. When the user slides their finger across this grid of buttons, I want all of the buttons touched to be essentially recorded so I know which buttons were touched.
I have been doing research on this and I do not see how this can be done, as most examples are with Bitmaps. The first view that it selected is the only one that is picked up from my OnTouchListener. From my readings, MotionEvent.ACTION_MOVE is what is to be used for this and I saw someone who said to use view.getRawX() and view.getRawY() but I don't understand how this would be used to determine which other buttons were pressed and which were not when the user is swiping their finger on the screen.
I am new to Android so my apologies if this is a much simpler task than I thought. Any input would be much appreciated as I wouldn't think this should be so complicated. Thank you for your time! :)
Once your View returns true to say that it's consuming the touch event, the rest of the views won't receive it. What you can do is make a custom ViewGroup (you say you have a grid, I'll just assume GridView?) that intercepts and handles all touch events:
public class InterceptingGridView extends GridView {
private Rect mHitRect = new Rect();
public InterceptingGridView (Context context) {
super(context);
}
public InterceptingGridView (Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent (MotionEvent ev) {
//Always let the ViewGroup handle the event
return true;
}
#Override
public boolean onTouchEvent (MotionEvent ev) {
int x = Math.round(ev.getX());
int y = Math.round(ev.getY());
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.getHitRect(mHitRect);
if (mHitRect.contains(x, y)) {
/*
* Dispatch the event to the containing child. Note that using this
* method, children are not guaranteed to receive ACTION_UP, ACTION_CANCEL,
* or ACTION_DOWN events and should handle the case where only an ACTION_MOVE is received.
*/
child.dispatchTouchEvent(ev);
}
}
//Make sure to still call through to the superclass, so that
//the ViewGroup still functions normally (e.g. scrolling)
return super.onTouchEvent(ev);
}
}
How you choose to handle the event depends on the logic that you require, but the takeaway is to let the container view consume all of the touch events, and let it handle dispatching the events to the children.
maybe this will help you:
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
int pointerId = event.getPointerId(pointerIndex);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
}
break;
}
return true;
}
it works for multitouch

Android (x,y) continuous touch (drag) coordinates

I'm new to android, and I've been trying for a while to find out how can I retrieve the coordinates of a continuous touch on the screen. for example have 2 vars (x,y) that update in real time as finger moves around. I got it how to find it when the touch is made, but I really don;t get it how to make it return the result after that , when the finger is moving.
I've been trying switch statements, while/for loops in different combination with ACTION_MOVE./ UP/ DOWN .. .still nothing.
I've found like the same question on the website, but the answers only fit for the first step(showing the coordination from the touch only)
I'd really appreciate a solution to this! Thanks!
Without seeing your code I'm just guessing, but essentially if you don't return true to the first call to onTouchEvent, you won't see any of the subsequent events in the gesture (MOVE, UP, etc).
Maybe that is your problem? Otherwise please put code sample up.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final TextView xCoord = (TextView) findViewById(R.id.textView1);
final TextView yCoord = (TextView) findViewById(R.id.textView2);
final View touchView = findViewById(R.id.textView3);
touchView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
xCoord.setText(String.valueOf((int) event.getX()));
yCoord.setText(String.valueOf((int) event.getY()));
break;
}
case MotionEvent.ACTION_MOVE:{
xCoord.setText(String.valueOf((int) event.getX()));
yCoord.setText(String.valueOf((int) event.getY()));
break;
}
}
return true;
}
});
}
You need to implement an OnTouchListener for whatever view you want to recognize the drag.
Then in the OnTouchListener you need to display the X and Y coordinates. I believe you can get those via MotionEvent.getRawX() and MotionEvent.getRawY()
You can use the MotionEvent.getAction() method to find out when a drag is occurring. I believe the constant is MotionEvent.ACTION_MOVE. Here is some psuedo-code:
Add OnTouchListener interface
public class XYZ extends Activity implements OnTouchListener
Register the listener in the onCreate method
public void onCreate(Bundle savedInstanceState)
{
//other code
View onTouchView = findViewById(R.id.whatever_id);
onTouchView.setOnTouchListener(this);
}
Implement the onTouch method
public boolean onTouch(View view, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_MOVE)
{
float x = event.getRawX();
float y = event.getRawY();
// Code to display x and y go here
// you can print the x and y coordinate in a textView for exemple
}
}

How to get a continuous Touch Event?

My class extends View and I need to get continuous touch events on it.
If I use:
public boolean onTouchEvent(MotionEvent me) {
if(me.getAction()==MotionEvent.ACTION_DOWN) {
myAction();
}
return true;
}
... the touch event is captured once.
What if I need to get continuous touches without moving the finger?
Please, tell me I don't need to use threads or timers. My app is already too much heavy.
Thanks.
Use if(me.getAction() == MotionEvent.ACTION_MOVE). It's impossible to keep a finger 100% completely still on the screen so Action_Move will get called every time the finger moves, even if it's only a pixel or two.
You could also listen for me.getAction() == MotionEvent.ACTION_UP - until that happens, the user must still have their finger on the screen.
You need to set this properties for the element
android:focusable="true"
android:clickable="true"
if not, just produce the down action.
Her is the simple code snippet which shows that how you can handle the continues touch event. When you touch the device and hold the touch and move your finder, the Touch Move action performed.
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
if(isTsunami){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Write your code to perform an action on down
break;
case MotionEvent.ACTION_MOVE:
// Write your code to perform an action on contineus touch move
break;
case MotionEvent.ACTION_UP:
// Write your code to perform an action on touch up
break;
}
}
return true;
}
Try this. It works to me:
public static OnTouchListener loadContainerOnTouchListener() {
OnTouchListener listener = new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
LinearLayout layout = (LinearLayout)v;
for(int i =0; i< layout.getChildCount(); i++)
{
View view = layout.getChildAt(i);
Rect outRect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
if(outRect.contains((int)event.getX(), (int)event.getY()))
{
Log.d(this.getClass().getName(), String.format("Over view.id[%d]", view.getId()));
}
}
}
Remember: the listener you´ll set must be a container layout (Grid, Relative, Linear).
LinearLayout layout = findViewById(R.id.yourlayoutid);
layout.setOnTouchListener(HelperClass.loadContainerOnTouchListener());
This might help,
requestDisallowInterceptTouchEvent(true);
on the parent view, like this -
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
view.getParent().requestDisallowInterceptTouchEvent(true);
switch(motionEvent.getAction()){
}
return false;
}
I was making a game with a custom view used as a thumb control. . . here is what I did
float x = 0, y = 0;
#Override
public boolean onTouchEvent(MotionEvent event) {
x = event.getX();
y = event.getY();
// handle touch events with
switch( event.getActionMasked() ) {
case MotionEvent.ACTION_DOWN :
if(cont)
{
// remove any previous callbacks
removeCallbacks(contin);
// post new runnable
postDelayed(contin, 10);
}
invalidate();
return true;
case MotionEvent.ACTION_MOVE :
if(!cont && thumbing != null)
{
// do non-continuous operations here
}
invalidate();
return true;
case MotionEvent.ACTION_UP :
// set runnable condition to false
x = 0;
// remove the callbacks to the thread
removeCallbacks(contin);
invalidate();
return true;
default :
return super.onTouchEvent(event);
}
}
public boolean cont = false;
// sets input to continuous
public void set_continuous(boolean b) { cont = b; }
public Runnable contin = new Runnable()
{
#Override
public void run() {
if(x != 0)
{
// do continuous operations here
postDelayed(this, 10);
}
}
};
A quick note however, make sure in your main activity that is calling this view removes the callbacks manually via the onPause method as follows
#Override
protected void onPause() {
if(left.cont) left.removeCallbacks(left.contin);
if(right.cont) right.removeCallbacks(left.contin);
super.onPause();
}
That way if you pause and come back touch events aren't being handled twice and the view is free from it's thread's overhead.
** tested on Samsung Galaxy S3 with hardware acceleration on **
All these answer are partially correct but they do not resolve in the right way the problem.
First of all, for everyone out there that decide to track when the event is ACTION_MOVE. Well that works only guess when? When user move his finger, so could if you decide to implement a custom thumb control is okay but for a normal custom button that's not the case.
Second, using a flag inside ACTION_DOWN and check it in ACTION_UP seems the logic way to do it, but as Clusterfux find out if you implement a while(!up_flag) logic you get stuck into troubles ;)
So the proper way to do it is mentioned here:
Continuous "Action_DOWN" in Android
Just keep in mind that if the logic you're going to write during the continuous press has to modify the UI in some way, you have to do it from the main thread in all the other cases it's better use another thread.
You can use the below code snippet as a reference in which I used the background to detect if the screen is held or not...
Main_Layout.setOnTouchListener(new View.OnTouchListener() {
#SuppressLint("ResourceAsColor")
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
Main_Layout.setBackgroundColor(R.color.green);
event.setAction(MotionEvent.ACTION_DOWN);
break;
default:
Main_Layout.setBackgroundColor(R.color.blue);
break;
}
return false;
}
});

Categories

Resources