android:在触摸移动时移动视图

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

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

我不需要使用一个表面,但一个简单的布局。

237245 次浏览

就像这样:

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

我发现了一个简单的方法来做到这一点与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;
}

按照@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;
}

改变了一点由@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;
}
}
}

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


}

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

我推荐使用view。translationX和view。翻译来移动你的观点。

芬兰湾的科特林片段:

yourView.translationX = xTouchCoordinate
yourView.translationY = yTouchCoordinate

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

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

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

在这个例子中,你可以在它的父边界内移动视图,无论它的大小、完美的动画和捕捉点击。

这种解决方案优于其他注释的原因是,这种方法使用定向垫来计算自己,不会依赖于视图位置,而视图位置是许多错误的来源。

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

这个版本被剥夺了大的东西,过去有网络实体,得到更新的live和这样,它应该工作。


你应该这样使用它

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

和@亚历克斯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);
}
}

//如果你想移动你的相机或任何东西,然后按照下面的方法来做 //我在相机上实现的情况下,你可以应用它在任何你想要的

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

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())