Разработка основного функционала
Основной функционал приложение разбит на 3 составляющие.
1. Переводчик текста
2. История передов и избранные переводы
3. Настройки переводов
Каждое окно приложение сделано в виде отдельного фрагмента переключение между которыми происходит по средствам свайпа либо же нажатиями клавиш.
Фрагмент (класс Fragment) - представляет поведение или часть пользовательского интерфейса в операции (класс Activity). Разработчик может объединить несколько фрагментов в одну операцию для построения многопанельного пользовательского интерфейса и повторного использования фрагмента в нескольких операциях. Фрагмент можно рассматривать как модульную часть операции. Такая часть имеет свой жизненный цикл и самостоятельно обрабатывает события ввода. Кроме того, ее можно добавить или удалить непосредственно во время выполнения операции. Это нечто вроде вложенной операции, которую можно многократно использовать в различных операциях.
Листинг фрагмента перевода текста:
* Метод перевода текста с последующим отображением
* @param text Текст для перевода
* @param fromLang С какого языка перевод
* @param toLang На какой язык перевод
* @param isDictionaryWord Нужно ли заменить текст в поле ввода
* */
public void translate(final String text, final String fromLang, final String toLang, boolean isDictionaryWord)
{
/** Нужно ли показывать словарь */
final boolean showDictionary = getPreferences().getBoolean("showDictionary",true);
/** Офлайн перевод */
final boolean offlineTranslate = getPreferences().getBoolean("offlineTranslate",true);
/** Проверка текста перевода на пустату */
if(TextUtils.isEmpty(text)) {
/** Скрываю frame с результатом перевода и словарем */
dictionatyResultFrame.setVisibility(View.GONE);
translateResultFrame.setVisibility(View.GONE);
return;
}
/** Язык перевода */
final String translateLang = fromLang + "-" + toLang;
/** Замена текста в поле ввода */
if(isDictionaryWord)
{
editText.removeTextChangedListener(editTextWatcher);
editText.setTextKeepState(text);
editText.addTextChangedListener(editTextWatcher);
clearText.setVisibility((text.length() > 0) ? View.VISIBLE : View.INVISIBLE);
}
/** Смена языков перевода spinners */
sFirst.setOnItemSelectedListener(null);
sSecond.setOnItemSelectedListener(null);
sFirst.setSelection(Arrays.asList(Translator.langsCode).indexOf(fromLang),false);
sSecond.setSelection(Arrays.asList(Translator.langsCode).indexOf(toLang),false);
sFirst.setOnItemSelectedListener(onSpinnerChanged);
sSecond.setOnItemSelectedListener(onSpinnerChanged);
/** Скрываю frame с результатом перевода и словарем */
dictionatyResultFrame.setVisibility(View.GONE);
translateResultFrame.setVisibility(View.GONE);
/** Включаю ProgressBar */
translateProgressBar.setVisibility(View.VISIBLE);
/** Офлайн перевод */
if(offlineTranslate) {
/** Проверяем в БД перевод такого текста и языка */
ArrayList<TranslateResultModel> dbTranslate = dataBaseHelper.getTranslate(text, translateLang);
/** Если пеервод встечается, то выводим и делаем запрос на словарь
* Словарь в БД не хранится
* */
if (dbTranslate.size() != 0) {
translateProgressBar.setVisibility(View.GONE);
translateResultFrame.setVisibility(View.VISIBLE);
translateText.setText(dbTranslate.get(0).to);
translateResultFrame.startAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.move));
if(showDictionary)
dictionary(text, fromLang, toLang);
return;
}
}
/** Онлайн перевод */
translator.translate(text, translateLang, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
try {
/** Вытаксиваем результат перевода */
JSONObject jObject = new JSONObject(response);
JSONArray jArray = jObject.getJSONArray("text");
final String translatedText = jArray.getString(0);
final String origText = text;
/** Добавляю в БД результат пеервода */
dataBaseHelper.insert(new TranslateResultModel(origText,translatedText,translateLang,false));
/** Обновляю историю переводов */
((MainActivity)getActivity()).getHistoryFragment().updateHistoryList();
/** Включаю фреймы */
translateResultFrame.setVisibility(View.VISIBLE);
translateProgressBar.setVisibility(View.GONE);
/** Ставлю текст перевода */
translateText.setText(translatedText);
translateResultFrame.startAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.move));
if(showDictionary)
dictionary(text,fromLang,toLang);
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
/** В случае ошибки ответа */
if(error.networkResponse != null)
translateText.setText(getResources().getString(R.string.failedTranslateText)
+ "\n" + getResources().getString(R.string.errorCode)
+ error.networkResponse.statusCode);
else
translateText.setText(getResources().getString(R.string.failedTranslateText)
+ "\n" + getResources().getString(R.string.checkInternet));
translateProgressBar.setVisibility(View.GONE);
translateResultFrame.setVisibility(View.VISIBLE);
translateResultFrame.startAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.move));
}
});
}
/**
* Метод получения словаря, с последующим отображением
* @param text Текст для перевода
* @param fromLang С какого языка перевод
* @param toLang На какой язык перевод
* */
public void dictionary(final String text, final String fromLang, final String toLang)
{
/** Язык перевода */
final String translateLang = fromLang + "-" + toLang;
/** Скрываю фрейм словаря */
dictionatyResultFrame.setVisibility(View.GONE);
/** Запрос к словарю */
dictionary.getWords(text, translateLang, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
/** Создание SpannableString
* Слова будут окрашены и начнут нажиматься
* */
SpannableString dict = dictionary.getDictionarySpannableString(response,fromLang,toLang);
/** Проверка на пустую строку */
if(!TextUtils.isEmpty(dict.toString())) {
dictionatyResultFrame.setVisibility(View.VISIBLE);
dictionaryText.setText(dict);
dictionatyResultFrame.startAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.move));
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
/** Ошибки словаря не выводятся */
}
});
}
@Override
public View onCreateView(final LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.translator_main, container, false);
((MainActivity)getActivity()).translatorFragment = this;
translator = ((MainActivity)getActivity()).getTranslator();
dictionary = ((MainActivity)getActivity()).getDictionary();
sFirst = (Spinner) rootView.findViewById(R.id.spinnerFromLang);
sSecond = (Spinner) rootView.findViewById(R.id.spinnerToLang);
translateText = (TextView)rootView.findViewById(R.id.translatedText);
dictionaryText = (TextView)rootView.findViewById(R.id.dictionaryText);
dictionaryText.setMovementMethod(LinkMovementMethod.getInstance());
translateResultFrame = (FrameLayout) rootView.findViewById(R.id.translateResult);
dictionatyResultFrame = (FrameLayout) rootView.findViewById(R.id.dictionaryResult);
translateProgressBar = (ProgressBar) rootView.findViewById(R.id.translateProgressBar);
TextView translatedByYandex = (TextView)rootView.findViewById(R.id.translatedByYandex);
translatedByYandex.setText(Html.fromHtml(getResources().getString(R.string.translatedByYandex)));
translatedByYandex.setMovementMethod(LinkMovementMethod.getInstance());
switchLang = (ImageButton) rootView.findViewById(R.id.switchLang);
clearText = (ImageButton) rootView.findViewById(R.id.clearText);
ImageButton openFullscreen = (ImageButton) rootView.findViewById(R.id.openFullscreen);
openFullscreen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), TextReaderActivity.class);
intent.putExtra("text",translateText.getText().toString());
startActivity(intent);
}
});
/** Листеер на очистку поля ввода */
clearText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
editText.setText("");
}
});
/** Листеер на смену языков местами */
switchLang.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/** Анимации */
switchLang.setRotation(0);
switchLang.animate().rotation(180).setDuration(500);
sFirst.animate().alpha(0).setDuration(300);
sSecond.animate().alpha(0).setDuration(300);
}
});
editText = (EditText)rootView.findViewById(R.id.editText);
/** TextWatcher */
editTextWatcher = new TextWatcher() {
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
/** Включаем скопку очистки поля если поле не пустое */
clearText.setVisibility((s.length() > 0) ? View.VISIBLE : View.INVISIBLE);
}
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
/** Наймер на задержку после ввода текста
* Лучше наверно сделать через Handler */
private Timer timer=new Timer();
/** Время через которое начнется перевод текста, после окончания ввода */
private final long DELAY = 1000;
@Override
public void afterTextChanged(final Editable s) {
if(s.toString().trim().length()>0) {
timer.cancel();
timer = new Timer();
timer.schedule(
new TimerTask() {
@Override
public void run() {
/** В UI потоке вызываем метод перевода текста */
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
translate(editText.getText().toString(),
Translator.langsCode[sFirst.getSelectedItemPosition()],
Translator.langsCode[sSecond.getSelectedItemPosition()],false);
}
});
}
},
DELAY
);
}
}
};
/** Додавляем листенер */
editText.addTextChangedListener(editTextWatcher);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
getContext(), R.array.langs, R.layout.spinner_lang_item);
sFirst.setAdapter(adapter);
sSecond.setAdapter(adapter);
onSpinnerChanged = new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
translate(editText.getText().toString(),
Translator.langsCode[sFirst.getSelectedItemPosition()],
Translator.langsCode[sSecond.getSelectedItemPosition()],false);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
/** Установка начальных языков перевода */
String sysLang = Locale.getDefault().getLanguage();
switch (sysLang)
{
case "en":
sFirst.setSelection(Arrays.asList(Translator.langsCode).indexOf("en"));
sSecond.setSelection(Arrays.asList(Translator.langsCode).indexOf("ru"));
break;
case "ru":
sFirst.setSelection(Arrays.asList(Translator.langsCode).indexOf("ru"));
sSecond.setSelection(Arrays.asList(Translator.langsCode).indexOf("en"));
break;
default:
sFirst.setSelection(Arrays.asList(Translator.langsCode).indexOf("auto"));
sSecond.setSelection(Arrays.asList(Translator.langsCode).indexOf("en"));
break;
}
return rootView;
}
}
Листинг фрагмента с историей и избранными переводами:
public static HistoryFragment newInstance() {
return new HistoryFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.history_main, container, false);
dbHelper = ((MainActivity)getActivity()).getDbHelper();
((MainActivity)getActivity()).historyFragment = this;
noTranslateHistory = (LinearLayout) rootView.findViewById(R.id.noTranslateHistory);
noHistoryText = (TextView) rootView.findViewById(R.id.noTranslateHistoryText);
clearHistory = (ImageButton) rootView.findViewById(R.id.clearHistory);
/** Вкладки для переключения межно историей и избранным */
mTabLayout = (TabLayout) rootView.findViewById(R.id.historyTab);
mTabLayout.addTab(mTabLayout.newTab().setText(R.string.history));
mTabLayout.addTab(mTabLayout.newTab().setText(R.string.favorite));
/** Листенер на смену вкладок */
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
/** Устанавливаю текст с SearchView
* Просто мелочь...
* */
switch (tab.getPosition()) {
case 0:
searchView.setQueryHint(getResources().getString(R.string.searchInHistory));
break;
case 1:
searchView.setQueryHint(getResources().getString(R.string.searchInFavorite));
break;
default:
ListView lv = (ListView)rootView.findViewById(R.id.searchList);
adapter = new HistoryArrayAdapter(getContext());
lv.setAdapter(adapter);
searchView = (SearchView)rootView.findViewById(R.id.searchView);
/** Начальное обновления списка */
updateHistoryList();
return rootView;
}
public void updateHistoryList()
{
/** Очищаю список*/
adapter.clear();
/** Добавляю в список значения из БД */
adapter.addAll(dbHelper.getHistory(searchView.getQuery().toString(),
(mTabLayout.getSelectedTabPosition() == 1)));
/** Обновляю в UI*/
adapter.notifyDataSetChanged();
/** Если в БД есть значения, то включаю кнопку очистки истории */
clearHistory.setVisibility((dbHelper.dbCount() > 0) ? View.VISIBLE : View.GONE);
/** Если есть элементы в БД */
if(adapter.getCount() > 0)
noTranslateHistory.setVisibility(View.GONE);
else
{
noTranslateHistory.setVisibility(View.VISIBLE);
noHistoryText.setText((mTabLayout.getSelectedTabPosition() == 0) ?
getResources().getString(R.string.noTranslateHistory) :
getResources().getString(R.string.noFavorites));
}
Заключение
Мною было разработано полноценное приложение в точности по ТЗ имеющее полный функционал переводчика включая:
1. Онлайн/оффлайн перевод текста
2. Показа словаря
3. История и избранные переводы
В качестве референса для дизайна использовалось официальное приложение Yandex Переводчик.
Также использовались технологии API Yandex Translate и API Yandex Dictionary через которые был реализован перевод текста и показ словаря.
Для сетевых запросов использовалась библиотека Volley.
Поддержка старых версий ОС Android реализована через библиотеку Support Library
Список литературы
1. IDE Android Studio -https://ru.wikipedia.org/wiki/Android_Studio
2. Сервис перевода текста API Yandex Translate - https://tech.yandex.ru/translate/
3. Yandex Mobilization - https://academy.yandex.ru/events/mobdev/msk-2017/
4. Сервис словаря API Yandex Dictionary - https://tech.yandex.ru/dictionary/
5. Библиотека сетевых запросов Volley - https://developer.android.com/training/volley/index.html
6. Фрагменты в ОС Android - https://developer.android.com/guide/components/fragments.html?hl=ru