java - Multitouch issue in Android - Stack Overflow

I am trying to implement throttlesteering controller app using FloatingActionButton as slider control.

I am trying to implement throttle/steering controller app using FloatingActionButton as slider control. I have an issue when both throttle and steering engaged: if moving steering pointer Y coordinate is near the steady throttle pointer Y, it affects throttle pointer coordinate and vice versa. I am testing on the Pixel 7 Pro device.

Here the activity xml

<?xml version="1.0" encoding="utf-8"?>
     <androidx.constraintlayout.widget.ConstraintLayout xmlns:android=";
xmlns:app=";
xmlns:tools=";
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DriveActivity">

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline4"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    app:layout_constraintGuide_percent="0.33" />

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline5"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_begin="353dp"
    app:layout_constraintGuide_percent="0.5" />

<LinearLayout
    android:id="@+id/linearLayout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginEnd="56dp"
    android:layout_marginBottom="30dp"
    android:orientation="horizontal"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toStartOf="@+id/guideline_right">

    <TextView
        android:id="@+id/steering_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/steering_label" />

    <TextView
        android:id="@+id/steering_value"
        android:layout_width="40dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_weight="0"
        android:text="0.000"
        android:textAlignment="viewEnd" />
</LinearLayout>

<LinearLayout
    android:id="@+id/linearLayout3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="56dp"
    android:orientation="horizontal"
    app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
    app:layout_constraintStart_toStartOf="@+id/guideline_left">

    <TextView
        android:id="@+id/throttle_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/throttle_label" />

    <TextView
        android:id="@+id/throttle_value"
        android:layout_width="40dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_weight="0"
        android:text="0.000"
        android:textAlignment="viewEnd" />
</LinearLayout>

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/zero_value_guideline"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    app:layout_constraintGuide_percent="0.5" />

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline_left"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_begin="90dp" />

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline_right"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_end="90dp"
    app:layout_constraintStart_toStartOf="@id/guideline5" />


<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="0dp"
    android:clipToPadding="false"
    android:gravity="center"
    android:orientation="vertical"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toStartOf="@+id/guideline_left"
    app:layout_constraintStart_toStartOf="@+id/guideline_left"
    app:layout_constraintTop_toTopOf="parent">

    <com.yuryrudakou.project.MovableFloatingActionButton
        android:id="@+id/throttle_slider_knob"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:clickable="true"
        app:backgroundTint="@android:color/holo_orange_light"
        app:srcCompat="@drawable/swap_vert_fill0_wght400_grad0_opsz24"
        app:tint="@color/white" />
</LinearLayout>

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="0dp"
    android:gravity="center_vertical"
    android:orientation="vertical"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toStartOf="@+id/guideline_right"
    app:layout_constraintStart_toStartOf="@+id/guideline_right"
    app:layout_constraintTop_toTopOf="parent">

    <com.yuryrudakou.project.MovableFloatingActionButton
        android:id="@+id/steering_slider_knob"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        android:clickable="true"
        app:backgroundTint="@android:color/holo_orange_light"
        app:srcCompat="@drawable/swap_horiz_fill0_wght400_grad0_opsz24"
        app:tint="@color/white" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

and MovableFloatingActionButton implementation

