2012-02-22 2 views
132

Я хотел бы сделать простой элемент управления: контейнер с внутренним видом. Если я касаюсь контейнера, и я перемещаю палец, я хочу переместить вид, чтобы следовать за моим пальцем.android: переместить вид при касании (ACTION_MOVE)

Какой контейнер (макет) следует использовать? Как это сделать?

Мне не нужно использовать поверхность, но простую компоновку.

+0

Вот пример http://thegeekyland.blogspot.com/2015/12/android-animations-explained.html – Arlind

+1

[Android Draggable View] (http://www.singhajit.com/android-draggable-view/) –

ответ

201

Что-то вроде этого:

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

+1

@appserv: Хорошая работа! Но я удивляюсь, почему вы помещаете 'layoutPrarms.rightMargin = -250' и то же самое с' bottomMargin' !! Вы можете это объяснить? В любом случае, спасибо вам большое! –

+1

Если моя память служит мне, без этих значений, представление будет сжато при перемещении вправо или вниз. Вы можете попытаться изменить их и посмотреть, что произойдет. –

+0

Если я реализую '_view' в файле' main.xml', могу ли я переместить '_view'? А если нет? Зачем? Спасибо вам!! –

251

Я нашел простой подход, чтобы сделать это с 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; 
} 
+1

этот ответ самый простой! отличная работа! @ ruan65 –

+0

@ ruan65 Могу ли я ограничить представление, чтобы не вытащить мой экран? –

+0

Лучший ответ до сих пор, и я нашел разные подходы. @Dhiraj - легко, просто найдите ширину и высоту экрана и поместите границы в предложения. DisplayMetrics displaymetrics = new DisplayMetrics(); windowManager.getDefaultDisplay(). GetMetrics (displaymetrics); final int height = displaymetrics.heightPixels; final int width = displaymetrics.widthPixels; – vanste25

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

Изменено решение, предоставленное @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; 
     } 
    } 
} 
0

В коде ниже я создал что-то под названием RegionView (git), который представляет собой многоразовый контейнер отвечает за управление перетаскивания и масштабирования операций для каждого из своих детей вложенными.

Здесь мы манипулируем top и left коэффициентов ребенка View «ы LayoutParams для имитации движения вокруг диаграммы. Развязывая интерпретацию обработки того, что понимается как операция перетаскивания, и что определено как операция масштабирования, мы можем обеспечить надежную манипуляцию с ребенком 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); 
    } 

}