android:通过触摸移动移动视图(ACTION_MOVE)

发布于 2025-01-08 06:06:44 字数 117 浏览 2 评论 0原文

我想做一个简单的控件:一个内部有视图的容器。如果我触摸容器并移动手指,我想移动视图以跟随我的手指。

我应该使用什么样的容器(布局)?如何做到这一点?

我不需要使用表面,而是使用简单的布局。

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 kind of container (layout) should I use? How to do this?

I don't need to use a surface, but a simple layout.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

紫﹏色ふ单纯 2025-01-15 06:06:44

我找到了一种使用 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;
}

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;
}
千紇 2025-01-15 06:06:44

像这样的东西:

public class MyActivity extends Activity implements View.OnTouchListener {

TextView _view;
ViewGroup _root;
private int _xDelta;
private int _yDelta;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    _root = (ViewGroup)findViewById(R.id.root);

    _view = new TextView(this);
    _view.setText("TextView!!!!!!!!");

    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 50);
    layoutParams.leftMargin = 50;
    layoutParams.topMargin = 50;
    layoutParams.bottomMargin = -250;
    layoutParams.rightMargin = -250;
    _view.setLayoutParams(layoutParams);

    _view.setOnTouchListener(this);
    _root.addView(_view);
}

public boolean onTouch(View view, MotionEvent event) {
    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();
            _xDelta = X - lParams.leftMargin;
            _yDelta = Y - lParams.topMargin;
            break;
        case MotionEvent.ACTION_UP:
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            break;
        case MotionEvent.ACTION_POINTER_UP:
            break;
        case MotionEvent.ACTION_MOVE:
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
            layoutParams.leftMargin = X - _xDelta;
            layoutParams.topMargin = Y - _yDelta;
            layoutParams.rightMargin = -250;
            layoutParams.bottomMargin = -250;
            view.setLayoutParams(layoutParams);
            break;
    }
    _root.invalidate();
    return true;
}}

main.xml 中,只需 RelativeLayout@+id/root

Something like this:

public class MyActivity extends Activity implements View.OnTouchListener {

TextView _view;
ViewGroup _root;
private int _xDelta;
private int _yDelta;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    _root = (ViewGroup)findViewById(R.id.root);

    _view = new TextView(this);
    _view.setText("TextView!!!!!!!!");

    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 50);
    layoutParams.leftMargin = 50;
    layoutParams.topMargin = 50;
    layoutParams.bottomMargin = -250;
    layoutParams.rightMargin = -250;
    _view.setLayoutParams(layoutParams);

    _view.setOnTouchListener(this);
    _root.addView(_view);
}

public boolean onTouch(View view, MotionEvent event) {
    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();
            _xDelta = X - lParams.leftMargin;
            _yDelta = Y - lParams.topMargin;
            break;
        case MotionEvent.ACTION_UP:
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            break;
        case MotionEvent.ACTION_POINTER_UP:
            break;
        case MotionEvent.ACTION_MOVE:
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
            layoutParams.leftMargin = X - _xDelta;
            layoutParams.topMargin = Y - _yDelta;
            layoutParams.rightMargin = -250;
            layoutParams.bottomMargin = -250;
            view.setLayoutParams(layoutParams);
            break;
    }
    _root.invalidate();
    return true;
}}

In main.xml just RelativeLayout with @+id/root

宛菡 2025-01-15 06:06:44

触摸容器,视图将跟随您的手指。

xml 代码

<?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 代码

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;
    }
}

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;
    }
}
回首观望 2025-01-15 06:06:44

按照@Andrew方法,如果您想将视图从其中心移动,您只需减去视图的一半高度和一半宽度即可。

float dX, dY;

@Override
public boolean onTouchEvent(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 - (view.getWidth() / 2f))
                .y(event.getRawY() + dY - (view.getHeight() / 2f))
                .setDuration(0)
                .start();
            break;
        default:
            return false;
    }
    return true;
}     

Following the @Andrew approach, if you want to move the view from its center, you only have to substract the view's half height and half width to the movement.

float dX, dY;

