Статьи

Создайте ASCII Art Editor: экспорт изображений и пользовательские настройки

Платформа Android предлагает широкий спектр вариантов хранения для использования в ваших приложениях. В этой серии руководств мы собираемся исследовать некоторые средства хранения данных, предоставляемые Android SDK, создав простой проект: художественный редактор ASCII.

В первой части этой серии мы создали элементы пользовательского интерфейса, в том числе основное действие с текстовым полем для ввода пользователями своих символов и задание конфигурации, в котором пользователь сможет выбирать параметры цвета. В этой части мы собираемся реализовать этот выбор пользовательской конфигурации, а также разрешить пользователям экспортировать свои произведения искусства в виде изображений PNG, а полученные файлы будут сохранены во внешнем каталоге Pictures на SD-карте. В двух последних частях серии мы будем работать с базой данных SQLite для сохранения, загрузки и удаления художественных работ пользователя.

План этой серии выглядит следующим образом:


Начнем с настроек конфигурации пользователя. В основной активности вашего приложения перед методом onCreate добавьте переменную для представления редактируемого текстового поля:

1
private EditText textArea;

Вам нужно будет добавить импорт:

1
import android.widget.EditText;

Внутри onCreate , после того как вы настроите представление содержимого, получите ссылку на текстовое поле, чтобы мы могли применить настройки отображения:

1
textArea = (EditText)findViewById(R.id.ascii_text);

Это идентификатор, который мы дали редактируемому текстовому полю в файле макета XML. Затем найдите ссылку на кнопку «Настройки» и определите клики:

1
2
Button setBtn = (Button)findViewById(R.id.set_colors_btn);
setBtn.setOnClickListener(this);

Вам понадобится следующий импорт:

1
import android.widget.Button;

Расширьте строку открытия объявления класса Activity для обработки кликов следующим образом:

1
public class MainActivity extends Activity implements OnClickListener {

Измените имя класса, если вы выбрали другое. Это требует другого импорта:

1
import android.view.View.OnClickListener;

Теперь ваш класс должен предоставить метод onClick , поэтому добавьте его после метода onCreate :

1
2
3
public void onClick(View v) {
 
}

Этот метод будет обрабатывать различные нажатия кнопок, поэтому мы будем добавлять к нему код на протяжении всей серии. Вам понадобится следующий импорт:

1
import android.view.View;

Внутри метода onClick нам нужно адаптировать то, что происходит с любой нажатой кнопкой, поскольку мы будем обрабатывать более одной кнопки:

1
2
3
if(v.getId()==R.id.set_colors_btn) {
         
}

Это кнопка «Настройки», которую мы включили в наш основной файл макета в прошлый раз.


В прошлый раз мы создали Activity для обработки пользовательских настроек, поэтому давайте запустим ее сейчас из нашего основного Activity внутри блока операторов onClick «if»:

1
2
Intent colorIntent = new Intent(this, ColorChooser.class);
this.startActivityForResult(colorIntent, COLOR_REQUEST);

Вам понадобится этот импорт:

1
import android.content.Intent;

Мы начинаем Намерение для Действия, которое мы создали, чтобы обработать выбор пользовательской цветовой схемы. В этом действии мы собираемся позволить пользователю выбрать опцию и вернуть результат в основное действие, поэтому мы используем startActivityForResult . Мы получим результат в методе onActivityResult, который мы добавим далее. Но сначала нам нужно определить, из какой деятельности мы возвращаемся, поэтому мы передаем константу в качестве второго параметра методу startActivityForResult . Добавьте константу в верхней части объявления вашего класса перед методом onCreate :

1
private final int COLOR_REQUEST=1;

Теперь добавьте метод onActivityResult после метода onClick :

1
2
3
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 
}

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

1
2
3
4
5
if (requestCode == COLOR_REQUEST) {
    if(resultCode == RESULT_OK){
 
    }
}

Когда пользователь нажимает кнопку «Настройки», запускается действие выбора. Пользователь выберет цветовую схему, и действие выбора будет завершено, возвращая данные, представляющие выбор пользователя, к основному действию, поскольку именно с этого момента было выбрано действие выбора. По возвращении к основному Activity будет выполняться метод onActivityResult , поэтому мы можем реализовать здесь выбор пользователя (что мы вскоре и сделаем).

Настройки деятельности

Теперь давайте обратим наше внимание на активность выбора, в которой пользователь может сделать выбор цветовой схемы. Откройте свою деятельность «ColorChooser». Метод onCreate уже должен быть завершен. Помните, что когда мы добавили кнопки изображения, представляющие каждую цветовую схему, в файл макета XML «color_choice», мы указали «setColors» в качестве их атрибута onClick и включили тег, представляющий два HEX-цвета для текста и фона — взгляните на « color_choice.xml «разметка сейчас, чтобы проверить это. Когда пользователи нажимают кнопки «Изображение», Android выполнит указанный метод в разделе «Действия», в котором размещен макет. Добавьте метод к вашей деятельности «ColorChooser» после onCreate :

