Android拖动ImageView非常滞后

时间:2015-05-15 14:19:52

标签: android multithreading imageview touch

我正在尝试在Android应用中实现触控拖动ImageView。在SO和其他地方有大量的示例代码,所有这些代码似乎都与this one一致。

我尝试了类似的东西,但是它非常迟钝而且我一直收到警告

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

我关注了the Android documentation advice并试图在一个单独的线程中完成工作,但这还没有解决问题。

基本上我在我的视图中注册了一个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;
            }
        });
    }

3 个答案:

答案 0 :(得分:0)

onTouch是一个反复触发的事件,并且每次onTouch被触发时,您都要求视图在新位置重绘自己。对于您的应用程序,Draw是一项非常昂贵的操作 - 永远不应该要求它在常规视图层次结构中重复执行此操作。你的目标应该是减少发生的绘图命令的数量,所以我会设置一个简单的模数运算,只允许它执行每次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;
}

答案 1 :(得分:0)

new Thread你刚刚为拖动动作添加了更多的波动。 正如@JohnWhite所提到的,onTouch被连续多次调用,并且创建和触发这样的新线程真是太糟糕了。

您开始遇到这些问题的一般原因是因为您关注的示例代码,虽然它有效,但它是一个糟糕的未经优化的代码。

每当你调用setLayoutParams时,系统都必须执行一个新的完整布局传递,这是很多事情要做的事情。

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

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

  1. 使用offsetTopAndBottomoffsetLeftAndRight在屏幕上移动您的ImageView。
  2. 使用setTranslationX(和Y)
  3. 的ViewTransformations
  4. 将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
     }
    
  5. 这最后一个选项非常有趣,因为它绝对不会改变布局。它只使ImageView处理整个区域并将绘图移动到正确的位置。非常有效的方式。

    您需要调用的其中一些选项"无效"在视图上,因此可以再次调用draw

答案 2 :(得分:0)

很长一段时间后我找到了一个真正的解决方案。

您应该使用Drawable而不是Bitmap。

\

移动图像(dx,dy)

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