@Override
public boolean onTouchEvent(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 - (view.getWidth() / 2f))
                .y(event.getRawY() + dY - (view.getHeight() / 2f))
                .setDuration(0)
                .start();
            break;
        default:
            return false;
    }
    return true;
}     
难理解 2025-01-15 06:06:44

创建一个自定义触摸监听器类(在 Kotlin 中):(

此代码限制您的视图从其父视图中拖出)

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
    }
}

如何使用它?

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

parentView 是视图的父视图。

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.

望笑 2025-01-15 06:06:44

Kotlin 中的相同实现

    rightPanel.setOnTouchListener(View.OnTouchListener { view, event ->
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {

                rightDX = view!!.x - event.rawX
                // rightDY = view!!.getY() - event.rawY;

            }
            MotionEvent.ACTION_MOVE -> {

                var displacement = event.rawX + rightDX

                view!!.animate()
                        .x(displacement)
                        // .y(event.getRawY() + rightDY)
                        .setDuration(0)
                        .start()
            }
            else -> { // Note the block
                return@OnTouchListener false
            }
        }
        true
 })

Same implementation in Kotlin

    rightPanel.setOnTouchListener(View.OnTouchListener { view, event ->
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {

                rightDX = view!!.x - event.rawX
                // rightDY = view!!.getY() - event.rawY;

            }
            MotionEvent.ACTION_MOVE -> {

                var displacement = event.rawX + rightDX

                view!!.animate()
                        .x(displacement)
                        // .y(event.getRawY() + rightDY)
                        .setDuration(0)
                        .start()
            }
            else -> { // Note the block
                return@OnTouchListener false
            }
        }
        true
 })
兲鉂ぱ嘚淚 2025-01-15 06:06:44

我建议使用 view.translationX 和 view.translationY 来移动您的视图。

科特林片段:

yourView.translationX = xTouchCoordinate
yourView.translationY = yTouchCoordinate

I recommend to use view.translationX and view.translationY to move your views.

Kotlin snippet:

yourView.translationX = xTouchCoordinate
yourView.translationY = yTouchCoordinate
夏の忆 2025-01-15 06:06:44

在下面的代码中,我创建了一个名为 RegionView 的东西 (git ),它是一个可重用容器,负责管理其每个嵌套子级的拖动和缩放操作。

在这里,我们操纵子 ViewLayoutParamstopleft 系数来模拟图表的移动。通过将处理被理解为拖动操作的解释和被确定为缩放操作的解释解耦,我们可以提供对子View的可靠操作。

package com.zonal.regionview;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Vibrator;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.RelativeLayout;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by Alexander Thomas (@Cawfree) on 20/07/2017.
 */

/** Enables users to customize Regions Of Interest on a Canvas. */
public class RegionView extends RelativeLayout implements View.OnTouchListener, GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener {

    /* Member Variables. */
    private final GestureDetector      mGestureDetector;
    private final ScaleGestureDetector mScaleGestureDetector;
    private final Map<Integer, View>   mViewMap;
    private       boolean              mScaling;
    private       float                mScale;
    private       boolean              mWrapContent;
    private       boolean              mDropOnScale;

    public RegionView(Context context) {
        // Implement the Parent.
        super(context);
        // Initialize Member Variables.
        this.mGestureDetector      = new GestureDetector(context, this);
        this.mViewMap              = new HashMap<>();
        this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.mScaling              = false;
        this.mScale                = Float.NaN;
        this.mWrapContent          = false;
        this.mDropOnScale          = false;
        // Register ourself as the OnTouchListener.
        this.setOnTouchListener(this);
    }

    public RegionView(Context context, @Nullable AttributeSet attrs) {
        // Implement the Parent.
        super(context, attrs);
        // Initialize Member Variables.
        this.mGestureDetector      = new GestureDetector(context, this);
        this.mViewMap              = new HashMap<>();
        this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.mScaling              = false;
        this.mWrapContent          = false;
        this.mDropOnScale          = false;
        // Register ourself as the OnTouchListener.
        this.setOnTouchListener(this);
    }

