我正在尝试在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;
}
});
}
答案 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线程上运行。
将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处理整个区域并将绘图移动到正确的位置。非常有效的方式。
您需要调用的其中一些选项"无效"在视图上,因此可以再次调用draw
。
答案 2 :(得分:0)
很长一段时间后我找到了一个真正的解决方案。
您应该使用Drawable而不是Bitmap。
\
移动图像(dx,dy)
override fun onDraw(canvas: Canvas?) {
canvas?.save()
canvas?.concat(myCustomMatrix)
mDrawable?.draw(canvas!!)
canvas?.restore()
}