Как написать мобильное приложение на java

Время на прочтение
8 мин

Количество просмотров 528K

В любом деле самое сложное — это начало. Часто бывает тяжело войти в контекст, с чем столкнулся и я, решив разработать свое первое Android-приложение. Настоящая статья для тех, кто хочет начать, но не знает с чего.

Статья затронет весь цикл разработки приложения. Вместе мы напишем простенькую игру “Крестики-Нолики” с одним экраном (в ОС Android это называется Activity).

Отсутствие опыта разработки на языке Java не должно стать препятствием в освоении Android. Так, в примерах не будут использоваться специфичные для Java конструкции (или они будет минимизированы на столько, на сколько это возможно). Если Вы пишете, например, на PHP и знакомы с основополагающими принципами в разработке ПО, эта статья будет вам наиболее полезна. В свою очередь так как, я не являюсь экспертом по разработке на Java, можно предположить, что исходный код не претендует на лейбл “лучшие практики разработки на Java”.

Установка необходимых программ и утилит

Перечислю необходимые инструменты. Их 3:

  1. JDK — набор для разработки на языке Java;
  2. Android SDK and AVD Manager — набор утилит для разработки + эмулятор;
  3. IDE c поддержкой разработки для Android:
    • Eclipse + ADT plugin;
    • IntelliJ IDEA Community Edition;
    • Netbeans + nbandroid plugin;

Утилиты устанавливаются в определенном выше порядке. Ставить все перечисленные IDE смысла нет (разве только если Вы испытываете затруднения с выбором подходящей). Я использую IntelliJ IDEA Community Edition, одну из самых развитых на данный момент IDE для Java.

Запуск виртуального устройства

Запустив AVD Manager и установив дополнительные пакеты (SDK различных версий), можно приступить к созданию виртуального устройства с необходимыми параметрами. Разобраться в интерфейсе не должно составить труда.

Список устройств

Создание проекта

Мне всегда не терпится приступить к работе, минимизируя подготовительные мероприятия, к которым относится создание проекта в IDE, особенно, когда проект учебный и на продакшн не претендует.

Итак, File->New Project:

По нажатию кнопки F6 проект соберется, откомпилируется и запустится на виртуальном девайсе.

Структура проекта

На предыдущем скриншоте видна структура проекта. Так как в этой статье мы преследуем сугубо практические цели, заострим внимание лишь на тех папках, которые будем использовать в процессе работы. Это следующие каталоги: gen, res и src.

В папке gen находятся файлы, которые генерируются автоматически при сборке проекта. Вручную их менять нельзя.

Папка res предназначена для хранения ресурсов, таких как картинки, тексты (в том числе переводы), значения по-умолчанию, макеты (layouts).

src — это папка в которой будет происходить основная часть работы, ибо тут хранятся файлы с исходными текстами нашей программы.

Первые строки

Как только создается Activity (экран приложения), вызывается метод onCreate(). IDE заполнила его 2 строчками:

super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Метод setContentView (равносильно this.setContentView) устанавливает xml-макет для текущего экрана. Далее xml-макеты будем называть «layout», а экраны — «Activity». Layout в приложении будет следующий:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/main_l"
    android:gravity="center"
    >
</TableLayout>

Для этого приложения идеально подойдет TableLayout. Id можно присвоить любому ресурсу. В данном случае, TableLayout присвоен id = main_l. При помощи метода findViewById() можно получить доступ к виду:


    private TableLayout layout; // это свойство класса KrestikinolikiActivity

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        layout = (TableLayout) findViewById(R.id.main_l);
        buildGameField();
    }

Теперь необходимо реализовать метод buildGameField(). Для этого требуется сгенерировать поле в виде матрицы. Этим будет заниматься класс Game. Сначала нужно создать класс Square для ячеек и класс Player, объекты которого будут заполнять эти ячейки.

Square.java

package com.example;

public class Square {
    private Player player = null;

    public void fill(Player player) {
        this.player = player;
    }

    public boolean isFilled() {
        if (player != null) {
            return true;
        }
        return false;
    }

    public Player getPlayer() {
        return player;
    }
}

Player.java

package com.example;


public class Player {
    private String name;

    public Player(String name) {
        this.name = name;
    }

    public CharSequence getName() {
        return (CharSequence) name;
    }
}

Все классы нашего приложения находятся в папке src.

Game.java

package com.example;

public class Game {
 /**
     * поле
     */
    private Square[][] field;
 
 /**
     * Конструктор
     *
     */
    public Game() {
        field = new Square[3][3];
        squareCount = 0;
        // заполнение поля
        for (int i = 0, l = field.length; i < l; i++) {
            for (int j = 0, l2 = field[i].length; j < l2; j++) {
                field[i][j] = new Square();
                squareCount++;
            }
        }
    }
 
 public Square[][] getField() {
        return field;
    }
}

Инициализация Game в конструкторе KrestikinolikiActivity.

public KrestikinolikiActivity() {
    game = new Game();
 game.start(); // будет реализован позже
}

Метод buildGameField() класса KrestikinolikiActivity. Он динамически добавляет строки и колонки в таблицу (игровое поле):

private Button[][] buttons = new Button[3][3];
 //(....)
    private void buildGameField() {
        Square[][] field = game.getField();
        for (int i = 0, lenI = field.length; i < lenI; i++ ) {
            TableRow row = new TableRow(this); // создание строки таблицы
            for (int j = 0, lenJ = field[i].length; j < lenJ; j++) {
                Button button = new Button(this);
                buttons[i][j] = button;
                button.setOnClickListener(new Listener(i, j)); // установка слушателя, реагирующего на клик по кнопке
                row.addView(button, new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
                        TableRow.LayoutParams.WRAP_CONTENT)); // добавление кнопки в строку таблицы
                button.setWidth(107);
                button.setHeight(107);
            }
            layout.addView(row, new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT,
                    TableLayout.LayoutParams.WRAP_CONTENT)); // добавление строки в таблицу
        }
    }

В строке 8 создается объект, реализующий интерфейс View.OnClickListener. Создадим вложенный класс Listener. Он будет виден только из KrestikinolikiActivity.

public class Listener implements View.OnClickListener {
        private int x = 0;
        private int y = 0;

        public Listener(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public void onClick(View view) {
            Button button = (Button) view;
        }
    }

Осталось реализовать логику игры.

public class Game {
    /**
     * игроки
     */
    private Player[] players;
    /**
     * поле
     */
    private Square[][] field;
    /**
     * начата ли игра?
     */
    private boolean started;
    /**
     * текущий игрок
     */
    private Player activePlayer;
    /**
     * Считает колличество заполненных ячеек
     */
    private int filled;
    /**
     * Всего ячеек
     */
    private int squareCount;

    /**
     * Конструктор
     *
     */
    public Game() {
        field = new Square[3][3];
        squareCount = 0;
        // заполнение поля
        for (int i = 0, l = field.length; i < l; i++) {
            for (int j = 0, l2 = field[i].length; j < l2; j++) {
                field[i][j] = new Square();
                squareCount++;
            }
        }
        players = new Player[2];
        started = false;
        activePlayer = null;
        filled = 0;
    }

    public void start() {
        resetPlayers();
        started = true;
    }

    private void resetPlayers() {
        players[0] = new Player("X");
        players[1] = new Player("O");
        setCurrentActivePlayer(players[0]);
    }

    public Square[][] getField() {
        return field;
    }

    private void setCurrentActivePlayer(Player player) {
        activePlayer = player;
    }

    public boolean makeTurn(int x, int y) {
        if (field[x][y].isFilled()) {
            return false;
        }
        field[x][y].fill(getCurrentActivePlayer());
        filled++;
        switchPlayers();
        return true;
    }

    private void switchPlayers() {
        activePlayer = (activePlayer == players[0]) ? players[1] : players[0];
    }

    public Player getCurrentActivePlayer() {
        return activePlayer;
    }

    public boolean isFieldFilled() {
        return squareCount == filled;
    }

    public void reset() {
        resetField();
        resetPlayers();
    }

    private void resetField() {
        for (int i = 0, l = field.length; i < l; i++) {
            for (int j = 0, l2 = field[i].length; j < l2; j++) {
                field[i][j].fill(null);
            }
        }
        filled = 0;
    }
}

Определение победителя

К. О. подсказывает, что в крестики-нолики выирывает тот, кто выстроет X или O в линию длиной, равной длине поля по-вертикали, или по-горизонтали, или по-диагонали. Первая мысль, которая приходит в голову — это написать методы для каждого случая. Думаю, в этом случае хорошо подойдет паттерн Chain of Responsobility. Определим интерфейс

package com.example;

public interface WinnerCheckerInterface {
    public Player checkWinner();
}

Так как Game наделен обязанностью выявлять победителя, он реализует этот интерфейс. Настало время создать виртуальных «лайнсменов», каждый из которых будет проверять свою сторону. Все они реализует интерфейс WinnerCheckerInterface.

WinnerCheckerHorizontal.java

package com.example;

public class WinnerCheckerHorizontal implements WinnerCheckerInterface {
    private Game game;

    public WinnerCheckerHorizontal(Game game) {
        this.game = game;
    }

    public Player checkWinner() {
        Square[][] field = game.getField();
        Player currPlayer;
        Player lastPlayer = null;
        for (int i = 0, len = field.length; i < len; i++) {
            lastPlayer = null;
            int successCounter = 1;
            for (int j = 0, len2 = field[i].length; j < len2; j++) {
                currPlayer = field[i][j].getPlayer();
                if (currPlayer == lastPlayer && (currPlayer != null && lastPlayer !=null)) {
                    successCounter++;
                    if (successCounter == len2) {
                        return currPlayer;
                    }
                }
                lastPlayer = currPlayer;
            }
        }
        return null;
    }
}

WinnerCheckerVertical.java

package com.example;

public class WinnerCheckerVertical implements WinnerCheckerInterface {
    private Game game;

    public WinnerCheckerVertical (Game game) {
        this.game = game;
    }
    public Player checkWinner() {
        Square[][] field = game.getField();
        Player currPlayer;
        Player lastPlayer = null;
        for (int i = 0, len = field.length; i < len; i++) {
            lastPlayer = null;
            int successCounter = 1;
            for (int j = 0, len2 = field[i].length; j < len2; j++) {
                currPlayer = field[j][i].getPlayer();
                if (currPlayer == lastPlayer && (currPlayer != null && lastPlayer !=null)) {
                    successCounter++;
                    if (successCounter == len2) {
                        return currPlayer;
                    }
                }
                lastPlayer = currPlayer;
            }
        }
        return null;
    }
}

WinnerCheckerDiagonalLeft.java

package com.example;

public class WinnerCheckerDiagonalLeft implements WinnerCheckerInterface {
    private Game game;

    public WinnerCheckerDiagonalLeft(Game game) {
        this.game = game;
    }

    public Player checkWinner() {
        Square[][] field = game.getField();
        Player currPlayer;
        Player lastPlayer = null;
        int successCounter = 1;
        for (int i = 0, len = field.length; i < len; i++) {
            currPlayer = field[i][i].getPlayer();
            if (currPlayer != null) {
                if (lastPlayer == currPlayer) {
                    successCounter++;
                    if (successCounter == len) {
                        return currPlayer;
                    }
                }
            }
            lastPlayer = currPlayer;
        }
        return null;
    }
}

WinnerCheckerDiagonalRight.java

package com.example;

public class WinnerCheckerDiagonalRight implements WinnerCheckerInterface {
    private Game game;

    public WinnerCheckerDiagonalRight(Game game) {
        this.game = game;
    }

    public Player checkWinner() {
        Square[][] field = game.getField();
        Player currPlayer;
        Player lastPlayer = null;
        int successCounter = 1;
        for (int i = 0, len = field.length; i < len; i++) {
            currPlayer = field[i][len - (i + 1)].getPlayer();
            if (currPlayer != null) {
                if (lastPlayer == currPlayer) {
                    successCounter++;
                    if (successCounter == len) {
                        return currPlayer;
                    }
                }
            }
            lastPlayer = currPlayer;
        }
        return null;
    }
}

Проинициализируем их в конструкторе Game:

//(....)
 /**
     * "Судьи" =). После каждого хода они будут проверять,
     * нет ли победителя
     */
    private WinnerCheckerInterface[] winnerCheckers;
 //(....)
    public Game() {
        //(....)
        winnerCheckers = new WinnerCheckerInterface[4];
        winnerCheckers[0] = new WinnerCheckerHorizontal(this);
        winnerCheckers[1] = new WinnerCheckerVertical(this);
        winnerCheckers[2] = new WinnerCheckerDiagonalLeft(this);
        winnerCheckers[3] = new WinnerCheckerDiagonalRight(this);
        //(....)
    }

Реализация checkWinner():

public Player checkWinner() {
        for (WinnerCheckerInterface winChecker : winnerCheckers) {
            Player winner = winChecker.checkWinner();
            if (winner != null) {
                return winner;
            }
        }
        return null;
    }

Победителя проверяем после каждого хода. Добавим кода в метод onClick() класса Listener

public void onClick(View view) {
            Button button = (Button) view;
            Game g = game;
            Player player = g.getCurrentActivePlayer();
            if (makeTurn(x, y)) {
                button.setText(player.getName());
            }
            Player winner = g.checkWinner();
            if (winner != null) {
                gameOver(winner);
            }
            if (g.isFieldFilled()) {  // в случае, если поле заполнено
                gameOver();
            }
        }

Метод gameOver() реализован в 2-х вариантах:

private void gameOver(Player player) {
        CharSequence text = "Player "" + player.getName() + "" won!";
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
        game.reset();
        refresh();
    }