    public RegionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        // Implement the Parent.
        super(context, attrs, defStyleAttr);
        // Initialize Member Variables.
        this.mGestureDetector      = new GestureDetector(context, this);
        this.mViewMap              = new HashMap<>();
        this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.mScaling              = false;
        this.mWrapContent          = false;
        this.mDropOnScale          = false;
        // Register ourself as the OnTouchListener.
        this.setOnTouchListener(this);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public RegionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        // Implement the Parent.
        super(context, attrs, defStyleAttr, defStyleRes);
        // Initialize Member Variables.
        this.mGestureDetector      = new GestureDetector(context, this);
        this.mViewMap              = new HashMap<>();
        this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.mScaling              = false;
        this.mWrapContent          = false;
        this.mDropOnScale          = false;
        // Register ourself as the OnTouchListener.
        this.setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(final View v, final MotionEvent event) {
        // Calculate the PointerId.
        final int lPointerId = event.getPointerId(event.getActionIndex());
        // Handle the TouchEvent.
        this.getGestureDetector().onTouchEvent(event);
        this.getScaleGestureDetector().onTouchEvent(event);
        // Did the user release a pointer?
        if(event.getAction() == MotionEvent.ACTION_UP) {
            // Was there a View associated with this Action?
            final View lView = this.getViewMap().get(lPointerId);
            // Does the View exist?
            if(lView != null) {
                // Remove the View from the Map.
                this.getViewMap().remove(lPointerId); /** TODO: Provide a Callback? */
            }
        }
        // Consume all events for now.
        return true;
    }

    @Override
    public boolean onDown(MotionEvent e) {
        // Calculate the PointerId.
        final Integer lPointerId = Integer.valueOf(e.getPointerId(e.getActionIndex()));
        // Fetch the View.
        final View    lView      = this.getViewFor(Math.round(e.getRawX()), Math.round(e.getRawY()));
        // Is it valid?
        if(lView != null) {
            // Watch the View.
            this.getViewMap().put(lPointerId, lView);
            // Configure the Anchor.
            lView.setPivotX(0);
            lView.setPivotY(0);
            // Assert that we handled the event.
            return true;
        }
        // Assert that we ignored the event.
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // Are we not scaling?
        if(!this.isScaling()) {
            // Calculate the PointerId.
            final Integer lPointerId = Integer.valueOf(e1.getPointerId(e1.getActionIndex()));
            // Fetch the View.
            final View    lView      = this.getViewMap().get(lPointerId);
            // Is the scroll valid for a given View?
            if(lView != null) {
                // Calculate the Scaled Width and Height of the View.
                final float lWidth    = lView.getWidth()  * lView.getScaleX();
                final float lHeight   = lView.getHeight() * lView.getScaleY();
                // Declare the initial position.
                final int[] lPosition = new int[] { (int)(e2.getX() - ((lWidth)  / 2)), (int)(e2.getY() - ((lHeight) / 2)) };
                // Are we wrapping content?
                if(this.isWrapContent()) {
                    // Wrap the Position.
                    this.onWrapContent(lPosition, lWidth, lHeight);
                }
                // Update the Drag.
                this.onUpdateDrag(lView, lPosition);
            }
            // Assert we handled the scroll.
            return true;
        }
        // Otherwise, don't permit scrolling. Don't consume the MotionEvent.
        return false;
    }

    /** Forces X/Y values to be coerced within the confines of the RegionView. */
    private final void onWrapContent(final int[] pPosition, final float pWidth, final float pHeight) {
        // Limit the parameters. (Top-Left)
        pPosition[0] = Math.max(pPosition[0], 0);
        pPosition[1] = Math.max(pPosition[1],  0);
        // Limit the parameters. (Bottom-Right)
        pPosition[0] = Math.min(pPosition[0], (int)(this.getWidth()  - pWidth));
        pPosition[1] = Math.min(pPosition[1], (int)(this.getHeight() - pHeight));
    }