```
    public class MovableFloatingActionButton extends FloatingActionButton implements  View.OnTouchListener {
private static final long TIME_CONSTANT_MS = 220;
private VelocityTracker mVelocityTracker = null;

public interface ValueObserver {
    void onValueUpdated(float value);
}

private ValueObserver mValueObserver = null;

// Engage listener
public interface EngageListener {
    void onEngaged(long timeMs, float pointerY);
}
public void setEngageListener(EngageListener listener) {
    mEngageListener = listener;
}
private EngageListener mEngageListener = null;

// Disengage listener
public interface DisengageListener {
    void  onDisengaged(long timeMs, float pointerY);
}
public void setDisengageListener(DisengageListener listener) {
    mDisengageListener = listener;
}
private DisengageListener mDisengageListener = null;

// Move listener
public interface MoveListener {
    void onMove(long timeMs, float pointerY);
}
public void setMoveListener(MoveListener listener) {
    mMoveListener = listener;
}
private MoveListener mMoveListener = null;

SliderValueCalculator mValueCalculator = null;
ValueSignChangeDetector mValueSignChangeDetector = null;


void setValueObserver(ValueObserver robs) {
    mValueObserver = robs;
}

public MovableFloatingActionButton(@NonNull Context context) {
    super(context);
    init();
}

public MovableFloatingActionButton(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init();
}

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

public void setTag(String tag) {
    mTag = tag;
}
private String mTag;
private final static float CLICK_DRAG_TOLERANCE = 10;
private float downRawX, downRawY;
private float dX, dY;
private float mZeroPos;

private int pointerID = -1;

public interface ValueTransformation {
    float get(float orig);
}
ValueTransformation mValueTransformation = null;
void setValueTransformation(ValueTransformation vt) {
    mValueTransformation = vt;
}


static final class EventParams {
    public float rawX;
    public float rawY;
    ViewGroup.MarginLayoutParams layoutParams = null;
    int parentWidth;
    int parentHeight;
    int viewWidth;
    int viewHeight;
    float pointerY;
}
static EventParams initEventParams(View view, MotionEvent motionEvent, int pointerIndex)   {
    EventParams ret = new EventParams();
    ret.layoutParams = (ViewGroup.MarginLayoutParams)view.getLayoutParams();
    final int[] location = {0, 0};
    view.getLocationOnScreen(location);

    ret.pointerY = motionEvent.getY(pointerIndex);
    ret.rawX = motionEvent.getX(pointerIndex) + location[0];
    ret.rawY = ret.pointerY + location[1];

    final View viewParent = (View) view.getParent();
    ret.parentWidth = viewParent.getWidth();
    ret.parentHeight = viewParent.getHeight();
    ret.viewWidth = view.getWidth();
    ret.viewHeight = view.getHeight();

    return ret;
}
void createSliderValueCalculatorIfRequired(EventParams params) {
    if (mValueCalculator == null) {
        mValueCalculator = new SliderValueCalculator(params.layoutParams.topMargin,
                params.parentHeight - params.viewHeight - params.layoutParams.bottomMargin);
        setZeroPos(mValueCalculator.getZeroPos());
    }
}

void createValueSignChangeDetectorIfRequired(View view) {
    if (mValueSignChangeDetector == null) {
        mValueSignChangeDetector = new ValueSignChangeDetector();
        mValueSignChangeDetector.setOnSignChangeListener(() -> {
            this.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
        });
    }
}

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
    if(this != view) {
        Log.d(mTag, "View does not match");
        return false;
    }
    ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)view.getLayoutParams();
    int action = motionEvent.getAction();

    int actionCode = action & MotionEvent.ACTION_MASK;
    final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    final int pid = motionEvent.getPointerId(pointerIndex);
    if (actionCode == MotionEvent.ACTION_DOWN) {
        if(pointerID == -1) {
            EventParams params = initEventParams(view, motionEvent, pointerIndex);
            createSliderValueCalculatorIfRequired(params);
            createValueSignChangeDetectorIfRequired(view);
            pointerID = pid;
            downRawX = params.rawX;
            downRawY = params.rawY;
            dX = view.getX() - downRawX;
            dY = view.getY() - downRawY;
            if(mEngageListener != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    mEngageListener.onEngaged(motionEvent.getEventTime(), motionEvent.getRawY(pointerIndex));
                }
            }

            view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
            mVelocity = 0.0f;
            mLastTimeMs = SystemClock.elapsedRealtime();
            mLastPointerY = params.pointerY;
        }
        return true; // Consumed

    }
    else if (actionCode == MotionEvent.ACTION_MOVE) {

        if(pid == this.pointerID) {
            EventParams params = initEventParams(view, motionEvent, pointerIndex);
            final long timeMs = motionEvent.getEventTime();
            final float dt = (float) (timeMs - mLastTimeMs);
            final float pointerY = params.pointerY;
            mVelocity = (mLastPointerY - pointerY) / dt;
            mLastTimeMs = timeMs;
            createSliderValueCalculatorIfRequired(params);
            createValueSignChangeDetectorIfRequired(view);
            Log.d(mTag, "ACTION_MOVE; " +
                    "; pointer ID: " + pid +
                    "; pointer index: " + pointerIndex +
                    "; Event X: " + motionEvent.getX(pointerIndex) +
                    "; Event Y: " + motionEvent.getY(pointerIndex) +
                    "; X: " +
                    params.rawX +
                    "; Y: " +
                    params.rawY +
                    ";");
            float newX = params.rawX + dX;
            newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent
            newX = Math.min(params.parentWidth - params.viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent

            float newY = params.rawY + dY;
            newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent
            newY = Math.min(params.parentHeight - params.viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent
            if (mValueObserver != null) {
                float value = mValueTransformation != null ? mValueTransformation.get(mValueCalculator.getValue(newY)) :
                        mValueCalculator.getValue(newY);

                if(Math.abs(value) == 0.0f) {
                    value = 0.0f;
                }
                mValueObserver.onValueUpdated(value);
                mValueSignChangeDetector.updateValue(value);
            }
            if(mMoveListener != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    mMoveListener.onMove(timeMs, motionEvent.getRawY(pointerIndex));
                }
            }

            view.animate()
                    .x(newX)
                    .y(newY)
                    .setUpdateListener(null)
                    .setDuration(0)
                    .start();
            return true; // Consumed
        }
        else {
            return false;
        }
    }
    else if (actionCode == MotionEvent.ACTION_UP) {
        if(pid == this.pointerID) {
            this.pointerID = -1;
            EventParams params = initEventParams(view, motionEvent, pointerIndex);
            createSliderValueCalculatorIfRequired(params);
            createValueSignChangeDetectorIfRequired(view);
            float newX = params.rawX + dX;
            newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent
            newX = Math.min(params.parentWidth - params.viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent
            float newY = params.rawY + dY;
            newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent
            newY = Math.min(params.parentHeight - params.viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent

            final float distanceKoeff = Math.abs(newY - mZeroPos) / mZeroPos;
            final long timeToRebound = (long) (TIME_CONSTANT_MS * distanceKoeff);
            Log.d(mTag, "MotionEvent raw Y: " + String.valueOf(params.rawY));
            Log.d(mTag, "Coordinate: " + String.valueOf(newY));
            Log.d(mTag, "Distance coeff.: " + String.valueOf(distanceKoeff));
            Log.d(mTag, "Time to rebound: " + String.valueOf(timeToRebound));
            mReboundStartPos = newY;

            view.animate()
                    .y(mZeroPos)
                    .setDuration(timeToRebound)
                    .setUpdateListener((animator) -> {
                        if (mValueObserver != null) {
                            final float y = view.getY();
                            float value = mValueTransformation != null ?
                                    mValueTransformation.get(mValueCalculator.getValue(y)) :
                                    mValueCalculator.getValue(y);
                            if (Math.abs(value) == 0.0f) {
                                value = 0.0f;
                            }

                            mValueObserver.onValueUpdated(value);
                            mValueSignChangeDetector.updateValue(value);
                        }
                    })
                    .start();

            float upRawX = motionEvent.getRawX();
            float upRawY = motionEvent.getRawY();

            float upDX = upRawX - downRawX;
            float upDY = upRawY - downRawY;

            if(mDisengageListener != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    mDisengageListener.onDisengaged(motionEvent.getEventTime(), motionEvent.getRawY(pointerIndex));
                }
            }

            if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { // A click
                return performClick();
            } else { // A drag
                return false; // Consumed
            }
        }
        else {
            return false;
        }
    }
    else {
        return super.onTouchEvent(motionEvent);
    }
}

private void init() {
    setOnTouchListener(this);
}
void setZeroPos(float pos) {
    mZeroPos = pos;
}
private Float animatedValue;
private float mReboundStartPos;

float mVelocity;
long mLastTimeMs;
float mLastPointerY;
}
```

I need any solution to eliminate this effect.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745549361a4632493.html

相关推荐

  • java - Multitouch issue in Android - Stack Overflow

    I am trying to implement throttlesteering controller app using FloatingActionButton as slider control.

    11小时前
    20

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信