[Из песочницы] Как внедрить баннеры в Android приложение не перекрыв другие элементы

Баннеры — один из наиболее популярных видов рекламы в мобильных приложениях. Они не занимают много места, как, например, полноэкранная (interstitial) реклама. И позволяют совместить их с элементами пользовательского интерфейса приложения. Их можно добавить на разные экраны в приложении.

Прочитав данную статью, вы узнаете, как лучше вставить баннеры таким образом, чтобы они не мешали пользователю и не портили вид приложения. При этом вам не придётся изменять layout xml и вносить много изменений в код приложения. Вы можете внедрить баннеры в своё готовое приложение, добавив всего несколько строк кода. Подход, описанный в статье, универсальный, вы можете использовать его для API любых рекламных сервисов. Статья будет интересна как для новичков, так и для опытных разработчиков. Если вы — новичок в разработке, то для того, чтобы понять предмет статьи, от вас не потребуется каких-либо глубоких знаний. Достаточно понимания базовых концепций разработки под Android. А опытные разработчики могут найти в ней готовое решение, которое они могут внедрить у себя. Но инициализация рекламного сервиса, работа с конкретными рекламными API и кеширование находятся за пределами данной статьи. Для решения таких вопросов, пожалуйста, обратитесь к руководству для вашего конкретного рекламного сервиса.
Идея статьи возникла от того, что в одном из наших приложений для Android нам было необходимо разместить баннеры в нескольких местах, но сделать это следовало таким образом, чтобы не испортить вид приложения и не перекрыть баннерами элементы управления. Код приложения был уже написан полностью и перекраивать его нам очень не хотелось, поэтому мы постарались сделать так, чтобы добавление баннеров было максимально простым, корректным и не затрагивало работу существующего кода. Другая причина — нам потребовалось создать платную версию приложения без рекламы. А если бы внедрение баннеров потребовало бы изменение layout xml, то это сильно бы усложнило создание версии без рекламы.

Чтобы было более наглядно и понятно то, о чём я пишу, посмотрите на следующий экран:

7762a51958b644179e130a5446293414.png

Элементы пользовательского интерфейса занимают всё пространство экрана. Пустых мест нет. В таком случае, мы можем разместить баннер внизу или вверху. Вариант размещения баннера снизу предпочтительнее, так как баннер будет находится подальше от кнопок и пользователь не заденет баннер, случайно пытаясь нажать на «Выбрать» или «Назад». Нам необходимо разместить баннер снизу экрана под GridView с фото. Так как баннер загружается по сети, он может быть не сразу и не всегда недоступен. Следовательно, не в каждый момент времени его можно показать и может получиться пустое место снизу. Если мы оставим это пустое место — получится очень некрасиво. Будет выглядеть, как будто это грубая недоработка дизайна интерфейса. Если мы разместим баннер поверх GridView, то он перекроет собой части фото и создаст неудобства пользователю, что тоже недопустимо.

Тогда сводим задачу к тому, что нам необходимо сделать так, чтобы не было дополнительных отступов. А когда баннер загружен и может быть показан — динамически добавить отступ снизу и показать баннер. С другой стороны, нам необходимо сделать код размещения баннеров максимально простым, без сложных инициализаций. Т.е. передавать id элементов или ссылки на контейнеры (ViewGroup) недопустимо. Вставлять баннеры в layout xml каждого экрана, куда нам необходимо добавить баннер — тоже недостустимо, т.к. потребует значительных изменений. В идеале, код установки баннера должен выглядеть следующим образом:

Ads.showBottomBanner(activity);


Всего одна строка кода, один вызов метода, которому передаётся только ссылка на Activity в которой будет размещён баннер. Такой код можно вставить в метод Activity onCreate.Динамическое добавление отступа
Для того, чтобы это реализовать, нам необходимо знать, что находится в View и получить к этому доступ. Прямого метода для доступа к content view в Activity нет. Но благодаря пользователю nickes со StackOverflow мы нашли решение. Необходимо идти через Window, в котором находится Activity. У Window есть DecorView, а в DecorView находится ContentView. Первый дочерний элемент в нём — это и есть ViewGroup из layout xml.

Так что нам требуется Window, затем мы получаем DecorView, затем получаем ContentView и затем получаем первый дочерний элемент ContentView. И у этого дочернего элемента мы изменяем отступ:

public static void setupContentViewPadding(Activity activity, boolean top, int margin) {
        View view = ((ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
        if (top)
                view.setPadding(view.getPaddingLeft(), margin, view.getPaddingRight(), view.getPaddingBottom());
        else
                view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), margin);
}