    /** Updates the Drag Position of a child View within the Layout. Implicitly, we update the LayoutParams of the View. */
    private final void onUpdateDrag(final View pView, final int pLeft, final int pTop) {
        // Allocate some new MarginLayoutParams.
        final MarginLayoutParams lMarginLayoutParams = new MarginLayoutParams(pView.getLayoutParams());
        // Update the Margin.
        lMarginLayoutParams.setMargins(pLeft, pTop, 0, 0);
        // Refactor the MarginLayoutParams into equivalent LayoutParams for the RelativeLayout.
        pView.setLayoutParams(new RelativeLayout.LayoutParams(lMarginLayoutParams));
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        // Calculate the ScaleFactor.
              float lScaleFactor = detector.getScaleFactor() - 1;
        // Fetch the Scaled View.
        final View  lView        = this.getViewMap().entrySet().iterator().next().getValue();
        // Update the ScaleFactor.
        final float lScale       = this.getScale() + lScaleFactor;
        // Calculate the Proposed Width and Height.
        final int   lWidth  = Math.round(lView.getWidth()  * lScale);
        final int   lHeight = Math.round(lView.getHeight() * lScale);
        // Is the View already too large for wrap content?
        if(lWidth >= this.getWidth() || lHeight >= this.getHeight()) {
            // Don't update the scale.
            return false;
        }
        // Persist this Scale for the View.
        lView.setScaleX(lScale);
        lView.setScaleY(lScale);
        // Assign the Scale.
        this.setScale(lScale);
        // Compute the Position.
        final int[] lPosition = new int[] { Math.round(detector.getFocusX()) - (lWidth / 2), Math.round(detector.getFocusY()) - (lHeight / 2) };
        // Are we wrapping the Position?
        if(this.isWrapContent()) {
            // Wrap the Position.
            this.onWrapContent(lPosition, lWidth, lHeight);
        }
        // Update the Drag.
        this.onUpdateDrag(lView, lPosition);
        // Assert that we handled the scale.
        return true;
    }

    /** Update the Drag. */
    private final void onUpdateDrag(final View pView, final int[] pPosition) {
        // Call the sub-implementation.
        this.onUpdateDrag(pView, pPosition[0], pPosition[1]);
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) { 
        // Is the user not dragging at all?
        if(this.getViewMap().size() == 1) {
            // Fetch the View.
            final View lView = this.getViewMap().entrySet().iterator().next().getValue();
            // Initialize the Scale.
            this.setScale(lView.getScaleX()); 
            // Assert that we've started scaling.
            this.setScaling(true);
            // Inform the callback.
            return true;
        }
        // Otherwise, don't allow scaling.
        return false;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        // Were we scaling?
        if(this.isScaling()) {
            // Assert that we've stopped scaling.
            this.setScaling(false);
            // Reset the Scale.
            this.setScale(Float.NaN);
            // Should we stop dragging now that we've finished scaling?
            if(this.isDropOnScale()) {
                // Clear the ViewMap.
                this.getViewMap().clear();
            }
        }
    }

    /** Returns the View colliding with the given co-ordinates. */
    private final View getViewFor(final int pX, final int pY) {
        // Declare the LocationBuffer.
        final int[] lLocationBuffer = new int[2];
        // Iterate the Views.
        for(int i = 0; i < this.getChildCount(); i++) {
            // Fetch the child View.
            final View lView = this.getChildAt(i);
            // Fetch its absolute position.
            lView.getLocationOnScreen(lLocationBuffer);
            // Determine if the MotionEvent collides with the View.
            if(pX > lLocationBuffer[0] && pY > lLocationBuffer[1] && (pX < lLocationBuffer[0] + (lView.getWidth() * lView.getScaleX())) && (pY < lLocationBuffer[1] + (lView.getHeight() * lView.getScaleY()))) {
                // Return the View.
                return lView;
            }
        }
        // We couldn't find a View.
        return null;
    }

    /* Unused Overrides. */
    @Override public void      onShowPress(MotionEvent e) {  }
    @Override public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }
    @Override public void      onLongPress(MotionEvent e) { }
    @Override public boolean       onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; }

    /* Getters and Setters. */
    private final GestureDetector getGestureDetector() {
        return this.mGestureDetector;
    }

    private final ScaleGestureDetector getScaleGestureDetector() {
        return this.mScaleGestureDetector;
    }

    private final Map<Integer, View> getViewMap() {
        return this.mViewMap;
    }

    private final void setScaling(final boolean pIsScaling) {
        this.mScaling = pIsScaling;
    }

    private final boolean isScaling() {
        return this.mScaling;
    }

    private final void setScale(final float pScale) {
        this.mScale = pScale;
    }

    private final float getScale() {
        return this.mScale;
    }

    /** Defines whether we coerce the drag and zoom of child Views within the confines of the Layout. */
    public final void setWrapContent(final boolean pIsWrapContent) {
        this.mWrapContent = pIsWrapContent;
    }

    public final boolean isWrapContent() {
        return this.mWrapContent;
    }

    /** Defines whether a drag operation is considered 'finished' once the user finishes scaling a view. */
    public final void setDropOnScale(final boolean pIsDropOnScale) {
        this.mDropOnScale = pIsDropOnScale;
    }

    public final boolean isDropOnScale() {
        return this.mDropOnScale;
    }

}

