» Программирование Android

Первые шаги в Android-программировании

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

Итак, статья будет посвящена полному циклу создания Android-приложения, от начала и до конца. Писать мы будем простую игру «Крестики-Нолики» для одного экрана с использованием Activity. Но чтобы не перегружать вас терминами с самого начала, всё по порядку.

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

Для разработки нужны инструменты. А для разработки приложений под ОС Android их три, это: JDK (Java Development Kit), Android SDK and AVD Manager, а также IDE с поддержкой разработки для системы Android (либо Eclipse с плагином ADT, либо IntelliJ IDEA Community Edition, либо Netbeans с плагином nbandroid). Перечисленные утилиты очень желательно устанавливать именно в таком порядке. Из IDE использовать сразу все смысла нет никакого – нужно выбрать одну. Мы будем работать на примере IntelliJ IDEA Community Edition – данная среда разработки достаточно удобна и развита.

Итак, нужно запустить виртуальное устройство. Запускаем AVD Manager, устанавливаем дополнительные пакеты (это SDK разных версий) и приступаем к самому созданию виртуального Android-девайса, задав интересующие параметры.

Список виртуальных устройств:

Теперь приступим к созданию нового проекта: «File» -> «New Project»:

Чтобы проект был собран, скомпилирован и запущен на нашем виртуальном устройстве, нужно нажать F6.

Разберем структуру проекта. Её видно на последнем скриншоте. Разберем папки, которые нас интересуют применимо конкретно к данному проекту: gen, res, src.

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

Директория res нужна для хранения ресурсов приложения, например картинок, текстов, layouts (макетов интерфейса) и так далее.

Директория src – это основная «строительная площадка», где происходит большая часть работы. Именно здесь хранятся файлы с исходным кодом.

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

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

Метод setContentView (то же самое, что this.setContentView) устанавливаем для текущего экрана соответствующий .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.java в конструкторе 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)); // добавление строки в таблицу

        }

    }

Создаем объект, который реализует интерфейс 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;

    }

}

Естественно, побеждает в «Крестики-нолики» тот, кто выстраивает линию из крестиков или ноликов на всю длину поля по горизонтали, вертикали или диагонали. В первую очередь навязывается мысль написать отдельные методы для каждого случая. Для этого отлично подходит pattern Chain of Responsibility. Определим наш интерфейс:

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() реализуется в двух возможных вариантах (победа одного или другого игрока, или же ничья):

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());

                }

            }

        }

    }

Вот и всё, мы создали простенькую игру «Крестики-нолики». С её работой вы можете ознакомиться на следующем видео: