Android - 在屏幕上移动ImageView(如拖动)

14

我正在尝试创建一个应用程序,可以像拖动一样移动设备上的ImageView,当我将大约75%的ImageView移到屏幕外时,例如显示一个Toast。我一直在阅读关于MotionEventonTouchListener的内容,并且我已经遵循了这个问题,但我并不满意。

编辑

我当前的代码是:

public class MainActivity extends AppCompatActivity implements View.OnTouchListener {

    int windowwidth;
    int windowheight;
    private ImageView mImageView;
    private ViewGroup mRrootLayout;
    private int _xDelta;
    private int _yDelta;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DisplayMetrics displaymetrics = new DisplayMetrics();
        this.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
        windowwidth = displaymetrics.widthPixels;
        windowheight = displaymetrics.heightPixels;
        mRrootLayout = (ViewGroup) findViewById(R.id.root);
        mImageView = (ImageView) mRrootLayout.findViewById(R.id.im_move_zoom_rotate);

        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 150);
        mImageView.setLayoutParams(layoutParams);
        mImageView.setOnTouchListener(this);
    }
    public boolean onTouch(View view, MotionEvent event) {
        final int X = (int) event.getRawX();
        final int Y = (int) event.getRawY();
        if(X == 0){
            Toast.makeText(this, "OUT", Toast.LENGTH_SHORT).show();
        }
        else if (Y == 0){
            Toast.makeText(this, "OUT", Toast.LENGTH_SHORT).show();
        }
        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;
        }
        mRrootLayout.invalidate();
        return true;
    }
}

我使用那些ifelse if只是为了知道ImageView是否正在设备的左侧和右侧移出,看起来好像还不错,但我想让它更加简洁而不是硬编码,此外,我不明白LayoutParams(150,150)为什么要选150?我也不明白为什么我必须创建一个RelativeLayout.LayoutParams并放入其中。

layoutParams.rightMargin = -250;
layoutParams.bottomMargin = -250;

我使用了 if/else if,因为我想在用户试图将 ImageView 移出设备时删除它,所以我需要控制当他尝试这样做时的情况,在目前我只得到了 TOP/LEFT/RIGHT,没有 DOWN,我还获取了我的设备尺寸,只是为了尝试一下 X 或 Y 是否与高度或宽度相同,如果是,就显示 Toast,但它并没有正确执行。

现在我的 ImageViewic_launcher,但它将会更大(几乎是屏幕中央)。

注意

如果您知道任何更简单或更清晰的方法,请随意在此处提出,我不关心我的代码,我可以适应它,我只希望它清晰明了且没有硬编码。


我遇到了类似的问题,然后找到了解决方案。 检查拖动是否超出边界 - Abdul Ahad
如果你知道如何解决它(已经测试过),请放心回答,我会测试一下 :) - Skizo-ozᴉʞS ツ
6个回答

10
你的例程大部分都是有效的。在下面的代码中,我已经注释掉了不需要的部分,并对需要一些解释的部分做了标注。这就是最终产品的样子:

enter image description here

这张图解释了左边距的计算方法。同样的计算方法也适用于上边距。