    private void gameOver() {
        CharSequence text = "Draw";
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
        game.reset();
        refresh();
    }

Для Java, gameOver(Player player) и gameOver() — разные методы. Воспользовавшись Builder’ом Toast.makeText, можно быстро создать и показать уведомление. refresh() обновляет состояние поля:

private void refresh() {
        Square[][] field = game.getField();

        for (int i = 0, len = field.length; i < len; i++) {
            for (int j = 0, len2 = field[i].length; j < len2; j++) {
                if (field[i][j].getPlayer() == null) {
                    buttons[i][j].setText("");
                } else {
                    buttons[i][j].setText(field[i][j].getPlayer().getName());
                }
            }
        }
    }

Готово! Надеюсь, эта статья помогла Вам освоиться в мире разработки под OS Android. Благодарю за внимание!

Видео готового приложения

Исходники .

PS: статья была опубликована по просьбе комментаторов этого поста.

#Руководства

  • 18 июл 2018

  • 1

Платформа Android открытая, поэтому каждый может написать своё приложение и распространять его через каталоги программ. Все инструменты бесплатны.

 vlada_maestro / shutterstock

Анатолий Ализар

Пишет про разработку в Skillbox Media. Работал главным редактором сайта «Хабрахабр», ведёт корпоративные блоги.

Язык программирования для мобильной разработки на Android очень простой — это Java. Сейчас Google активно продвигает Kotlin как язык, который сможет заменить Java. Приложения пишут и на C++.

Создание простейшего приложения состоит из нескольких этапов:

  • проект в Android Studio;
  • создание пользовательского интерфейса;
  • добавление активностей, навигации и действий;
  • тест-драйв приложения в эмуляторе.

Первым делом установите программу Android Studio. Это официальная среда разработки (IDE) для Android, она работает на Windows, macOS и Linux. Хотя при разработке программ для Android можно использовать и другие среды, кроме Android Studio.

Если на компьютере не установлены Android SDK и другие компоненты, то Android Studio автоматически скачает их. Android SDK — это среда программирования, в которую входят библиотеки, исполняемые файлы, скрипты, документация и т.д.

Android SDK компилирует код вместе с любыми данными и ресурсами в файл с расширением .apk. Он содержит всё необходимое для установки приложения на Android-устройство.

Полезно установить и эмулятор Android, чтобы запускать и тестировать приложения. Эмулятор поставляется в комплекте с Android Studio.

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

Android-приложение состоит из четырёх компонентов. Каждый компонент — это точка входа, через которую система или пользователь может получить доступ.

  • Активность (activity) — элементы интерактивного пользовательского интерфейса.
    Одна активность задействует другую и передаёт информацию о том, что намерен делать пользователь, через класс Intent (намерения). Активности подобны веб-страницам, а намерения — ссылкам между ними. Запуск приложения — это активность Main.
  • Сервис (service) — универсальная точка входа для поддержания работы приложения в фоновом режиме.
    Этот компонент выполняет длительные операции или работу для удалённых процессов без визуального интерфейса.
  • Широковещательный приемник (broadcast receiver) транслирует нескольким участникам намерения из приложения.
  • Поставщик содержимого (content provider) управляет общим набором данных приложения из файловой системы, базы данных SQLite, интернета или другого хранилища.

Теперь попробуем сделать своё приложение для Android.

Выбираем название приложения, домен компании, путь к проекту и название пакета. Указываем, включить ли поддержку опциональных языков программирования C++ и Kotlin.

Задаём одну или несколько целевых платформ для сборки. Для этого используется SDK и AVD, менеджер виртуальных устройств Android. Инструмент позволяет устанавливать в SDK пакеты, которые поддерживают несколько версий ОС Android и несколько уровней API (интерфейсов программирования приложений).


Справка

Чем ниже версия Android, тем больше устройств, на которых приложение запустится. Чем выше версия, тем богаче функциональность API.


Выбираем основную активность, которая будет запускаться при нажатии на иконку приложения, и даём ей имя.

После нескольких минут сборки Android Studio открывает интерфейс IDE. Здесь три основных момента.

Если выбрать в выпадающем меню вид Android, то вы увидите файлы проекта. Например, наша основная активность называется app > java > ru.skillbox.skillboxapp > FullscreenActivity. При создании проекта мы указали вместо активности Main полноэкранную активность.

Далее можно посмотреть файл app > res > layout > activity_fullscreen.xml. Это XML-файл с макетом для UI нашей основной активности.

Наконец, третий важный файл app > manifests > AndroidManifest.xml описывает фундаментальные характеристики приложения и определяет все его компоненты.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ru.skillbox.skillboxapp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".FullscreenActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:label="@string/app_name"
            android:theme="@style/FullscreenTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Созданное нами приложение — это одна активность, которая запускается в полноэкранном режиме и не имеет графических элементов.

Запускаем на Android-устройстве или в эмуляторе.

Смартфон или планшет для этого подключаем в режиме USB-отладки, которая активируется в Настройках разработчика в меню Настройки.

Для запуска в эмуляторе нажимаем в Android Studio кнопку Run в меню Run (Shift+F10). Выбираем подходящее устройство и версию ОС, портретную или ландшафтную (альбомную) ориентацию.

Android Studio установит эмулятор и запустит его.

Пользовательский интерфейс Android-приложения создаётся через иерархию макетов (layouts, объекты ViewGroup) и виджетов (объекты View). Макеты управляют расположением дочерних виджетов на экране. Сами виджеты — это непосредственно компоненты UI: кнопки, текстовые поля на экране и т.п.

Интерфейс активностей создаётся в Android Studio в редакторе макетов (Layout Editor) и хранится по большей части в XML-файлах.

  • Открываем файл app > res > layout > activity_fullscreen.xml.
  • Добавляем на экран из палитры (Palette) виджеты перетаскиванием мышью.
  • Например, берём текстовое поле (PlainText). Это виджет EditText, куда пользователь может вводить текст.
  • Добавляем кнопки и другие нужные элементы.

Также на экран можно перетащить кнопки и другие элементы.

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

  • Заходим в код app > java > FullscreenActivity.
  • Добавляем метод SendMessage() в класс FullscreenActivity, чтобы при нажатии на кнопку вызывался этот метод.
  • Создаём намерения (класс Intent) для перехода от одной активности к другой, новые активности, навигацию и всё остальное, что необходимо для приложения.

И, конечно, начинаем мечтать, как монетизировать приложение.

Учись бесплатно:
вебинары по программированию, маркетингу и дизайну.

Участвовать

Научитесь: Профессия Мобильный разработчик
Узнать больше

Cat gives five

Hello Kitty: Пример для Java
Здороваемся с вашим котом (Kotlin)
Здороваемся с вашим котом (Java)

После установки Android Studio (далее Студия) можно приступать к созданию своей первой программы.

Учтите, что студия постоянно обновляется, поэтому внешний вид окон и другие детали могут отличаться от данного примера. Иногда изменения носят косметический характер и новичкам достаточно просто разобраться и вникнуть в изучаемый материал. А иногда изменения слишком значительны и могут сбить с толку. Скриншоты у многих уроков сделаны со старых версий студии от 2.3 до 4.2. Если что-то сильно изменилось, то напишите мне, я постараюсь заменить картинки.

В качестве языка программирования для Android используется Java или Kotlin. Для создания пользовательского интерфейса используется XML.

Здесь следует сделать небольшое отступление. В Android Studio 3.0 добавлена полноценная поддержка нового языка Kotlin, разработанная котанами. Google объявила в 2017 году новый «кошачий» язык основным. Но вы должны понимать, что за предыдущие годы было написано огромное количество примеров на Java. Если вы новичок в программировании, то лучше в первый период обучения полностью сосредоточиться на Java, вам будет проще находить ответы на вопросы. Kotlin от вас никуда не денется, перейти потом на него будет проще, а вот обратный процесс будет проходить тяжелее. Когда немного освоитесь в Java, то можете параллельно изучать примеры на Kotlin. Google сейчас активно переписывает документацию под Kotlin, но до полного перехода ещё далеко, даже меньше 50%. Новые уроки я пишу в основном под Kotlin, но на сайте полно примеров под Java.

Статья в очередной раз переписана. На этот раз основной упор сделан на Kotlin. Возможно, где-то в статье остались хвосты от Java-варианта. Хвосты — это нормально.

По традиции, заложенной в прошлом веке, каждый программист должен был написать «Hello World!» (Здравствуй, Мир!) в качестве первой программы. Времена меняются, и программа «Hello World!» уже встроена в среду разработки под Android в целях совместимости, а современные программисты должны писать программу Hello Kitty! (Привет, киска!). Согласитесь, что здороваться с котёнком имеет больше здравого смысла, чем с каким-то миром.

Разобьём задачу на две части. Сначала запустим готовую программу Hello World! без написания кода, чтобы убедиться, что весь инструментарий корректно установился, и мы можем создавать и отлаживать программы. А потом уже напишем свою первую программу.

Создание нового проекта

Запускаем Студию и выбираем File | New | New Project…. Появится диалоговое окно мастера.

New Project

Окно имеет несколько разделов. В основном, мы будем использовать раздел Phone and Tablet.

Здесь следует выбрать внешний вид экрана приложения. Предложенные шаблоны позволяют сэкономить время на написание стандартного кода для типичных ситуаций. Опытный разработчик может вручную написать любой из предложенных вариантов, используя вариант No Activity, где никаких заготовок не будет.

Несколько лет назад был только один шаблон. Список шаблонов постоянно меняется (добавляют и удаляют). В 3.1 было 13, в Android Studio 3.2 добавили 14-й шаблон Fragment+ViewModel. А в 3.3 число сократилось до 12 (причём один из них относится к экзотическому C++). В версии 4.0 шаблонов стало 15.

В версии Dolphin 2020.3.1 шаблонов стало 21.

  • No Activity
  • Basic Activity
  • Basic Activity (Material 3)
  • Bottom Navigation Activity
  • Empty Compose Activity
  • Empty Compose Activity (Material 3)
  • Empty Activity
  • Fullscreen Activity
  • Google AdMob Ads Activity
  • Google Maps Activity
  • Google Pay Activity
  • Login Activity
  • Primary/Detail Flow
  • Navigation Drawer Activity
  • Responsive Activity
  • Settings Activity
  • Scrolling Activity
  • Tabbed Activity
  • Fragment + ViewModel
  • Game Activity(C++) (NEW!)
  • Native C++

В версию Dolphin добавили новый шаблон Game Activity(C++) (стало 21).

В версию Chipmunk добавили три новых шаблона (стало 20).

Шаблон Empty Activity предназначен для обычных телефонов. На картинке над названием шаблона вы видите приблизительный вид приложения с использованием данной заготовки. Для учебных программ в 99% подойдёт этот вариант. Практически все примеры на сайте написаны с помощью данного шаблона.

Шаблон Primary/Detail Flow (раньше было Master/Detail Flow, BLM и всё такое) предназначен для планшетов с реализацией двухпанельного режима. Шаблон Fullscreen Activity можно использовать для игр, когда требуется дополнительное пространство без лишних деталей. Другие шаблоны нужны для создания приложений с гуглокартами или сервисами Google Play.

В следующем окне настраиваются параметры проекта.

Configure your project

Поле Name — понятное имя для приложения, которое будет отображаться в заголовке приложения. По умолчанию у вас уже может быть My Application. Заменим на Hello World. В принципе вы могли написать здесь и Здравствуй, мир!, но у Android есть замечательная возможность выводить нужные строки на телефонах с разными языками. Скажем, у американца на телефоне появится надпись на английском, а у русского — на русском. Поэтому в первоначальных настройках всегда используются английские варианты, а локализованные строки подготовите позже. Необходимо сразу вырабатывать привычку к правильному коду.

Поле Package name формирует специальный Java-пакет. В Java используется перевёрнутый вариант для наименования пакетов, поэтому сначала идёт ru, а потом уже название сайта. Пакет служит для уникальной идентификации вашего приложения, когда вы будете его распространять. Если сто человек напишет сто приложений с названием «Cat», то будет непонятно, где приложение, написанное разработчиком Василием Котовым. А приложение с именем пакета ru.vaskakotov.cat проще найти. Обратите внимание, что Гугл в своей документации использует пакет com.example в демонстрационных целях. Если вы будете просто копировать примеры из документации и в таком виде попытаетесь выложить в Google Play, то у вас ничего не выйдет — это название зарезервировано и запрещено к использованию в магазине приложений.

Третье поле Save location позволяет выбрать место на диске для создаваемого проекта. Вы можете создать на своём диске отдельную папку для своих проектов и хранить свои программы в ней. Студия запоминает последнюю папку и будет автоматически предлагать сохранение в ней. В случае необходимости вы можете задать другое местоположение для отдельного проекта через кнопку с значком папки.

В поле Language можно выбрать язык: Kotlin или Java.

В блоке Minimum API level выбираем минимальную версию системы, под которую будет работать приложение. Выберите свой вариант. На данный момент Гугл поддерживает версии, начиная с API 9, выпуская специальные библиотеки совместимости для старых устройств. Но вы можете выбрать более современный вариант. У меня в наличии телефон с минимальной версией Android 5.0, поэтому я выставляю этот вариант.

Если щёлкнуть по ссылке Help me choose, то откроется окно с графиком. Если вам интересно, можете посмотреть, но котиков там нет.

Если по каким-то причинам нужно поддерживать старые библиотеки, то ставьте флажок Use legacy android.support libraries. К вам это вряд ли относится, игнорируем флажок.

Сразу покажу приём, который вам пригодится в дальнейшем при обновлении библиотек. Откройте файл build.gradle, который относится к модулю.

Gradle

Там будут подсвечены несколько строк у библиотек, у которых доступны обновления.

Gradle implementation

При подведении мыши появится подсказка, что библиотека устарела. Выбираем пункт для обновления Change to …. Повторяйте эти шаги для других библиотек при необходимости.

Gradle implementation

Затем нажмите ссылку синхронизации Sync Now в верхнем правом углу студии.

Gradle sync

Данным способом вы потом будете неоднократно пользоваться в своих проектах, когда библиотеки будут устаревать. Студия сама определит, что пора обновиться (это относится не ко всем библиотекам, некоторые нужно проверять вручную).

Мы закончили с первоначальной настройкой. Нажимаем кнопку Finish и смотрим, что дальше будет.

А дальше студия формирует проект и создаёт необходимую структуру из различных файлов и папок. Поначалу глаза разбегаются. Давайте разбираться.

Боковая левая часть студии имеет несколько боковых вкладок. Скорее всего у вас будет активна первая вкладка 1:Project. Вкладки Structure и ResourceManager и другие используются гораздо реже.

В левой части среды разработки на вкладке Android появится иерархический список из папок, которые относятся к проекту. В некоторых случаях желательно переключиться на режим Project, который показывает истинное расположение файлов. Но на первых порах удобнее использовать именно вид Android, который прячет служебные файлы, чтобы не путать новичков.

Содержание проекта

Вкладка Android содержит две основные папки: app и Gradle Scripts. Первая папка app является отдельным модулем для приложения и содержит все необходимые файлы приложения — код, ресурсы картинок и т.п. Вторая папка служит для различных настроек, управления проектом и многих других вещей.

Сейчас нас должна интересовать папка app. Раскройте её. В ней находятся папки: manifest, java, res.

manifests

Папка manifests (раньше была в единственном числе) содержит единственный файл манифеста AndroidManifest.xml. В этом файле должны быть объявлены все активности, службы, приёмники и контент-провайдеры приложения. Также он должен содержать требуемые приложению разрешения. Например, если приложению требуется доступ к сети, это должно быть определено здесь. «AndroidManifest.xml» можно рассматривать, как описание для развёртывания Android-приложения.

Более подробно о структуре манифеста читайте в дополнительной статье Файл AndroidManifest.xml

java

Папка java содержит три подпапки — одну рабочую и два для тестов. Рабочая папка имеет название вашего пакета и содержит файлы классов. Сейчас там один класс MainActivity. Папки для тестов можете не трогать. Если вы знаете, как работают пакеты в Java, то можете создавать новые папки и подпапки.

res

Папка res содержит файлы ресурсов, разбитых на отдельные подпапки. В режиме Android выводятся виртуальные папки, если вы посмотрите на реальное содержимое на диске компьютера, то структура будет немного иной. Чтобы увидеть реальную структуру, не обязательно открывать Проводник (Windows) и искать там свой проект, можно просто переключиться в режим Project.

