Приложение "Светофор" Часть 1: Создаем экран

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

Для начало нам нужно создать ресурсы цветов которые будем использовать. Для этого в структуре проекта ( левая часть экрана где находятся все папки) открываем файл  “res/values/colors.xml”. Это означает что в папке “res” открываем папку “values” и в данной папке открываем файл “colors.xml”.

В файле “colors.xml” вставляем следующий код:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="grey">#8A8989</color>
<color name="green">#34FA07</color>
<color name="yellow">#FDD203</color>
<color name="red">#FF0000</color>
</resources>

Далее приступаем к созданию экрана. Открываем файл “res/layout/activity_main.xml” и пишем код как на видео уроке или удаляем все и заменяем на этот код (для ленивых):

<?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">

<LinearLayout
android:id="@+id/bulb_3"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="8dp"
android:background="@color/grey"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bulb_2">

</LinearLayout>

<LinearLayout
android:id="@+id/bulb_2"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="8dp"
android:background="@color/grey"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bulb_1">

</LinearLayout>

<LinearLayout
android:id="@+id/bulb_1"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="80dp"
android:background="@color/grey"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

</LinearLayout>

<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
android:onClick="onClickStart"
android:text="Start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bulb_3" />

</androidx.constraintlayout.widget.ConstraintLayout>
 

На старой версии Андроид Студио у вас скорее всего будут старые пакеты от андроид студио и данный код может выдать ошибку, для того чтобы перевести ваш проект на новые пакеты андроид “androidx” вам нужно сделать следующее:

Выбираем на панели вверху “Refactor” и в открывшемся меню выбираем “Migrate to AndroidX…“. Далее просто следуем инструкциям и готово. Если что не ясно пишите в комментариях!

Приложение "Светофор" Часть 2: Пишем алгоритм работы

Начнем писать алгоритм работы приложения. На данном уроке мы подготовим основные части кода. Самое первое нам нужно инициализировать наши 3 квадрата которые у нас в качестве лампочек и кнопку “Start“. Мы создадим переменные типа “LinearLayout” так как квадраты на экране которые мы будем использовать в качестве лампочек и есть “LinearLayout” и переменную для кнопки “Button“. У нас три лампочки, значит создадим 3 переменных (bulb_1, bulb_2, bulb_3) и для кнопки (b). Теперь после того как мы создали переменные нам нужно присвоить им наши реальные лампочки (квадраты) и кнопку , для того чтобы с помощью переменной мы могли изменять цвет квадрата и надпись на кнопке, так как переменная после присвоения ей реального значения будет представлять нашу лампочку (квадрат) в коде и кнопку. Для того чтобы получить элемент экрана в коде мы используем функцию которая есть у класса Activity “findViewById();“. В этой функции мы просто указываем id нашего элемента “findViewById(R.id.bulb_1);” и готово (данную функцию мы впишем в функции “onCreate”), установим цвета для лампочек по умолчанию с , что бы при открытии приложения первая лампочка была зеленая а две других желтые, используем функцию “setBackgroundColor(getResource.getColor(R.color.grey));“, данная функция выбирает цвет фона элемента на экране, мы используем ее для всех трех лампочек. Теперь у нас есть доступ для управления лампочками и кнопкой с помощью четырех переменных, но нам нужно чтобы цвет менялся автоматически через определенный промежуток времени. Как быть?? Мы создадим повторяющийся цикл который запустится при нажатии на кнопку “Start” и остановится при нажатии на кнопку “Stop“. Но есть одна проблема!! Весь код который мы писали до этого момента работает на основном потоке, на котором работает UI (User Interface)  а именно где пользователь взаимодействует с нашим приложением, если мы запустим цикл на основном потоке то пока цикл не закончится экран перестанет реагировать на нажатия пользователя, это означает что пользователь не сможет нажать на кнопку “Stop”  и цикл никогда не остановиться!! Вот дела!! Нет проблем, есть решение, а именно создать второстепенный поток который не будет влиять на основной. Для этого мы используем класс  Thread этот класс запускает новый поток и он не влияет на основной поток так что пользователь сможет взаимодействовать с нашим приложением при запущенном цикле. создаем новый поток в слушатели нажатий onClickStart(); и таким образом новый поток будет создан при нажатии на кнопку. Внутри нового потока пишем цикл while. Работа цикла зависит от логического значения boolean, если это true то цикл повторяется пока значение не станет false, тогда цикл завершиться. По этому создадим переменную типа “boolean start_stop = false;” по умолчанию присвоим значение false и передадим ее в наш цикл “while(start_stop){}“. При нажатии на кнопку “Start” создастся новый поток и внутри нового потока запустится цикл, так как по умолчанию мы присвоили значение нашей переменной “start_stop = false;” поток не запустится, мы это сделали для того что бы подготовить код к следующему шагу, в следующей части мы это доделаем. Когда цикл будет запущен если хотим остановить цикл то нам нужно просто установить нашей переменной значение false start_stop = false;“. Мы будем останавливать цикл при нажатии на кнопку “Stop” и в случае если пользователь не нажав кнопку “Stop” закроет приложение. То есть мы должны убедиться что если пользователь закрыл приложение цикл остановлен. Это легко можно сделать в функции “onDestroy” на прошлом уроке мы говорили о цикле жизни Activity и об функции “onDestroy“. Теперь нам осталось дописать код для переключения цветов и для остановки цикла при нажатии на кнопку “Stop“, это мы сделаем в следующей части, ниже находиться код MainActivity, копируем все в ваш файл MainActivity только оставляйте самую верхнюю строку с названием вашего пакета, например у меня это “package com.neco_desarrollo.trafficlight;” так как имя пакета у всех разное, все остальное можно скопировать.

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity {
LinearLayout b_1, b_2, b_3;
private boolean start_stop = false;
private Button b;
@Override
 protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b = findViewById(R.id.button1);
b_1 = findViewById(R.id.bulb_1);
b_2 = findViewById(R.id.bulb_2);
b_3 = findViewById(R.id.bulb_3);
b_1.setBackgroundColor(getResources().getColor(R.color.green));
b_2.setBackgroundColor(getResources().getColor(R.color.grey));
b_3.setBackgroundColor(getResources().getColor(R.color.grey)); } public void onClickStart(View view) { new Thread(new Runnable() { @Override public void run() { while (start_stop) { } } }).start(); } @Override protected void onDestroy() { super.onDestroy(); start_stop = false; } }