enter image description here

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnTouchListener {

    int windowwidth; // Actually the width of the RelativeLayout.
    int windowheight; // Actually the height of the RelativeLayout.
    private ImageView mImageView;
    private ViewGroup mRrootLayout;
    private int _xDelta;
    private int _yDelta;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // We are interested when the image view leaves its parent RelativeLayout
        // container and not the screen, so the following code is not needed.
//        DisplayMetrics displaymetrics = new DisplayMetrics();
//        this.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
//        windowwidth = displaymetrics.widthPixels;
//        windowheight = displaymetrics.heightPixels;
        mRrootLayout = (ViewGroup) findViewById(R.id.root);
        mImageView = (ImageView) mRrootLayout.findViewById(R.id.im_move_zoom_rotate);

        // These these following 2 lines that address layoutparams set the width
        // and height of the ImageView to 150 pixels and, as a side effect, clear any
        // params that will interfere with movement of the ImageView.
        // We will rely on the XML to define the size and avoid anything that will
        // interfere, so we will comment these lines out. (You can test out how a layout parameter
        // can interfere by setting android:layout_centerInParent="true" in the ImageView.
//        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 150);
//        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 150);
//        mImageView.setLayoutParams(layoutParams);
        mImageView.setOnTouchListener(this);

        // Capture the width of the RelativeLayout once it is laid out.
        mRrootLayout.post(new Runnable() {
            @Override
            public void run() {
                windowwidth = mRrootLayout.getWidth();
                windowheight = mRrootLayout.getHeight();
            }
        });
    }

    // Tracks when we have reported that the image view is out of bounds so we
    // don't over report.
    private boolean isOutReported = false;

    public boolean onTouch(View view, MotionEvent event) {
        final int X = (int) event.getRawX();
        final int Y = (int) event.getRawY();

        // Check if the image view is out of the parent view and report it if it is.
        // Only report once the image goes out and don't stack toasts.
        if (isOut(view)) {
            if (!isOutReported) {
                isOutReported = true;
                Toast.makeText(this, "OUT", Toast.LENGTH_SHORT).show();
            }
        } else {
            isOutReported = false;
        }
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                // _xDelta and _yDelta record how far inside the view we have touched. These
                // values are used to compute new margins when the view is moved.
                _xDelta = X - view.getLeft();
                _yDelta = Y - view.getTop();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_DOWN:
            case MotionEvent.ACTION_POINTER_UP:
                // Do nothing
                break;
            case MotionEvent.ACTION_MOVE:
                RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) view
                    .getLayoutParams();
                // Image is centered to start, but we need to unhitch it to move it around.
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                    lp.removeRule(RelativeLayout.CENTER_HORIZONTAL);
                    lp.removeRule(RelativeLayout.CENTER_VERTICAL);
                } else {
                    lp.addRule(RelativeLayout.CENTER_HORIZONTAL, 0);
                    lp.addRule(RelativeLayout.CENTER_VERTICAL, 0);
                }
                lp.leftMargin = X - _xDelta;
                lp.topMargin = Y - _yDelta;
                // Negative margins here ensure that we can move off the screen to the right
                // and on the bottom. Comment these lines out and you will see that
                // the image will be hemmed in on the right and bottom and will actually shrink.
                lp.rightMargin = view.getWidth() - lp.leftMargin - windowwidth;
                lp.bottomMargin = view.getHeight() - lp.topMargin - windowheight;
                view.setLayoutParams(lp);
                break;
        }
        // invalidate is redundant if layout params are set or not needed if they are not set.
//        mRrootLayout.invalidate();
        return true;
    }

    private boolean isOut(View view) {
        // Check to see if the view is out of bounds by calculating how many pixels
        // of the view must be out of bounds to and checking that at least that many
        // pixels are out.
        float percentageOut = 0.50f;
        int viewPctWidth = (int) (view.getWidth() * percentageOut);
        int viewPctHeight = (int) (view.getHeight() * percentageOut);

        return ((-view.getLeft() >= viewPctWidth) ||
            (view.getRight() - windowwidth) > viewPctWidth ||
            (-view.getTop() >= viewPctHeight) ||
            (view.getBottom() - windowheight) > viewPctHeight);
    }
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/im_move_zoom_rotate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/circle" />

</RelativeLayout>

回答得很好,解释得也很清楚。我用我的ic_launcher做了一下,但它并没有100%的工作效果。当从上/下移动时,它会显示OUT,但左/右移动时并不完全有效。需要将layout_width设置为150dp吗?不能使用wrap_content吗?我的图片几乎与屏幕大小相同,但现在我正在使用ic_launcher进行测试... - Skizo-ozᴉʞS ツ
是的,请查看我的XML布局,现在它不会移动,因为我把它放在屏幕中央...(虽然我想让它在屏幕中央),我把大小设置为250sp,但我想要更大... - Skizo-ozᴉʞS ツ
看这里@Cheticamp,我现在正在使用这个 图片,因为我觉得75%太大了,能不能小一些?比如50%或者再小一点点?我已经编辑了xml layout - Skizo-ozᴉʞS ツ
这意味着API 15也会将其居中吗?您认为将其放在wrap_content上还是进行您所做的检查更好? - Skizo-ozᴉʞS ツ
在视图上监听触摸事件是否比在活动中更容易呢? - stdout
显示剩余9条评论

3

我尝试了最后一个例子,但是如何确定视图何时要“离开”设备?另外,我将它放在ConstraintLayout上,是否会影响它?我正在尝试这个例子,你觉得它可以吗?另外,如何检测它是否要离开设备?我想用窗口高度和宽度来比较X和Y。 - Skizo-ozᴉʞS ツ
我已经编辑了我的问题,并附上了我的当前代码。你能帮助我解决我提出的问题吗? - Skizo-ozᴉʞS ツ
have you studied my case? - Skizo-ozᴉʞS ツ
是的,Y.S. 我肯定做了,但是当我尝试这个示例时它没有动。这是示例代码:code - Skizo-ozᴉʞS ツ
{btsdaf} - Yash Sampat
{btsdaf} - Skizo-ozᴉʞS ツ

