Цель статьи
В данной статье будут рассмотрены проблемы Android разработки и разработки в целом. Все мы знаем, что разработка программы — это большой сложный труд на который уходит довольно много времени и сил, и порой для поиска решения той или иной проблемы приходится тратить куча времени, т.к некоторые решение из интернета не всегда работают.
В данной статье будут рассмотрены следующие вопросы:
- Кастомная клавиатура под Android
- Многопоточность
- Интеграция рекламы в программу
Кастомная клавиатура под Android
В моей программе, в моём калькуляторе мне надо было сделать свою клавиатуру, т.к вводить математические формулы с системной клавиатуры крайне не удобно. Пытаясь решить эту проблему, я облазил кучу форумов, опробывал несколько разных решений, но все они не дали должного результата.
Начнём:
- Создадим новый проект в android studio.
- Создадим пару классов
- Протестируем приложение
Сделаем простенькую клавиатуру из 9 символов с возможностью удалять эти символы.
Назовём наш проект KeyBoardTest
Выберем пустую активити и начнём
Готово мы создали наш новый проект. Теперь займёмся самым основным, а именно создадим layout файл в папке — res/layout и назовём его keyboard, тут у нас будет размещён внешний вид клавиатуры.
Нарисуем эту клавиатуру:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_gravity="bottom"
android:background="@color/colorBlue"
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/one"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/one"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/two"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/two"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/three"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/three"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/four"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/four"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/five"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/five"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/six"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/six"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/seven"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/seven"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/eight"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/eight"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/nine"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/nine"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/delete"
android:textColor="@color/colorWhite"
android:gravity="center"
android:textSize="30sp"
android:text="@string/delete"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
После того, как наша клавиатура нарисована, мы можем создать класс, который будет регистрировать все наши нажатия и писать то, что нам нужно. Обратите внимание на то, что у каждого textview есть id — это очень важно!
Назовём этот класс KeyBoardListener.
Пропишем конструктор нашего класса, он в качестве аргументов принимает View — поле в котором мы работаем, иными словами место расположения нашего editText, и так же он принимает сам editText, в котором мы будем печатать символы с нашей клавиатуры.
package keyboard.develop.keyboardtest;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
public class KeyBoardListener {
EditText editText;
private StringBuilder finalText = new StringBuilder();
KeyBoardListener(View view, final EditText editText) {
this.editText = editText;
TextView one = view.findViewById(R.id.one);
TextView two = view.findViewById(R.id.two);
TextView three = view.findViewById(R.id.three);
TextView four = view.findViewById(R.id.four);
final TextView five = view.findViewById(R.id.five);
TextView six = view.findViewById(R.id.six);
TextView seven = view.findViewById(R.id.seven);
TextView eight = view.findViewById(R.id.eight);
TextView nine = view.findViewById(R.id.nine);
TextView delete = view.findViewById(R.id.delete);
final LinearLayout layout = view.findViewById(R.id.keyBoard);
one.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "1");
setTextSelection();
}
});
two.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "2");
setTextSelection();
}
});
three.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "3");
setTextSelection();
}
});
four.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "4");
setTextSelection();
}
});
five.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "5");
setTextSelection();
}
});
six.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "6");
setTextSelection();
}
});
seven.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "7");
setTextSelection();
}
});
eight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "8");
setTextSelection();
}
});
nine.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
finalText.insert(selection, "9");
setTextSelection();
}
});
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int selection = editText.getSelectionEnd();
if (finalText.length() > 0) {
finalText = stringToBuilder(finalText.substring(0, selection - 1 == -1 ? 0 : selection - 1) + finalText.substring(selection));
editText.setText(finalText);
}
editText.setSelection(selection - 1 <= 0 ? 0 : selection - 1);
}
});
editText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
layout.setVisibility(View.VISIBLE);
}
});
}
private StringBuilder stringToBuilder(String s) { return new StringBuilder(s); }
private void setTextSelection() {
int selection = editText.getSelectionEnd();
editText.setText(finalText);
editText.setSelection(selection + 1);
}
}
Теперь подробно рассмотрим этот код. В сам конструктор мы передали editText и view для того, чтобы взять кнопки и назначить им выполнение ввода. Хочется отметить, что в методе кнопки delete используется «синтаксический сахар», иными словами — это сокращённая запись кода. Не всем известна эта конструкция, поэтому я решил что надо обратить на неё внимание. Особенно это может пригодится новичкам.
Работает эта конструкция таким образом
int p = (условие) ? вариант 1 : вариант 2;
int p = k == 2 ? 7 : 3;
// это можно записать и так
if (k == 2)
p = 7;
else
p = 3;
Но мы отошли от темы. Теперь после того, как наш конструктор готов, и мы можем откликаться на нажатие кнопок, мы можем использовать наш класс. Для начала нам надо вызвать этот класс в нашей основной активити
package keyboard.develop.keyboardtest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private LinearLayout layout;
private ExecutorThread executorThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); // Убираем системную клавиатуру, при этом оставляем курсор
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText editText = findViewById(R.id.edit);
layout = findViewById(R.id.keyBoard);
new KeyBoardListener(layout, editText);
executorThread = new ExecutorThread((TextView)findViewById(R.id.textView));
editText.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
if (!editable.toString().equals("")) {
executorThread.setK(Integer.parseInt(editText.getText().toString()));
executorThread.doWork();
}
}
});
}
@Override
public void onBackPressed() {
layout.setVisibility(View.GONE); // Клавиатура исчезает
}
}
Стоит обратить внимание на эту строку
getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
В этой строке мы отключаем системную клавиатуру, но при этом оставляем курсор, чтобы мы могли передвигаться между символами и вставлять цифры/буквы и т.д. Как раз именно поэтому мы в коде выше использовали класс StringBuilder и метод insert.
За счёт того, что весь этот метод вызывается в виде отдельного класса, мы можем добавить его куда-угодно и использовать в любых программах. Таким образом получается объектность кода.
Но я не показал вам, как сделать это в xml коде, как нам указать расположение этой клавиатуры, а всё очень просто
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="keyboard.develop.keyboardtest.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:textColor="@color/colorPrimary"
android:gravity="center"
android:textSize="25sp"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="Write your text here!"/>
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="50dp"/>
</LinearLayout>
<LinearLayout // Вот тут и располагается наша клавиатура
android:visibility="gone"
android:id="@+id/keyBoard"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_gravity="bottom">
<include layout="@layout/keyboard"/> // А точнее тут, в этой строчке
</LinearLayout>
</FrameLayout>
Теперь лёгкими манипуляциями мы можем использовать нашу клавиатур где-угодно, даже в фрагментах.
В нашем же приложении клавиатура выглядит так
Многопоточность
Многопотчность — понятно из название, что это много потоков. А много потоков — это значит выполнение нескольких операций одновременно. Многопоточность — это довольно проблемная тема в программировании. Что в C++, что в Java, что в других языках с многопоточностью всегда были проблемы. Благо почти во всех языках есть высокоуровневое решение этой проблемы. Но мы сейчас занимаемся разработкой под Android, поэтому про Anroid, а точнее про язык программирования Java мы будем говорить.
В ЯП Java есть такая вещь как Thread — она удобна при рисовании, при частой мгновенной перерисовки изображения, есть много статей на эту тему, в том числе и на Хабре, поэтому я не буду рассматривать этот вариант. Меня интересует так называемый — «засыпающий поток», поток который ждёт своего вызова для решение какой-либо поставленной задачи. Это тот случай, когда ты вызвал поток, он отработал и уснул, не тратя при этом ресурсов устройства во время ожидания новой задачи.
И название тому, что я описал выше — это класс из стандартного пакета java.util.concurrent ExecutorServise
Суть этого класса заключается в том, что он может повторно использовать один и тот же поток, не создавая новый. А теперь рассмотрим как он работает, не будем создавать новую программу, а продолжим работать в нашей.
Для этого создадим новый класс, который будет называться ExecutorThread. Поставим нам задачу, что нам надо прибавлять к цифре p единицу, пока это p не будет равно введённое нами числу в 4 степени. Всё делается для того, чтобы вычисление было более долгое. И чтобы весь интерфейс не зависал, мы всё вынесем это в отдельный поток.
package keyboard.develop.keyboardtest;
import android.widget.TextView;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorThread {
private ExecutorService executorService;
private Thread thread;
private int p = 0;
private int pow = 0;
private int k = 0;
private TextView textView;
ExecutorThread(final TextView text) {
p = 0;
textView = text;
thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < pow; i++)
p++;
String s = "Result " + k + "^4 = " + String.valueOf(p);
textView.setText(s);
}
});
thread.start();
executorService = Executors.newSingleThreadExecutor();
}
void doWork() {
executorService.submit(thread);
}
void setK(int k) {
p = 0;
this.k = k;
pow = (int) Math.pow(k, 4);
textView.setText("Please wait. We are calcing!");
}
}
Как мы видим, в тот момент пока мы не посчитали ещё, то у нас виднеется запись «Please wait. We are calcing!», что ясно — «Подождите пожалуйста. Мы считаем!». И после того, как мы посчитаем, то мы выведем текст в наш textView, который мы передали в конструктор нашего класса. Для того, чтобы всё заработало мы должны в наше activity_main, которое у нас было при создании проекта, добавить textView после нашего editText.
<TextView
android:gravity="center"
android:textColor="@color/colorPrimary"
android:textSize="25sp"
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="50dp"/>
И мы так же должны добавить код ниже в наш основной класс — MainActivity.
executorThread = new ExecutorThread((TextView)findViewById(R.id.textView));
editText.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
if (!editable.toString().equals("")) {
executorThread.setK(Integer.parseInt(editText.getText().toString()));
executorThread.doWork();
}
}
});
Стоит отметить, что метод addTextChangeListener отвечает за то, что текст в нашем editText меняется. Как мы видим, внтури этого метода мы вызываем функцию doWork(), которая в свою очередь выполняет вот эти строки
executorService.submit(thread);
Всё вышеизложенное я проверял на своём телефоне, так что, если вы сделали всё правильно, то проблем или ошибок у вас не должно возникнуть.
Как мы видим, этот метод довольно удобен и прост в понимании, я постарался как можно проще всё описать, так что надеюсь вам всё понятно, а я ничего не упустил.
И теперь пожалуй перейдём к 3 пункту нашей статьи, а именно к интеграции рекламы.
Интеграция рекламы
На самом деле по этому поводу я не могу много рассказать, я могу только посоветовать. С рекламой не всё так легко и прозрачно. Лично я бы посоветовал использовать вам Appodeal, он не так давно на рынке, но довольно успешно работает.
Так же стоит отметить, что там очень хорошая русскоязычная поддержка, которая почти всегда сразу отвечает на твой вопрос или на твою проблему. Это сразу даёт понять насколько эта сеть лояльна.
Хочу сразу сказать, если вы вдруг будете ею пользоваться, обязательно настройте оплату в AdMob и в Appodeal, иначе реклама просто не будет грузится. Из-за того, что я не настроил счета я впустую потратил целый день, и потом мне в поддержке сказали: «А настроил ли я счета?». И после того, как я это сделал, то реклама спустя 2 часа появилась.
Заключение
Так как это статья предназначена для начинающих программистов, хочу отметить одну очевидную вещь. Если вам действительно нравится программирование, и вы готовы тонны часов тратить на решение той или иной проблемы, то программирование это ваше, иначе нет. Так же не стоит выходить за рамки. Так как тоже слишком долгое решение, чего-то не слишком сложного и не слишком простого — не есть хорошо. Но это и так очевидный факт. На самом деле, я эту фразу прочитал где-то в интернете. По факту она верна. Если задуматься, то действительно, если бы программирование, серьёзное программирование было бы таким лёгким, то им бы не занималась малая часть людей от общего количества всех людей.
Надеюсь, что моя статья оказалась кому-то полезной и кому-то действительно помогла, помогла сэкономить время, т.к лично я на клавиатуру, казалось бы столь простую вещь, убил 3 дня, а на потоки и поиск нормального решения у меня ушло 2 дня.