Всем желающим использовать меню из браузера и новой камеры
добро пожаловать под кат.
Предыстория
Во время работы над одним из приложений встал вопрос как делать меню для пользователя. Вдоволь наигравшись с приложением Browser для ICS было решено использовать его опциональный компонент PieMenu. В другом проекте возникла необходимость в меню, которое не только симпатично появляется, но и умеет это делать в произвольном месте.
Для меня остается большой загадкой почему данные меню не были добавлены в API: довольно большому числу людей данные меню нравятся, да и разработчикам не было бы необходимости изобретать велосипед.
Исходники
Итак, путь мой лежал на AOSP(Android Open Source Project). Поиск там исходников данных приложений увенчался успехом. Обе версии меню были взяты из master branch'ей.
Модификация бокового меню
Далее были убраны все привязки к приложению: поля, методы, listener'ы и т.п. (а код действительно очень связан с компонентами приложений, т.е. банально взять и объявить объект не получится). Для удобства использования бокового меню добавил возможность устанавливать отдельные Listener'ы на разные пункты меню, добавление Item
'ов из List
'а. В исходной реализации совершенно не работал параметр level в конструкторе. Он отвечает за номер кольца в меню, где 1ое кольцо с минимальным радиусом, а с ростом level'а растет радиус. После изучения кода выяснилось, что Path
, который рисует задний фон для иконки, переприсваивался для каждого нового кольца в методе отрисовки, что не давало возможности использовать multi-level меню. Данный недостаток был устранен.
Данное меню поддерживает отображение подменю. Со стороны кода это выглядит как добавление Item
'ов к другому Item
'у.
Важный момент: выставляйте количество подменю для каждого item'а совпадающее с каким либо кольцом. Например, у вас 3 item'а в первом кольце и 2 во втором. Тогда на подменю item'а 1го кольца можно поставить либо 2 элемента (т.к. при развертывании останется только 2 позиции), либо 2 элемента 2го кольца (т.к. их можно полностью заместить). Изначально код при нарушении данного условия уходил к апостолу Петру и жаловался, что его заставляют делать страшные вещи. Однако сейчас вы можете поэкспериментировать с тем как будет вести себя код не опасаясь Exception'ов.
PieControl
Данный класс позволяет реализовать боковое меню.
Объявим наследника данного класса и переопределим методы populateMenu()
. Также советую добавить setListeners()
для удобной установки Listener'ов нажатий. Элемент PieItem
создаем с помощью makeItem()
. В качестве параметров указываем ресурс иконки и level (номер кольца). Затем создаем объект нашего нового класса, привязываем его к frame'у через attachToContainer()
и устанавливаем Listener'ы вызывая метод setListeners()
.
private class TestMenu extends PieControl {
List<PieItem> plus_sub;
List<PieItem> 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<PieItem>(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<PieItem>(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();
}
});
}
}
TestMenu testMenu = new TestMenu(this);
testMenu.attachToContainer(some_container);
testMenu.setListeners();
PieRenderer
Данный класс позволяет реализовать круговое меню из приложения Camera. Использовать его, по моему мнению, гораздо проще, чем PieControl. Концептуально это меню отличается от бокового: привязывать его можно не к любым наследникам ViewGroup
, а лишь к специальному RenderOverlay
(который на самом деле является обычным FrameLayout
с небольшим набором дополнительных методов, но это уже inner workings). К RenderOverlay
можно привязать объекты типа Renderer
, наследником которого является PieRenderer
. Он то нам и нужен для отрисовки меню. Так же нам необходим класс PieController
для добавления элементов в меню. Итак, приступим:
Создаем объект PieRenderer
, объект PieController
, элементы меню с помощью метода makeItem в PieController
. Добавляем элементы меню в PieRenderer
через addItem. Затем создаем объект RenderOverlay
(либо находим через findViewById
, если вы любите определять все в xml). Добавляем PieRendere
к RenderOverlay
через addRenderer
. Теперь последний штрих: в onTouchEvent
пошлите событие к обработчику PieRenderer
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);
}
}
Послесловие
Исходники получившейся библиотеки можно стянуть отсюда.
Запускается на Android 3.1 и выше (основная причина: использует Animation классы для отрисовки).
Спасибо за внимание и удачного Вам дня!
Автор: Malinskiy