3
我使用这种方法来拖动ImageView,希望能对您有所帮助: 因此,我定义了该类的这些属性:
 private float xCoOrdinate, yCoOrdinate;
 private double screenCenterX, screenCenterY;

然后我将这段代码实现在活动的OnCreate()方法下:

 mRrootLayout.getBackground().setAlpha(255);

    /**
     * Calculate max hypo value and center of screen.
     */
    final DisplayMetrics display = getResources().getDisplayMetrics();
    screenCenterX = display.widthPixels / 2;
    screenCenterY = (display.heightPixels - getStatusBarHeight()) / 2;
    final double maxHypo = Math.hypot(screenCenterX, screenCenterY);

    mImageView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            /**
             * Calculate hypo value of current imageview position according to center
             */
            double centerYPos = mImageView.getY() + (mImageView.getHeight() / 2);
            double centerXPos = mImageView.getX() + (mImageView.getWidth() / 2);
            double a = screenCenterX - centerXPos;
            double b = screenCenterY - centerYPos;
            double hypo = Math.hypot(a, b);

            /**
             * change alpha of background of layout
             */
            alpha = (int) (hypo * 255) / (int) maxHypo;
            if (alpha < 255)
                mRrootLayout.getBackground().setAlpha(255 - alpha);

            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    xCoOrdinate = mImageView.getX() - event.getRawX();
                    yCoOrdinate = mImageView.getY() - event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    mImageView.animate().x(event.getRawX() + xCoOrdinate).y(event.getRawY() + yCoOrdinate).setDuration(0).start();
                    break;
                case MotionEvent.ACTION_UP:
                     if (alpha > 50) {
                        Toast.makeText(ImageViewerActivity.this, "Out", Toast.LENGTH_SHORT).show();
                        return false;
                    } else {
                        Toast.makeText(ImageViewerActivity.this, "In", Toast.LENGTH_SHORT).show();
                        mImageView.animate().x(0).y((float) screenCenterY - mImageView.getHeight() / 2).setDuration(100).start();
                        mRrootLayout.getBackground().setAlpha(255);
                    }
                default:
                    return false;
            }
            return true;
        }
    });

你能否用一个简单的提示框来代替动画呢?我的意思是,如果图片即将从屏幕中移出(大约在50%左右),那么就弹出提示框。 - Skizo-ozᴉʞS ツ
{btsdaf} - Imene Noomene
{btsdaf} - Imene Noomene
{btsdaf} - Skizo-ozᴉʞS ツ
{btsdaf} - Skizo-ozᴉʞS ツ

3

以下是如何使用onTouch移动RelativeLayout中所有视图的示例。希望能有所帮助:

public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
    private RelativeLayout mRelLay;
    private float mInitialX, mInitialY;
    private int mInitialLeft, mInitialTop;
    private View mMovingView = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRelLay = (RelativeLayout) findViewById(R.id.relativeLayout);

        for (int i = 0; i < mRelLay.getChildCount(); i++)
            mRelLay.getChildAt(i).setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        RelativeLayout.LayoutParams mLayoutParams;

        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mMovingView = view;
                mLayoutParams = (RelativeLayout.LayoutParams) mMovingView.getLayoutParams();
                mInitialX = motionEvent.getRawX();
                mInitialY = motionEvent.getRawY();
                mInitialLeft = mLayoutParams.leftMargin;
                mInitialTop = mLayoutParams.topMargin;
                break;

            case MotionEvent.ACTION_MOVE:
                if (mMovingView != null) {
                    mLayoutParams = (RelativeLayout.LayoutParams) mMovingView.getLayoutParams();
                    mLayoutParams.leftMargin = (int) (mInitialLeft + motionEvent.getRawX() - mInitialX);
                    mLayoutParams.topMargin = (int) (mInitialTop + motionEvent.getRawY() - mInitialY);
                    mMovingView.setLayoutParams(mLayoutParams);
                }
                break;

            case MotionEvent.ACTION_UP:
                mMovingView = null;
                break;
        }

        return true;
    }
}

{btsdaf} - Skizo-ozᴉʞS ツ
{btsdaf} - from56
你能否制作一个样本,以便我可以测试并批准它? - Skizo-ozᴉʞS ツ

3

更新

