Description: I have a class called Container which can hold items. In the code below I added only one item to the container. The user can move this item by dragging it and rotating it by 90 degrees by double-clicking on it.
Before double-clicking on it (before applying the rotation), it moves as expected when the user drags the item. If the user double-clicks the item (applies 90 degrees rotation) and then tries to move it by dragging it, it unexpectedly moves so quickly out of the window and in a random direction.
Demo:
Code:
Container.java
public class Container extends ViewGroup {
public Container(Context context) {
super(context);
}
public Container(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Container(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//Layout all children used fixed position
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for (int i = 0; i<count; i ) {
View child = getChildAt(i);
int left = child.getLeft();
int top = child.getTop();
int right = child.getRight();
int bottom = child.getBottom();
child.layout(left, top, right, bottom);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i<count; i ) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, heightMeasureSpec);
}
}
}
Item.java
public class Item extends View {
// default width and height for this item
private static final int WIDTH = 200;
private static final int HEIGHT = 80;
private final Paint mPaint;
private final static int OFFSET = 1;
//Store the click position
private float mStartX = 0;
private float mStartY = 0;
//Used to detect double-click on this item
private long mStartTime = 0;
private static final int DOUBLE_TAP_THRESHOLD = 200;
public Item(Context context, int x, int y) {
super(context);
// Set the size of the item,
// No need to override onMeasure()
setLeft(x);
setTop(y);
setRight(x WIDTH);
setBottom(y HEIGHT);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
canvas.drawRect(OFFSET, OFFSET, width-OFFSET, height-OFFSET, mPaint);
}
// Rotate the item if the user double click on it
// And move the item if the user drag it
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
long currentTime = System.currentTimeMillis();
if(currentTime - mStartTime < DOUBLE_TAP_THRESHOLD) {
//Double click detected
// Apply rotation
setRotation(getRotation() 90); // rotation that cause the problem
return true;
}
mStartTime = currentTime;
mStartX = event.getX();
mStartY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
// compute the new position for the item
float deltaX = event.getX() - mStartX;
float deltaY = event.getY() - mStartY;
float newX = getX() deltaX;
float newY = getY() deltaY;
//move the item
setX(newX);
setY(newY);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Container container = findViewById(R.id.container);
container.addView(new Item(this, 100, 200));
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.package.name.Container
android:id="@ id/container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Question: Why does the item move so quickly when rotating it? and how to fix this issue.
CodePudding user response:
Your problem is different frames of reference. X and Y are relative to the view's upper left hand corner. When you rotate and move the view, you're changing the relative coordinates of the view, which is going to lead to some funnky math.
There's 2 solutions to this. The one that changes your code the least is to convert all touch coordienates to a fixed coordinate system first. For example, the parent view's coordinate system, or the screens. view.getX is relative to its parent already.
The second and probably better way to do it- don't have the individual views control that. Do it all in a touch handler of the parent. The parent knows where all its children are and is responsible for laying them out, so this not only is more aligned with how Android works, but it avoids the problem of changing coordinate systems. All you have to do to make that work is make the views ignore all touches (their default behavior) and touches on them will fall through to their parent.