这里我展示了一个示例用例:

package com.zonal.regionview;

import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.AnalogClock;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Allocate a RegionView.
        final RegionView lRegionView = new RegionView(this);
        // Add some example items to drag.
        lRegionView.addView(new AnalogClock(this));
        lRegionView.addView(new AnalogClock(this));
        lRegionView.addView(new AnalogClock(this));
        // Assert that we only want to drag Views within the confines of the RegionView.
        lRegionView.setWrapContent(true);
        // Assert that after we've finished scaling a View, we want to stop being able to drag it until a new drag is started.
        lRegionView.setDropOnScale(true);
        // Look at the RegionView.
        this.setContentView(lRegionView);
    }

}

In the code below, I've created something called the RegionView (git), which is a reusable container responsible for managing drag and zoom operations for each of its nested children.

Here, we manipulate the top and left coefficients of a child View's LayoutParams to simulate movement about the diagram. By decoupling the interpretation of handling what's understood as a drag operation, and what is determined to be a scale operation, we can provide reliable manipulation of a child View.

package com.zonal.regionview;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Vibrator;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.RelativeLayout;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by Alexander Thomas (@Cawfree) on 20/07/2017.
 */

/** Enables users to customize Regions Of Interest on a Canvas. */
public class RegionView extends RelativeLayout implements View.OnTouchListener, GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener {

    /* Member Variables. */
    private final GestureDetector      mGestureDetector;
    private final ScaleGestureDetector mScaleGestureDetector;
    private final Map<Integer, View>   mViewMap;
    private       boolean              mScaling;
    private       float                mScale;
    private       boolean              mWrapContent;
    private       boolean              mDropOnScale;

    public RegionView(Context context) {
        // Implement the Parent.
        super(context);
        // Initialize Member Variables.
        this.mGestureDetector      = new GestureDetector(context, this);
        this.mViewMap              = new HashMap<>();
        this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.mScaling              = false;
        this.mScale                = Float.NaN;
        this.mWrapContent          = false;
        this.mDropOnScale          = false;
        // Register ourself as the OnTouchListener.
        this.setOnTouchListener(this);
    }

    public RegionView(Context context, @Nullable AttributeSet attrs) {
        // Implement the Parent.
        super(context, attrs);
        // Initialize Member Variables.
        this.mGestureDetector      = new GestureDetector(context, this);
        this.mViewMap              = new HashMap<>();
        this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.mScaling              = false;
        this.mWrapContent          = false;
        this.mDropOnScale          = false;
        // Register ourself as the OnTouchListener.
        this.setOnTouchListener(this);
    }

    public RegionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        // Implement the Parent.
        super(context, attrs, defStyleAttr);
        // Initialize Member Variables.
        this.mGestureDetector      = new GestureDetector(context, this);
        this.mViewMap              = new HashMap<>();
        this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.mScaling              = false;
        this.mWrapContent          = false;
        this.mDropOnScale          = false;
        // Register ourself as the OnTouchListener.
        this.setOnTouchListener(this);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public RegionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        // Implement the Parent.
        super(context, attrs, defStyleAttr, defStyleRes);
        // Initialize Member Variables.
        this.mGestureDetector      = new GestureDetector(context, this);
        this.mViewMap              = new HashMap<>();
        this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.mScaling              = false;
        this.mWrapContent          = false;
        this.mDropOnScale          = false;
        // Register ourself as the OnTouchListener.
        this.setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(final View v, final MotionEvent event) {
        // Calculate the PointerId.
        final int lPointerId = event.getPointerId(event.getActionIndex());
        // Handle the TouchEvent.
        this.getGestureDetector().onTouchEvent(event);
        this.getScaleGestureDetector().onTouchEvent(event);
        // Did the user release a pointer?
        if(event.getAction() == MotionEvent.ACTION_UP) {
            // Was there a View associated with this Action?
            final View lView = this.getViewMap().get(lPointerId);
            // Does the View exist?
            if(lView != null) {
                // Remove the View from the Map.
                this.getViewMap().remove(lPointerId); /** TODO: Provide a Callback? */
            }
        }
        // Consume all events for now.
        return true;
    }

