5

I need to create the same buttons like in the picture below. Button's must be with the text inside.

  • enter image description here

When I was making XML layout I encountered with a problem of Button's touch area. Each next button is cover previous button with a rectangular Button's area.

  • enter image description here

Is it right to place hexagons in the XML markup like I done, to implement hexagons like in picture? Please, help me to solve problem with touch area and if possible tell me how to create a layout right, because I'm not sure that since I'm doing is correctly.

Here is part of my test layout:

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:layout_marginTop="?attr/actionBarSize">


            <Button
                android:id="@+id/button1"
                android:layout_width="130dp"
                android:layout_height="134dp"
                android:background="@drawable/hexagon_shape_img"
                android:text="Home page"
                android:textSize="@dimen/small_text" />

            <Button
                android:id="@+id/button2"
                android:layout_width="130dp"
                android:layout_height="134dp"
                android:layout_marginLeft="65dp"
                android:layout_marginTop="-20dp"
                android:background="@drawable/hexagon_shape_img"
                android:text="Tavern"
                android:textSize="@dimen/small_text" />

        </LinearLayout>
AADProgramming
  • 6,077
  • 11
  • 38
  • 58
AskQuestion
  • 346
  • 4
  • 18

1 Answers1

1

Ok, this turned out to be more complicated than I thought. The basic problem is that the highest Z-ordered button always gets the touch event, but doesn't pass it on the the lower Z-orders even if it doesn't dispose of the event. To get both buttons to see the event, a work-around is needed. Here's the basic approach:

1: Create a container in the Fragment/Activity that currently holds your hex buttons

2: Create a Fragment that contains the buttons and another button on top of all of them with alpha=0

3: Add a getOverlay():View method that returns that alpha=0 button

4: implement a hitTest():Button method in the fragment

5: In the main Activity/Fragment, set up listeners to handle the touch events of the overlay button

I made (and tested) an Application to demonstrate the concept. I'm going to leave the hitTest to you as it's rather tedious

activity_main.xml:

<RelativeLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" 
android:layout_width="match_parent"
android:layout_height="match_parent" 
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" 
tools:context=".MainActivity">

    <FrameLayout android:id="@+id/main_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

fragment_buttons.xml:

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.me.testapplication.Buttons">

    <Button android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/lorem"/>
    <Button android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/lorem_ipsum"
        android:alpha="0.4"/>
    <Button android:id="@+id/overlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:alpha="0"/>

</FrameLayout>

MainActivity.java:

public class MainActivity extends ActionBarActivity {
    public static final String DEBUG_TAG = "TEST";

    private Buttons mButtons;
    private GestureDetector mGestureDetector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButtons = Buttons.newInstance();
        getFragmentManager().beginTransaction()
                .replace(R.id.main_container, mButtons)
                .commit();

        mGestureDetector = new GestureDetector(this, mGestureListener);
    }

    @Override
    protected void onStart() {
        super.onStart();

        View overlay = mButtons.getOverlay();
        if (overlay != null) {
            overlay.setOnTouchListener(mTouchListener);
        }
    }
    ...
    private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
             return mGestureDetector.onTouchEvent(event);
        }
    };

    private GestureDetector.OnGestureListener mGestureListener
            =new GestureDetector.OnGestureListener()
    {
            @Override
            public boolean onDown(MotionEvent e) {
            String buttonHit = mButtons.hitTest(e);
            Log.i(DEBUG_TAG, buttonHit);
            return true;
        }
        ...
    };
}

Buttons.java:

public class Buttons extends Fragment {

    Button mButton1, mButton2;
    View mOverlay;

    public static Buttons newInstance() {
        return new Buttons();
    }

    public Buttons() {}

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

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_buttons, container, false);
        mButton1 = (Button) root.findViewById(R.id.button1);
        mButton2 = (Button) root.findViewById(R.id.button2);
        mOverlay = root.findViewById(R.id.overlay);
        return root;
    }

    @Nullable
    public View getOverlay() {
        return mOverlay;
    }

    public String hitTest(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();

        if (x > mButton1.getLeft() && mButton1.getRight() > x &&
                y > mButton1.getTop() && mButton1.getBottom() > y)
        {
            return "Button 1";
        } else if (x > mButton2.getLeft() && mButton2.getRight() > x &&
                y > mButton2.getTop() && mButton2.getBottom() > y)
        {
            return "Button 2";
        } else {
            return "None";
        }
    }
}

Good luck.

ETA: Sample hitTest

/**
 * UNTESTED
 * I'm going to assume square buttons (equilateral hexagons)
 * this just calculates if the distance from the center of the button is less than its width. It may be good enough for government work.
 */
public View hitTest(MotionEvent e) {
    for (Button hex : SomeIterableThingHoldingYourButtons) { //ArrayList<Button> maybe
        float x = e.getX();
        float y = e.getY();

        if (isInHex(hex, x, y)) return hex;
     }
     return null;
}

private boolean isInHex(Button hex, float x, float y) {
    float radius = hex.getRight() - hex.getLeft() / 2;
    float centerX = hex.getLeft() + radius;
    float centerY = hex.getTop() + radius;

    float dist = FloatMath.sqrt(...) //euclidean distance

    return dist < radius;
}
h0x0
  • 485
  • 3
  • 11
  • It will be working with hex buttons? As I understand hitTest() method will check max width and height coordinates of the button's area rect. – AskQuestion Jan 23 '15 at 17:06
  • And why second btn has 0.4 alpha? – AskQuestion Jan 23 '15 at 17:10
  • Just so I could see that I was clicking on the button1 area through the larger button. Your buttons could use any alpha you like. – h0x0 Jan 23 '15 at 17:13
  • hitTest() needs to test which hexagon the the motion event occurred in. It's non-trivial since the hexagon does not fill the view bounds and you may actually want a different hex than the one you're considering. I'll write a quick shot at it, just to give you the idea – h0x0 Jan 23 '15 at 17:20
  • Look at my second picture. If I will press bottom button, but in the area where it covers first btn or second then, I think will be triggered first btn or second but not third(bottom). Or no? – AskQuestion Jan 23 '15 at 17:25
  • The idea here is that you aren't invoking the Button.onClick(View) method directly (because the overlapping button won't ever invoke it). But you're getting the same result by figuring out if the event was in the bounds of one of your hexagons – h0x0 Jan 23 '15 at 17:40