【问题标题】:How to find all pixels touched on an image view?如何找到在图像视图上触摸的所有像素?
【发布时间】:2014-12-23 22:31:06
【问题描述】:

在我的应用程序中,我有一个 ImageView,我将其转换为位图进行编辑。我需要检测用户触摸了 ImageView 上的哪些像素。另外,如果用户用手指画了一条线,我需要知道所有被触摸的像素才能改变它们。如何检测触摸了哪些像素?

【问题讨论】:

  • 您能否详细说明“需要检测用户触摸了 ImageView 上的哪些像素”的意思?听起来您想创建某种图像编辑工具,允许用户在其中用手指在位图上“绘制”路径。如果是这种情况,您是否尝试为您的视图注册触摸和拖动侦听器并处理事件坐标?
  • 我是一名初级程序员,所以我真的不知道从什么开始。在我的应用程序中,我显示一张图片,当用户触摸图片时,这些像素会变成另一张图片上的匹配像素。这会产生混合效果。我知道如何融合图片,但不知道如何让听众触摸。

标签: java android bitmap imageview pixel


【解决方案1】:

好的,乔纳,这里有一些方向。
我猜您希望混合效果能够快速响应用户输入,因此首先您最好选择自定义 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 以获得完整的工作示例。但是请注意,如果您只需要混合效果,您将不需要游戏动画线程(尽管拥有一个不会有坏处),就像 Lunar Lander 示例中编码的线程一样。该线程的目的是创建一个游戏循环,在该循环中不断生成游戏帧来为可能依赖或不依赖于用户输入的对象设置动画。在您的情况下,您只需要在处理每个触摸事件后触发帧重绘。

编辑:以下代码修复了您在 cmets 中提供的代码,使其正常工作。
以下是对 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
    }

}

此代码对我有用。我只是希望我没有忘记任何事情 =:)
如果您还有问题,请告诉我,好吗?

【讨论】:

  • 非常感谢!但是如何在 Surface View 上设置更新的位图?我在 OnActionUp 中放了什么?
  • 您不会将其“设置”位图,就好像它是 ImageView 一样。您抓住它的 Canvas 对象并在其上绘制位图(就像我在最后一段代码中展示的那样)。现在,关于触摸事件,MotionEvent.ACTION_UP 通常很重要的告诉你,MotionEvent.ACTION_DOWN 开始的“手势”已经结束。它可能对你很重要,也可能不重要。例如,假设您有一个游戏,用户必须在棋盘中拖动棋子。 MotionEvent.ACTION_UP 是告诉您用户刚刚完成拖动的事件,因此是时候将棋子放在最近的有效棋盘位置了。
  • 我完成了所有步骤,但应用程序仍然有错误。以下是我的 MainActivity 的链接:docs.google.com/document/d/… 和 MainView docs.google.com/document/d/… 我做错了什么?
  • 如果不查看抛出异常的完整堆栈跟踪,就很难判断。无论如何,我查看了上面的链接,我的猜测是您的 MainView 类中缺少正确的构造函数。如果您有一个引用自定义 SurfaceView(在本例中为 MainView)的布局文件,则布局管理器将查找以下构造函数 public MainView(Context context, AttributeSet attrs) {...}。如果它不存在,您将收到 NoSuchMethodException。如果我的猜测不正确,请添加指向异常的完整堆栈跟踪和布局文件的链接。
  • 我更新了MainView和Mainactivity,并在MainActivity中添加了FrameLayout来保存MainView,但是还是有错误。主视图:docs.google.com/document/d/… 主活动:docs.google.com/document/d/… 布局:docs.google.com/document/d/…
【解决方案2】:

所以这个问题的答案是你必须有点聪明,但它真的不应该那么糟糕。与其发布所有代码来做你想做的事,我会给你一个链接here和一个解释。

因此,通过管理应用程序的触摸事件,您可以计算出触摸事件的平均坐标。使用该信息,您可以确定所有触摸像素的中心,并在用手指绘制线条时继续跟踪该中心。要跟踪直线,请使用

case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:

确定行的开始和结束的子句。如果您想跟踪一条不直且未绘制的线,则需要使用

进行更多跟踪
case MotionEvent.ACTION_MOVE:

这应该让你开始。您可能需要一点逻辑来处理这样一个事实,即您将画一条非常细的线,我怀疑这不是您想要的。也许它是。无论哪种方式,这都应该是一个很好的起点。

编辑

关于您的第一条评论,here 是您可以用作示例的链接。不过,我必须做一个小小的免责声明。要让所有部分正确地协同工作,一开始可能看起来并不那么简单。我向您保证,这是最简单的示例之一,并将本教程分成几个部分。

对于您想做的事情,我认为您需要特别注意第 2 部分(不要与第 2 步混淆):

 2. Facilitate Drawing

我建议这样做是因为它显示了使用 TouchEvent 信息的不同方式。第 1 节中包含的内容将解释一些关于设置显示 TouchEvent 捕获的数据的环境,而第 3 节主要是关于美学的。这可能不会直接回答您的问题,但我怀疑它会让您到达您需要的地方。

编码愉快!如果您有任何问题,请发表评论。

【讨论】:

  • 您能否发送一个简单工作代码的链接,这样我就可以从某个地方开始了,因为我很迷茫。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多