  • drawable — в этих папках хранят графические ресурсы — картинки и xml-файлы, описывающие цвет и фигуры. В реальности на диске находятся папки drawable, drawable-anydpi, drawable-hdpi, drawable-mdpi, drawable-v24, drawable-xhdpi, drawable-xxhdpi. Состав папок менялся в каждой версии студии, сейчас там вообще две папки drawable и drawable-v24
  • layout — в данной папке содержатся xml-файлы, описывающие внешний вид форм и различных элементов форм. После создания проекта там уже имеется файл activity_main.xml, который отвечает за внешний вид главного окна приложения.
  • mipmap — здесь хранят значки приложения под разные разрешения экрана. В реальности это папки mipmap-anydpi-v26, mipmap-hdpi, mipmap-mdpi, mipmap-xhdpi, mipmap-xxhdpi, mipmap-xxxhdpi
  • values — тут размещаются строковые ресурсы, ресурсы цветов, тем, стилей и измерений, которые мы можем использовать в нашем проекте. Здесь вы можете видеть файлы colors.xml, strings.xml, styles.xml. В старых проектах был ещё файл dimens.xml, сейчас от него отказались

В студии версии 4.1 убрали файл styles.xml, а вместо неё добавили файл themes.xml в папке values, и файл themes.xml в новой папке values-night. Файлы предназначены для обычной и ночной темы.

Со временем вы будет свободно ориентироваться в этих папках, пока не забивайте себе голову.

Работа с проектом — Здравствуй, Мир!

Как уже говорилось, программа Hello, World! уже встроена в любой новый проект, поэтому вам даже не нужно ничего писать. Просто нужно запустить проект и получить готовую программу!

Для изучения вам нужно открыть два файла — MainActivity.kt (скорее всего он уже открыт) и activity_main.xml (res/layout) в центральной части Студии. Если файлы не открыты, то откройте их самостоятельно двойным щелчком для редактирования (или просмотра). Таким способом вы можете открыть любой нужный вам файл.

Не будем пока изучать код, а просто нажмём на зелёный треугольник Run (Shift+F10) на панели инструментов в верхней части студии для запуска приложения.

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

Если всё сделали правильно, то в эмуляторе или на устройстве загрузится ваша программа. Поздравляю!

Итак, если программа запустилась, то увидите окно приложения с надписью Hello World!. Заголовок у программы будет также Hello World (берётся название проекта). Все эти строки можно найти в файле res/values/strings.xml и отредактировать при желании.

Hello World

Теперь посмотрим на код. Сначала изучим activity_main.xml.

Смотреть его можно в разных режимах — Code, Split, Design (до версии 3.6 — Design и Text).

Откройте в режиме Code. Подробнее о режимах чуть ниже.


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Сейчас в студии используется новый код шаблона на основе ConstraintLayout, который появился в Android Studio 2.3 в марте 2017 года. Раньше использовался другой код с RelativeLayout (а ещё раньше и другой код с LinearLayout). Если вам будут попадаться старые примеры, то в студии есть контекстное меню, которое поможет сконвертировать старый код в новый.

Немного о XML-коде. Имеется специальный контейнер ConstraintLayout, в котором размещён компонент TextView, предназначенный для вывода текста.

Теперь посмотрим на Kotlin-код (MainActivity.kt)


package ru.alexanderklimov.helloworld

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

В первой строке идёт название пакета — его мы задавали при создании проекта (Package Name). Далее идут строки импорта необходимых классов для проекта. Для экономии места они свёрнуты в одну группу. Разверните её. Если однажды вы увидите, что имена классов выводятся серым цветом, значит они не используются в проекте (подсказка Unused import statement) и можете спокойно удалить лишние строки. Также они могут удаляться автоматически (настраивается).

Далее идёт объявление самого класса, который наследуется (двоеточие :) от абстрактного класса Activity. Это базовый класс для всех экранов приложения. У вас будет AppCompatActivity. В старых версиях не было плюшек, которые появились после Android 4, поэтому для них была создана специальная библиотека совместимости, которая позволяет использовать новинки от новых версий Android в старых программах. Класс AppCompatActivity как раз и относится к библиотеке совместимости. Считайте её родственником базовой Activity. У неё есть все нужные методы и вспомогательные классы, но названия могут немного различаться.

На разных этапах использовались разные названия класса активности, которые могут вам встретиться в старых проектах. Например, сначала использовался FragmenActivity, затем ActionBarActivity, а 22 апреля 2015 года вышла новая версия библиотеки совместимости и на данный момент используется новый класс AppCompatActivity.

В самом классе мы видим метод onCreate() – он вызывается, когда приложение создаёт и отображает разметку активности. Метод помечен ключевым словом override (переопределён из базового класса). Ключевое слово может пригодиться вам. Если вы сделаете опечатку в имени метода, то компилятор сможет предупредить вас, сообщив об отсутствии такого метода у родительского класса Activity.

Разберём код метода.

Строка super.onCreate(savedInstanceState) – это конструктор родительского класса, выполняющий необходимые операции для работы активности. Эту строчку вам не придётся трогать, оставляйте без изменений.

Вторая строчка setContentView(R.layout.activity_main) представляет больший интерес. Метод setContentView(int) подключает содержимое из файла разметки. В качестве аргумента мы указываем имя файла без расширения из папки res/layout. По умолчанию проект создаёт в нём файл activity_main.xml. Вы можете переименовать файл или создать свой файл с именем cat.xml и подключить его к своей активности. Тогда код будет выглядеть так:


setContentView(R.layout.cat)

Чтобы ваш код был аккуратным, старайтесь придерживаться стандартов. Если вы создаёте разметку для активности, то используйте префикс activity_ для имени файла. Например, разметка для второй активности может иметь имя activity_second.xml.

Hello Kitty!

Вы создали новую программу, но это ещё не повод считать себя программистом, так как вы не написали не единой строчки кода. Настало время набраться смелости и создать программу «Hello Kitty!».

Создаём новый проект. Снова выбираем шаблон Empty Activity и устанавливаем нужные настройки.

Kotlin support

На данный момент наша программа слишком проста. Представьте себе, что у вас на экране должны располагаться несколько кнопок, текстовых полей, картинок. Каждому объекту нужно задать размеры, координаты, цвет, текст и так далее. Android поддерживает способ, основанный на XML-разметке, который будет напоминать разметку веб-страницы. Начинающие программисты могут использовать визуальный способ перетаскивания объектов с помощью мыши. Более продвинутые могут писать код вручную. Чаще используется комбинированный подход.

Файлы XML-разметки находятся в папке res/layout вашего проекта. Слово «res» является сокращением от слова «resources» (ресурсы). Папка содержит ресурсы, не связанные с кодом. Кроме разметки, там же содержатся изображения, звуки, строки для локализации и т.д.

Раскройте слева в структуре проектов папки res/layout и дважды щёлкните на файле activity_main.xml, если он у вас закрыт. Обратите внимание, что XML-файлы можно просматривать в разных режимах: текстовом, визуальном и смешанном. Для этого предназначены три вкладки в верхней части окна редактора: Code, Split, Design (если вы не кот и не пользуетесь мышкой, а предпочитаете клавиатуру, то используйте комбинацию Alt + Shift + Left/Right Arrow (Windows) для циклического переключения между режимами).

Переключитесь в режим Code (текстовый режим).

Структура XML-файла достаточна проста — стандартное дерево XML-элементов, где каждый узел является именем класса View (TextView — один из элементов View). Вы можете создать интерфейс программы, используя структуру и синтаксис XML. Подобный подход позволяет разделить код программы и визуальное представление.

Когда разметка открыта в графическом представлении, то слева от основной части редактора кода можно увидеть панель инструментов, в которой сгруппированы различные элементы по группам Widgets, Text, Layouts и так далее. В группе Buttons найдите элемент ImageButton, перетащите её на форму и отпустите. Точное расположение нас не интересует, поэтому не заморачивайтесь по этому поводу, постарайтесь разместить компонент в центре экрана активности. У вас появится диалоговое окно с просьбой выбрать изображение для кнопки. Пока выбирайте любую, например, первую. Потом заменим.

Теперь научимся менять фон для экрана приложения. Сейчас у нас экран белого цвета. Возвращаемся в файл разметки activity_main.xml. Справа найдите вкладку Attributes, в которой отображаются свойства для выбранного элемента. А слева есть вкладка Component Tree, который отображает структуру компонентов на экране. Вам нужно нужно выделить какой-нибудь компонент, что на вкладке свойств увидеть все доступные свойства компонента. Новички часто путаются на первых порах и начинают менять свойства не у тех элементов, которые им были нужны. Сейчас у вас есть окно активности, графическая кнопка ImageButton и текстовая метка TextView с надписью Hello World!. Пощёлкайте по этим элементами, чтобы увидеть, как меняется содержание свойств в панели свойств. Так как мы собираемся работать с фоном экрана приложения, то щёлкните на ConstraintLayout. В панели свойств отобразятся самые употребительные свойства выбранного компонента. К ним относятся идентификатор, ширина и высота.

Прокручиваем список атрибутов в All attributes и находим свойство background. Щёлкните рядом с этим словом во второй колонке, где нужно прописывать значения. Появится текстовое поле, в которое можно ввести значение вручную. Рядом есть маленькая кнопка, которая запустит диалоговое окно для создания ресурса.

Переходим на вкладку Color и выбираем цвет. Вы можете выбрать цвет, определённый в приложении (секция app) или в системе (секция android).

Давайте выберем цвет colorAccent и нажмём кнопку OK.

res

Экран окрасится в розовый цвет. Получилось глаМУРненько (в предыдущих версиях в качестве colorAccent использовался другой цвет, поэтому здесь расхождение между описанием и реальным цветом. Можете самостоятельно попытаться установить розовый цвет.

Если переключиться в текстовый режим, то увидим, что у элемента ConstraintLayout добавилась строчка:


android:background="@color/colorAccent"

Мы связали фон экран с именем цветового ресурса. Существует неправильный подход, когда можно сразу напрямую указать значение цвета.


android:background="#ffffc0cb"

Старайтесь так не делать, всегда используйте ресурсы.

Далее поменяем картинку для графической кнопки. Находим подходящее изображение и копируем его в папку res/drawable. Картинку можете взять у меня.

Hello Kitty

Простое перетаскивание из проводника в папку студии не сработает. Поэтому лучше скопировать картинку в буфер, затем щёлкнуть правой кнопкой мыши на папке drawable в студии, выбрать команду «Вставить». Сначала появится первое диалоговое окно для выбора папки, выбираем папку drawable.

Возможно первое окно у вас не появится, тогда в следующем диалоговом окне подтверждаем операцию копирования файла.

Когда вы поместите графический файл в указанную папку, то студия автоматически создаёт ресурс типа Drawable с именем файла без расширения, к которому можно обращаться программно.

Выделяем элемент ImageButton на форме и в панели свойств выбираем свойство srcCompat, щёлкаем на кнопку и выбираем в диалоговом окне ресурс в категории Drawable — вы там должны увидеть ресурс pinkhellokitty (имя добавленного ранее файла).

srcCompat

Запомните, что имена ресурсов должны начинаться с буквы и могут содержать буквы и цифры, а также знак нижнего подчёркивания. Другие символы типа тире, решётки и т.д. использовать нельзя.

Мы закончили работу с графическим интерфейсом приложения. Напоследок, выделите элемент TextView с надписью Hello, World и в окне свойств посмотрите на его идентификатор (ID). Если там пусто, то удалите его, он не оправдал наших надежд. В разделе Text найдите компонент TextView (самый первый) и перетащите его на форму приложения. Постарайтесь разместить его под графической кнопкой с котёнком.

У этого компонента точно будет что-то написано в свойстве id. Скорее всего, это будет textView. Запомните его. Впрочем, мы могли не удалять первый компонент, а прописать идентификатор вручную. Но мне пришлось бы объяснять лишние детали, а так сразу получили результат. Вот я не удалял его и у меня экран выглядит так. А у вас будет текст TextView. Ничего страшного.

Если текст вам кажется мелковатым, то у свойства textAppearance установите значение AppCompat.Display2.

Hello Kitty

У меня получилось следующее.


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffc0cb"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.AppCompat.Display2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageButton"
        tools:text="Имя кота" />

    <ImageButton
        android:id="@+id/imageButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/pinkhellokitty" />

</androidx.constraintlayout.widget.ConstraintLayout>

Здесь очень много кода, который генерирует ConstraintLayout при размещении и выравнивании компонентов. Это новый контейнер, к которому нужно привыкать. Сейчас нет смысла объяснять принцип его работы, на сайте есть отдельная статья по нему. Пока советую при ручном размещении нажимать значок Infer Constraints (изображение волшебной палочки) в режиме дизайна — студия попытается предугадать нужные настройки самостоятельно.

Различия c Java начинаются с имени файла класса MainActivity.kt. У него расширение kt вместо java, но располагается в аналогичном пакете как у Java. Скорее всего он у вас уже открыт в редакторе. Если нет, то двойным щелчком запустите его из левой части студии.

Синтаксис кода немного отличается от Java. Первое, что бросается в глаза — не нужно ставить точку с запятой в конце оператора.

Так как мы собираемся менять текст в текстовой метке, необходимо прописать данный элемент в коде. До метода onCreate() наберите строчку:


private lateinit var mHelloTextView: TextView

Мы объявили переменную типа TextView под именем mHelloTextView.

Если вы набирали вручную и при подсказках использовали клавишу Enter, то класс TextView автоматически импортируется и запись появится в секции import. Если вы просто копируете текст с сайта, то студия подчеркнёт название класса TextView и предложит импортировать его вручную.

Далее внутри метода onCreate() после вызова метода setContentView() добавьте строку:


mHelloTextView = findViewById(R.id.textView) // помните, я просил запомнить идентификатор?

Избегайте соблазна скопировать строку с сайта и вставить в код, пишите самостоятельно и активно используйте автозавершение (Ctrl+Пробел) при наборе слов. Студия часто сама активно помогает подсказками. Теперь система знает о существовании элемента TextView, и мы можем к нему обращаться для изменения различных свойств, например, поменять текст.

Далее объявляем компонент ImageButton и пишем код для обработчика щелчка — обращаемся к элементу mHelloTextView и через его метод setText() программно меняем текст на нужные слова.

Общий код класса получится следующим. Я намерено оставил вещи, которые можно было упростить.


package ru.alexanderklimov.hellokot

import android.os.Bundle
import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    private lateinit var mHelloTextView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mHelloTextView = findViewById(R.id.textView)

        var imageButton: ImageButton = findViewById(R.id.imageButton)
        // альтернативный вариант
        // val imageButton = findViewById<ImageButton>(R.id.imageButton)
        // или val imageButton = findViewById(R.id.imageButton) as ImageButton

        imageButton.setOnClickListener {
            mHelloTextView.setText("Hello Kitty")
        }
    }
}

