Android: Quick control menu

Everyone who wants to use the menu from the browser and the new camera,

camera_samplebrowser_sample

welcome to cat.



Background

While working on one of the applications, the question arose of how to make a menu for the user. Having played enough with the Browser application for ICS, it was decided to use its optional component PieMenu. In another project, there was a need for a menu that not only looks pretty, but also knows how to do it in an arbitrary place.
It remains a big mystery to me why these menus were not added to the API: quite a lot of people like these menus, and developers would not need to reinvent the wheel.

Source code

So, my path lay on AOSP (Android Open Source Project). The search for the source data for these applications was successful. Both versions of the menu were taken from the master branch.

Sidebar Modification

multi-level pieThen all the bindings to the application were removed: fields, methods, listeners, etc. (and the code is really very connected with application components, i.e. it’s corny to take and declare an object). For the convenience of using the side menu, I added the ability to install separate Listener'y on different menu items, adding Item's from List' a. In the original implementation, the level parameter in the constructor did not work at all. He is responsible for the number of the ring in the menu, where the first ring with a minimum radius, and with increasing level, the radius grows. After studying the code, it turned out that the Pathone that draws the background for the icon was reassigned for each new ring in the rendering method, which made it impossible to use the multi-level menu. This flaw has been eliminated.
This menu supports the display of submenus. On the code side, this looks like adding Item's to another Item' y.
An important point : set the number of submenus for each item matching the same ring. For example, you have 3 item in the first ring and 2 in the second. Then, on the item’s submenu of the 1st ring, you can put either 2 elements (since when deploying, only 2 positions will remain) or 2 elements of the 2nd ring (because they can be completely replaced). Initially, the code in violation of this condition went to the apostle Peter and complained that he was forced to do terrible things. However, now you can experiment with how the code will behave without fear of Exception.

Piecontrol

browser_sampleThis class allows you to implement a side menu.
We declare the heir of this class and redefine the methods populateMenu(). I also recommend adding setListeners()clicks for convenient installation of Listener's. We PieItemcreate an element using makeItem(). As parameters we indicate the resource of the icon and level (ring number). Then we create an object of our new class, bind it to frame through attachToContainer()and set Listener's by calling the method setListeners().

Heir example:
private class TestMenu extends PieControl {
        List plus_sub;
        List minus_sub;
        public TestMenu(Activity activity) {
            super(activity);
        }
        protected void populateMenu() {
            PieItem plus = makeItem(android.R.drawable.ic_input_add,1);
            {
                plus_sub = new ArrayList(2);
                plus_sub.add(makeItem(android.R.drawable.ic_input_add,2));
                plus_sub.add(makeItem(android.R.drawable.ic_input_add, 2));
                plus.addItems(plus_sub);
            }
            PieItem minus = makeItem(android.R.drawable.ic_menu_preferences,1);
            {
                minus_sub = new ArrayList(2);
                minus_sub.add(makeItem(android.R.drawable.ic_menu_preferences,1));
                minus_sub.add(makeItem(android.R.drawable.ic_menu_preferences, 1));
                minus.addItems(minus_sub);
            }
             PieItem close = makeItem(android.R.drawable.ic_menu_close_clear_cancel,1);
            mPie.addItem(plus);
            mPie.addItem(minus);
            mPie.addItem(close);
            PieItem level2_0 = makeItem(android.R.drawable.ic_menu_report_image, 2);
            mPie.addItem(level2_0);
            PieItem level2_1 = makeItem(android.R.drawable.ic_media_next, 2);
            mPie.addItem(level2_1);
        }
        public void setListeners() {
            this.setClickListener(plus_sub.get(0),new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(getApplicationContext(),"Clicked plus 1", Toast.LENGTH_SHORT).show();
                }
            });
            this.setClickListener(plus_sub.get(1), new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(getApplicationContext(),"Clicked plus 2", Toast.LENGTH_SHORT).show();
                }
            });
            this.setClickListener(minus_sub.get(0), new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(getApplicationContext(),"Clicked minus 1", Toast.LENGTH_SHORT).show();
                }
            });
            this.setClickListener(minus_sub.get(1), new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(getApplicationContext(),"Clicked minus 2", Toast.LENGTH_SHORT).show();
                }
            });
            this.setClickListener(close, new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    finish();
                }
            });
        }
    }


Example menu declaration:

        TestMenu testMenu = new TestMenu(this);
        testMenu.attachToContainer(some_container);
        testMenu.setListeners();



