С помощью библиотеки ZXing вы можете создавать приложения для Android с функцией сканирования штрих-кода. В Android SDK: создание считывателя штрих-кода мы реализовали базовое считывание штрих-кода с помощью библиотеки в простом приложении. В этой серии руководств мы будем опираться на то, что мы узнали, чтобы создать приложение, которое будет сканировать книги и извлекать связанную с ними информацию из API Google Книг.
Эта серия статей о создании приложения для сканирования книг состоит из трех частей:
- Настройка API Google Книг и ZXing
- Создание интерфейса и поиск книг
- Получение и отображение информации о книге
Вот предварительный просмотр приложения, над которым мы работаем:
После запуска финального приложения пользователь сможет сканировать штрих-коды книг. Приложение представит подборку данных о отсканированных книгах, в том числе о том, где они доступны для покупки. Приложение также предоставит ссылку на страницу отсканированной книги в Google Книгах.
1. Разобрать результаты поиска
Шаг 1
В последнем разделе мы выполнили поисковый запрос в Google Книгах, используя отсканированный номер ISBN (EAN) , а затем вернули возвращенные данные в приложение в виде строки. Теперь давайте разберем возвращаемую строку, которая является фидом JSON . Мы сделаем это в методе onPostExecute внутреннего класса AsyncTask, который мы создали. Добавьте схему метода после метода doInBackground, который мы завершили в прошлый раз.
1
2
3
|
protected void onPostExecute(String result) {
//parse search results
}
|
Когда вы анализируете фид JSON или любые другие входящие данные из внешнего источника данных, вам необходимо сначала ознакомиться со структурой входящих данных. Просмотрите разделы » Поиск» и » Объем» в документации по API Google Книг, чтобы увидеть структуру того, что будет возвращать поисковый запрос.
Мы будем использовать библиотеки Java JSON для обработки результатов поиска. Добавьте следующий импорт в ваш основной класс активности.
01
02
03
04
05
06
07
08
09
10
11
|
import java.io.BufferedInputStream;
import java.net.URL;
import java.net.URLConnection;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.net.Uri;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
|
Шаг 2
Давайте начнем работать с результатом поиска JSON . В вашем методе onPostExecute добавьте блоки try и catch, чтобы справиться с любыми входными исключениями.
1
2
3
4
5
6
|
try{
//parse results
}
catch (Exception e) {
//no result
}
|
В блоке catch отвечайте на случаи, когда не было правильного результата поиска.
1
2
3
4
5
6
7
8
9
|
e.printStackTrace();
titleText.setText(«NOT FOUND»);
authorText.setText(«»);
descriptionText.setText(«»);
dateText.setText(«»);
starLayout.removeAllViews();
ratingCountText.setText(«»);
thumbView.setImageBitmap(null);
previewBtn.setVisibility(View.GONE);
|
Мы просто устанавливаем все элементы пользовательского интерфейса, чтобы указать, что верный результат поиска не был возвращен. Это может произойти, если отсканированная книга отсутствует в Google Книгах или что-то не так при вводе данных.
Шаг 3
Вернувшись в блок try , установите кнопку предварительного просмотра, чтобы она была видимой, при условии, что у нас есть правильный результат:
1
|
previewBtn.setVisibility(View.VISIBLE);
|
Теперь начните извлекать объекты JSON из возвращенной строки.
1
2
|
JSONObject resultObject = new JSONObject(result);
JSONArray bookArray = resultObject.getJSONArray(«items»);
|
Как видно из структуры, описанной в инструкциях по поиску в документации по API Google Книг, результат запроса будет содержать массив с именем « items », содержащий любые результаты, соответствующие переданному номеру ISBN . Поскольку мы ожидаем только один результат для отсканированной книги, давайте получим первый элемент в массиве.
1
|
JSONObject bookObject = bookArray.getJSONObject(0);
|
Теперь нам нужен объект volumeInfo, который содержит некоторые данные, которые мы хотим отобразить.
1
|
JSONObject volumeObject = bookObject.getJSONObject(«volumeInfo»);
|
Этот объект даст нам доступ к большинству элементов данных книги, которые мы ищем.
Шаг 4
В некоторых случаях вы обнаружите, что результаты поиска в Google Книгах будут возвращать некоторые, но не все элементы данных, поэтому мы будем использовать повторяющиеся блоки try и catch для обработки случаев, когда отдельные значения данных отсутствуют. Начните с заголовка.
1
2
3
4
5
|
try{ titleText.setText(«TITLE: «+volumeObject.getString(«title»));
catch(JSONException jse){
titleText.setText(«»);
jse.printStackTrace();
}
|
Если строку можно получить, мы отобразим ее в соответствующем текстовом представлении . В противном случае мы установим для него отображение пустой строки с блоком catch, позволяющим приложению продолжить обработку результатов поиска, несмотря на этот отсутствующий элемент.
Далее давайте обработаем автора, который представляется в виде массива, если их больше одного. Мы построим всю строку, используя цикл.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
StringBuilder authorBuild = new StringBuilder(«»);
try{
JSONArray authorArray = volumeObject.getJSONArray(«authors»);
for(int a=0; a<authorArray.length(); a++){
if(a>0) authorBuild.append(«, «);
authorBuild.append(authorArray.getString(a));
}
authorText.setText(«AUTHOR(S): «+authorBuild.toString());
}
catch(JSONException jse){
authorText.setText(«»);
jse.printStackTrace();
}
|
Авторы будут разделены запятыми, если их несколько, в противном случае будет отображаться одно имя автора. Теперь разберем дату публикации.
1
2
3
4
5
|
try{ dateText.setText(«PUBLISHED: «+volumeObject.getString(«publishedDate»));
catch(JSONException jse){
dateText.setText(«»);
jse.printStackTrace();
}
|
Далее мы обработаем описание.
1
2
3
4
5
|
try{ descriptionText.setText(«DESCRIPTION: «+volumeObject.getString(«description»));
catch(JSONException jse){
descriptionText.setText(«»);
jse.printStackTrace();
}
|
Теперь давайте разберемся со звездным рейтингом. Помните, что мы создали макет и объявили массив для хранения звездных изображений . Вернитесь к вашему методу onCreate на мгновение и создайте экземпляр массива после существующего кода.
1
2
3
4
|
starViews=new ImageView[5];
for(int s=0; s<starViews.length; s++){
starViews[s]=new ImageView(this);
}
|
Может быть максимум пять звезд. Вернитесь в onPostExecute после последнего блока catch, который мы добавили для описания, добавьте блоки try и catch для звездочек.
1
2
3
4
5
6
7
|
try{
//set stars
}
catch(JSONException jse){
starLayout.removeAllViews();
jse.printStackTrace();
}
|
Когда мы получим количество звездочек, присвоенных книге, мы динамически добавим представления изображений в макет, который мы создали для рейтинга звезд. Если звездный рейтинг не может быть получен, мы удалим все ранее добавленные представления в блоке catch . В этом блоке try извлекают количество звездочек из возвращенного JSON и приводят его к целому числу.
1
2
|
double decNumStars = Double.parseDouble(volumeObject.getString(«averageRating»));
int numStars = (int)decNumStars;
|
Установите количество звездочек, отображаемых в данный момент в качестве тега объекта макета. Мы будем использовать это позже, когда будем иметь дело с сохранением состояния приложения. Удалите все существующие виды из макета для повторного сканирования.
1
2
|
starLayout.setTag(numStars);
starLayout.removeAllViews();
|
Теперь мы можем просто зациклить, чтобы добавить соответствующее количество звездочек.
1
2
3
4
|
for(int s=0; s<numStars; s++){
starViews[s].setImageResource(R.drawable.star);
starLayout.addView(starViews[s]);
}
|
Помните, что в прошлый раз мы добавили изображение звезды в приложение drawables. После блока «Звездная секция» мы можем иметь дело с подсчетом рейтинга.
1
2
3
4
5
|
try{ ratingCountText.setText(» — «+volumeObject.getString(«ratingsCount»)+» ratings»);
catch(JSONException jse){
ratingCountText.setText(«»);
jse.printStackTrace();
}
|
Теперь давайте проверим, есть ли у книги предварительный просмотр в Google Книгах. Установите кнопку предварительного просмотра, чтобы включить или отключить соответственно.
01
02
03
04
05
06
07
08
09
10
|
try{
boolean isEmbeddable = Boolean.parseBoolean
(bookObject.getJSONObject(«accessInfo»).getString(«embeddable»));
if(isEmbeddable) previewBtn.setEnabled(true);
else previewBtn.setEnabled(false);
}
catch(JSONException jse){
previewBtn.setEnabled(false);
jse.printStackTrace();
}
|
Мы будем иметь дело с тем, что происходит при нажатии кнопки предварительного просмотра позже. Затем получите URL-адрес страницы книги в Google Книгах и установите его в качестве тега для кнопки ссылки.
1
2
3
4
5
6
7
8
|
try{
linkBtn.setTag(volumeObject.getString(«infoLink»));
linkBtn.setVisibility(View.VISIBLE);
}
catch(JSONException jse){
linkBtn.setVisibility(View.GONE);
jse.printStackTrace();
}
|
Мы осуществим клики по этому позже. Единственный оставшийся фрагмент информации, который мы все еще хотим обработать, — это миниатюрное изображение, которое немного сложнее. Мы хотим использовать дополнительный класс AsyncTask для извлечения его в фоновом режиме.
2. Получить эскиз изображения
Шаг 1
После класса « GetBookInfo » добавьте еще один для получения эскиза книги.
1
2
3
|
private class GetBookThumb extends AsyncTask<String, Void, String> {
//get thumbnail
}
|
Мы передадим миниатюрную строку URL-адреса из другой AsyncTask, когда получим ее из JSON . Внутри этого нового класса AsyncTask добавьте метод doInBackground .
1
2
3
4
|
@Override
protected String doInBackground(String… thumbURLs) {
//attempt to download image
}
|
Шаг 2
Внутри doInBackground добавьте блоки try и catch в случае исключений ввода-вывода.
1
2
3
4
5
6
|
try{
//try to download
}
catch(Exception e) {
e.printStackTrace();
}
|
В блоке try попытайтесь установить соединение, используя переданный миниатюрный URL.
1
2
3
|
URL thumbURL = new URL(thumbURLs[0]);
URLConnection thumbConn = thumbURL.openConnection();
thumbConn.connect();
|
Теперь получите входные и буферизованные потоки.
1
2
|
InputStream thumbIn = thumbConn.getInputStream();
BufferedInputStream thumbBuff = new BufferedInputStream(thumbIn);
|
Мы хотим перенести изображение в приложение в виде растрового изображения, поэтому добавьте новую переменную экземпляра вверху объявления класса активности.
1
|
private Bitmap thumbImg;
|
Вернувшись в doInBackground для класса « GetBookThumb », после создания буферизованного входного потока считайте изображение в это растровое изображение.
1
|
thumbImg = BitmapFactory.decodeStream(thumbBuff);
|
Закройте потоки.
1
2
|
thumbBuff.close();
thumbIn.close();
|
После блока catch верните пустую строку для завершения метода.
1
|
return «»;
|
После doInBackground добавьте метод onPostExecute, чтобы показать загруженное изображение.
1
2
3
|
protected void onPostExecute(String result) {
thumbView.setImageBitmap(thumbImg);
}
|
3. Выполните классы извлечения и анализа
Шаг 1
Теперь давайте соберем вместе эти нити. В вашем методе onActivityResult после строки, в которой вы создали строку поискового запроса, создайте и выполните экземпляр класса AsyncTask для извлечения результатов поиска.
1
|
new GetBookInfo().execute(bookSearchString);
|
В методе onPostExecute класса « GetBookInfo » после блока catch для кнопки ссылки добавьте последний раздел для извлечения эскиза с использованием данных JSON и другого класса AsyncTask .
1
|
try{ JSONObject imageInfo = volumeObject.getJSONObject(«imageLinks»);
|
4. Обработка оставшихся кликов
Шаг 1
Давайте разберемся с кнопками предварительного просмотра и ссылки. Начните с кнопки ссылки. Мы уже установили класс в качестве прослушивателя щелчков для кнопок, поэтому добавьте новый раздел в метод onClick после условия для кнопки сканирования.
1
2
3
4
5
6
7
8
|
else if(v.getId()==R.id.link_btn){
//get the url tag
String tag = (String)v.getTag();
//launch the url
Intent webIntent = new Intent(Intent.ACTION_VIEW);
webIntent.setData(Uri.parse(tag));
startActivity(webIntent);
}
|
Этот код извлекает URL из тега кнопки, который мы установили при обработке JSON . Затем код запускает ссылку в браузере.
Для запуска предварительного просмотра нам нужен номер ISBN книги. Вернитесь к методу onActivityResult на мгновение. Добавьте строку перед строкой, в которой вы создали строку поискового запроса. Установите отсканированный ISBN в качестве тега для кнопки предварительного просмотра.
1
|
previewBtn.setTag(scanContent);
|
Теперь вернитесь в onClick и добавьте условие для кнопки предварительного просмотра, в которой вы получите тег.
1
2
3
4
|
else if(v.getId()==R.id.preview_btn){
String tag = (String)v.getTag();
//launch preview
}
|
Шаг 2
Мы собираемся создать новый вид деятельности для предварительного просмотра книги. Добавьте новый класс в свой проект в том же пакете, что и основной вид деятельности, назвав его « EmbeddedBook ». Дайте вашему классу следующую схему.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
public class EmbeddedBook extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
|
Создайте новый файл макета в папке « res / layout » вашего приложения, присвоив ему имя « embedded_book.xml ». Введите следующий макет.
1
2
3
4
|
<WebView xmlns:android=»http://schemas.android.com/apk/res/android»
android:id=»@+id/embedded_book_view»
android:layout_width=»fill_parent»
android:layout_height=»fill_parent» />
|
Мы представим предварительный просмотр книги в WebView . Вернувшись в новый класс, установите его как представление содержимого в onCreate после существующего кода.
1
|
setContentView(R.layout.embedded_book);
|
Добавьте переменную экземпляра вверху класса.
1
|
private WebView embedView;
|
В onCreate получите ссылку на WebView, который мы включили в макет.
1
|
embedView = (WebView)findViewById(R.id.embedded_book_view);
|
Установите JavaScript для включения.
1
|
embedView.getSettings().setJavaScriptEnabled(true);
|
Когда мы запустим действие, мы передадим ISBN. Получите эти переданные данные далее в onCreate .
1
|
Bundle extras = getIntent().getExtras();
|
Теперь проверьте, есть ли у нас какие-либо дополнения.
1
2
3
|
if(extras !=null) {
//get isbn
}
|
Внутри условно попытайтесь получить ISBN.
1
|
String isbn = extras.getString(«isbn»);
|
Мы собираемся включить содержимое WebView в файл ресурсов в формате HTML. Добавить попробуйте и поймать блоки для загрузки.
1
2
3
4
5
6
7
8
9
|
try{
//load asset
}
catch(IOException ioe){
embedView.loadData
(«<html><head></head><body>Whoops! Something went wrong.</body></html>»,
«text/html», «utf-8»);
ioe.printStackTrace();
}
|
Если файл не может быть загружен, мы выведем сообщение об ошибке. Мы скоро вернемся к блоку try . Сначала создайте новый файл в папке « assets » вашего приложения, назвав его « embedded_book_page.html ». Найдите новый файл в проводнике пакетов, щелкните правой кнопкой мыши и выберите « Открыть с помощью », а затем « Текстовый редактор ». Введите следующий код.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
<html xmlns=»http://www.w3.org/1999/xhtml»>
<head>
<meta http-equiv=»content-type» content=»text/html; charset=utf-8″/>
<title>Google Books Embedded Viewer API Example</title>
<script type=»text/javascript» src=»https://www.google.com/jsapi»></script>
<script type=»text/javascript»>
google.load(«books», «0»);
function initialize() {
var viewer = new google.books.DefaultViewer(document.getElementById(‘viewerCanvas’));
viewer.load(‘ISBN:$ISBN$’);
}
google.setOnLoadCallback(initialize);
</script>
</head>
<body>
<div id=»viewerCanvas» style=»width: 600px; height: 500px»></div>
</body>
</html>
|
Это стандартный код разметки, используемый для отображения предварительного просмотра Google Книг с помощью API Embedded Viewer . Обратите внимание, что страница содержит « $ ISBN $ ». Мы будем использовать это для передачи номера ISBN из действия.
Вернитесь к блоку try в своем классе » EmbeddedBook «. Попробуйте загрузить страницу, используя следующий алгоритм.
1
2
3
4
5
6
7
8
|
InputStream pageIn = getAssets().open(«embedded_book_page.html»);
BufferedReader htmlIn = new BufferedReader(new InputStreamReader(pageIn));
StringBuilder htmlBuild = new StringBuilder(«»);
String lineIn;
while ((lineIn = htmlIn.readLine()) != null) {
htmlBuild.append(lineIn);
}
htmlIn.close();
|
Затем передайте ISBN, заменив раздел, который мы включили для него в HTML.
1
|
String embeddedPage = htmlBuild.toString().replace(«$ISBN$», isbn);
|
Завершите новое действие, загрузив страницу.
1
|
embedView.loadData(embeddedPage, «text/html», «utf-8»);
|
Шаг 3
Вернувшись в метод onClick основного класса действий, в условный блок, который вы добавили для кнопки предварительного просмотра, после строки, в которой вы получили тег, запустите новое действие, передав ISBN.
1
2
3
|
Intent intent = new Intent(this, EmbeddedBook.class);
intent.putExtra(«isbn», tag);
startActivity(intent);
|
Добавьте новый класс в файл манифеста приложения после основного элемента активности.
1
|
<activity android:name=»com.example.barcodescanningapp.EmbeddedBook» />
|
При необходимости измените имя пакета, чтобы оно соответствовало вашему.
5. Сохранить состояние приложения
Шаг 1
Вы можете запустить приложение на этом этапе, и оно должно функционировать. Однако давайте сначала позаботимся об изменении состояния приложения. В своем основном классе деятельности добавьте следующий метод.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
protected void onSaveInstanceState(Bundle savedBundle) {
savedBundle.putString(«title», «»+titleText.getText());
savedBundle.putString(«author», «»+authorText.getText());
savedBundle.putString(«description», «»+descriptionText.getText());
savedBundle.putString(«date», «»+dateText.getText());
savedBundle.putString(«ratings», «»+ratingCountText.getText());
savedBundle.putParcelable(«thumbPic», thumbImg);
if(starLayout.getTag()!=null)
savedBundle.putInt(«stars», Integer.parseInt(starLayout.getTag().toString()));
savedBundle.putBoolean(«isEmbed», previewBtn.isEnabled());
savedBundle.putInt(«isLink», linkBtn.getVisibility());
if(previewBtn.getTag()!=null)
savedBundle.putString(«isbn», previewBtn.getTag().toString());
}
|
Мы не будем вдаваться в подробности о сохранении состояния, потому что это не главное в этом уроке. Посмотрите код, чтобы убедиться, что вы его понимаете. Мы просто сохраняем отображаемые данные, чтобы сохранить их при изменении состояния приложения. В onCreate после существующего кода получите эту информацию.
1
|
if (savedInstanceState != null){ authorText.setText(savedInstanceState.getString(«author»));
|
Опять же, мы не будем вдаваться в подробности по этому вопросу. Код просто выполняет то, что код делает сразу после сканирования. В этом случае он следует за изменением состояния. Расширьте открывающий тег вашего основного элемента активности в манифесте проекта для обработки изменений конфигурации.
1
|
android:configChanges=»orientation|keyboardHidden|screenSize»
|
Это завершает приложение! Вы должны быть в состоянии запустить его в этот момент. Когда он запустится, нажмите кнопку и отсканируйте книгу. Если книга указана в Google Книгах, соответствующая информация должна появиться в интерфейсе. Если некоторые элементы данных отсутствуют, другие приложения должны по-прежнему отображать. Если доступен предварительный просмотр, кнопка предварительного просмотра должна быть включена и должна запускать действие встроенной книги.
Вывод
В этой серии мы использовали Android SDK: создание сканера штрих-кода для создания приложения для сканирования книг в сочетании с библиотекой сканирования ZXing и API Google Книг. Поэкспериментируйте с приложением, пытаясь извлекать и отображать различные элементы данных из возвращенного канала JSON или реагируя на сканирование различных типов штрих-кодов, а не только EAN . Использование внешних ресурсов через API-интерфейсы является ключевым навыком в любой дисциплине разработки, так как позволяет максимально эффективно использовать существующие данные и функциональные возможности, чтобы сосредоточиться на уникальных деталях ваших собственных приложений.