Приложение "Светофор" Часть 3 Дописываем алгоритм работы

Приступаем к 3-й заключительной части урока “Светофор“. Начнем с того что нам нужно будет сделать что бы при нажатии на кнопку “Start” цикл запускался и надпись на кнопки изменялась на “Stop“, таким образом нам не придется использовать вторую кнопку для остановки цикла. Сначала мы создадим текстовый ресурс “Start” и “Stop” в файле strings.xml , который находиться в “res/values/strings.xml“.

Он должен выглядеть так:

<resources>
    <string name="app_name">TrafficLight</string>
    <string name="start">Start</string>
    <string name="stop">Stop</string>
</resources>
В кавычках после слова name находиться идентификатор по которому мы будем находить наш текстовый ресурс, идентификатор может быть любое слово латинскими буквами, главное что бы не было пробелов,а в треугольных скобках сам ресурс">Start<", это может быть любой текст

Теперь дописываем алгоритм работы. Самое первое что нам нужно сделать это убедится что мы не создаем несколько потоков. Если мы запустим код написанный до этого момента то при каждом нажатии на кнопку будет создаваться новый поток и новый цикл. Чтобы этого избежать мы создадим условие “if” которое будет проверять цикл уже запущен или нет. Если цикл еще не запущен то мы создаем новый поток, меняем текст на кнопке на “Stop”  и запускаем цикл, а если цикл уже запущен то мы останавливаем цикл и меняем текст на кнопке на “Start“, и возвращаем первоначальный цвет лампочек. Мы сделаем это с помощью переменной “booleanstart_top. Изначально эта переменная равна “false“, мы проверяем если переменная start_stop равна “false” значит цикл еще не запущен, если же переменная равна “true” значит цикл запущен. Таким образом у нас получится следующий код, в слушатели на нажатие onClickStart():

public void onClickStart(View view)
    {
        if(!start_stop){
            start_stop = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
while (start_stop)
{
}
}
}).start();
}
else
 {
b.setText(getString(R.string.start));
start_stop = false;
b_1.setBackgroundColor(getResources().getColor(R.color.green));
b_2.setBackgroundColor(getResources().getColor(R.color.grey));
b_3.setBackgroundColor(getResources().getColor(R.color.grey));
} 
}

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

