Adding actionable callbacks

Let's convert this into an actionable menu. We could map an action to each face of the cube. As we are rotating the cube horizontally, or on the y axis, we could map an action to each of the four available faces.

For added clarity, as currently the rotation might end in the middle of a face, let's add a small feature: whenever the animation finishes, let's snap it to the closest face, so we'll always have a fully aligned front face of the cube when there is no animation running.

Implementing snapping is fairly simple. We have got to check if the animation has finished and, in that case, check which face is facing to the camera. We could do so by simply dividing the current rotation angle by 90; 360 degrees split by four faces is 90 degrees each. To see if we are closer to that face than from the next one, we have got to get the fractional part of the rotation angle. If we calculate the angle modulo 90, we will get a number between 0 and 89. If that result is smaller than half the degrees needed to switch from one face to another, we will be on the right face. However, in the opposite case, if that result is bigger than 45, or smaller than -45, we'd have to rotate to the next or previous face, respectively. Let's write this small logic in our onDraw() method, just after the call to scroller.computeScrollOffset:

if (scroller.isFinished()) { 
    int lastX = scroller.getCurrX(); 
    int modulo = lastX % 90; 
    int snapX = (lastX / 90) * 90; 
    if (modulo >= 45) snapX += 90; 
    if (modulo <- 45) snapX -= 90; 
 
    if (lastX != snapX) { 
        scroller.startScroll(lastX, 0, snapX - lastX, 0); 
    } 
} 

To calculate the snap angle, we do an integer division by 90 and multiply the result by 90. As it's an integer division, it'll get rid of the decimal part and calculate the absolute angle value of that face. Another way of writing that code would be the following:

int face = lastX / 90; 
int snapX = face * 90; 

Then, depending on the modulo result, we are adding 90 or subtracting 90 to effectively go to the next or previous face.

Now, let's add the code to manage the user clicks. First, let's create an interface of a listener to delegate the handling of the event to that listener:

interface OnMenuClickedListener { 
    void menuClicked(int option); 
} 

Also, let's add an OnMenuClickedListener variable to our class and a setter method:

private OnMenuClickedListener listener; 
 
public void setOnMenuClickedListener(OnMenuClickedListener listener) { 
    this.listener = listener; 
} 

Now, we can implement the onSingleTapUp method on the MenuGestureListener:

@Override 
public boolean onSingleTapUp(MotionEvent e) { 
    scroller.computeScrollOffset(); 
    int angle = scroller.getCurrX(); 
    int face = (angle / 90) % 4; 
    if (face < 0) face += 4; 
 
    if (listener != null) listener.menuClicked(face); 
    return true; 
} 

Let's also add an id to our custom view in the activity_main layout file, so we can get the GLDrawer view from the code:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:id="@+id/activity_main" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical" 
    android:padding="@dimen/activity_vertical_margin" 
    tools:context="com.packt.rrafols.draw.MainActivity"> 
 
<com.packt.rrafols.draw.GLDrawer 
android:id="@+id/gldrawer"
android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>

Finally, modify the MainActivity class to create an OnMenuClickedListener and set it to the GLDrawer view:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GLDrawer glDrawer = (GLDrawer) findViewById(R.id.gldrawer);
glDrawer.setOnMenuClickedListener(new
GLDrawer.OnMenuClickedListener() {
@Override
public void menuClicked(int option) {
Log.i("Example36-Menu3D", "option clicked " + option);
}
});
}

If we run this example, we will see how the MainActivity is logging which face are we clicking on the cube:
com.packt.rrafols.draw I/Example36-Menu3D: option clicked 3
com.packt.rrafols.draw I/Example36-Menu3D: option clicked 2.

We will also see how the snapping works. Play with it to see how it snaps to the current face, to the next one, or to the previous one if we are scrolling backwards.