Android拖动ImageView非常滞后

问题描述 投票:0回答:3

我正在尝试实现一个可触摸拖动的 ImageView在一个Android应用中。在SO和其他地方有大量的示例代码,而且似乎都是类似于 这个.

我试过类似的东西,但它是非常滞后的,我一直得到警告。

Choreographer﹕ Skipped XX frames!  The application may be doing too much work on its main thread.

我按照 安卓文档建议 上,并试图在另一个帖子中进行工作,但这并没有解决这个问题。

基本上我注册了一个 OnTouchListener 在我看来,那么在 onTouch() 方法,我启动了一个新的线程来尝试并完成这项工作(布局参数的最终更新会被送回UI线程,以便利用 View.post()).

我看不出如何通过在主线程上少做一点工作来实现。有没有什么方法可以修复我的代码?还是有更好的方法?

代码。

    private void setTouchListener() {
        final ImageView inSituArt = (ImageView)findViewById(R.id.inSitu2Art);
        inSituArt.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                final MotionEvent event2 = event;

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) inSituArt.getLayoutParams();
                        switch (event2.getAction()) {
                            case MotionEvent.ACTION_DOWN: {
                                int x_cord = (int) event2.getRawX();
                                int y_cord = (int) event2.getRawY();
                                // Remember where we started
                                mLastTouchY = y_cord;
                                mLastTouchX = x_cord;
                            }
                            break;
                            case MotionEvent.ACTION_MOVE: {
                                int x_cord = (int) event2.getRawX();
                                int y_cord = (int) event2.getRawY();
                                float dx = x_cord - mLastTouchX;
                                float dy = y_cord - mLastTouchY;

                                layoutParams.leftMargin += dx;
                                layoutParams.topMargin += dy;
                                layoutParams.rightMargin -= dx;


                                final FrameLayout.LayoutParams finalLayoutParams = layoutParams;
                                inSituArt.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        inSituArt.setLayoutParams(finalLayoutParams);
                                    }
                                });


                                mLastTouchX = x_cord;
                                mLastTouchY = y_cord;
                            }
                            break;
                            default:
                                break;
                        }

                    }
                }).start();
                return true;
            }
        });
    }
android multithreading imageview touch
3个回答
0
投票

new Thread 正如 @JohnWhite 所说,onTouch 会被连续调用很多次,像这样创建和启动新的线程是非常非常糟糕的。

一般来说,你之所以会遇到这些问题,是因为你所遵循的示例代码,虽然它可以工作,但它是一个未经优化的糟糕代码。

每次你在调用 setLayoutParams 系统必须执行一个新的完整的布局通道,而这是一个连续不断的事情。

我不会完全给你写代码,但我会给你指出可能的替代方案。

首先去掉你使用的所有线程逻辑。你并没有真正执行任何复杂的计算,一切都可以在UI线程上运行。

  1. 使用 偏移顶部和底部左、右偏移 在屏幕上移动您的ImageView。
  2. 使用ViewTransformations与 setTranslationX (和Y)
  3. 将你的ImageView改为 match_parent 并创建一个自定义的ImageView。在这个自定义视图中,您将覆盖 onDraw 方法来移动图像,然后再绘制它。像这样。

     @Override
     public void onDraw(Canvas canvas) {
       int saveCount = canvas.save();
       canvas.translate(dx, dy); // those are the values calculated by your OnTouchListener
       super.onDraw(canvas); // let the image view do the normal draw
       canvas.restoreToCount(saveCount); // restore the canvas position
     }
    

最后一个选项非常有趣,因为它完全没有改变布局。它只是让ImageView负责整个区域,并将绘图移动到正确的位置。非常高效的方式。

在其中一些选项上,你需要在视图上调用 "invalidate",所以在视图上的 draw 可以再次被调用。


0
投票

onTouch 是一个反复触发的事件,你要求视图每次都在新的位置重新绘制。onTouch 被触发。对于你的应用程序来说,绘制是一个非常昂贵的操作--在常规的视图层次结构中,永远不应该要求它重复做这件事。你的目标应该是减少发生的绘制命令的数量,所以我会设置一个简单的模数操作,只允许它每隔一段时间执行一次移动,比如,10 onTouch 事件。你最终会有一个权衡,在那里它 样子 波涛汹涌,因为如果你把你的模数放得太高,它不是每一帧都会发生,但应该在5-30的范围内有一个平衡。

在你的类上面,你会想要这样的东西。

static final int REFRESH_RATE = 10; //or 20, or 5, or 30, whatever works best

int counter = 0;

然后在你的触摸事件中,

case MotionEvent.ACTION_MOVE: {
    counter += 1;
    if((REFRESH_RATE % counter) == 0){ //this is the iffstatement you are looking for
      int x_cord = (int) event2.getRawX();
      int y_cord = (int) event2.getRawY();
      float dx = x_cord - mLastTouchX;
      float dy = y_cord - mLastTouchY;
      layoutParams.leftMargin += dx;
      layoutParams.topMargin += dy;
      layoutParams.rightMargin -= dx;

      final FrameLayout.LayoutParams finalLayoutParams = layoutParams;
      inSituArt.post(new Runnable() {
        @Override
        public void run() {
           inSituArt.setLayoutParams(finalLayoutParams);
        }
      });

      mLastTouchX = x_cord;
      mLastTouchY = y_cord;
    }
    counter = 0;
}

0
投票

我花了很长时间才找到一个真正的解决方案

你应该使用Drawable而不是Bitmap。

override fun onDraw(canvas: Canvas?) {
        canvas?.save()
        canvas?.concat(myCustomMatrix)
        mDrawable?.draw(canvas!!)
        canvas?.restore()
    }

移动图像(dx, dy)

myCustomMatrix.postTranslate(dx, dy)
invalidate()
© www.soinside.com 2019 - 2024. All rights reserved.