简体   繁体   中英

How to find all pixels touched on an image view?

On my app I have an ImageView which I turned into a Bitmap for editing. I need to detect which pixels on the ImageView were touched by the user. In addition, if the user draws a line with his finger, I need to know all the pixels that were touched in order to change them. How do I detect which pixels were touched?

Ok Jonah, here are some directions for you.
I guess you want that blending effect to react quickly to user input so first thing you'd better go for a custom SurfaceView instead of a ImageView because it is more suitable for drawing high frame rate animations required in 2D action games and animations. I strongly recommend you to read this guide ; giving special attention to the part about the use of SurfaceView , before going any further. You will basically need to create a class that extends SurfaceView and implements SurfaceHolder.Callback . This view will then be responsible to listen for user touch events and to render the frames to animate the blending effect.
Take a look at following code as a reference:

    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
        }
    }

Then use it on the layout of your "drawing" activity like this:

    <com.xxx.yyy.MainView
        android:id="@+id/main_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

To draw to this surface you need the following code:

        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);
            }
        }

A fully functional example would be impractical to put in an answer so I recommend you to download the Lunar Lander sample game from Google for a full working example. Note however, that you won't need a game animation thread (although it won't hurt having one), like the one coded in the Lunar Lander sample, if all you need is the blending effect. The purpose of that thread is to create a game loop in which game frames are constantly generated to animate objects that may or may not depend on user input. In your case, all you need is to trigger a frame redraw after processing each touch event.

EDIT: The following code are fixes to get the code you've provided in the comments, working.
Here are the changes to 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);

}
...

Here are the changes to the MainView class:

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
    }

}

This code works for me. I just hope I didn't forget anything =:)
Let me know if you still have trouble, ok?

So the answer to this is that you're going to have to be a little clever, but it really shouldn't be so bad. Instead of posting all the code to do what you want to do, I'll give you a link here and an explanation.

So by managing the touch events of an application, you can figure out the average coordinates of a touch event. Using that information you can determine the center of all the pixels touched and continue to track that as a line is drawn with the finger. To track a straight line use the

case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:

clauses to determine the start and end of the line. If you want to track a line that is not straight and drawn, you're going to need a little more tracking using

case MotionEvent.ACTION_MOVE:

and that should get you fairly started. You may need a little bit of logic to deal with the fact that you will be drawing a very thin line and I suspect that's not quite what you're going for. Maybe it is though. Either way this should be a good place to get started.

EDIT

In regards to your first comment, here is a link you can use for an example. I have to make a small disclaimer though. To get all of the pieces to work together correctly, it may not seem that simple at first. I assure you that this is one of the simplest examples and breaks the tutorial into sections.

For what you would like to do, I think you'll want to pay particular attention to section 2 (no to be confused with step 2):

 2. Facilitate Drawing

I suggest this because it shows different ways to use information form the TouchEvent. The things included in section 1 will explain a little bit about the environment to setup displaying a TouchEvent's captured data whereas section 3 is mostly about aesthetics. This may not directly answer your question, but I suspect it will get you where you need to be.

Happy coding! Leave a comment if you have any questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM