0

I have multiple buttons in my LinearLayout on my activity_main, and want to detect which Button is closest to the users finger. When the users moves his finger in a single gesture the closest button should highlight and when he removes his finger the closest button should do its onClick function.

It should act like the default keyboard on android or the iphone calculator. It selects the button closest to the finger. When you drag your finger across it will change the selection to the closest key and only when you release your finger does it do the onClick function.

Referencing Get button coordinates and detect if finger is over them - Android I got to the point where selection works, but only if I tap anywhere that isn't a button and it doesn't work to select closest button when not over a button.

(Programming for API 21 in case thats important)

activity_main.xml

<TextView/>
<ButtonLayout>
    <LinearLayout1>
        <Button1/>
        <Space/>
        <Button2/>
        <Space/>
        <Button3/>
        <Space/>
        <Button4/>
    </LinearLayout1>
    <LinearLayout2>
        <Button5/>
        <Space/>
        <Button6/>
        <Space/>
        <Button7/>
        <Space/>
        <Button8/>
    </LinearLayout2>
</OuterLinearLayout>

Java

private View.OnTouchListener buttonLayoutTouchListener= new View.OnTouchListener(){

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        int x = (int) event.getX();
        int y = (int) event.getY();


        //OuterLayout
        for (int i = 0; i < buttonLayout.getChildCount(); i++) {
            View inner = buttonLayout.getChildAt(i);
            //Inner
            if(inner instanceof LinearLayout) {

                for (int j = 0;j<((LinearLayout) inner).getChildCount();j++) {
                    View current = ((LinearLayout) inner).getChildAt(j);
                    if (current instanceof Button) {
                        Button b = (Button) current;
                        Rect rect = new Rect();
                        b.getGlobalVisibleRect(rect);
                        //factors for textview
                        rect.top-=300;
                        rect.bottom-=300;

                        if (!isPointWithin(x, y, rect.left, rect.right, rect.top,
                                rect.bottom)) {
                            b.getBackground().setState(defaultStates);
                        }

                        if (isPointWithin(x, y, rect.left, rect.right, rect.top,
                                rect.bottom)) {
                            if (b != mLastButton) {
                                mLastButton = b;
                                b.getBackground().setState(STATE_PRESSED);
                                //highlight button finger currently over
                           Log.d("button",mLastButton.getText().toString());
                            }
                        }

                    }
                }
            }
        }
        return true;
    }

};

static boolean isPointWithin(int x, int y, int x1, int x2, int y1, int y2) {
    return (x <= x2 && x >= x1 && y <= y2 && y >= y1);
}
A V L
  • 59
  • 1
  • 4
  • as long as you don't have any dead space between your buttons, then there shouldn't be a problem... that is... don't have any space between the buttons (default keyboard on android or the iphone calculator). – Nerdy Bunz Apr 11 '18 at 06:54

1 Answers1

0

Make a new class to extend LinearLayout and override onInterceptTouchEvent. Set your outer LinearLayout to the new class.

Warning: This has a side effect of calling onClick twice if you just tap a button (Once on parent, once on child). Here is my super dirty workaround. Please consider finding a real workaround and posting a reply.

Double Click workaround

//call if(doubleClick()) in buttons' onClicklistener
public mLastClickTime=0;
public boolean doubleClick() {
    //29 is arbitrary
    if (SystemClock.elapsedRealtime() - mLastClickTime < 29) {
        return true;
    }
    mLastClickTime = SystemClock.elapsedRealtime();
    return false;
}

ExtendedLinearLayout

import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

public class ButtonLayout extends LinearLayout {

    public int layoutTop;

    public ButtonLayout(Context context) {
        super(context);
    }

    public ButtonLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public ButtonLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public ButtonLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Rect rect = new Rect();
        this.getGlobalVisibleRect(rect);
        layoutTop =rect.top;
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Button keep=null;

        int x = (int) ev.getX();
        int y = (int) ev.getY();

        int count = 0;
        //number of buttons
        int buttonMax =0;
        int distance = Integer.MAX_VALUE;

        //Outer LinearLayout
        outerLoop:
        for (int i = 0; i < this.getChildCount(); i++) {
            View inner = this.getChildAt(i);
            if (inner instanceof LinearLayout) {
                //Inner LinearLayout
                for (int j = 0; j < ((LinearLayout) inner).getChildCount(); j++) {
                    View current = ((LinearLayout) inner).getChildAt(j);
                    if (current instanceof Button) {
                        buttonMax++;
                        Button b = (Button) current;
                        Rect rect = new Rect();
                        b.getGlobalVisibleRect(rect);
                        rect.top -= layoutTop;
                        rect.bottom -= layoutTop;

                        //finger in button
                        if (isPointWithin(x, y, rect.left, rect.right, rect.top,
                                rect.bottom)) {
                            b.setPressed(true);
                            keep=b;
                            break outerLoop;
                        }else{
                            b.setPressed(false);
                            count++;

                            int buttonDistance = distance(x, y, rect.left, rect.right, rect.top, rect.bottom);
                            if(buttonDistance<distance){
                                keep=b;
                                distance=buttonDistance;
                            }
                        }

                    }
                }
            }
        }
        //if non are selected let button be selected
        if(count==buttonMax){
            keep.setPressed(true);
        }
        //on release
        if(ev.getAction()==MotionEvent.ACTION_UP){
            keep.callOnClick();
            return false;
        }

        return super.onInterceptTouchEvent(ev);
    }

    static boolean isPointWithin(int x, int y, int x1, int x2, int y1, int y2) {
        return (x <= x2 && x >= x1 && y <= y2 && y >= y1);
    }

    static int distance(int x, int y,int x1, int x2, int y1, int y2){
        x1 = Math.abs(x1-x);
        x2 = Math.abs(x2-x);
        y1 = Math.abs(y1-y);
        y2 = Math.abs(y2-y);
        x = (x1>x2?x2:x1);
        y = (y1>y2?y2:y1);
        return x+y;
    }

}
A V L
  • 59
  • 1
  • 4