在我的应用程序上,我有一个ImageView,我将其转换为Bitmap进行编辑。我需要检测用户触摸ImageView上的哪些像素。此外,如果用户用手指画线,我需要知道所有被触摸的像素才能更改它们。如何检测触摸了哪些像素?
答案 0 :(得分:2)
好的Jonah,这里有一些指示。
我想你希望混合效果能够快速响应用户输入,所以首先你最好选择自定义SurfaceView而不是ImageView,因为它更适合绘制2D动作游戏和动画所需的高帧率动画。我强烈建议你阅读this guide;在进一步讨论之前,要特别注意有关使用SurfaceView
的部分。您基本上需要创建一个扩展SurfaceView
并实现SurfaceHolder.Callback
的类。然后,该视图将负责监听用户触摸事件并渲染帧以设置混合效果的动画
请查看以下代码作为参考:
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
public MainView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this); // Register this view as a surface change listener
setFocusable(true); // make sure we get key events
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
// Check if the touch pointer is the one you want
if (event.getPointerId(event.getActionIndex()) == 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// User touched screen...
case MotionEvent.ACTION_CANCEL:
// User dragged his finger out of the view bounds...
case MotionEvent.ACTION_UP:
// User raised his finger...
case MotionEvent.ACTION_MOVE:
// User dragged his finger...
// Update the blending effect bitmap here and trigger a frame redraw,
// if you don't already have an animation thread to do it for you.
return true;
}
return false;
}
/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
// You need to wait for this call back before attempting to draw
}
/*
* Callback invoked when the Surface has been destroyed and must no longer
* be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// You shouldn't draw to this surface after this method has been called
}
}
然后在“绘图”活动的布局上使用它,如下所示:
<com.xxx.yyy.MainView
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
要绘制到此曲面,您需要以下代码:
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (c != null)
c.drawBitmap(blendingImage, 0, 0, null); // Render blending effect
}
} catch (Exception e) {
Log.e("SurfaceView", "Error drawing frame", e);
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
一个功能齐全的例子对于回答是不切实际的,所以我建议你下载Lunar Lander sample game from Google以获得完整的工作示例。但是请注意,您不需要游戏动画线程(尽管它不会有任何影响),就像月球着陆器样本中编码的那样,如果您需要的只是混合效果。该线程的目的是创建一个游戏循环,其中不断生成游戏帧以动画可能或可能不依赖于用户输入的对象。在您的情况下,您只需要在处理每个触摸事件后触发帧重绘。
编辑:以下代码是获取您在评论中提供的代码的修复工具。
以下是MainActivity的更改:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// put pics from drawables to Bitmaps
Resources res = getResources();
BitmapDrawable bd1 = (BitmapDrawable) res.getDrawable(R.drawable.pic1);
// FIX: This block makes `operation` a mutable bitmap version of the loaded resource
// This is required because immutable bitmaps can't be changed
Bitmap tmp = bd1.getBitmap();
operation = Bitmap.createBitmap(tmp.getWidth(), tmp.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(operation);
Paint paint = new Paint();
c.drawBitmap(tmp, 0f, 0f, paint);
BitmapDrawable bd2 = (BitmapDrawable) res.getDrawable(R.drawable.pic2);
bmp = bd2.getBitmap();
myView = new MainView(this, operation, bmp);
FrameLayout preview = (FrameLayout) findViewById(R.id.preview);
preview.addView(myView);
}
...
以下是对MainView类的更改:
public class MainView extends SurfaceView implements Callback {
private SurfaceHolder holder;
private Bitmap operation;
private Bitmap bmp2;
private boolean surfaceReady;
// took out AttributeSet attrs
public MainView(Context context, Bitmap operation, Bitmap bmp2) {
super(context);
this.operation = operation;
this.bmp2 = bmp2;
holder = getHolder(); // Fix: proper reference the instance variable
holder.addCallback(this); // Register this view as a surface change
// listener
setFocusable(true); // make sure we get key events
}
// Added so the blending operation is made in one place so it can be more easily upgraded
private void blend(int x, int y) {
if (x >= 0 && y >= 0 && x < bmp2.getWidth() && x < operation.getWidth() && y < bmp2.getHeight() && y < operation.getHeight())
operation.setPixel(x, y, bmp2.getPixel(x, y));
}
// Added so the drawing is now made in one place
private void drawOverlays() {
Canvas c = null;
try {
c = holder.lockCanvas(null);
synchronized (holder) {
if (c != null)
c.drawBitmap(operation, 0, 0, null); // Render blending
// effect
}
} catch (Exception e) {
Log.e("SurfaceView", "Error drawing frame", e);
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (!surfaceReady) // No attempt to blend or draw while surface isn't ready
return false;
// Check if the touch pointer is the one you want
if (event.getPointerId(event.getActionIndex()) == 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// User touched screen. Falls through ACTION_MOVE once there is no break
case MotionEvent.ACTION_MOVE:
// User dragged his finger...
blend((int) event.getX(), (int) event.getY());
}
// Update the blending effect bitmap here and trigger a frame
// redraw,
// if you don't already have an animation thread to do it for you.
drawOverlays();
return true;
}
return false;
}
/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
surfaceReady = true;
drawOverlays();
}
/*
* Callback invoked when the Surface has been destroyed and must no longer
* be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// You shouldn't draw to this surface after this method has been called
surfaceReady = false;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
}
此代码适用于我。我只希望我没有忘记任何事情= :) 如果你还有问题,请告诉我,好吗?
答案 1 :(得分:0)
所以答案就是你必须要有点聪明,但它确实不应该那么糟糕。我不会发布所有代码来执行您想要执行的操作,而是会给您一个链接here和解释。
因此,通过管理应用程序的触摸事件,您可以计算出触摸事件的平均坐标。使用该信息,您可以确定所触摸的所有像素的中心,并在用手指绘制线条时继续跟踪该信息。要跟踪直线,请使用
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
子句用于确定行的开头和结尾。如果您想跟踪不直线和绘制的线条,您需要使用
进行更多跟踪case MotionEvent.ACTION_MOVE:
这应该让你开始。您可能需要一些逻辑来处理这样一个事实,即您将画出一条很细的线条,我怀疑这并不是您想要的。也许是的。无论哪种方式,这应该是一个开始的好地方。
修改
关于您的第一条评论,here是您可以用作示例的链接。我必须做一个小的免责声明。为了使所有部分能够正确地协同工作,起初可能看起来并不那么简单。我向您保证,这是最简单的示例之一,并将教程分为几个部分。
对于你想做的事情,我想你会特别注意第2节(不要与第2步混淆):
2. Facilitate Drawing
我建议这是因为它显示了使用TouchEvent信息的不同方法。第1部分中包含的内容将解释一下设置显示TouchEvent捕获数据的环境,而第3部分主要是关于美学。这可能不会直接回答你的问题,但我怀疑它会让你到达你需要的地方。
快乐的编码!如果您有任何疑问,请发表评论。