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

Posted

技术标签:

【中文标题】Android - 在屏幕上移动 ImageView(如拖动)【英文标题】:Android - move an ImageView on screen (Like dragging) 【发布时间】:2018-03-28 11:35:00 【问题描述】:

我正在尝试创建一个应用程序,它可以像拖动一样在您的设备上移动 ImageView,例如,当我将 75% 的 ImageView 移出屏幕时,会显示 Toast。我一直在阅读有关MotionEventonTouchListener 的信息,并且我已经关注了question,但这并不能说服我。

编辑

我当前的代码是:

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/对不对,我还获取设备的尺寸只是为了尝试 X 或 Y 是否与高度或宽度相同,只是显示 Toast 但它没有正确执行。

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

注意

如果您知道任何其他更简单或更简洁的方法,请随时将其放在这里,我不在乎我的代码,我可以调整它,我只是希望它清晰而不是硬编码。

【问题讨论】:

我遇到了类似的问题,然后得到了解决方案。 Checking Drag Out of Bounds 如果您知道如何解决它(如果已测试),请随时提出答案,我会对其进行测试:) 【参考方案1】:

您的日常工作大部分时间都有效。在下面的代码中,我已经注释掉了不需要的部分,并对需要解释的部分进行了注释。这是成品的样子:

此图说明了如何计算左边距。相同类型的计算适用于上边距。

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_
    android:layout_
    tools:context=".MainActivity">

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

</RelativeLayout>

【讨论】:

很好的答案和很好的解释,我用我的 ic_launcher 做的,它没有 100% 工作,它说 OUT 当由 TOP/BOTTOM 出去但左/右它不是 100% 工作,这是必要的把layout_widht 150dp?不能是 wrap_content 吗?我的图像将与屏幕大小几乎相同,但现在我正在使用 ic_launcher 对其进行测试.... @Skizo-ozᴉʞS 这很好奇。它对我来说没问题,但我们的环境必须不同。您能否发布您正在使用的相关 XML 布局以及您的 ic_launcher 的描述?它只是一个标准的 ic_launcher 图标吗?我想知道您的图像是否比看起来大。顺便说一句,我更新了代码中负边距的设置方式。查看更新的答案。 是的,看我的XML LAYOUT,现在它不动了,因为我把它放在屏幕中间...(不过我想把它放在屏幕中间),我放 250sp 但我想把它放得更大.... 看@Cheticamp,我现在正在使用这个image,因为我看到75% 太多了,可以少一点吗?像 50% 或更少?我已经编辑了xml layout @Skizo-ozᴉʞS 代码已更改,允许初始放置在屏幕中心。请参阅 onTouch() 中的 removeRule() 语句。添加了参数以改变在 toast 显示之前必须“out”的图像百分比。见isOut()【参考方案2】:

该框架有一个名为View.OnDragListener 的类。请参阅Drag & Drop 教程。

如果您想研究如何做到这一点,另请参阅DraggablePanel 项目。

【讨论】:

我已经尝试了最后一个示例,但我如何确定视图何时“离开”设备?我在 ConstraintLayout 上也有这个,它会影响这个吗?我正在尝试这个example 你觉得可以吗?另外,我如何检测它是否从设备中取出?我想将 X 和 Y 与 windowheight 和 windowwidth 进行比较 我已经用我当前的代码编辑了我的问题,你能帮我解决我的问题吗? 你研究过我的案子吗? 是的我确实做到了,但是当我尝试示例时,它并没有移动这里的示例code @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;
    

【讨论】:

似乎在工作,但在左边看起来它没有检测到它,对吧?当用户试图将图像移出设备时,我想敬酒什么的 那么你应该限制移动,在 ACTION_MOVE 中添加类似 if(topMargin 窗口高度)topMargin = 窗口高度 - 高度;左边距和宽度也一样。 你能做一个样本,这样我可以测试并批准它吗?【参考方案4】:

我正在使用这个方法来拖动一个 ImageView ,希望对你有帮助: 所以我定义了类的那些属性:

 private float xCoOrdinate, yCoOrdinate;
 private double screenCenterX, screenCenterY;

然后我在activity的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;
        
    );

【讨论】:

Nmm 你能放一个简单的吐司而不是动画吗?我的意思是,如果您要离开设备敬酒(当图像在屏幕外大约 50% 时)? 我为你添加了敬酒 sis 。试试看,如果您有任何问题,请随时问我 PS:当你调用 ACTION_UP 时会显示 Toasts,如果你在屏幕外的 50% 会显示“Out”,如果你仍然在屏幕的 50% 会显示“in”class="comcopy">跨度> 好吧,那50%的图片是随机数,但是是的,今晚我会做一个测试,我会告诉你,但它不能被action_move捕捉到?跨度> 我已经测试过了,好像不行,作为getStatusBarHeight() method I got this from this question,可以吗?如果我在屏幕中间,它会说 OUT【参考方案5】:

更新

在步骤 3 中添加右/下边距以防止图像缩放。 您可以看到如果您不更改右/下边距,图像将按相对布局缩放。

    getMeasuredHeight/Width 避免MATCH_PARENTWRAP_CONTENT。 如果有工具栏/操作栏,则topMargin + height &gt; relativeLayout's height 也适用于底部确定。

    out of bound的记录状态避免toast连续出现。

    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 是为了让我知道我什么时候离开屏幕 Yes toast 提示超出边界。我的例子只有一次超出边界的时候 我会在大约 1 小时内测试 int,我会告诉你 ^^ 我已经测试过了,我不明白为什么如果我把它拖到右边它会缩小,我用另一个图像(更小)尝试过它,似乎它可以工作,但是当我放置我的真实图像时,它会在我尝试移动图像时放大/缩小。可能是因为太大了? 是的,如果我们不改变左/下边距,那么图像将放大/缩小。我已经通过结果预览更新了我的答案。【参考方案6】:

您可以通过此代码实现此目的。

DisplayMetrics metrics = getResources().getDisplayMetrics();
int windowWidth = metrics.widthPixels;
int windowHeight = metrics.heightPixels;

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

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


【讨论】:

复制相同的答案是没有用的,所以如果你给出了一个答案并且你不想被否决,请在示例项目上对其进行测试并发布你的真实代码。 兄弟,这是解决我问题的代码,如果你想要一个示例,请告诉我,我会为你做一个:) 对我来说测试它更容易,是的。

以上是关于Android - 在屏幕上移动 ImageView(如拖动)的主要内容,如果未能解决你的问题,请参考以下文章

Android 在屏幕上拖放图像?

如何按住浮动按钮并将其移动到 android 屏幕上的任何位置?

Kevin Learn Android:Android 手签板

Kevin Learn Android:Android 手签板

当我单击按钮进入下一个屏幕时,移动 Android 应用程序崩溃

触摸屏幕上的任何位置,除了 GUI 按钮 Android Unity