在第三步中添加右侧/底部边距以防止图片缩放。 如不更改右侧/底部边距,您会发现相对布局会导致图片缩放。 preview solution old and new

  1. getMeasuredHeight/Width avoid MATCH_PARENT and WRAP_CONTENT.
  2. If there is a toolbar/actionbar then topMargin + height > relativeLayout's height also applies to out of bottom determination.
  3. record state of out of bound avoid toast continuous appear.

    public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
    
        Point lastPoint = new Point();
        RelativeLayout relativeLayout;
    
        boolean lastOutOfTop = false;
        boolean lastOutOfLeft = false;
        boolean lastOutOfRight = false;
        boolean lastOutOfBottom = false;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            (findViewById(R.id.imageView)).setOnTouchListener(this);
            relativeLayout = (RelativeLayout)findViewById(R.id.relativeLayout);
        }
    
        @Override
        public boolean onTouch(View view, MotionEvent event) {
            //1. user's finger
            final Point point = new Point((int) event.getRawX(), (int) event.getRawY());
    
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN:
                    // 2. record the last touch point
                    lastPoint = point;
                    break;
                case MotionEvent.ACTION_UP:
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    break;
                case MotionEvent.ACTION_MOVE:
                    // 3. get the move offset
                    final Point offset = new Point(point.x-lastPoint.x, point.y-lastPoint.y);
                    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view
                            .getLayoutParams();
                    layoutParams.leftMargin += offset.x;
                    layoutParams.topMargin += offset.y;
                    // * also check right/bottom Margin
                    layoutParams.rightMargin = relativeLayout.getMeasuredWidth() - layoutParams.leftMargin+view.getMeasuredWidth();
                    layoutParams.bottomMargin = relativeLayout.getMeasuredHeight() - layoutParams.topMargin+view.getMeasuredHeight();
                    view.setLayoutParams(layoutParams);
                    // 4. record the last touch point
                    lastPoint = point;
                    break;
            }
    
            // 5. check bounds
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
            boolean outOfTop = layoutParams.topMargin < 0;
            boolean outOfLeft = layoutParams.leftMargin < 0;
            boolean outOfBottom = layoutParams.topMargin+view.getMeasuredHeight() > relativeLayout.getMeasuredHeight();
            boolean outOfRight = layoutParams.leftMargin+view.getMeasuredWidth() > relativeLayout.getMeasuredWidth();
    
            // 6. if out of bound
            if (outOfLeft&&!lastOutOfLeft) Toast.makeText(this, "OUT Left", Toast.LENGTH_SHORT).show();
            if (outOfTop&&!lastOutOfTop) Toast.makeText(this, "OUT Top", Toast.LENGTH_SHORT).show();
            if (outOfBottom&&lastOutOfBottom) Toast.makeText(this, "OUT Bottom", Toast.LENGTH_SHORT).show();
            if (outOfRight&&lastOutOfRight)  Toast.makeText(this, "OUT Right", Toast.LENGTH_SHORT).show();
    
            // 7. record
            lastOutOfTop = outOfTop;
            lastOutOfLeft = outOfLeft;
            lastOutOfBottom = outOfBottom;
            lastOutOfRight = outOfRight;
            return true;
        }
    }
    

我说Toast,当我移出屏幕时通知我。 - Skizo-ozᴉʞS ツ
{btsdaf} - Codus
我将在大约1小时内测试int,并且会告诉你结果^^ - Skizo-ozᴉʞS ツ
我已经测试了它,但不理解为什么当我向右拖动时它会缩小。我尝试使用另一张更小的图片来测试,似乎可以正常工作。但是当我使用真实图片时,每次尝试移动图像时它都会自动放大或缩小。也许是因为图片太大了? - Skizo-ozᴉʞS ツ
如果我们不改变左/底边距,则图像将放大/缩小。我已经更新了我的答案,并提供了结果预览。 - Codus

1
您可以通过以下代码实现此目标。
DisplayMetrics metrics = getResources().getDisplayMetrics();
int windowWidth = metrics.widthPixels;
int windowHeight = metrics.heightPixels;

现在在您的onTouch方法中,计算目标位置是否超出上述尺寸。

如果(currentXLocation + deltaX > windowWidth){

// this will ensure that target location 
// is always <= windowHeight
deltaX = windowWidth - currentXLocation; 

} else if( currentXLocation + deltaX < 0){

deltaX = -(currentXLocation);

} else if (...){

// perform similar calculations for the rest 

}

复制相同的答案是没有用的,所以如果你提供了一个答案并且不想被踩,那么请在一个样例项目中测试它并发布你的真实代码。 - Skizo-ozᴉʞS ツ
兄弟,这是解决我的问题的代码,如果你需要一个样例,请告诉我,我会给你做一个 :) - Abdul Ahad
对我来说测试它更容易,是的。 - Skizo-ozᴉʞS ツ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接