    @Override
    public boolean onDown(MotionEvent e) {
        // Calculate the PointerId.
        final Integer lPointerId = Integer.valueOf(e.getPointerId(e.getActionIndex()));
        // Fetch the View.
        final View    lView      = this.getViewFor(Math.round(e.getRawX()), Math.round(e.getRawY()));
        // Is it valid?
        if(lView != null) {
            // Watch the View.
            this.getViewMap().put(lPointerId, lView);
            // Configure the Anchor.
            lView.setPivotX(0);
            lView.setPivotY(0);
            // Assert that we handled the event.
            return true;
        }
        // Assert that we ignored the event.
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // Are we not scaling?
        if(!this.isScaling()) {
            // Calculate the PointerId.
            final Integer lPointerId = Integer.valueOf(e1.getPointerId(e1.getActionIndex()));
            // Fetch the View.
            final View    lView      = this.getViewMap().get(lPointerId);
            // Is the scroll valid for a given View?
            if(lView != null) {
                // Calculate the Scaled Width and Height of the View.
                final float lWidth    = lView.getWidth()  * lView.getScaleX();
                final float lHeight   = lView.getHeight() * lView.getScaleY();
                // Declare the initial position.
                final int[] lPosition = new int[] { (int)(e2.getX() - ((lWidth)  / 2)), (int)(e2.getY() - ((lHeight) / 2)) };
                // Are we wrapping content?
                if(this.isWrapContent()) {
                    // Wrap the Position.
                    this.onWrapContent(lPosition, lWidth, lHeight);
                }
                // Update the Drag.
                this.onUpdateDrag(lView, lPosition);
            }
            // Assert we handled the scroll.
            return true;
        }
        // Otherwise, don't permit scrolling. Don't consume the MotionEvent.
        return false;
    }

    /** Forces X/Y values to be coerced within the confines of the RegionView. */
    private final void onWrapContent(final int[] pPosition, final float pWidth, final float pHeight) {
        // Limit the parameters. (Top-Left)
        pPosition[0] = Math.max(pPosition[0], 0);
        pPosition[1] = Math.max(pPosition[1],  0);
        // Limit the parameters. (Bottom-Right)
        pPosition[0] = Math.min(pPosition[0], (int)(this.getWidth()  - pWidth));
        pPosition[1] = Math.min(pPosition[1], (int)(this.getHeight() - pHeight));
    }

    /** Updates the Drag Position of a child View within the Layout. Implicitly, we update the LayoutParams of the View. */
    private final void onUpdateDrag(final View pView, final int pLeft, final int pTop) {
        // Allocate some new MarginLayoutParams.
        final MarginLayoutParams lMarginLayoutParams = new MarginLayoutParams(pView.getLayoutParams());
        // Update the Margin.
        lMarginLayoutParams.setMargins(pLeft, pTop, 0, 0);
        // Refactor the MarginLayoutParams into equivalent LayoutParams for the RelativeLayout.
        pView.setLayoutParams(new RelativeLayout.LayoutParams(lMarginLayoutParams));
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        // Calculate the ScaleFactor.
              float lScaleFactor = detector.getScaleFactor() - 1;
        // Fetch the Scaled View.
        final View  lView        = this.getViewMap().entrySet().iterator().next().getValue();
        // Update the ScaleFactor.
        final float lScale       = this.getScale() + lScaleFactor;
        // Calculate the Proposed Width and Height.
        final int   lWidth  = Math.round(lView.getWidth()  * lScale);
        final int   lHeight = Math.round(lView.getHeight() * lScale);
        // Is the View already too large for wrap content?
        if(lWidth >= this.getWidth() || lHeight >= this.getHeight()) {
            // Don't update the scale.
            return false;
        }
        // Persist this Scale for the View.
        lView.setScaleX(lScale);
        lView.setScaleY(lScale);
        // Assign the Scale.
        this.setScale(lScale);
        // Compute the Position.
        final int[] lPosition = new int[] { Math.round(detector.getFocusX()) - (lWidth / 2), Math.round(detector.getFocusY()) - (lHeight / 2) };
        // Are we wrapping the Position?
        if(this.isWrapContent()) {
            // Wrap the Position.
            this.onWrapContent(lPosition, lWidth, lHeight);
        }
        // Update the Drag.
        this.onUpdateDrag(lView, lPosition);
        // Assert that we handled the scale.
        return true;
    }