Запускаем программу и нажимаем на кнопку с изображением котёнка. Если всё сделали правильно, то отобразится замечательная фраза. С этого момента можете считать себя настоящим программистом — вы научились создавать цветовые и графические ресурсы, менять фон у приложения через XML-разметку, обрабатывать нажатия кнопки и выводить текстовые сообщения.

Hello Kitty

В данном случае я написал код в Java-стиле, не используя особенностей Kotlin. Студия подчеркнёт волнистой чертой метод setText() и предложить заменить его на свойство.

Kotlin позволяет заменить пары Java-методов вида getXX/setXX свойствами без приставок. Были методы getText()/setText(), остались рожки да ножки свойство text. Если вы что-то присваиваете свойству, значит вы хотите использовать setText(), а если хотите прочитать значение свойства — значит вы хотите вызвать getText(). Вот так изящно работает Kotlin.

Второй момент связан с рефакторингом кода. Раньше в эпоху Java было принято присваивать глобальным переменным имена с приставкой m (member). Сейчас в этом нет необходимости, студия выделяет подобные переменные другим цветом. Чтобы вручную не изменять в проекте все упоминания переменной, используют так называемый рефакторинг (изменение кода без потери его функциональности). Сделать это проще всего через комбинацию клавиш SHIFT+F6. Выделяем нужное слово, нажимаем указанную комбинацию клавиш и выбираем один из предложенных вариантов или печатаем свой вариант. В нашем случае подойдёт вариант helloTextView.

Refactoring

Можно заменить строчку кода.


imageButton.setOnClickListener {
    helloTextView.text = "Hello Kitty!"
}

В папке appbuildoutputsapk проекта можно найти готовый APK-файл, который вы можете выложить у себя на сайте и дать скачать своим знакомым (в телефоне должно быть разрешение на установку неподписанных приложений), вы станете невероятно круты в их глазах.

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

Пример для Java

Примечание: Для примера на Java — Там же в окне свойств у ImageButton находим свойство onClick и вручную прописываем onClick — это будет именем метода для обработки нажатия на кнопку. Вы можете придумать и другое имя, например, onButtonPressed. Для Kotlin можно не использовать эту конструкцию.

В текстовом режиме установите курсор мыши внутри текста «onClick» у кнопки и нажмите комбинацию Alt+Enter

Комбинация клавиш

В всплывающем окне выберите вариант Create ‘onClick(View)’ in ‘MainActivity’.

onClick

В коде класса MainActivity появится заготовка для обработки щелчка кнопки.

Раз уж у нас теперь открыт файл MainActivity.java, то продолжим теперь работу в нём. Так как мы собираемся менять текст в текстовой метке, необходимо прописать данный элемент в коде. До метода onCreate() наберите строчку:


private TextView mHelloTextView;

Мы объявили переменную типа TextView под именем mHelloTextView.

Если вы набирали вручную и при подсказках использовали клавишу Enter, то класс TextView автоматически импортируется и запись появится в секции import. Если вы просто копируете текст с сайта, то студия подчеркнёт название класса TextView и предложит импортировать его вручную.

Далее внутри метода onCreate() после вызова метода setContentView() добавьте строку:


mHelloTextView = findViewById(R.id.textView); // помните, я просил запомнить идентификатор?

Избегайте соблазна скопировать строку с сайта и вставить в код, пишите самостоятельно и активно используйте автозавершение (Ctrl+Пробел) при наборе слов. Студия часто сама активно помогает подсказками. Теперь система знает о существовании элемента TextView, и мы можем к нему обращаться для изменения различных свойств, например, поменять текст.

В старых примерах вам может встретиться вариант со скобками.


mHelloTextView = (TextView) findViewById(R.id.textView);

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


// было
public View findViewById(int id);

// стало
public <T extends View> T findViewById(int id);

Эта возможность появилась совсем недавно, поэтому во всех старых примерах будет попадаться старый код. Можете сразу привыкать к новому способу, студия будет подсказывать о новой возможности.

Переходим к заготовке для щелчка кнопки.


public void onClick(View view) {
}

В следующих занятиях мы подробнее разберём работу с данным методом, пока просто пишем код между фигурными скобками:


mHelloTextView.setText("Hello Kitty!");

Мы обращаемся к элементу mHelloTextView и через его метод setText() программно меняем текст на нужные слова.

Исходный код для ленивых

Ткните лапкой, чтобы развернуть текст


// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.

package ru.alexanderklimov.hellokitty;

import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private TextView mHelloTextView;

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

        mHelloTextView = findViewById(R.id.textView);
    }

    public void onClick(View view) {
        mHelloTextView.setText("Hello Kitty!");
    }
}

Здороваемся с вашим котом (Kotlin)

Программа получилась замечательная, но у неё есть недостаток. Она показывает одну и ту же фразу «Hello Kitty!». Вряд ли ваш кот знает английский, да и здороваться лучше по имени. Не пытайтесь с котом мяукать, иначе разговор выглядит следующим образом.

Разговор с котом

Поздороваемся с котом по человечески. Найдите в разделе Text компонент Plain Text и перетащите его на экран активности, разместив где-то над картинкой. Оставляем все свойства без изменений, разве только в свойстве hint можно добавить строчку-подсказку, которая будет исчезать при вводе текста.


<EditText
    android:id="@+id/editText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="29dp"
    android:ems="10"
    android:inputType="textPersonName"
    android:text="Name"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

Отредактируем код для щелчка кнопки.


private lateinit var editText: EditText // до метода onCreate()

imageButton.setOnClickListener {
    if (editText.text.isEmpty()) {
        helloTextView.text = "Hello Kitty!";
    } else {
        helloTextView.text = "Привет, " + editText.text
    }
}

Мы внесли небольшую проверку. Если в текстовом поле пустой текст, мы по-прежнему выводим надпись «Hello Kitty!». Если пользователь введёт имя своего кота, то приложение поздоровается с ним. Какая умная и вежливая программа у нас получилась.

Здороваемся с вашим котом (Java)

Переходим в класс MainActivity и добавляем новую переменную рядом с переменной mHelloTextView:


private EditText mNameEditText;

Свяжем созданную переменную с компонентом в методе onCreate():


mNameEditText = findViewById(R.id.editText);

Поменяем код для щелчка кнопки.


public void onClick(View view) {
    if (mNameEditText.getText().length() == 0) {
        mHelloTextView.setText("Hello Kitty!");
    } else {
        mHelloTextView.setText("Привет, " + mNameEditText.getText());
    }
}

Мы внесли небольшую проверку. Если в текстовом поле пустой текст, то длина текста составляет ноль символов, и мы по-прежнему выводим надпись «Hello Kitty!». Если пользователь введёт имя своего кота, то приложение поздоровается с ним.

Дополнительное чтение

Обсуждение статьи на форуме.

Исходник на Гитхабе (Java)

Реклама

Android – базирующаяся на ОС Linux операционная система с открытым исходным кодом, предназначенная для мобильных устройств – таких, как, например, смартфоны или планшетные компьютеры.

Это руководство предназначено для введения в основные понятия программирования под Android, так что после его изучения вы будете понимать некоторые базовые концепции программирования под эту ОС.

От вас же, в свою очередь, требуется только желание и базовое понимание программирования на языке Java. Не так много, правда? Что ж, начнём!

Среда разработки

Для разработки приложений под Android можно использовать любые из перечисленных операционных систем:

  • Microsoft Windows XP или более поздняя версия
  • Mac OS X 10.5.8 или более поздняя версия с чипом Intel
  • Linux, включающая GNU C Library 2.7 или более позднюю версию

При этом все необходимые инструменты для разработки Android-приложений находятся в свободном доступе и вы можете загрузить их из Интернета. Для того, чтобы начать создавать приложения под Android, вам понадобятся:

  • Java JDK5 или более поздняя версия
  • Android Studio

Структура приложений

Поздравляем, ваше оборудование готово к работе! Однако прежде чем приступить к вашему первому приложению, поговорим о том, что из себя представляет приложение на Android и из чего оно состоит.

Компоненты приложения являются своего рода «строительными блоками» для приложения Android. Эти компоненты связаны файлом-манифестом приложения AndroidManifest.xml, который описывает каждый компонент приложения и взаимодействие этих компонентов между собой.

Есть четыре базовых типа компонентов, которые могут быть использованы в приложении Android:

  • Операции (Activities) представляют собой элементы пользовательского интерфейса (одна операция – один экран) и отвечают за взаимодействие пользователя с экраном мобильного устройства;
  • Службы (Services) представляют собой длительные операции, работающие в фоновом режиме и не имеющие пользовательского интерфейса (например, передача данных), вместо этого они, как правило, запускаются иными элементами, уже имеющими пользовательский интерфейс, и взаимодействуют с ними;
  • Приемники широковещательных сообщений (Broadcast receivers) представляют собой компоненты, реагирующие на объявления самой ОС, передаваемые всей системе (как хороший пример – объявление о низком уровне заряда батареи устройства). Они также не имеют пользовательского интерфейса, однако могут передавать данные другим компонентам, где они демонстрируются пользователю в виде уведомлений;
  • Поставщики контента (Content providers) представляют собой компоненты, управляющие взаимодействием приложения с его базой данных — посредством поставщика контента другие компоненты приложения могут запрашивать или изменять данные.

Помимо четырех базовых типов, существуют дополнительные типы компонентов, используемые для выстраивания взаимодействий и связей как между базовыми типами компонентов, так и между компонентами и внешними элементами. К ним относятся:

  • Фрагменты (Fragments) – части пользовательского интерфейса в Операциях (см. выше);
  • Виды (Views) – элементы пользовательского интерфейса, отображаемые на экране, например, кнопки, списки и т. д.;
  • Макеты (Layouts) – определяют элементы пользовательского интерфейса, их свойства и расположение;
  • Намерения (Intents) – соединяют вместе различные компоненты приложения или связывают друг с другом работу разных приложений;
  • Ресурсы (Resources) – внешние элементы, такие, как строки, константы или изображения;
  • Манифест (Manifest) – конфигурационный файл приложения.

Теперь, когда вы получили базовое понимание о структуре приложений на Android, хотелось бы предложить вам самим попробовать создать свое первое приложение.

Первое приложение

Итак, давайте приступим к созданию простого Android-приложения, которое будет выводить на экран «Hello World!».