Размещение баннера
Мы нашли решение, как динамически добавить отступ. Теперь нам необходимо разместить сам баннер. У разных рекламных сервисов разные API. У некоторых есть View баннера, который вы можете создать и добавить в ViewGroup. Но некоторые рекламные API не имеют доступа к View баннера, а имеют только метод, который показывает баннер. Рассмотрим оба варианта.API, в котором есть View баннера
Назовём класс View баннера — Banner. (Чтобы узнать, как он реально называется в вашем случае и как с ним работать, пожалуйста, обратитесь к руководству вашего рекламного сервиса.)

Сначала, нам необходимо создать объект Banner:

final Banner banner = new Banner(activity);


Затем ему следует назначить слушатель событий. Нас интересует событие успешной загрузки баннера (это опять код — пример. Для того, чтобы узнать как слушатель называется и как его использовать, обратитесь, пожалуйста, к руководству вашего рекламного сервиса):

banner.setBannerListener(new BannerListener() {
                @Override
                public void onReceiveAd(View view) {
                                // добавить отступ снизу
                                setupContentViewPadding(activity, true, BANNER_HEIGHT);
                }
        });


Когда баннер загружен, мы вызываем setupContentViewPadding, чтобы динамически добавить отступ снизу.

Затем мы добавляем наш баннер в Window. Мы добавляем его поверх существующих элементов. В классе Window есть метод addContentView для этого:

FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT, 
                        height);//  Utils.toDIP(activity, BANNER_HEIGHT));

layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
activity.getWindow().addContentView(banner, layoutParams);


API без View баннера
У нас нет View баннера и мы не можем создать и разместить его явным образом. Но API имеет методы, вроде showBanner — показать баннер.

Я условно назову класс рекламного API — AdAPI (вам следует обратится к руководству вашего рекламного сервиса, чтобы узнать, как называется класс в которой есть методы размещения баннеров). В этом случае, код размещения баннера будет выглядеть примерно так:

Ad banner = AdAPI.loadBanner();
banner.addListener(new AdListener() {
     public void adLoaded() {
            // add bottom padding, when banner is loaded.
            setupContentViewPadding(activity, true, BANNER_HEIGHT);
     }
});


Где BANNER_HEIGHT — константа равная высоте баннера.

Здесь возникают некоторые затруднения. Вам следует точно узнать или установить высоту баннера. У нас была такая проблема: мы запускали наше приложение на 3.7 дюймовом смартфоне и на 10.1 дюймовом планшете. Размеры баннера оказались разными на разных устройствах. На смартфоне баннер выглядел отлично, но на планшете он оказался слишком большим и отнял слишком много места у других элементов. Если ваш рекламный сервис позволяет явно задать высоту баннера — лучше задайте, чтобы не было таких неприятных неожиданностей.

Результат
0d714ef508344aa88ee18c0a35f44e78.png

Как вы можете видеть, баннер показан и не перекрывает другие элементы. Отступ снизу добавлен динамически.

Это то, что нам требовалось.

Использование
Подводя итог всему вышесказанному, привожу способ интеграции кода в ваше приложение.

Класс Ads:

public class Ads {
        // замените на реальное значение высоты баннера
        final private static int BANNER_HEIGHT = 75;

        public static void showBottomBanner(Activity activity)  {
                // замените на реальный код для работы с вашим рекламным сервисом

                final Banner banner = new Banner(activity);

                banner.setBannerListener(new BannerListener() {
                        @Override
                        public void onReceiveAd(View view) {
                                // добавить отступ снизу
                                setupContentViewPadding(activity, true, BANNER_HEIGHT);
                        }
                });

                FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                                FrameLayout.LayoutParams.MATCH_PARENT, 
                                height);//  Utils.toDIP(activity, BANNER_HEIGHT));

                layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
                activity.getWindow().addContentView(banner, layoutParams);
        }

        public static void setupContentViewPadding(Activity activity, boolean top, int margin) {
                View view = ((ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
                if (top)
                        view.setPadding(view.getPaddingLeft(), margin, view.getPaddingRight(), view.getPaddingBottom());
                else
                        view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), margin);
        }
}


Замените код в методе showBottomBanner на вызовы API вашего рекламного сервиса.

Чтобы разместить баннер, добавьте строчку кода Ads.showBottomBanner (this) в метод Activity onCreate.

Заключение
В статье я привёл описание как интегрировать баннеры в приложение наиболее корректным и простым способом. Есть и другие способы размещения баннеров. Например, можно взять первый экран показанный в статье и разместить баннер не снизу, а между элементами.

Надеюсь, статья была полезна для вас.

Пожалуйста, пишите ваши замечания в комментариях.

Благодарю за внимание. Успехов вам в разработке!

© Habrahabr.ru