Home > Blockchain >  What is causing the item(View) in the Container (ViewGroup) class to move quickly and randomly after
What is causing the item(View) in the Container (ViewGroup) class to move quickly and randomly after

Time:02-06

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:

enter image description here

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.

  • Related