    /** Update the Drag. */
    private final void onUpdateDrag(final View pView, final int[] pPosition) {
        // Call the sub-implementation.
        this.onUpdateDrag(pView, pPosition[0], pPosition[1]);
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) { 
        // Is the user not dragging at all?
        if(this.getViewMap().size() == 1) {
            // Fetch the View.
            final View lView = this.getViewMap().entrySet().iterator().next().getValue();
            // Initialize the Scale.
            this.setScale(lView.getScaleX()); 
            // Assert that we've started scaling.
            this.setScaling(true);
            // Inform the callback.
            return true;
        }
        // Otherwise, don't allow scaling.
        return false;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        // Were we scaling?
        if(this.isScaling()) {
            // Assert that we've stopped scaling.
            this.setScaling(false);
            // Reset the Scale.
            this.setScale(Float.NaN);
            // Should we stop dragging now that we've finished scaling?
            if(this.isDropOnScale()) {
                // Clear the ViewMap.
                this.getViewMap().clear();
            }
        }
    }

    /** Returns the View colliding with the given co-ordinates. */
    private final View getViewFor(final int pX, final int pY) {
        // Declare the LocationBuffer.
        final int[] lLocationBuffer = new int[2];
        // Iterate the Views.
        for(int i = 0; i < this.getChildCount(); i++) {
            // Fetch the child View.
            final View lView = this.getChildAt(i);
            // Fetch its absolute position.
            lView.getLocationOnScreen(lLocationBuffer);
            // Determine if the MotionEvent collides with the View.
            if(pX > lLocationBuffer[0] && pY > lLocationBuffer[1] && (pX < lLocationBuffer[0] + (lView.getWidth() * lView.getScaleX())) && (pY < lLocationBuffer[1] + (lView.getHeight() * lView.getScaleY()))) {
                // Return the View.
                return lView;
            }
        }
        // We couldn't find a View.
        return null;
    }

    /* Unused Overrides. */
    @Override public void      onShowPress(MotionEvent e) {  }
    @Override public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }
    @Override public void      onLongPress(MotionEvent e) { }
    @Override public boolean       onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; }

    /* Getters and Setters. */
    private final GestureDetector getGestureDetector() {
        return this.mGestureDetector;
    }

    private final ScaleGestureDetector getScaleGestureDetector() {
        return this.mScaleGestureDetector;
    }

    private final Map<Integer, View> getViewMap() {
        return this.mViewMap;
    }

    private final void setScaling(final boolean pIsScaling) {
        this.mScaling = pIsScaling;
    }

    private final boolean isScaling() {
        return this.mScaling;
    }

    private final void setScale(final float pScale) {
        this.mScale = pScale;
    }

    private final float getScale() {
        return this.mScale;
    }

    /** Defines whether we coerce the drag and zoom of child Views within the confines of the Layout. */
    public final void setWrapContent(final boolean pIsWrapContent) {
        this.mWrapContent = pIsWrapContent;
    }

    public final boolean isWrapContent() {
        return this.mWrapContent;
    }

    /** Defines whether a drag operation is considered 'finished' once the user finishes scaling a view. */
    public final void setDropOnScale(final boolean pIsDropOnScale) {
        this.mDropOnScale = pIsDropOnScale;
    }

    public final boolean isDropOnScale() {
        return this.mDropOnScale;
    }

}

Here I show an example use case:

package com.zonal.regionview;