Pierenderer

camera_sampleThis class allows you to implement a circular menu from the Camera application. Using it, in my opinion, is much simpler than PieControl. Conceptually, this menu differs from the side menu: you can not bind it to any heirs ViewGroup, but only to a special one RenderOverlay(which is actually normal FrameLayoutwith a small set of additional methods, but this is inner workings). You RenderOverlaycan bind to objects of the type Rendererwhose descendant is PieRenderer. We then need it to render the menu. We also need a class PieControllerto add items to the menu. So here we go :
Create an object PieRenderer, an object PieController, menu items using makeItem's method PieController. Add menu items in PieRenderervia addItem. Then create an objectRenderOverlay(or find through findViewByIdif you like to define everything in xml). Add PieRendereto RenderOverlayvia addRenderer. Now the final touch: in onTouchEventsend the event to the handlerPieRenderer

Example activity code:

public class MainActivity extends Activity {
    private static float FLOAT_PI_DIVIDED_BY_TWO = (float) Math.PI / 2;
    private final static float sweep = FLOAT_PI_DIVIDED_BY_TWO / 2;
    private PieRenderer pieRenderer;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.main);
        pieRenderer = new PieRenderer(getApplicationContext());
        PieController pieController = new PieController(this, pieRenderer);
        RenderOverlay renderOverlay = (RenderOverlay) findViewById(R.id.render_overlay);
        PieItem item0 = pieController.makeItem(android.R.drawable.arrow_up_float);
        item0.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO, sweep);
        item0.setOnClickListener(new PieItem.OnClickListener() {
            @Override
            public void onClick(PieItem item) {
                Toast.makeText(getApplicationContext(), "some cmd", Toast.LENGTH_SHORT).show();
            }
        });
        PieItem item1 = pieController.makeItem(android.R.drawable.arrow_up_float);
        item1.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep);
        item1.setOnClickListener(new PieItem.OnClickListener() {
            @Override
            public void onClick(PieItem item) {
                Toast.makeText(getApplicationContext(), "some cmd 2", Toast.LENGTH_SHORT).show();
            }
        });
        PieItem item7 = pieController.makeItem(android.R.drawable.arrow_up_float);
        item7.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep);
        item7.setOnClickListener(new PieItem.OnClickListener() {
            @Override
            public void onClick(PieItem item) {
                Toast.makeText(getApplicationContext(), "some cmd 7", Toast.LENGTH_SHORT).show();
            }
        });
        pieRenderer.addItem(item0);
        pieRenderer.addItem(item1);
        pieRenderer.addItem(item7);
        PieItem item0_0 = pieController.makeItem(android.R.drawable.ic_menu_add);
        item0_0.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO, sweep);
        item0_0.setOnClickListener(new PieItem.OnClickListener() {
            @Override
            public void onClick(PieItem item) {
                Toast.makeText(getApplicationContext(), "some cmd", Toast.LENGTH_SHORT).show();
            }
        });
        PieItem item0_6 = pieController.makeItem(android.R.drawable.ic_menu_add);
        item0_6.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep);
        item0_6.setOnClickListener(new PieItem.OnClickListener() {
            @Override
            public void onClick(PieItem item) {
                Toast.makeText(getApplicationContext(), "some cmd 2", Toast.LENGTH_SHORT).show();
            }
        });
        PieItem item0_7 = pieController.makeItem(android.R.drawable.ic_menu_add);
        item0_7.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep);
        item0_7.setOnClickListener(new PieItem.OnClickListener() {
            @Override
            public void onClick(PieItem item) {
                Toast.makeText(getApplicationContext(), "some cmd 7", Toast.LENGTH_SHORT).show();
            }
        });
        item0.addItem(item0_0);
        item0.addItem(item0_6);
        item0.addItem(item0_7);
        renderOverlay.addRenderer(pieRenderer);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        pieRenderer.onTouchEvent(event);
        return super.onTouchEvent(event);
    }
}


Afterword

Sources of the resulting library can be pulled from here .
Runs on Android 2.2.1 and higher (theoretically works on Android 1.0 and higher).

Thank you for your attention and have a nice day!

Update:
Thanks to Prototik for the tip about the ported Animation classes. Now the menu also works on older APIs: JakeWharton indicates a minimum API of 1.0, but I managed to check for 2.2.1 and higher.

Also popular now: