我想做一个简单的控制:一个内部有视图的容器。如果我触摸容器并移动手指,我想移动视图以跟随我的手指。
我应该使用哪种容器(布局)?怎么做?
我不需要使用曲面,只需要简单的布局。
答案 0 :(得分:320)
我使用ViewPropertyAnimator找到了一种简单的方法:
{{1}}
答案 1 :(得分:225)
这样的事情:
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
答案 2 :(得分:9)
按照@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;
}
答案 3 :(得分:2)
触摸容器,视图就会随您的手指移。
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 :(得分:1)
在下面的代码中,我创建了一个名为RegionView
(git)的东西,它是一个可重用的容器,负责管理每个嵌套子代的拖动和缩放操作。
在这里,我们操纵孩子top
的{{1}}的{{1}}和left
系数来模拟图表的移动。通过解耦处理被理解为拖拽操作的解释以及确定为扩展操作的解释,我们可以提供对孩子View
的可靠操纵。
LayoutParams
这里我展示了一个用例示例:
View
答案 5 :(得分:1)
在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
答案 6 :(得分:1)
在此示例中,无论大小,无瑕的动画和捕捉点击,您都可以在其父边界内移动视图。
此解决方案优于其他评论的原因是,该方法使用了 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);
答案 7 :(得分:1)
与@ 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);
}
}
答案 8 :(得分: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;
}
}
}
答案 9 :(得分:0)
我建议使用view.translationX和view.translationY来移动视图。
科特琳片段:
yourView.translationX = xTouchCoordinate
yourView.translationY = yTouchCoordinate
答案 10 :(得分:0)
创建一个自定义的触摸侦听器类(在Kotlin中):
(此代码限制您的视图从其父视图中拖出)
class CustomTouchListener(val screenWidth: Int, val screenHeight: Int) : View.OnTouchListener {
private var dX: Float = 0.toFloat()
private var dY: Float = 0.toFloat()
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()
}
else -> return true
}
return true
}
}
如何使用它?
parentView.viewTreeObserver.addOnGlobalLayoutListener { view.setOnTouchListener(CustomTouchListener(parentView.width, parentView.height)) }
parentView
是视图的父级。
答案 11 :(得分:0)
//如果你想移动你的相机或其他任何东西,请按照以下方法进行..在我的 //如果我在相机上实现,你可以将它应用到你想要的任何东西
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;
}