У вас к этому времени уже должен быть установлен Android Studio последней версии. Ниже будет приведена небольшая пошаговая инструкция:

  1. Откройте Android Studio.
  2. В открывшемся окне кликните на «Start a new Android Studio project», чтобы создать новый проект.
  3. В открывшемся окне в строку «Application name» введите название вашего будущего приложения. Нажмите Next.
  4. В следующем окне вам нужно выбрать тип устройств, для которых создается приложение – в нашем случае необходимо выбрать «Phone and Tablet» (смартфоны и планшетные компьютеры), а в выпадающем списке под названием «Minimum SDK» нужно выбрать версию Android, для которой создается приложение (обычно указывается самая ранняя версия, способная запустить приложение) – в нашем конкретном случае выберем версию Android 6.0. Если в вашей версии есть возможность выбрать язык программирования (выпадающее окно Language), выберите пункт “Java”. Остальные опции можно оставить без изменений. Нажмите Next.
  5. На следующем этапе выберите пункт Empty Activity – это будет означать, что экран нашего приложения не будет иметь никаких дополнительных элементов. Нажмите Next.

Теперь перед вами открылась привычная среда разработки. К сожалению или к счастью, но сейчас вам не нужно будет писать код – среда разработки уже сделала это за вас, создав файлы для приложения, выводящего «Hello world!» на экран, по умолчанию. Вместо этого хотелось бы обратить ваше внимание на несколько созданных файлов и папок, найти которые вы можете в колонке слева, отображающей все элементы проекта.

  • Файл MainActivity.java

В папке «Java» содержатся исходные файлы формата .java для вашего приложения. По умолчанию в ней находится исходный файл MainActivity.java, имеющий класс Операция – он запускается при нажатии пользователем на иконку приложения на устройстве. Этот файл содержит главный код приложения, и именно он преобразуется в файл .exe для запуска приложения на устройстве.

  • Файл AndroidManifest.xml

Это файл типа «Манифест», который описывает основные характеристики приложения и определяет каждый из его компонентов. Он является своего рода интерфейсом между ОС Android и вашим приложением – если компонент не упомянут в этом файле, он не будет отображен и в операционной системе.

  • Файл Build.gradle

Это автоматически генерируемый файл, содержащий определённые данные касательно приложения – такие, как, например, версия SDK.

Запустить приложение можно двумя способами: на реальном устройстве или на эмуляторе в самой среде разработки. В нашем случае мы рассмотрим более универсальный случай – запуск на эмуляторе, полностью имитирующем работу устройства с операционной системой Android.

Запуск приложения на эмуляторе

Попытайтесь запустить приложение кнопкой «Run» – в появившемся диалоговом окне выберите пункт «Create New Virtual Device». В последующих окнах нужно будет выбрать размер экрана и версию Android – помните, что она должна быть не ниже, чем указанная на этапе создания проекта. В случае, если данная версия Android будет отсутствовать на компьютере, Android Studio предложит ее загрузить. Остальные пункты можно оставить без изменений – на данный момент нет необходимости их изменять. После выбора всех настроек нажмите кнопку «Finish», и если вы увидели на своем мониторе экран телефона с названием вашего приложения сверху и с надписью «Hello world!» на экране, значит, вы можете себя поздравить – вы создали свое первое Android-приложение!

Теперь у вас есть базовое понимание и минимальный опыт в создании приложений на Android. Этого, разумеется, совершенно не хватит, чтобы заниматься разработкой, но это необходимый для дальнейшего развития фундамент – продолжайте изучать и практиковаться! Всего наилучшего!

Также можно научиться создавать приложения на Android и другие ОС после прохождения нашего шестимесячного курса «Профессия: Разработчик» 👉 Узнать подробности!

Разработка мобильных приложений, Разработка под Android, JAVA


Рекомендация: подборка платных и бесплатных курсов Smm — https://katalog-kursov.ru/

Два с половиной года назад я опубликовал статью Пишем, собираем и запускаем HelloWorld для Android в блокноте. Она стала пользоваться огромной популярностью и набрала около 80 000 просмотров. С появлением новых инструментов, таких как Jack ToolChain, возникла необходимость переиздания и обновления статьи.

Когда я начал изучать Android, захотелось полностью написать и скомпилировать Android-приложение вручную — без использования IDE. Однако эта задача оказалась непростой и заняла у меня довольно много времени. Но как оказалось — такой подход принёс большую пользу и прояснил многие тонкости, которые скрывают IDE.

Используя только блокнот, мы напишем совсем маленькое учебное Android-приложение. А затем скомпилируем его, соберём и запустим на устройстве — и всё через командную строку. Заинтересовало? Тогда прошу.

Вступление

Я был поражён, насколько сложным и запутанным является шаблонное приложение в Android Studio. Оно просто нагромождено ресурсами. И в меньшей степени — кодом и скриптами. Хотя всё что оно должно делать — это выводить на экран HelloWorld! Кроме того, в книгах и руководствах, которые я просмотрел, объясняется, как с помощью диалоговых окон создать IDEA-шный или эклипсовый HelloWorld — и от него уже идёт дальнейшее повествование. А что происходит «под капотом» — остаётся только гадать.

Мы создадим свой шаблонный проект, который идеально использовать для учебных целей. Там не будет ничего лишнего, только всё самое необходимое. А потом детально разберём, как его собрать и запустить на вашем Android-устройстве. В конце статьи будет ссылка на скачивание архива с итоговым проектом — если возникнут какие-то вопросы — можете свериться с ним.

Таким образом, вы будете на 100% знать и понимать состав вашего проекта и процесс его сборки. Хотя этот тестовый проект предназначен для обучения, при небольшой доработке его можно будет использовать как прочный фундамент для ваших реальных проектов.

Подготовка

Для начала нам нужно скачать и установить инструменты командной строки (command line tools). Ссылка на их скачивание находится внизу страницы, посвящённой Android Studio (https://developer.android.com/studio/index.html).

Android SDK 24 это как раз Android

N

(

N

ougat / 7). Принимаем условия, скачиваем установщик, запускаем его. Оставим всё по умолчанию. Он установится в директорию вида C:UserskcirayAppDataLocalAndroidandroid-sdk. Запомните этот путь, там будут находится наши основные инструменты.

Далее, запускаете SDK Manager (из папки android-sdk) и тоже устанавливаете набор по-умолчанию. Там есть всё необходимое, включая новый Jack-компилятор. Также вам понадобится JDK 8.

Главное требование перед прочтением этой статьи — кроме установленного софта вы должны уже уметь запускать на вашем девайсе тот Helloworld, который поставляется вместе с Eclipse или Android Studio. Т.е. у вас должен быть настроен драйвер usb, включена отладка по usb на вашем девайсе и т.д… Или же создан и настроен эмулятор. Это совсем элементарные вещи, и их рассмотрение выходит за рамки данной статьи — в сети достаточно информации. Кстати прочитать пару глав из книг тоже будет не лишним — хотя бы понимать, как устроен манифест, ресурсы, да и вообще основы языка Java. А в этой статье я опишу то, о чём книги молчат.

Написание проекта

Для начала, создайте некоторую папку, где будет ваш проект. Назовём её testapp. В ней создайте ещё 3 папки — bin, res и src.

Создайте в testapp пустой текстовый файл и измените его имя на AndroidManifest.xml.

Добавьте в него следующее:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testapp">
    <uses-sdk android:targetSdkVersion="24" />

    <application android:label="TestApp">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Тут всё просто. Мы намерены сделать приложение с именем TestApp, которое при старте запускает класс MainActivity. Осталось только написать этот небольшой класс — и приложение готово. Если нужно — отредактируйте в теге uses-sdk свойство android:targetSdkVersion — поставьте ту версию, которая у вас.

Далее — создадим простейший ресурс — строку Hello test app. Вообще-то мы могли обойтись и без ресурса, вставив эту строку прямо в Java код. Но некоторые шаги сборки работают с ресурсами, и чтобы увидеть интересные моменты — мы всё-таки поработаем с ними.

Давайте создадим в папке res папку values. Все ресурсы следует разбивать по папкам. Далее — в ней создадим пустой файл strings.xml, а в нём напишем:


<resources>
    <string name="hello">Hello test app!</string>
</resources>

Вот и все ресурсы, нам необходимые. Просто, не так ли? Далее создадим внутри src папку com, в ней папку example, потом ещё ниже по иерархии папку testapp — а там уже наш класс MainActivity.java. Добавим туда код:

package com.example.testapp;

import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button button = new Button(this);
        button.setText("Big button");

        button.setOnClickListener(v -> {
            new AlertDialog.Builder(MainActivity.this)
                    .setTitle("From lambda")
                    .setMessage(getString(R.string.hello))
                    .show();
        });
        
        setContentView(button);
    }
}

Это простейшая Activity, которая содержит одну кнопку на весь экран. При нажатии на эту кнопку вызывается диалоговое окно, которое показывает строку из ресурсов. Обратите внимание на лямбду (конструкция v -> {…}). Jack ToolChain позволяет нам использовать многие возможности Java 8 под андроидом. Более подробно можете почитать на developer.android.com и source.android.com.

Структура каталогов должна получится такая

¦   AndroidManifest.xml
+---bin
+---res
¦   L---values
¦           strings.xml
¦
L---src
    L---com
        L---example
            L---testapp
                    MainActivity.java

И это собственно всё, что нам было нужно для простейшего проекта. Для сравнения —

HelloWorld от Android Studio (2.1.3)

¦   .gitignore
¦   build.gradle
¦   gradle.properties
¦   gradlew
¦   gradlew.bat
¦   local.properties
¦   MyApplication2.iml
¦   settings.gradle
¦
+---.gradle
¦   L---2.14.1
¦       L---taskArtifacts
¦               cache.properties
¦               cache.properties.lock
¦               fileHashes.bin
¦               fileSnapshots.bin
¦               fileSnapshotsToTreeSnapshotsIndex.bin
¦               taskArtifacts.bin
¦
+---.idea
¦   ¦   .name
¦   ¦   compiler.xml
¦   ¦   encodings.xml
¦   ¦   gradle.xml
¦   ¦   misc.xml
¦   ¦   modules.xml
¦   ¦   runConfigurations.xml
¦   ¦   workspace.xml
¦   ¦
¦   +---copyright
¦   ¦       profiles_settings.xml
¦   ¦
¦   L---libraries
¦           animated_vector_drawable_24_2_0.xml
¦           appcompat_v7_24_2_0.xml
¦           hamcrest_core_1_3.xml
¦           junit_4_12.xml
¦           support_annotations_24_2_0.xml
¦           support_compat_24_2_0.xml
¦           support_core_ui_24_2_0.xml
¦           support_core_utils_24_2_0.xml
¦           support_fragment_24_2_0.xml
¦           support_media_compat_24_2_0.xml
¦           support_v4_24_2_0.xml
¦           support_vector_drawable_24_2_0.xml
¦
+---app
¦   ¦   .gitignore
¦   ¦   app.iml
¦   ¦   build.gradle
¦   ¦   proguard-rules.pro
¦   ¦
¦   +---libs
¦   L---src
¦       +---androidTest
¦       ¦   L---java
¦       ¦       L---com
¦       ¦           L---example
¦       ¦               L---kciray
¦       ¦                   L---myapplication
¦       ¦                           ApplicationTest.java
¦       ¦
¦       +---main
¦       ¦   ¦   AndroidManifest.xml
¦       ¦   ¦
¦       ¦   +---java
¦       ¦   ¦   L---com
¦       ¦   ¦       L---example
¦       ¦   ¦           L---kciray
¦       ¦   ¦               L---myapplication
¦       ¦   ¦                       MainActivity.java
¦       ¦   ¦
¦       ¦   L---res
¦       ¦       +---drawable
¦       ¦       +---layout
¦       ¦       ¦       activity_main.xml
¦       ¦       ¦
¦       ¦       +---mipmap-hdpi
¦       ¦       ¦       ic_launcher.png
¦       ¦       ¦
¦       ¦       +---mipmap-mdpi
¦       ¦       ¦       ic_launcher.png
¦       ¦       ¦
¦       ¦       +---mipmap-xhdpi
¦       ¦       ¦       ic_launcher.png
¦       ¦       ¦
¦       ¦       +---mipmap-xxhdpi
¦       ¦       ¦       ic_launcher.png
¦       ¦       ¦
¦       ¦       +---mipmap-xxxhdpi
¦       ¦       ¦       ic_launcher.png
¦       ¦       ¦
¦       ¦       +---values
¦       ¦       ¦       colors.xml
¦       ¦       ¦       dimens.xml
¦       ¦       ¦       strings.xml
¦       ¦       ¦       styles.xml
¦       ¦       ¦
¦       ¦       L---values-w820dp
¦       ¦               dimens.xml
¦       ¦
¦       L---test
¦           L---java
¦               L---com
¦                   L---example
¦                       L---kciray
¦                           L---myapplication
¦                                   ExampleUnitTest.java
¦
+---build
¦   L---generated
¦           mockable-android-24.jar
¦
L---gradle
    L---wrapper
            gradle-wrapper.jar
            gradle-wrapper.properties

Выглядит страшнее, чем 2 года назад

Собственно, автоматизация через gradle, работа с git и IDE — вещи очень важные, однако на этапе изучения Android мне бы очень хотелось от них абстрагироваться.

Сборка

Теперь же подходим к самому важному и сложному этапу. Мы будем много работать с командной строкой, поэтому рекомендую вам все команды, данные здесь, записывать в один файл и назвать его Compile.bat. В конце файла после команд можете добавить pause, чтобы был виден результат и ошибки — если таковые возникнут.

Подготовка путей

Первое, что мы сделаем для удобства и краткости — создадим специальные переменные, в которых будем хранить пути. Для начала — определим наши основные директории. Вам нужно заменить пути к JDK и Android SDK на те, которые у вас.

