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

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

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


当前回答

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

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

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

Kotlin snippet:

yourView.translationX = xTouchCoordinate
yourView.translationY = yTouchCoordinate

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

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

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