1
2
3
public void setColors(View view){
         
}

Добавьте следующий класс в класс:

1
2
import android.view.View;
import android.content.Intent;

Внутри метода setColors сначала извлеките тег из представления, по которому щелкнули:

1
String tagInfo = (String)view.getTag();

Тег имеет следующий формат: «# 000000 #ffffff» — разделить два цвета на массив строк:

1
String[] tagColors = tagInfo.split(» «);

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

1
Intent backIntent = new Intent();

Добавьте два цвета в качестве дополнительных данных:

1
2
backIntent.putExtra(«textColor», tagColors[0]);
backIntent.putExtra(«backColor», tagColors[1]);

Первый цвет представляет текст, а второй — фон. Установите возвращаемый результат Intent:

1
setResult(RESULT_OK, backIntent);

Вернитесь к Деятельности, которая вызывала это, завершив:

1
finish();

Две строки цвета HEX будут переданы методу onActivityResult в основном классе Activity.


Вернувшись в ваш основной метод Activity onActivityResult , внутри двух блоков операторов if, которые мы добавили для обнаружения возвратов из действия выбора, возвращаем строки, которые мы передали обратно:

1
2
String chosenTextColor = data.getStringExtra(«textColor»);
String chosenBackColor = data.getStringExtra(«backColor»);

Давайте используем вспомогательный метод, чтобы установить цвета, чтобы мы могли выполнить тот же процесс в другом месте:

1
updateColors(chosenTextColor, chosenBackColor);

Добавьте вспомогательный метод после метода onActivityResult :

1
2
3
private void updateColors(String tColor, String bColor){
 
}

Внутри этого метода мы теперь можем установить цвета для редактируемого текста текстового поля и фона:

1
2
textArea.setTextColor(Color.parseColor(tColor));
textArea.setBackgroundColor(Color.parseColor(bColor));

Это требует другого импорта:

1
import android.graphics.Color;
Цветовая схема применяется

Мы обновили внешний вид текстового поля, чтобы он соответствовал выбору пользователя, но мы хотим запомнить выбор, чтобы каждый раз, когда они запускали приложение, он наблюдался. Давайте добавим переменную экземпляра вверху класса, чтобы мы могли ссылаться на Shared Preferences где угодно:

1
private SharedPreferences asciiPrefs;

Добавьте импорт, если необходимо:

1
import android.content.SharedPreferences;

В onCreate , после существующего кода, получите Shared Preferences, используя выбранное вами имя (вы должны использовать одно и то же имя каждый раз, когда вы получаете настройки в своем приложении):

1
asciiPrefs = getSharedPreferences(«AsciiPicPreferences», 0);

Вернувшись в метод onActivityResult , после вызова вспомогательного метода получите редактор общих настроек:

1
SharedPreferences.Editor prefsEd = asciiPrefs.edit();

Передайте данные, представляющие выбор пользователя, и передайте их настройкам приложения:

1
2
prefsEd.putString(«colors», «»+chosenTextColor+» «+chosenBackColor);
prefsEd.commit();

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


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

1
String chosenColors = asciiPrefs.getString(«colors», «»);

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

1
2
3
4
if(chosenColors.length()>0){
String[] prefColors = chosenColors.split(» «);
updateColors(prefColors[0], prefColors[1]);
}

Мы снова разделяем цветную строку и вызываем вспомогательный метод для применения настроек.


Помните, что мы включили кнопку «Экспорт», чтобы пользователи могли сохранять свои произведения в виде файлов изображений. В методе onCreate вашего основного занятия определите клики по нему:

1
2
Button saveImgBtn = (Button)findViewById(R.id.export_btn);
saveImgBtn.setOnClickListener(this);

Теперь добавьте «else if» внутри метода onClick :

1
2
3
else if(v.getId()==R.id.export_btn) {
saveImg();
}

Это будет еще один вспомогательный метод, поэтому добавьте его в свой файл класса после метода «updateColors»:

1
2
3
private void saveImg(){
 
}

Система Android работает на разных устройствах, и мы не можем принимать как должное то, что есть у пользователя. Прежде чем пытаться что-либо записать во внешнее хранилище, вы должны сначала убедиться, что оно доступно. Внутри нового вспомогательного метода добавьте проверку состояния хранилища пользователя с помощью Environment:

1
String state = Environment.getExternalStorageState();

Вам нужен импорт для этого:

1
import android.os.Environment;