set JAVA_HOME=C:Program FilesJavajdk1.8.0_73
set ANDROID_HOME=C:UserskcirayAppDataLocalAndroidandroid-sdk
set DEV_HOME=%CD%

Далее — пути непосредственно к программам. Я рекомендую вам просмотреть каталоги ваших SDK и убедится в том, что всё на месте. Также подкорректировать версии, которые присутствуют в путях.

set JACK_JAR="%ANDROID_HOME%build-tools24.0.2jack.jar"
set AAPT_PATH="%ANDROID_HOME%build-tools24.0.2aapt.exe"
set ANDROID_JAR="%ANDROID_HOME%platformsandroid-24android.jar"
set ADB="%ANDROID_HOME%platform-toolsadb.exe"
set JAVAVM="%JAVA_HOME%binjava.exe"

Между прочим, в более старых версиях утилита aapt находилась в platform-tools — и я не исключаю что она иили другие могут слинять куда-нибудь ещё. Так что будьте внимательны. Если вы всё правильно сверите сейчас — то остальная часть статьи должна пройти гладко.

И ещё — в пару переменных забьём наши пакеты и классы. Если заходите их сменить — вам не придётся бегать по коду — все настройки вначале.

set PACKAGE_PATH=com/example/testapp
set PACKAGE=com.example.testapp
set MAIN_CLASS=MainActivity

Подготовка к компиляции

Для начала спрошу — а вы никогда не задумывались, как работает загадочный класс R? Собственно меня он сперва смутил из-за его сверхъестественных возможностей. Как на этапе компиляции можно через поля класса обращаться к XML-файлам в других каталогах? Я предположил, что тут орудует прекомпилятор — так оно и оказалось.

Собственно, есть специальная утилита AAPT — она проходится по каталогам ваших ресурсов и создаёт тот самый R.java. Оказывается, всё очень даже просто — это просто класс, в составе которого другие статические вложенные классы с целочисленными константами. И всё! Он выглядит примерно так:

R.java

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.example.testapp;

public final class R {
    public static final class attr {
    }
    public static final class string {
        public static final int hello=0x7f020000;
    }
}

Теперь давайте создадим его у вас. Для этого используем следующие команды:

call %AAPT_PATH% package -f -m -S %DEV_HOME%res -J %DEV_HOME%src -M %DEV_HOME%AndroidManifest.xml -I %ANDROID_JAR%

Давайте разберёмся, что к чему. AAPT — Android Asset Packaging Tool — буквально «упаковщик андроид-имущества». Его опции:

  • package — говорит, что нам нужно именно упаковать ресурсы (а не добавить или удалить)
  • -f — перезапись существующего R.java, если таковой имеется
  • -m — разместить R.java в надлежащих пакетах, а не в корне указанного в -J пути
  • -S — после этой опции мы указываем каталог с ресурсами
  • -J — после этой опции мы указываем куда сохранить получившийся R.java
  • -I — после этой опции мы указываем путь к подключаемой библиотеке — включаем android.jar

После его выполнения в каталоге src должен появится тот самый файл R.java. Проверьте.

Теперь в нашем проекте нет никакой магии и он полностью синтаксически корректен в рамках Java. А теперь самое интересное. Помните, как классические Java-программы компилируются через javac? Раньше он также входил в последовательность сборки Android-приложений. Мы брали наши исходники (*.java), получали из них байт-код JVM (*.class) и уже потом из него получали байт-код для Dalvic (*.dex). С появлением Jack ToolChain мы сократили нашу последовательность сборки на один шаг. Из исходников (*.java) мы сразу же получаем байт-код для Dalvic (*.dex).

Где же взять Джека? Он находится в папке build-tools в виде jack.jar и запускается как обычный Java-архив.

%JAVAVM% -jar %JACK_JAR% --output-dex "%DEV_HOME%bin" -cp %ANDROID_JAR% -D jack.java.source.version=1.8 "%DEV_HOME%srccomexampletestappR.java" "%DEV_HOME%srccomexampletestappMainActivity.java" 

Аргументы следующие:

  • -jar — Стандартная опция для JVM, указывающая на то, что нужно запустить Java-архив. Не имеет никакого отношения к Джеку
  • —output-dex — Папка, в которую нужно поместить итоговый dex-файл. Пускай он будет в bin
  • -D jack.java.source.version=1.8 — «D» указывает на то, что мы задаём свойство. jack.java.source.version позволяет нам указать, что мы используем Java 8. Без неё лямбды не будут работать и будут ошибки. Полный список свойств можете посмотреть по команде %JAVAVM% -jar %JACK_JAR% —help-properties
  • [Список из *.java — файлов] — Ваши исходники. У нас всего 2 файла — R.java и MainActivity.java

Полный список опций для Джека можете посмотреть по команде %JAVAVM% -jar %JACK_JAR% —help

Убедитесь в том, что в папке bin находится наш classes.dex. Теперь осталось только упаковать его вместе с ресурсами в APK-файл. Сделаем это:

call %AAPT_PATH% package -f -M %DEV_HOME%/AndroidManifest.xml -S %DEV_HOME%/res -I %ANDROID_JAR% -F %DEV_HOME%/bin/AndroidTest.unsigned.apk %DEV_HOME%/bin

Здесь опции аналогичны тем, которые мы использовали при создании R.java:

  • package — говорит, что нам нужно именно упаковать ресурсы (а не добавить или удалить)
  • -f — перезапись существующего AndroidTest.unsigned.apk, если таковой имеется
  • -M — после этой опции мы указываем путь к файлу манифеста
  • -S — после этой опции мы указываем каталог с ресурсами
  • -I — после этой опции мы указываем путь к подключаемой библиотеке — включаем android.jar
  • -F — после этой опции мы указываем куда сохранить получившийся AndroidTest.unsigned.apk
  • последний аргумент — путь к папке с dex — файлами

В папке bin теперь должен появится AndroidTest.unsigned.apk. И мы назвали его не просто так! У него нет цифровой подписи. Андроид запрещает устанавливать и запускать приложения без подписи. Но создать её не так-то трудно, как может показаться на первый взгляд

call %JAVA_HOME%/bin/keytool -genkey -validity 10000 -dname "CN=AndroidDebug, O=Android, C=US" -keystore %DEV_HOME%/AndroidTest.keystore -storepass android -keypass android -alias androiddebugkey -keyalg RSA -v -keysize 2048
call %JAVA_HOME%/bin/jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore %DEV_HOME%/AndroidTest.keystore -storepass android -keypass android -signedjar %DEV_HOME%/bin/AndroidTest.signed.apk %DEV_HOME%/bin/AndroidTest.unsigned.apk androiddebugkey

Собственно, эти строчки запускают 2 Java-утилиты, которые не имеют никакого отношения к Android SDK — но они необходимы. Первая создаёт файл AndroidTest.keystore (проверьте его наличие), а вторая — этот файл соединяет с AndroidTest.unsigned.apk. Получается файл AndroidTest.signed.apk. Вот такой дикий крафт файлов. Но однажды создав bat-скрипт запускайте его — и он будет делать всё это в автоматическом режиме.

Я думаю, не стоит тратить время на детальный разбор опций этих утилит в пределах данной статьи. Просто нужно уловить суть — они берут AndroidTest.unsigned.apk, подписывают его файлом AndroidTest.keystore и сохраняют в AndroidTest.signed.apk. Если есть желание, можете почитать в документации.

У вас, скорее всего, будет предупреждение «Warning: No -tsa or -tsacert is provided and this jar…«, но не обращайте внимание.

Запуск

Теперь, когда мы наконец собрали наш apk-файл — можем его запустить. Подключите по usb ваше устройство, или же запустите эмулятор. А затем выполните

call %ADB% uninstall %PACKAGE%
call %ADB% install %DEV_HOME%/bin/AndroidTest.signed.apk
call %ADB% shell am start %PACKAGE%/%PACKAGE%.%MAIN_CLASS%

Собственно, первая строчка удаляет программку, если она уже там есть. Для повторных запусков пригодится. Вторая — устанавливает APK на ваш девайс или эмулятор. Третья же — запускает. Давайте более подробно разберём её аргументы:

  • shell — мы хотим выполнить некоторые команды на нашем девайсе
  • am — используем для выполнения команд activity manager
  • start — мы хотим запустить некоторое Activity
  • имя пакета и полное имя класса (включая пакет), которые мы стартуем

Внимание — во время установки на устройстве может появится диалоговое окно с подтверждением. Если вовремя его не одобрить, то установка произойдёт с ошибкой [INSTALL_FAILED_USER_RESTRICTED]. Также у вас может возникнуть вопрос — зачем делать uninstall/install вместо install -r. Я сделал так для чистоты эксперимента. Скрипт полностью удаляет все продукты своей деятельности и создаёт их с нуля при каждом запуске. Даже ключи. Вы можете использовать install -r, но тогда следует убрать код, который отвечает за пересоздание ключей. Иначе вы столкнётесь с ошибкой [INSTALL_FAILED_UPDATE_INCOMPATIBLE].

Если всё прошло удачно, вы увидите что-то вроде этого:

Заключение

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

¦   AndroidManifest.xml
¦   AndroidTest.keystore
¦   Clear.bat
¦   Compile.bat
¦
+---bin
¦       AndroidTest.signed.apk
¦       AndroidTest.unsigned.apk
¦       classes.dex
¦
+---res
¦   L---values
¦           strings.xml
¦
L---src
    L---com
        L---example
            L---testapp
                    MainActivity.java
                    R.java

Теперь вы можете наглядно увидеть и понять, как происходит сборка андроид-приложения на более низком уровне. Когда будете использовать IDE — если сборка вдруг пойдёт не так (а такое часто бывает) — сможете вырулить ситуацию как надо. Также обратите внимание на то, что итоговый apk-файл занимает всего около 4 килобайт.

Выкладываю архив проекта. Обратите внимание, что я добавил туда ещё один маленький скрипт — Clear.bat. Он удаляет все созданные при сборке файлы. И поставил его запуск на начало Compile.bat. Также добавил комментарии с помощью Rem — по шагам.

Таким образом, скрипт производит полную очистку и пересборку проекта, включая подпись, а также удаление его на устройстве, установку и запуск.

Мои параметры

ПК

ОC: Windows 10 Pro x64
JDK: 1.8.0_73
Android SDK: 24

Мобильное устройство

Модель: Meizu MX4
Android: 5.1
ОС: Flyme 5.6.8.9 beta

