Как написать простое Android ToDo-приложение на Java

—

от автора

Предисловие

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

Я расскажу вам как написать простенькое 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) для оставшихся двух окон:

Скриншоты: еще два макета
Макет Activity_Settings.xml
Макет Activity_Settings.xml
 Макет Activity_Advanced.xml
Макет Activity_Advanced.xml

Программируем на 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.

ссылка на оригинал статьи https://habr.com/ru/post/547200/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *