admin管理员组文章数量:1429990
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.
本文标签: javaMultitouch issue in AndroidStack Overflow
版权声明:本文标题:java - Multitouch issue in Android - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745549360a2662859.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论