Далее нам нужно сделать так что бы цикл повторялся с частотой в 1 сек. Цикл по умолчанию повторяется с очень большой скоростью а нам это не нужно. Для замедления цикла мы используем функцию “sleep” у класса Thread. Таким образом: Thread.sleep(1000);  время “засыпания” потока пишется в миллисекундах 1 секунда = 1000 миллисекунд. Мы напишем данную линию кода внутри цикла, и таким образом цикл дойдя до этой строчки будет засыпать на 1 сек. Теперь у нас есть цикл который повторяется с частотой в 1 сек, мы можем начинать считать! Например мы можем считать до 9-ти, тогда каждые 3 сек мы будем изменять цвет лампочек. Например: У нас по умолчанию у светофора зеленый свет, счетчик считает до 3 и загорается желтый цвет, далее когда счетчик досчитает до 6-ти загорится красный свет, далее когда счетчик досчитает до 9-ти загорится снова зеленый свет, счетчик сбросится на “0” и процесс повторится пока мы его не остановим нажатием кнопки “Stop“. Для того чтобы считать от “0” до “9” мы создадим новую переменную  “int counter = 0;” добавим ее в наш цикл для счета. “counter++;“. Два плюса после переменной означают что при каждом цикле значение переменной будет увеличиваться на “1”, вот и счетчик готов! Теперь нам нужно поставить переключатель который будет следить за счетчиком и включать нужный цвет. Для этого мы используем переключатель “switch” мы передаем переменную “counter” в данный переключатель и он будет сравнивать значение данной переменной со значением “case” которое мы напишем. 

 

 

 import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {
LinearLayout b_1, b_2, b_3;
private boolean start_stop = false;
private int counter = 0;
private Button b;
@Override
 protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b_1 = findViewById(R.id.bulb_1);
b_2 = findViewById(R.id.bulb_2);
b_3 = findViewById(R.id.bulb_3);
b = findViewById(R.id.button1);
b_1.setBackgroundColor(getResources().getColor(R.color.green));
b_2.setBackgroundColor(getResources().getColor(R.color.grey));
b_3.setBackgroundColor(getResources().getColor(R.color.grey));
}

public void onClickStart(View view)
{
//Проверяем значение переменной start_stop
 if(!start_stop){
b.setText(getString(R.string.stop));
start_stop = true;
//Создаем новый поток
 new Thread(new Runnable() {
@Override
 public void run() {
//Запускаем цикл
while (start_stop)
{
//Счетчик циклов
 counter++;
//Переключатель цвета
 switch (counter)
{
case 3:
b_1.setBackgroundColor(getResources().getColor(R.color.grey));
b_2.setBackgroundColor(getResources().getColor(R.color.yellow));
b_3.setBackgroundColor(getResources().getColor(R.color.grey));
break;
case 6:
b_1.setBackgroundColor(getResources().getColor(R.color.grey));
b_2.setBackgroundColor(getResources().getColor(R.color.grey));
b_3.setBackgroundColor(getResources().getColor(R.color.red));
break;
case 9:
b_1.setBackgroundColor(getResources().getColor(R.color.green));
b_2.setBackgroundColor(getResources().getColor(R.color.grey));
b_3.setBackgroundColor(getResources().getColor(R.color.grey));
//Сбрасываем счетчик на "0"
 counter = 0;
break;
}
//try это исключение о котором мы еще поговорим, пока просто пишем как есть
 try {
//"Усыпляем" поток на 1 сек
 Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
else
 {
b.setText(getString(R.string.start));
start_stop = false;
b_1.setBackgroundColor(getResources().getColor(R.color.green));
b_2.setBackgroundColor(getResources().getColor(R.color.grey));
b_3.setBackgroundColor(getResources().getColor(R.color.grey));
counter = 0;

}
}

@Override
 protected void onDestroy() {
super.onDestroy();
//Останавливаем цикл
 start_stop = false;
}
}

Когда одно из значений совпадет данный “case” запустит наш код и включит нужный цвет выбрав цвет фона нашего квадрата. Вот и все, наш светофор готов!!