import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.AnalogClock;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Allocate a RegionView.
        final RegionView lRegionView = new RegionView(this);
        // Add some example items to drag.
        lRegionView.addView(new AnalogClock(this));
        lRegionView.addView(new AnalogClock(this));
        lRegionView.addView(new AnalogClock(this));
        // Assert that we only want to drag Views within the confines of the RegionView.
        lRegionView.setWrapContent(true);
        // Assert that after we've finished scaling a View, we want to stop being able to drag it until a new drag is started.
        lRegionView.setDropOnScale(true);
        // Look at the RegionView.
        this.setContentView(lRegionView);
    }

}
瞄了个咪的 2025-01-15 06:06:44

在此示例中,您可以在其父边界内移动视图,无论其大小、完美的动画和捕获点击。

该解决方案优于其他评论的原因是,该方法使用方向板,它可以自行计算,并且不会中继视图位置,而视图位置是许多错误的根源。

// 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;
}
}

这个版本从过去具有实时更新的网络实体的大东西中剥离出来,它应该可以工作。


你应该以这种方式使用它

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);
    }
}

,而不是

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

你也应该有这个,但这可以很容易地删除

//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);
        }
    };
}

如果你有 ScrollView 或双维 ScrollView 内的容器,你应该将此行添加到 onTouch

view.getParent().requestDisallowInterceptTouchEvent(true);

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);
冰之心 2025-01-15 06:06:44

与@Alex Karshin的答案相同,我改变了一点。

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);
}
}

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);
}
}
美煞众生 2025-01-15 06:06:44

对 @Vyacheslav Shylkin 提供的解决方案进行了一些更改,以消除手动输入数字的依赖性。

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;
        }
    }
}

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;
        }
    }
}
血之狂魔 2025-01-15 06:06:44

//如果您想移动相机或其他任何东西,请按照我的方法进行操作
//我在相机上实现的情况你可以将它应用到你想要的任何地方

public class VideoCallActivity extends AppCompatActivity implements 
  View.OnTouchListener {
 FrameLayout myLayout1;

@SuppressLint("ClickableViewAccessibility")
@Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  //in the frame layout I am setting my camera
   myLayout1.setOnTouchListener(this);
  
  }

   float dX, dY;

@Override
public boolean onTouch(View view, MotionEvent event) {
    switch (event.getAction()) {
 //this is your code
        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;
}

//if you want to move your camera or whatever then do it by following method..in my
//case I am implementing on camera you can apply it on whateveer u want

public class VideoCallActivity extends AppCompatActivity implements 
  View.OnTouchListener {
 FrameLayout myLayout1;

@SuppressLint("ClickableViewAccessibility")
@Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  //in the frame layout I am setting my camera
   myLayout1.setOnTouchListener(this);
  
  }

   float dX, dY;

@Override
public boolean onTouch(View view, MotionEvent event) {
    switch (event.getAction()) {
 //this is your code
        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;
}
淡水深流 2025-01-15 06:06:44

kotlin 的简单代码:

var dx = 0f
var dy = 0f

private fun setMyViewListener(): OnTouchListener {
    return OnTouchListener { view, event ->

        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                dx = view.x - event.rawX
                dx = view.y - event.rawY
            }
            MotionEvent.ACTION_MOVE -> view.animate()
                    .x(event.rawX + dx)
                    //.y(event.rawY + dy) // uncomment this line to move y
                    .setDuration(0)
                    .start()
        }

        true
    }
}

然后像这样调用它:

var myView = findViewById<ConstraintLayout>(R.id.myView)
myView.setOnTouchListener(setMyViewListener())

Simple code for kotlin:

var dx = 0f
var dy = 0f

private fun setMyViewListener(): OnTouchListener {
    return OnTouchListener { view, event ->

        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                dx = view.x - event.rawX
                dx = view.y - event.rawY
            }
            MotionEvent.ACTION_MOVE -> view.animate()
                    .x(event.rawX + dx)
                    //.y(event.rawY + dy) // uncomment this line to move y
                    .setDuration(0)
                    .start()
        }

        true
    }
}

And then call it like this:

var myView = findViewById<ConstraintLayout>(R.id.myView)
myView.setOnTouchListener(setMyViewListener())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文