Последнее обновление: 13.01.2023

  1. Глава 1. Начало работы с Android

    1. Введение. Установка Android Studio и Android SDK

    2. Первый проект в Android Studio

    3. Создание графического интерфейса

  2. Глава 2. Основы создания интерфейса

    1. Создание интерфейса в коде java

    2. Определение интерфейса в файле XML. Файлы layout

    3. Определение размеров

    4. Ширина и высота элементов

    5. Внутренние и внешние отступы

    6. ConstraintLayout

    7. Размеры элементов в ConstraintLayout

    8. Цепочки элементов в ConstraintLayout

    9. Программное создание ConstraintLayout и позиционионирование

    10. LinearLayout

    11. RelativeLayout

    12. TableLayout

    13. FrameLayout

    14. GridLayout

    15. ScrollView

    16. Gravity и позиционирование внутри элемента

    17. Вложенные layout

  3. Глава 3. Основные элементы управления

    1. TextView

    2. EditText

    3. Button

    4. Приложение Калькулятор

    5. Всплывающие окна. Toast

    6. Snackbar

    7. Checkbox

    8. ToggleButton

    9. RadioButton

    10. DatePicker

    11. TimePicker

    12. Ползунок SeekBar

  4. Глава 4. Ресурсы

    1. Работа с ресурсами

    2. Ресурсы строк

    3. Ресурсы dimension

    4. Ресурсы Color и установка цвета

  5. Глава 5. Activity

    1. Activity и жизненный цикл приложения

    2. Файл манифеста AndroidManifest.xml

    3. Введение в Intent. Запуск Activity

    4. Передача данных между Activity. Сериализация

    5. Parcelable

    6. Получение результата из Activity

    7. Взаимодействие между Activity

  6. Глава 6. Работа с изображениями

    1. Ресурсы изображений

    2. ImageView

    3. Изображения из папки assets

  7. Глава 7. Адаптеры и списки

    1. ListView и ArrayAdapter

    2. Ресурс string-array и ListView

    3. Выбор элемента в ListView

    4. Добавление и удаление в ArrayAdapter и ListView

    5. Расширение списков и создание адаптера

    6. Оптимизация адаптера и View Holder

    7. Сложный список с кнопками

    8. Выпадающий список Spinner

    9. Виджет автодополнения AutoCompleteTextView

    10. GridView

    11. RecyclerView

    12. Обработка выбора элемента в RecyclerView

  8. Глава 8. Стили и темы

    1. Стили

    2. Темы

  9. Глава 9. Меню

    1. Создание меню

    2. Группы в меню и подменю

  10. Глава 10. Фрагменты

    1. Введение во фрагменты

    2. Жизненный цикл фрагментов

    3. Взаимодействие между фрагментами

    4. Фрагменты в альбомном и портретном режиме

  11. Глава 11. Многопоточность

    1. Создание потоков и визуальный интерфейс

    2. Потоки, фрагменты и ViewModel

    3. Класс AsyncTask

    4. AsyncTask и фрагменты

  12. Глава 12. Работа с сетью. WebView

    1. WebView

    2. Загрузка данных и класс HttpURLConnection

  13. Глава 13. Работа с мультимедиа

    1. Работа с видео

    2. Воспроизведение аудио

  14. Глава 14. Настройки и состояние приложения

    1. Сохранение состояния приложения

    2. Создание и получение настроек SharedPreferences

    3. PreferenceFragmentCompat

  15. Глава 15. Работа с файловой системой

    1. Чтение и сохранение файлов

    2. Размещение файлов во внешнем хранилище

  16. Глава 16. Работа с базами данных SQLite

    1. Подключение к базе данных SQLite

    2. SQLiteOpenHelper и SimpleCursorAdapter, получение данных из SQLite

    3. Добавление, удаление и обновление данных в SQLite

    4. Использование существующей БД SQLite

    5. Динамический поиск по базе данных SQLite

    6. Модель, репозиторий и работа с базой данных

  17. Глава 17. Перелистывание страниц и ViewPager2

    1. ViewPager2 и разделение приложения на страницы

    2. Заголовки страниц и TabLayout

  18. Глава 18. Сервисы

    1. Введение в сервисы Android

  19. Глава 19. Диалоговые окна

    1. DatePickerDialog и TimePickerDialog

    2. DialogFragment и создание своих диалоговых окон

    3. Передача данных в диалоговое окно

    4. Взаимодействие диалогового окна с Activity

  20. Глава 20. Анимация

    1. Cell-анимация

    2. Tween-анимация

  21. Глава 21. Провайдеры контента

    1. Работа с контактами

    2. Добавление контактов

    3. Создание провайдера контента. Часть 1

    4. Создание провайдера контента. Часть 2

    5. Создание провайдера контента. Часть 3

    6. Асинхронная загрузка данных

  22. Глава 22. JSON

    1. Работа с json

  23. Глава 23. Работа с XML

    1. Ресурсы XML и их парсинг

    2. Получение xml по сети

  • Глава 1. Начало работы с Android
    • Введение. Установка Android Studio и Android SDK
    • Первый проект в Android Studio
    • Создание графического интерфейса
  • Глава 2. Основы создания интерфейса
    • Создание интерфейса в коде java
    • Определение интерфейса в файле XML. Файлы layout
    • Определение размеров
    • Ширина и высота элементов
    • Внутренние и внешние отступы
    • ConstraintLayout
    • Размеры элементов в ConstraintLayout
    • Цепочки элементов в ConstraintLayout
    • Программное создание ConstraintLayout и позиционионирование
    • LinearLayout
    • RelativeLayout
    • TableLayout
    • FrameLayout
    • GridLayout
    • ScrollView
    • Вложенные layout
    • Gravity и позиционирование внутри элемента
  • Глава 3. Основные элементы управления
    • TextView
    • EditText
    • Button
    • Приложение Калькулятор
    • Всплывающие окна. Toast
    • Snackbar
    • Checkbox
    • ToggleButton
    • RadioButton
    • DatePicker
    • TimePicker
    • Ползунок SeekBar
  • Глава 4. Ресурсы
    • Работа с ресурсами
    • Ресурсы строк
    • Ресурсы dimension
    • Ресурсы Color и установка цвета
  • Глава 5. Activity
    • Activity и жизненный цикл приложения
    • Файл манифеста AndroidManifest.xml
    • Введение в Intent. Запуск Activity
    • Передача данных между Activity. Сериализация
    • Parcelable
    • Получение результата из Activity
    • Взаимодействие между Activity
  • Глава 6. Работа с изображениями
    • Ресурсы изображений
    • ImageView
    • Изображения из папки assets
  • Глава 7. Адаптеры и списки
    • ListView и ArrayAdapter
    • Ресурс string-array и ListView
    • Выбор элемента в ListView
    • Добавление и удаление в ArrayAdapter и ListView
    • Расширение списков и создание адаптера
    • Оптимизация адаптера и View Holder
    • Сложный список с кнопками
    • Выпадающий список Spinner
    • Виджет автодополнения AutoCompleteTextView
    • GridView
    • RecyclerView
    • Обработка выбора элемента в RecyclerView
  • Глава 8. Стили и темы
    • Стили
    • Темы
  • Глава 9. Меню
    • Создание меню
    • Группы в меню и подменю
  • Глава 10. Фрагменты
    • Введение во фрагменты
    • Жизненный цикл фрагментов
    • Взаимодействие между фрагментами
    • Фрагменты в альбомном и портретном режиме
  • Глава 11. Многопоточность
    • Создание потоков и визуальный интерфейс
    • Потоки, фрагменты и ViewModel
    • Класс AsyncTask
    • AsyncTask и фрагменты
  • Глава 12. Работа с сетью. WebView
    • WebView
    • Загрузка данных и класс HttpURLConnection
  • Глава 13. Работа с мультимедиа
    • Работа с видео
    • Воспроизведение аудио
  • Глава 14. Настройки и состояние приложения
    • Сохранение состояния приложения
    • Создание и получение настроек SharedPreferences
    • PreferenceFragmentCompat
  • Глава 15. Работа с файловой системой
    • Чтение и сохранение файлов
    • Размещение файлов во внешнем хранилище
  • Глава 16. Работа с базами данных SQLite
    • Подключение к базе данных SQLite
    • SQLiteOpenHelper и SimpleCursorAdapter, получение данных из SQLite
    • Добавление, удаление и обновление данных в SQLite
    • Использование существующей БД SQLite
    • Динамический поиск по базе данных SQLite
    • Модель, репозиторий и работа с базой данных
  • Глава 17. Перелистывание страниц и ViewPager2
    • ViewPager2 и разделение приложения на страницы
    • Заголовки страниц и TabLayout
  • Глава 18. Сервисы
    • Введение в сервисы Android
  • Глава 19. Диалоговые окна
    • DatePickerDialog и TimePickerDialog
    • DialogFragment и создание своих диалоговых окон
    • Передача данных в диалоговое окно
    • Взаимодействие диалогового окна с Activity
  • Глава 20. Анимация
    • Cell-анимация
    • Tween-анимация
  • Глава 21. Провайдеры контента
    • Работа с контактами
    • Добавление контактов
    • Создание провайдера контента. Часть 1
    • Создание провайдера контента. Часть 2
    • Создание провайдера контента. Часть 3
    • Асинхронная загрузка данных
  • Глава 22. JSON
    • Работа с json
  • Глава 23. Работа с XML
    • Ресурсы XML и их парсинг
    • Получение xml по сети

Предисловие

Я не являюсь профессиональным разработчиком с огромным стажем в данной области (и это даже не хобби, а лишь нужда в разработке конкретного приложения), потому данная статья, полагаю, будет полезна новичкам, таким же, как и я был в начале разработке своего приложения. Возможно, кто-то найдет что-то полезное из данной статьи, какие-то кусочки окажутся частью ваших будущих разработок.

Я расскажу вам как написать простенькое ToDo-приложение на Android с тремя активностями (рабочими экранами).

Ссылка на проект на Github будет в конце данной статьи.

Установка и первичная настройка

Для разработки приложения я рассмотрю использование бесплатной IDE Intellij от разработчиков JetBrains — Android Studio, у меня версия 4.1.1.

После успешной установки IDE и запуска нажимаем на самую первую кнопку Start a new Android Studio Project. Далее появится мастер первичной подготовки проекта:

  • выберем подходящий шаблон, в моем случае это Empty Activity — он самый простой для новичков, так как при первом запуске будет всего 1 XML файл с версткой и один java файл MainActivity.

  • На следующем экране придумываем имя приложению; помните, что package name, после публикации на Google Play изменить нельзя (иначе Google Play посчитает это другим приложением (поправьте меня, если я ошибаюсь). Выбираем язык Java, так как по нему данная статья, а также, по нему больше информации в Интернете, чем по Kotlin.

  • Минимальный SDK выбираем под Android 5.0, так как данного API будет предостаточно для наших задач, заодно мы получим большой охват, в том числе старых устройств: планшеты, смартфоны, встроенные системы.

Скриншоты: установка и первичная настройка

Далее раскрываем вкладку Project и находим в каталоге Java><Ваш_Проект> файл MainActivity.java, в котором мы будем описывать все происходящее на главном экране.

Подготовка макетов (layouts) — внешний облик приложения

После рассмотрим файл MainActivity.xml, для этого нам нужно найти каталог res>layout>. Откроем MainActivity.xml для создания облика первой — главной страницы и перетягивая с панели Palette необходимые нам типы объектов.

Советую вам размещать объекты под ConstraintLayout, так объекты можно будет привязывать узелками к родительскому ConstraintLayout, который по умолчанию занимает всю пространство, а после привязки узелков, мы можем размещать объекты на нужном нам вертикальном и горизонтальном выравнивании.

Кстати, вместо px, тут используется другая величина — dp, позволяющая на разных экранах видеть одинаковый и желаемый результат.

Кстати, также, советую названия Текст полей переназначать в String значения, чтобы в дальнейшем было проще делать перевод интерфейса — подобный функционал уже встроен в Android Studio. Для этого нажимаем на объект, далее в меню Свойств объекта находим поле text и нажимаем на маленькую плашку-кнопку справа от текста. В открывшимся окне, нажимаем на плюсик слева сверху и создаем название String-переменной и ее значение по умолчанию:


Создание String-переменной

Создание String-переменной

Для перевода интерфейса, необходимо сохранить изменения и над нашим конструктором Layout нажать на кнопку Default (en-us) и выбрать Edit Translations, далее найти слева сверху значок глобуса и нажать на него для добавления нового языка:


Переводы для интерфейсов

Переводы для интерфейсов

Таким образом создадим дополнительные макеты (layouts) для оставшихся двух окон:

Скриншоты: еще два макета

Программируем на Java под Android

Еще раз повторюсь, что это Tutorial больше для новичков; дальше я буду комментировать практически каждую строчку. Ссылка на проект на Github будет в конце данной статьи.

Открываем файл Main_Activity.java, который будет отвечать за логику наших переключателей и главного экрана в целом, а она такова:

  • В самом верху должен отображаться пользовательский заголовок, если он настроен.

  • На переключателях должен отображаться тот текст, который пользователь настраивает из окна с макетом Activity_Settings.xml

  • Количество переключателей должно соответствовать заданному числу из окна макета Activity_Advanced.xml

  • После выхода из приложения и повторного запуска все переключатели должны оставаться в том же положении, в котором пользователь их оставил

  • Сброс переключателей возможен только, если переключатель Уверен/-а? включен.

  • А также, должны работать оставшиеся кнопки меню.

    Пишем следующее:

Код под спойлером: 156 строчек

package com.bb.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    //Создаем 9 переключателей с помощью массива
    SwitchCompat[] switcharray = new SwitchCompat[9];
    Boolean Reset; //Булев для выключения переключателя
    Button NextButton;
    public int[] list_of_switches = {
            R.id.switch_compat1,
            R.id.switch_compat2,
            R.id.switch_compat3,
            R.id.switch_compat4,
            R.id.switch_compat5,
            R.id.switch_compat6,
            R.id.switch_compat7,  
            R.id.switch_compat8, 
            R.id.switch_compat10, //переключатель "Вы уверены?" //8
    };
    //Нажатие кнопки Сброс
    public void ResetButtonClick (View view) throws IllegalAccessException {
        Reset =false;
        if (switcharray[8].isChecked()) { //Если переключатель "Вы уверены?" нажат, то разрешаем переключить в false остальные переключатели
            SharedPreferences.Editor editor = getSharedPreferences("save"
                    ,MODE_PRIVATE).edit();
            //Сохраняем в Intent значения всех переключателей в False
            for (int k=0; k<10; k++) {
                editor.putBoolean("value"+k, false);
            }
            editor.apply();
            //Устанавливаем все переключатели в значение False
            for (int i=0;i<9;i++){
                switcharray[i].setChecked(false);
            }
            //Reset background color of checked SwitchCompats
            for (int i = 0; i < 9; i++) {
                findViewById(list_of_switches[i]).setBackgroundColor(Color.TRANSPARENT);
            }
        }
    }

    //Создание формы / открытие приложения
    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Назначаем полям значения по умолчанию и сохраняем их в Intent
        String[] tsfield = new String[8];
        SharedPreferences prefs = getSharedPreferences("MY_DATA", MODE_PRIVATE);
        tsfield[0] = prefs.getString("KEY_F0", "Выключил газ");
        tsfield[1] = prefs.getString("KEY_F1", "Выключил воду");
        tsfield[2] = prefs.getString("KEY_F2", "Покормил кошек");
        tsfield[3] = prefs.getString("KEY_F3", "Закрыл окна");
        tsfield[4] = prefs.getString("KEY_F4", "Выключил Интернет");
        tsfield[5] = prefs.getString("KEY_F5", "Закрыл дверь");
        tsfield[6] = prefs.getString("KEY_F6", "Выключил везде свет");
        tsfield[7] = prefs.getString("KEY_F7", "Вынес мусор");
        //Получаем настройки текста заголовка
        String hellotext = prefs.getString("hellotitletext", "");
        switcharray[6] = findViewById(list_of_switches[6]);
        switcharray[7] = findViewById(list_of_switches[7]);
        //Получаем настройки количества полей
        String sixfields = prefs.getString("sixfields", "true");
        String sevenfields = prefs.getString("sevenfields", "false");
        String eightfields = prefs.getString("eightfields", "false");

        if (sixfields.equals("true")){
            switcharray[6].setVisibility(View.GONE);
            switcharray[7].setVisibility(View.GONE);
        }
        else if (sevenfields.equals("true")) {
            switcharray[6].setVisibility(View.VISIBLE);
            switcharray[7].setVisibility(View.GONE);
        }
        else if (eightfields.equals("true")) {
            switcharray[6].setVisibility(View.VISIBLE);
            switcharray[7].setVisibility(View.VISIBLE);
        }
        //Создаем массив из TextView
        TextView[] textarr = new TextView[8];
        //Каждому переключателю назначаем текст из итерации поля tsfield
        for (int i=0; i<8;i++){
            textarr[i] = (TextView) findViewById(list_of_switches[i]);
            textarr[i].setText(tsfield[i]);
        }
        //Назначаем текст заголовка
            TextView textView5 = (TextView) findViewById(R.id.textView5);
            textView5.setText(hellotext);
        //Отображать заголовок, если соотв. поле заполнено
        if(!hellotext.matches(""))
        {
            textView5.setVisibility(View.VISIBLE);
        }

        //Создаем связь каждого элемента переключателя по id из XML с соответствующей переменной типа SwitchCompat
        for (int i=0;i<9;i++) {
            switcharray[i] = findViewById(list_of_switches[i]);
        }
        //Создаем связь кнопки по id bt_next из xml переменной NextButton
        NextButton = findViewById(R.id.bt_next);

        //Используем SharedPreferences = "save"
        SharedPreferences sharedPreferences = getSharedPreferences("save"
                , MODE_PRIVATE);
        //При первом запуске - все переключатели в False
        for (int k=0; k<9; k++) {
        switcharray[k].setChecked(sharedPreferences.getBoolean("value"+k, false));
        }
        //При переключении переключателей сохраняем данные, а также, проверяем их при повторном запуске
        for (int k=0; k<9; k++) {
            final int finalK = k;
            switcharray[k].setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (switcharray[finalK].isChecked()) {
                        //когда переключатель включен
                        Reset = true; //
                        switcharray[finalK].setBackgroundColor(Color.parseColor("#c8a2c6"));
                        SharedPreferences.Editor editor = getSharedPreferences("save"
                                , MODE_PRIVATE).edit();
                        editor.putBoolean("value" + finalK, true);
                        editor.apply();
                        switcharray[finalK].setChecked(true);
                    } else {
                        //когда переключатель выключен
                        SharedPreferences.Editor editor = getSharedPreferences("save"
                                , MODE_PRIVATE).edit();
                        editor.putBoolean("value" + finalK, false);
                        Reset = false;
                        switcharray[finalK].setBackgroundColor(Color.TRANSPARENT);
                        editor.apply();
                        switcharray[finalK].setChecked(false);
                    }
                }
            });
        }
        //Кнопка открытия страницы настроек
        NextButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Go to next activity
                Intent intent2 = new Intent(MainActivity.this, Activity_settings.class);
                startActivity(intent2);
            }
        });
    }
}

