android: move a view on touch move (ACTION_MOVE)

后端 未结 11 2209
终归单人心
终归单人心 2020-11-22 13:54

I\'d like to do a simple control: a container with a view inside. If I touch the container and I move the finger, I want to move the view to follow my finger.

What

相关标签:
11条回答
  • 2020-11-22 14:10

    Changed a bit a solution provided by @Vyacheslav Shylkin to remove dependencies of manually entered numbers.

    import android.app.Activity;
    import android.os.Bundle;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewTreeObserver;
    import android.widget.ImageView;
    import android.widget.RelativeLayout;
    
    public class MainActivity extends Activity implements View.OnTouchListener
    {
        private int       _xDelta;
        private int       _yDelta;
        private int       _rightMargin;
        private int       _bottomMargin;
        private ImageView _floatingView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            this._floatingView = (ImageView) findViewById(R.id.textView);
    
            this._floatingView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
            {
                @Override
                public boolean onPreDraw()
                {
                    if (_floatingView.getViewTreeObserver().isAlive())
                        _floatingView.getViewTreeObserver().removeOnPreDrawListener(this);
    
                    updateLayoutParams(_floatingView);
                    return false;
                }
            });
    
            this._floatingView.setOnTouchListener(this);
        }
    
        private void updateLayoutParams(View view)
        {
            this._rightMargin = -view.getMeasuredWidth();
            this._bottomMargin = -view.getMeasuredHeight();
    
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(view.getMeasuredWidth(), view.getMeasuredHeight());
            layoutParams.bottomMargin = this._bottomMargin;
            layoutParams.rightMargin = this._rightMargin;
    
            view.setLayoutParams(layoutParams);
        }
    
        @Override
        public boolean onTouch(View view, MotionEvent event)
        {
            if (view == this._floatingView)
            {
                final int X = (int) event.getRawX();
                final int Y = (int) event.getRawY();
    
                switch (event.getAction() & MotionEvent.ACTION_MASK)
                {
                    case MotionEvent.ACTION_DOWN:
                        RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
                        this._xDelta = X - lParams.leftMargin;
                        this._yDelta = Y - lParams.topMargin;
                        break;
    
                    case MotionEvent.ACTION_MOVE:
                        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
                        layoutParams.leftMargin = X - this._xDelta;
                        layoutParams.topMargin = Y - this._yDelta;
                        layoutParams.rightMargin = this._rightMargin;
                        layoutParams.bottomMargin = this._bottomMargin;
                        view.setLayoutParams(layoutParams);
                        break;
                }
    
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 14:13

    Touch the container and the view will follow your finger.

    xml code

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        android:id="@+id/floating_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
    
        <ImageView
          android:id="@+id/btn_chat"
          android:layout_width="42dp"
          android:layout_height="42dp"
          />
    
    <LinearLayout>
    

    Java code

    public class DashBoardActivity extends Activity implements View.OnClickListener, View.OnTouchListener {
    
        float dX;
        float dY;
        int lastAction;
        LinearLayout floatingLayout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dashboard);
    
            floatingLayout = findViewById(R.id.floating_layout);
            floatingLayout.setOnTouchListener(this);    
    
    
    
         @Override
        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    dX = view.getX() - event.getRawX();
                    dY = view.getY() - event.getRawY();
                    lastAction = MotionEvent.ACTION_DOWN;
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    view.setY(event.getRawY() + dY);
                    view.setX(event.getRawX() + dX);
                    lastAction = MotionEvent.ACTION_MOVE;
                    break;
    
                case MotionEvent.ACTION_UP:
                    if (lastAction == MotionEvent.ACTION_DOWN)
                        Toast.makeText(DashBoardActivity.this, "Clicked!", Toast.LENGTH_SHORT).show();
                    break;
    
                default:
                    return false;
            }
            return true;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 14:16

    I've found an easy approach to do that with the ViewPropertyAnimator:

    float dX, dY;
    
    @Override
    public boolean onTouch(View view, MotionEvent event) {
    
        switch (event.getAction()) {
    
            case MotionEvent.ACTION_DOWN:
    
                dX = view.getX() - event.getRawX();
                dY = view.getY() - event.getRawY();
                break;
    
            case MotionEvent.ACTION_MOVE:
    
                view.animate()
                        .x(event.getRawX() + dX)
                        .y(event.getRawY() + dY)
                        .setDuration(0)
                        .start();
                break;
            default:
                return false;
        }
        return true;
    }
    
    0 讨论(0)
  • 2020-11-22 14:16

    The same as @Alex Karshin's answer, I change a bit.

    public class MovingObject implements OnTouchListener {
    private RelativeLayout.LayoutParams lParams;
    private PointF viewPoint, prePoint, currPoint;
    
    public MovingObject() {
        lParams = null;
        viewPoint = new PointF();
        prePoint = new PointF();
        currPoint = new PointF();
    }
    
    public boolean onTouch(View view, MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            viewPoint.set(view.getX(), view.getY());
            prePoint.set(event.getRawX(), event.getRawY());
            lParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
            break;
        case MotionEvent.ACTION_UP:
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            break;
        case MotionEvent.ACTION_POINTER_UP:
            break;
        case MotionEvent.ACTION_MOVE:
            currPoint.set(event.getRawX(), event.getRawY());
            moveToCurrentPoint(view);
            break;
        }
        view.invalidate();
        return true;
    }
    
    private void moveToCurrentPoint(View view) {
        float dx = currPoint.x - prePoint.x - prePoint.x + viewPoint.x;
        float dy = currPoint.y - prePoint.y - prePoint.y + viewPoint.y;
        lParams.leftMargin = (int) (prePoint.x + dx);
        lParams.topMargin = (int) (prePoint.y + dy);
        view.setLayoutParams(lParams);
    }
    }
    
    0 讨论(0)
  • 2020-11-22 14:18

    Create a custom touch listener class (in Kotlin):

    (This code restrict your view from dragging out of its parent view)

    class CustomTouchListener(
      val screenWidth: Int, 
      val screenHeight: Int
    ) : View.OnTouchListener {
        private var dX: Float = 0f
        private var dY: Float = 0f
    
        override fun onTouch(view: View, event: MotionEvent): Boolean {
    
            val newX: Float
            val newY: Float
    
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    dX = view.x - event.rawX
                    dY = view.y - event.rawY
                }
                MotionEvent.ACTION_MOVE -> {
    
                    newX = event.rawX + dX
                    newY = event.rawY + dY
    
                    if ((newX <= 0 || newX >= screenWidth - view.width) || (newY <= 0 || newY >= screenHeight - view.height)) {
                        return true
                    }
    
                    view.animate()
                        .x(newX)
                        .y(newY)
                        .setDuration(0)
                        .start()
                }
            }
            return true
        }
    }
    

    How to use it?

    parentView.viewTreeObserver.addOnGlobalLayoutListener { view.setOnTouchListener(CustomTouchListener(parentView.width, parentView.height)) }
    

    parentView is the parent of your view.

    0 讨论(0)
  • 2020-11-22 14:20

    In this example you can move the view within it's parent bounds no matter it's size, flawless animation, and catch clicks.

    The reason that this solution is superior to other comments is that this approach uses a Directional Pad which calculate itself and won't relay on the View positions which is a the source for a-lot of bugs.

    // we could use this gameobject as a wrapper that controls the touch event of the component(the table)
    // and like so, we can have a click event and touch events
    public abstract class GameObjectStackOverflow {
    
    private static final int CLICK_DURATION = 175;
    protected View view;
    protected ViewGroup container;
    protected Context mContext;
    
    private boolean onMove = false;
    private boolean firstAnimation = true;
    private Animator.AnimatorListener listener;
    
    protected float parentWidth;
    protected float parentHeight;
    
    protected float xmlHeight;
    protected float xmlWidth;
    
    // Those are the max bounds
    // whiting the xmlContainer
    protected float xBoundMax;
    protected float yBoundMax;
    
    // This variables hold the target
    // ordinates for the next
    // animation in case an animation
    // is already in progress.
    protected float targetX;
    protected float targetY;
    
    private float downRawX;
    private float downRawY;
    
    public GameObjectStackOverflow(@NonNull Context context, @NonNull ViewGroup container)
    {
        mContext = context;
        this.container = container;
    }
    
    // This method is the reason the constructor
    // does not get view to work with in the first
    // place. This method helps us to work with
    // android main thread in such way that we
    // separate the UI stuff from the technical
    // stuff
    protected View initGraphicView(@NonNull LayoutInflater inflater, int resource, boolean add)
    {
        view = inflater.inflate(resource, container, add);
        view.post(getOnViewAttach());
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return onTouchEvent(event);
            }
        });
        return view;
    }
    
    // This method attach an existing
    // view that is already inflated
    protected void attachGraphicView(@NonNull final View view)
    {
        this.view = view;
        view.post(getOnViewAttach());
    }
    
    // This method is anti-boiler code.
    // attaching runnable to the view
    // task queue to finish the
    // initialization of the game object.
    private Runnable getOnViewAttach()
    {
        return new Runnable() {
            @Override
            public void run() {
                parentHeight = container.getHeight();
                parentWidth = container.getWidth();
                view.setX(currentX);
                view.setY(currentY);
            }
        };
    }
    
    private void click() {
        // recover the view to the previous location [not needed]
        // not needed
        //view.animate()
        //    .x(prevPosX)
        //    .y(prevPosY)
        //    .setDuration(0)
        //    .start();
    }
    
    // maybe restore the View view, Motion event
    public boolean onTouchEvent(MotionEvent event)
    {
        view.getParent().requestDisallowInterceptTouchEvent(true);
        //if(!selected) return false;
        switch (event.getAction())
        {
            case MotionEvent.ACTION_UP:
                if (event.getEventTime() - event.getDownTime() < CLICK_DURATION) click(); // are you missing break here?
                onMove = false;
                // if needed to update network entity do it here
                break;
            case MotionEvent.ACTION_DOWN:
                firstAnimation = true;
                xBoundMax = parentWidth - xmlWidth;
                yBoundMax = parentHeight - xmlHeight;
                downRawX = event.getRawX();
                downRawY = event.getRawY();
                break;
    
            case MotionEvent.ACTION_MOVE:
                if (!onMove) {
                    if (event.getEventTime() - event.getDownTime() < CLICK_DURATION) break;
                    else onMove = true;
                }
    
                // Calculating the position the
                // view should be posed at.
                float offsetX = event.getRawX() - downRawX;
                float offsetY = event.getRawY() - downRawY;
                downRawX = event.getRawX();
                downRawY = event.getRawY();
                targetX = currentX + offsetX;
                targetY = currentY + offsetY;
    
                // Checking if view
                // is within parent bounds
                if (targetX > parentWidth - xmlWidth) targetX = xBoundMax;
                else if (targetX < 0) targetX = 0;
                if (targetY > parentHeight - xmlHeight) targetY = yBoundMax;
                else if (targetY < 0) targetY = 0;
    
                // This check is becuase the user may just click on the view
                // So if it's a not a click, animate slowly but fastly
                // to the desired position
                if (firstAnimation) {
                    firstAnimation = false;
                    animate(70, getNewAnimationListener());
                    break;
                }
    
                if (listener != null) break;
                animate(0, null);
                break;
    
            case MotionEvent.ACTION_BUTTON_PRESS:
            default:
                return false;
        }
        return true;
    }
    
    // this method gets used only in
    // one place. it's wrapped in a method
    // block because i love my code like
    // i love women - slim, sexy and smart.
    public Animator.AnimatorListener getNewAnimationListener() {
        listener = new Animator.AnimatorListener() {
            @Override public void onAnimationStart(Animator animation) { }
            @Override public void onAnimationCancel(Animator animation) { }
            @Override public void onAnimationRepeat(Animator animation) { }
            @Override public void onAnimationEnd(Animator animation) {
                animation.removeListener(listener);
                listener = null;
                view.setAnimation(null);
                animate(0, null);
            }
        };
        return listener;
    }
    
    float currentX = 0, currentY = 0;
    
    private void animate(int duration, @Nullable Animator.AnimatorListener listener) {
        view.animate()
                .x(targetX)
                .y(targetY)
                .setDuration(duration)
                .setListener(listener)
                .start();
            currentX = targetX;
            currentY = targetY;
    }
    
    protected void setSize(float width, float height)
    {
        xmlWidth = width;
        xmlHeight = height;
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
        layoutParams.width = (int) width;
        layoutParams.height = (int) height;
        view.setLayoutParams(layoutParams);
    }
    
    public View getView() {
        return view;
    }
    
    
    //This interface catches the onclick even
    // that happened and need to decide what to do.
    public interface GameObjectOnClickListener {
        void onGameObjectClick(GameObjectStackOverflow object);
    }
    
    public float getXmlWidth() {
        return xmlWidth;
    }
    
    public float getXmlHeight() {
        return xmlHeight;
    }
    }
    

    This version got stripped from the big stuff which used to have network entity that gets updated live and such, it should work.


    you should use it this way

    public class Tree extends GameObject
    {
        public Tree(Context context, ViewGroup container, View view, int width, int height) {
            super(context, manager, container);
            attachGraphicView(view);
            super.setSize(_width, _height);
        }
    }
    

    and than

    mTree= new Tree(mContext, mContainer, xmlTreeView);     
    mTree.getView().setOnTouchListener(getOnTouchListener(mTree));
    

    you should have this too but this can be easily removed

    //Construct new OnTouchListener that reffers to the gameobject ontouchevent
    private View.OnTouchListener getOnTouchListener(final GameObject object) {
        return new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent event) {
                return object.onTouchEvent(event);
            }
        };
    }
    

    If you have the container inside a ScrollView or double dimension ScrollView you should add this line to the onTouch

    view.getParent().requestDisallowInterceptTouchEvent(true);
    
    0 讨论(0)
提交回复
热议问题