На днях для одного из разрабатываемого нашей командой приложения, заказчик внес правку в дизайн, которая требовала разработать выдвижное меню с довольно не стандартным расположением view компонентов. Хотя на данный момент и существуют различные виды реализации данной задачи, они оказывались либо слишком объемными, либо не предоставляли реализацию нужного функционала.

Обдумав некоторое время данную задачу, я решил реализовать данное меню на основе стандартного компонента DrawerLayout, в основу которого было вложено 2 root элемента — RelativeLayout для основной разметки окна, а также еще один RelativeLayout как контейнер для бокового меню. Хотелось бы добавить, что именно 2 root элемента должно быть внутри DrawerLayout, подробнее об этом контейнере можно прочесть в официальной документации гугла.

Реализация


Xml файл разметки для основной activity


<?xml version="1.0" encoding="utf-8"?>

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

       <!-- Основной контейнер разметки -->
     
    </RelativeLayout>

    <!-- Контейнер, содержащий выдвижное меню -->
    <RelativeLayout
        android:id="@+id/left_drawer"
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="none"
        <!-- обязательно добавляем данный атрибут, иначе все, что находится под выдвинутым 
               меню будет кликабельно, даже если нажимать на элементы самого меню -->
        android:clickable="true"
        android:background="#FFFFFF"
        xmlns:android="http://schemas.android.com/apk/res/android" />

</android.support.v4.widget.DrawerLayout>

Разметка основной activity готова, теперь приступим к написанию класса, который будет выполнять основную логику. Создадим класс, наследующий RelativeLayout. Данный класс реализует всю логику нашего меню, в том числе устанавливает разметку и определяет все view.

public class NavigationLayout extends RelativeLayout
{
    Button ok;
    public NavigationLayout(Context context,RelativeLayout parent)
    {
        super(context);
        initView(context,parent);
    }

    public void initView(final Context context,RelativeLayout parent)
    {
        // надуваем любой xml файл разметки
        View view= LayoutInflater.from(context).inflate(R.layout.view_drawer_layout,parent,true);

        ok=(Button)view.findViewById(R.id.ok);

        ok.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context,"Ok",Toast.LENGTH_SHORT).show();
            }
        });


    }
}

В конструктор следует передать context и parent.

parent — RelativeLayout, который был объявлен в разметке для основной activity ( )

Далее функция initView(final Context context,RelativeLayout parent) — надувает основную разметку, которая будет помещена в выдвижное меню, а также определим тут все view компоненты и их слушатели.

В R.layout.view_drawer_layout для примера я объявил всего одну кнопку.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="300dp"
    android:background="@color/screen_background"
    android:layout_height="match_parent">

    <Button
        android:layout_width="110dp"
        android:layout_height="55dp"
        android:textAllCaps="false"
        android:id="@+id/ok"
        android:text="Next"
        android:textStyle="bold"
        android:textSize="25sp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:textColor="@color/buttonTextColor" />

</RelativeLayout>

На данном этапе основная часть готова, осталось лишь добавить наш NavigationLayout при помощи addView к основному parent контейнеру.

Создадим класс, наследующий AppCompactActivity

public class ParentNavigationActivity extends AppCompatActivity {
    NavigationLayout navigationLayout;
    RelativeLayout left_drawer;

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        super.setContentView(layoutResID);
        setupMenu();
    }

    public void setupMenu()
    {
        left_drawer=(RelativeLayout) findViewById(R.id.left_drawer);
        navigationLayout=new NavigationLayout(getApplicationContext(),left_drawer);

        left_drawer.addView(navigationLayout);
    }
}

Данный класс переопределяет стандартный метод setContentView, добавляя в него вызов функции, которая 'инициализирует' выдвижное меню. Также здесь мы создаем объект ранее написанного нами NavigationLayout класса и добавляем его при помощи left_drawer.addView(navigationLayout) к родителю, который и является контейнером бокового меню.

Осталось дело за малым — чтобы все заработало, нужно лишь создать экран (activity) и унаследовать ParentNavigationActivity, который мы только что создали.

public class MainActivity extends ParentNavigationActivity {

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

}

Таким образом, при наследовании ParentNavigationActivity и вызове функции setContentView, в нашей activity появляется готовое меню.



Хотелось бы добавить, что 2 контейнера, лежащих в основе DrawerLayout, необязательно должны быть RelativeLayout. Вместо них можно использовать constraintlayout, framelayout, linearlayout и другие.

На данном этапе разработка выдвижного меню завершена!


Данный способ является довольно простым в реализации, а также гибким в плане добавления меню к любым activity. Надеюсь что данная статья поможет android разработчикам упростить создание бокового меню для своих приложений.
Поделиться с друзьями
-->

Комментарии (10)


  1. Tiberal
    28.07.2017 17:19

    Форматирование и области видимости не подвезли?


    1. AlexandrGritsenko
      29.07.2017 14:38

      А в чем именно проблема с форматированием, можете подсказать?


      1. dakatso
        02.08.2017 14:22

        Скобки как попало


  1. gshock
    28.07.2017 17:51

    Вроде даже встроенная в Android Studio заготовка проекта с боковым меню «умеет» вложить внешний лайаут в выдвижное меню. Не понял вообще проблематики из статьи


    1. AlexandrGritsenko
      29.07.2017 14:33

      Стандартный шаблон хоть и реализует разметку, но довольно объемный выходит по коду и набору xml файлов


  1. KamiSempai
    28.07.2017 18:33

    Похоже на велосипед. Не вижу здесь ни какой проблемы. Почему бы просто не использовать фрагменты?

    Пример xml разметки
    <android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:id="@+id/content_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        
        <fragment
            android:id="@+id/left_drawer"
            android:layout_width="300dp"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:name="com.package.SlidingMenuFragment"/>
    
    </android.support.v4.widget.DrawerLayout>


    1. AlexandrGritsenko
      29.07.2017 14:24

      Ну с фрагментом можно реализовать аналогичный функционал, но с ними часто возникают проблемы, из-за которых getActivit() возвращает null, да и вариант с использование обычной view выходит проще и потребляет чуть меньше ресурсов


      1. KamiSempai
        31.07.2017 13:29

        Если getActivit() возвращает null, значит вы либо не вовремя к ней обращаетесь, либо уже поздно.

        На счет того, что «использование обычной view выходит проще» не соглашусь. В моем примере все сделано в одном xml у вас же еще куча дополнительных классов.

        Аргумент про «чуть меньше ресурсов» тоже весьма спорный. Если уж так хочется экономить ресурсы, советую начать использовать FrameLayout вместо RelativeLayout. Как раз сэкономит ресурсы, что-бы для фрагментов хватило.


  1. IgeNiaI
    28.07.2017 20:16

    Покажите как оно в итоге выглядит. Судя по описанию задачи, стандартного функционала должно быть достаточно.


    1. AlexandrGritsenko
      29.07.2017 14:35

      Из статьи вышло просто меню с одной кнопкой скрин . Как мне кажется, эта реализация имеет свои плюсы, поэтому решил написать