开发者

Can handle events on irregular shapes on Android?

As far as I know for now (which is little), all the Views in Android have square or rectangular shapes. This is fine almost all the time until you want - what I actually want - to create non-square shapes that could handle events.

My goal is to have a circle divided in 3 sections of 120° each. Each section of the circle开发者_如何学Python should behave like a button. The problem is that if we look at those 3rds of a circle and place them in a square box that strictly contains them, they are overlapping each other: not practical to know which the user meant to click...

I tried with a custom view in which I drew my sections but the events are triggering on all the surface of the view.

Any suggestion or direction is very welcome.

Thx, Paul


I believe the way to handle this is to override onTouch on each control, perform your own geometric checks on the touch coordinates, and if it's outside the custom area return false which will pass over the event to another view. Otherwise return invoking the super version of the method. I'm not 100% sure so someone please correct me if I'm wrong, but it could be worth a try.


I don't know whether you can create those shapes explicitly but there would definitely be a way to use this in a custom view. But this is quite difficult.

First you would need to hook on to the OnTouch event via setting an OnTouchListener, more here. The easiest way would be to only react on ACTION_UP actions of the MotionEvent, accessable via MotionEvent.getAction(), details over here. Of this MotionEvent you can then get the X and Y coordinates, where the event occured via getX() and getY().

Now begins the maths... you now have to calculate in which section the click occured. To do this, you need the size of your custom view in pixels and its location (upper left corner), I'm afraid I can't tell you the appropriate methods right now... you would have to dig them up... Assuming the center of your circle is always in the center of your view and that the circle extends fully to the views boundaries, you can now calculate the sector.

Let's call the events X and Y coordinates eventX and eventY respectively and let centerX and centerY be the circles center coordinates as well as radius being the circles radius.

First check, whether the event occured in the circle, then calculate angle:

int deltaX = eventX - centerX;
    int deltaY = eventY - centerY;

    if (Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)) > radius) {
        // out of circle!
    }

    // offset for radiant, depending on quadrant
    double offset = 0;
    if (deltaX > 0 && deltaY < 0) {
        // top right -> nothing to do
    } else if (deltaX > 0 && deltaY > 0) {
        // bottom right -> add 90 degrees
        offset = Math.PI/2;
    } else if (deltaX < 0 && deltaY > 0) {
        // bottom left -> add 180 degrees
        offset = Math.PI;
    } else if (deltaX < 0 && deltaY < 0) {
        // top left -> add 270 degrees
        offset = Math.PI * 3/2;
    }

    //now calculate angle
    double angle = Math.asin(deltaY / deltaX);
    double total = angle + offset;

In the end, total should be the clockwise angle of the click in radiants which you can check against your sections ;) Correct me if there is something wrong^^


First thank you a lot for all your suggestions which helped me to reach the goal.

Since there is no way to trigger events on shapes, using onTouch() on the view which contains the shapes is the way to go.

Following, all you need to run it.


First, the custom view Zones.java:

package com.vector;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

public class Zones extends View {
    RectF rectf = new RectF(0, 0, 300, 300);
    Paint paint = new Paint();
    Canvas canvas = null;
    Integer zone = 0;

    // important: do not forget to add AttributeSet, it is necessary to have this
    // view called from an xml view file
    public Zones(Context context, AttributeSet attributeset) {
        super(context, attributeset);
        this.setBackgroundColor(0xFF207CA1);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        // set layout of the view
        layout(0, 0, 300, 300);

        // expose canvas at view level
        this.canvas = canvas;

        // check for zone 1 to 3
        if(zone >= 1 && zone <= 3)
        {
            drawTouchZones(zone);
        }
    }

    protected void drawTouchZones(Integer zone)
    {
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(2);
        paint.setColor(Color.WHITE);
        paint.setAlpha(75);

        Path path = new Path();

        if(zone == 1) {
            path.moveTo(150,150);
            path.lineTo(150,0);
            path.arcTo(rectf, 270, 120);
            path.close();
        } else if(zone == 2) {
            path.moveTo(150,150);
            path.arcTo(rectf, 30, 120);
            path.lineTo(150,150);
            path.close();
        } else if(zone == 3) {
            path.moveTo(150,0);
            path.lineTo(150,150);
            path.arcTo(rectf, 150, 120);
            path.close();
        }

        canvas.drawPath(path, paint);       
    }
}

Second, the main activity, Design.java:

package com.vector;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class Design extends Activity {
    /** Called when the activity is first created. */
    private Zones v;
    protected Integer zone = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        try {
            // get our custom view
            setContentView(R.layout.main);
            v = (Zones) findViewById(R.id.zone);

            // add onClick Listener
            v.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    Log.i("zone clicked", "" + zone);

                    // tell to our view which zone has been clicked
                    v.zone = zone;

                    // invalidate to call onDraw method of the custom view and draw the zone
                    v.invalidate();
                }
            });

            v.setOnTouchListener(new View.OnTouchListener() {

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    zone = getZone(event);
                    return false;
                }
            });
        }
        catch(Exception e)
        {
            Log.e("e", e.getMessage());
        }
    }

    // detect clicked zone through MotionEvent
    public int getZone(MotionEvent e)
    {
        Float x = e.getX();
        Float y = e.getY();

        // 0:00 to 4:00
        if((x > 150 && x < 300 && y < 150) ||
           (x > 150 && x < 300 && y > 150 && (x - 150) / (y - 150) > 1.5))
        {
            return 1;
        }
        // 4:00 to 8:00
        else if((x >= 150 && x < 300 & y > 150 && (x - 150) / (y - 150) < 1.5) ||
                (x > 0 && x < 150 && y > 150 && (150 - x) / (y - 150) < 1.5))
        {

            return 2;
        }
        // 8:00 to 0:00
        else
        {
            return 3;
        }       
    }
}

... and the main xml view (which embeds our custom class view) main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <com.vector.Zones android:id="@+id/zone" android:layout_height="wrap_content" android:layout_width="wrap_content">
    </com.vector.Zones>
</LinearLayout>

Any comments appreciated! Paul :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