Следующим этапом будет написание кода для корректной работы макета Activity_Settings.XML, а логика его такова:

  • Введенные пользователь записи сохраняются даже после перезапуска приложения

  • Количество полей соответствуют числу, заданному в настройках из макета Activity_Advanced.xml

  • А также, должны работать оставшиеся кнопки меню.

Код по спойлером: 124 строчки

package com.bb.myapplication;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class Activity_settings extends AppCompatActivity {
    //Initialize Variable
    Button btBack;
    Button fcSubmit;
    Button btAdvanced;
    //Ассоциируем поля ввода с переменными с помощью массива
    EditText[] InputFields = new EditText[8];
    //Назначаем полям значения по умолчанию и сохраняем их в Intent
    String[] tsfield = new String[8];
    //Создаем массив элементов из XML по id
    public int[] list_of_fields = {
            R.id.inputField0,
            R.id.inputField1,
            R.id.inputField2,
            R.id.inputField3,
            R.id.inputField4,
            R.id.inputField5,
            R.id.inputField6,
            R.id.inputField7,
    };

    private SharedPreferences prefs;

    //Создание формы / открытие приложения
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        //Назначаем полям значения по умолчанию и сохраняем их в Intent
        prefs = getSharedPreferences("MY_DATA", MODE_PRIVATE);
        //Получаем данные по количеству используемых полей
        String sixfields = prefs.getString("sixfields", "true");
        String sevenfields = prefs.getString("sevenfields", "false");
        String eightfields = prefs.getString("eightfields", "false");
        EditText inputField71var = (EditText) findViewById(list_of_fields[6]);
        EditText inputField81var = (EditText) findViewById(list_of_fields[7]);
        if (sixfields.equals("true")){
            inputField71var.setVisibility(View.INVISIBLE);
            inputField81var.setVisibility(View.INVISIBLE);
        }
        else if (sevenfields.equals("true")) {
            inputField71var.setVisibility(View.VISIBLE);
            inputField81var.setVisibility(View.INVISIBLE);
        }
        else if (eightfields.equals("true")) {
            inputField71var.setVisibility(View.VISIBLE);
            inputField81var.setVisibility(View.VISIBLE);
        }
        tsfield[0] = prefs.getString("KEY_F0", "Выключил газ");
        tsfield[1] = prefs.getString("KEY_F1", "Выключил воду");
        tsfield[2] = prefs.getString("KEY_F2", "Покормил кошек");
        tsfield[3] = prefs.getString("KEY_F3", "Закрыл окна");
        tsfield[4] = prefs.getString("KEY_F4", "Выключил Интернет");
        tsfield[5] = prefs.getString("KEY_F5", "Закрыл дверь");
        tsfield[6] = prefs.getString("KEY_F6", "Выключил везде свет");
        tsfield[7] = prefs.getString("KEY_F7", "Вынес мусор");
        //Назначаем полям ввода текст из SharedPreferences
        for (int i=0; i<8; i++) {
            InputFields[i] = (EditText) findViewById(list_of_fields[i]);
            InputFields[i].setText(tsfield[i]);
        }

        //Создаем переменные для кнопок
        btBack = findViewById(R.id.bt_back);
        fcSubmit = findViewById(R.id.submit_fc);
        btAdvanced = findViewById(R.id.btAdvanced);

        //Кнопка Назад
        btBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Go back
                Intent intent = new Intent (
                        Activity_settings.this,MainActivity.class
                );
                startActivity(intent);
            }
        });
        //Кнопка Расширенные настройки/Дополнительно
        btAdvanced.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Open Advanced Settings
                Intent intent = new Intent (
                        Activity_settings.this,Activity_advanced.class
                );
                startActivity(intent);
            }
        });
    }
    //Ссылка-значок на внешний ресурс - ссылка на мой телеграм
    public void tglink(View view){
        Intent myWebLink = new Intent(android.content.Intent.ACTION_VIEW);
        myWebLink.setData(Uri.parse("https://t.me/EndlessNights"));
            startActivity(myWebLink);
    }

    //Кнопка Сохранить данные
    public void SaveData(View view)
    {
        for (int i=0; i<8;i++) {
            tsfield[i] = InputFields[i].getText().toString();

        SharedPreferences.Editor editor = prefs.edit();
        editor.putString("KEY_F"+i, tsfield[i]);
            editor.apply();
        }
        // Открываем главную страницу
        startActivity(new Intent(getApplicationContext(), MainActivity.class));
    }
}

И наконец опишем логику работы последнего окна в приложении — с Дополнительными настройками:

  • Количество полей для отображения — в данном случае выбор с помощью радиокнопок — 6, 7 или 8 полей.

  • Текстовый заголовок, который пользователь может ввести и который будет отображаться на главной странице/активности.

  • Также, текстовый заголовок и выбранное количество полей с помощью радиокнопок должны сохранять свое состояние даже после перезапуска приложения.

  • И наконец должны работать оставшиеся кнопки меню.

Код под спойлером: 134 строчки

package com.bb.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Switch;
import android.widget.TextView;

public class Activity_advanced extends AppCompatActivity {

    Button btBack;
    //Назначаем радиокнопкам значения по умолчанию
    Boolean sixbool = true;
    Boolean sevenbool = false;
    Boolean eightbool = false;
    private SharedPreferences prefsadv;
    //Поле ввода текста для заголовка
    private EditText hellotitletext;
    RadioGroup rdGroup;
    //Переменные для радиокнопок
    public RadioButton r1, r2, r3;
    //Переменные для передачи состояния из boolean в sharedPrefs
    String sixdata;
    String sevendata;
    String eightdata;
    Switch bgswitchvar;
    private SharedPreferences prefs;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_advanced);
        bgswitchvar = findViewById(R.id.bgswitch);
        prefsadv = getSharedPreferences("MY_DATA", MODE_PRIVATE);
        rdGroup = (RadioGroup)findViewById(R.id.radioGroup);
        //Поле заголовка
        String hellotitletext1 = prefsadv.getString("hellotitletext","");
        hellotitletext = (EditText) findViewById(R.id.hellotitletext);
        hellotitletext.setText(hellotitletext1);
        //Ассоциируем переменные с полями по id из xml
        r1 = findViewById(R.id.sixfields);
        r2 = findViewById(R.id.sevenfields);
        r3 = findViewById(R.id.eightfields);
        //При нажатии на радиокнопку, вызываем функцию Update с заданным ключом
        r1.setChecked(Update("rbsix"));
        r2.setChecked(Update("rbseven"));
        r3.setChecked(Update("rbeight"));

        //При нажатии первой кнопки добавляем True с ключом rbsix в RBDATA
        r1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean r1_isChecked) {
                SaveIntoSharedPrefs("rbsix", r1_isChecked);
            }
        });
        //При нажатии второй кнопки добавляем True с ключом rbsix в RBDATA
        r2.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean r2_isChecked) {
                SaveIntoSharedPrefs("rbseven", r2_isChecked);
            }
        });
        //При нажатии третьей кнопки добавляем True с ключом rbsix в RBDATA
        r3.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean r3_isChecked) {
                SaveIntoSharedPrefs("rbeight", r3_isChecked);
            }
        });

        //Back button
        btBack = findViewById(R.id.btBackadvanced);
        btBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Go back
                Intent intent = new Intent (
                        Activity_advanced.this,Activity_settings.class
                );
                startActivity(intent);
            }
        });
    }
    //Сохранение данных в SharedPreferences - ожидая ключ и значение булева типа
    private void SaveIntoSharedPrefs(String key, boolean value){
        SharedPreferences sp = getSharedPreferences("RBDATA",MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putBoolean(key,value);
        editor.apply();
    }
    //Функция обновления значения в SharedPreferences
    private boolean Update(String key){
        SharedPreferences sp = getSharedPreferences("RBDATA",MODE_PRIVATE);
        return sp.getBoolean(key, false);
    }
    //Сохраняем данные по количеству полей
    public void SaveDataAdvanced(View view)
    {
        int checkedId = rdGroup.getCheckedRadioButtonId();
        if(checkedId == R.id.sixfields) {
            sixbool = true;
            sevenbool = Boolean.FALSE;
            eightbool = Boolean.FALSE;
        }
        else if (checkedId == R.id.sevenfields){
            sevenbool = true;
            sixbool = Boolean.FALSE;
            eightbool = Boolean.FALSE;
        }
        else if (checkedId == R.id.eightfields){
            eightbool = true;
            sevenbool = Boolean.FALSE;
            sixbool = Boolean.FALSE;
        }
        sixdata = String.valueOf(sixbool);
        sevendata = String.valueOf(sevenbool);
        eightdata = String.valueOf(eightbool);
        String hellofield = hellotitletext.getText().toString();
        SharedPreferences.Editor editor = prefsadv.edit();

        editor.putString("sixfields", sixdata);
        editor.putString("sevenfields", sevendata);
        editor.putString("eightfields", eightdata);
        editor.putString("hellotitletext", hellofield);
        editor.apply();
        startActivity(new Intent(getApplicationContext(), MainActivity.class));
    }
}

Подготовка приложения к публикации

Для отладки и проверки работоспособности приложения советую вам использовать настоящее устройство на Android, так вы сразу сможете отследить наличие, как минимум проблем с оформлением.

Здесь я приложил видео-инструкцию, как подключить свой смартфон к Android studio для отладки вашего приложения. На видео вы можете заметить первую версию данного приложения с очень плохим кодом:

Регистрация в Google Play

Для публикации приложения нам следует создать специальный аккаунт разработчика, вот прямая ссылка.

Далее вам предстоит оплатить пошлину в $35 за возможность публиковать приложения, это почти в 3 раза дешевле, чем в Steam, при том, что Steam просит $100 за каждое публикуемое приложение/игру, даже бесплатное, а с аккаунтом разработка, в Google Play вы можете публиковать несчётное множество приложений.

После оплаты и успешной авторизации в консоли разработчика, необходимо нажать на синюю кнопку «Создать приложение«, далее заполнить все необходимые поля:

После создания приложения в консоли разработчика Google Play, необходимо перейти в раздел Рабочая версия и нажать на кнопку Создать новый выпуск. Вам предложат получить электронную подпись для вашего приложения с расширением *.jks, с помощью которой вам предстоит подписать свое первое приложение, а также, все дальнейшие выпуски с обновлениями.

Возвращаемся в Android Studio и необходимо заполнить немного информации о нашем приложении, для этого нажимаем File>Project Structure и заполняем поля Version Code и Version Name — без них Google Play Google Play не допустит ваше приложение до публикации:

Наконец, переходим в следующий раздел: пункт меню Build>Generate Signed Bundle / APK

В открывшимся окне выбираем APK. В подразделе Key Store Path выбираем Create new, далее заполняем все поля (прямая ссылка на официальную инструкцию), далее данный ключ потребуется загрузить в консоль Google Play. Затем вернемся в Android Studio и после ввода всех необходимых данных, нажимаем Next

В следующем окне отмечаем все чекбоксы, выбираем release и нажимаем Finish — Android Studio скомпилирует подписанное приложение, которое можно опубликовать в Google Play.

Итог

После загрузки файла приложения APK потребуется заполнить множество форм и подготовить множество материалов: описание на разных языках (если необходимо), изображения на разных языках (надписи на изображениях я имею в виду), логотипы, иконки разных размеров, скриншоты со смартфона и планшета.

Наконец отправляем приложение в публикацию. Сотрудники Google Play будут проверять ваше приложение в течении 2 недель, судя по официальным данным. Данное приложение рассматривали в течении 5 суток. Также, стоит учесть, что каждое обновление, также, будут проверять, но на обновления уходит не более 2-3 суток.

Ссылка на GitHub, как обещано. Ссылка на приложение в Google Play.

Понравилась статья? Поделить с друзьями:
  • Как написать много отзывов на авито
  • Как написать много денег
  • Как написать много букв
  • Как написать мнение об уроке
  • Как написать мнение автора клише