【问题标题】:android: move a view on touch move (ACTION_MOVE)android:在触摸移动时移动视图(ACTION_MOVE)
【发布时间】:2012-03-12 23:21:39
【问题描述】:

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

我应该使用哪种容器(布局)?如何做到这一点?

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

【问题讨论】:

标签: android touch


【解决方案1】:

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

【讨论】:

  • @ruan65 我可以限制视图不被拖出我的屏幕吗?
  • 如果有人和我一样对为什么会这样感到困惑,那么只要知道 getX() 返回一个相对于视图的 X 坐标,而 getRawX() 返回一个相对于设备的绝对坐标屏幕。 stackoverflow.com/a/20636236/4258848
  • 天才,刚刚添加了一些边界检查,它非常适合水平滚动滑块按钮
  • 虽然这与前面的答案一样有效,但最好在移动事件中使用 translationX 和 translationY 方法。要使位置保持不变,请在“up”事件中设置视图的布局属性。翻译方法是使用手机的硬件层。布局属性不是。
  • 我们也可以直接使用setXsetY,而不是应用一个持续时间为0的动画。
【解决方案2】:

类似这样的:

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

【讨论】:

  • @appserv:干得好!但我想知道为什么你把layoutPrarms.rightMargin = -250bottomMargin 一样!你能解释一下吗?总之,非常感谢!!
  • 如果我没记错的话,如果没有这些值,视图将在向右或向下移动时被压缩。您可以尝试更改它们,看看会发生什么。
  • 它们不必是最终的。我将它们设为 final 只是为了避免重新分配这些变量。
  • 它工作正常,但有什么办法可以限制从屏幕外移动,这意味着只能在屏幕边界内移动..
  • @VyacheslavShilkin 我在这段代码中发现的唯一问题是我无法使布局从 xml 文件膨胀到移动。这真的是代码的问题还是因为我的无知而错过了?
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      创建一个自定义触摸监听器类(在 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 是您的视图的父级。

      【讨论】:

      • 太棒了!!,也是 kotlin 的简单解决方案。
      【解决方案6】:

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

      【讨论】:

      • 这个答案的有趣之处在于我写了它,它多次帮助了我。
      【解决方案7】:

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

      Kotlin sn-p:

      yourView.translationX = xTouchCoordinate
      yourView.translationY = yTouchCoordinate
      

      【讨论】:

        【解决方案8】:

        在下面的代码中,我创建了一个名为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);
            }
        
        }
        

        【讨论】:

          【解决方案9】:

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

          此解决方案优于其他 cmets 的原因在于此方法使用了一个 Directional Pad,它会自行计算并且不会中继 View 位置,而 View 位置是很多错误。

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

          【讨论】:

            【解决方案10】:

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

            【讨论】:

              【解决方案11】:

              稍微改变了@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;
                      }
                  }
              }
              

              【讨论】:

                【解决方案12】:

                //如果你想移动你的相机或其他任何东西,那么按照下面的方法..在我的 //如果我在相机上实现,你可以将它应用到你想要的任何东西上

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

                【讨论】:

                  猜你喜欢
                  • 2017-06-20
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多