Теперь добавьте условный тест на результат, чтобы мы могли адаптировать то, что происходит дальше:

1
2
3
4
5
6
if (Environment.MEDIA_MOUNTED.equals(state)) {
 
}
else {
         
}

Блок «if» экспортирует изображение во внешнее хранилище, а блок «else» обрабатывает ситуации, в которых нет доступного хранилища. В таком случае все, что мы хотим сделать, это написать пользователю сообщение об ошибке, сообщив ему, что мы не можем экспортировать изображение — внутри блока else:

1
2
Toast.makeText(this.getApplicationContext(), «Sorry — you don’t have an external» +
    » storage directory available!», Toast.LENGTH_SHORT).show();

Добавьте необходимый импорт:

1
import android.widget.Toast;

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

1
2
3
4
5
import java.io.File;
import java.io.FileOutputStream;
import java.util.Date;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;

Вернемся к вспомогательному методу для сохранения изображений, внутри блока «if» получите ссылку на каталог Pictures:

1
File picDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

Теперь у нас есть первая часть пути, который мы будем использовать для создания файла изображения. Изображение будет содержать видимое содержимое редактируемого текстового поля, включая цвет фона — т.е. оно будет отображать то, что вы можете видеть, глядя на экран приложения при экспорте. Для этого мы используем кеш для View:

1
2
3
textArea.setDrawingCacheEnabled(true);
textArea.buildDrawingCache(true);
Bitmap bitmap = textArea.getDrawingCache();

Это устанавливает редактируемое текстовое поле «Вид» для рисования, а затем сохраняет его текущий вид в виде растрового изображения с использованием кэша чертежа.

Теперь нам нужно дать нашему файлу имя, поэтому давайте используем текущую дату и время, чтобы сделать каждый файл уникальным, добавив немного информативного текста и расширение файла изображения:

1
2
Date theDate = new Date();
String fileName = «asciipic»+theDate.getTime()+».png»;

Здесь мы указываем имя файла, теперь давайте создадим файл, используя его вместе с путем к каталогу Pictures:

1
File picFile = new File(picDir+»/»+fileName);

Так как мы собираемся сделать какой-то файловый ввод / вывод, нам нужно попробовать и перехватить блоки:

1
2
3
4
try {
 
}
catch (Exception e) { e.printStackTrace();

Это позволяет программе продолжать работу, если что-то пойдет не так с операциями ввода / вывода. Внутри блока try создайте файл и передайте его в выходной поток:

1
2
picFile.createNewFile();
FileOutputStream picOut = new FileOutputStream(picFile);

Сожмите растровое изображение и запишите его в выходной поток, сохранив результат как логическое значение:

1
boolean worked = bitmap.compress(CompressFormat.PNG, 100, picOut);

Вывести сообщение пользователю в зависимости от результата операции записи:

1
2
3
4
5
6
7
8
if(worked){
    Toast.makeText(getApplicationContext(), «Image saved to your device Pictures » +
        «directory!», Toast.LENGTH_SHORT).show();
}
else {
    Toast.makeText(getApplicationContext(), «Whoops! File not saved.»,
        Toast.LENGTH_SHORT).show();
}

Теперь закройте файл:

1
picOut.close();

Мы можем освободить ресурсы, используемые для кэша чертежа, после блока catch :

1
textArea.destroyDrawingCache();

На этом операция вывода файла завершена. Пользователь может просматривать свои экспортированные изображения в виде изображения, просматривая папку «Изображения» своего устройства в любое время.

Экспорт изображения

Совет: Если вы запускаете свое приложение на эмуляторе в Eclipse, вы можете настроить его так, чтобы оно содержало внешнее хранилище, отредактировав AVD и введя объем дискового пространства в поле «Размер» SD-карты. Запустив AVD, запустите на нем свое приложение и экспортируйте изображение. В Eclipse откройте перспективу DDMS из меню «Окно», «Открыть перспективу». Выберите устройство и перейдите в каталог «Изображения» (mnt / sdcard / Pictures). Вы должны увидеть любые изображения, написанные приложением в этой папке. Чтобы извлечь его из виртуального устройства, выберите его и нажмите кнопку «Извлечь файл из устройства», чтобы сохранить и просмотреть его на своем компьютере.

Вытащить файл с устройства

На этом настройка приложения и экспорт изображения завершены. Если вы запустите приложение сейчас, вы сможете установить цветовую схему и экспортировать изображения на SD-карту (при условии, что она присутствует). Проверьте загрузку исходного кода, если вы ни в чем не уверены. В этом руководстве мы использовали внешнее хранилище и общие настройки. В следующих частях мы создадим базу данных SQLite, используя вставки, запросы и обновления для ее сохранения, загрузки, удаления и редактирования своих работ ASCII.