Статьи

Android Full App, часть 7. Использование меню параметров и настраиваемых диалогов для взаимодействия с пользователем

Это седьмая часть серии «Android Full Tutorial» . Полное приложение предназначено, чтобы обеспечить легкий способ выполнять поиск фильмов / актеров через Интернет. В первой части серии ( «Основной интерфейс действий» ) мы создали проект Eclipse и настроили базовый интерфейс для основной деятельности приложения. Во второй части ( «Использование HTTP API» ) мы использовали клиентскую библиотеку Apache HTTP для использования внешнего HTTP API и интеграции возможностей поиска API в наше приложение. В третьей части ( «Анализ XML-ответа» ) мы увидели, как анализировать XML-ответ, используя встроенные в Android возможности синтаксического анализа XML. В четвертой части ( «Выполнение запроса API асинхронно из основного действия» ) мы связали вместе службы HTTP-ретривера и XML-парсера, чтобы выполнить запрос поиска API из основного действия нашего приложения. Запрос был выполнен асинхронно в фоновом потоке, чтобы избежать блокировки основного потока пользовательского интерфейса. В пятой части ( «Запуск новых действий с намерениями» ) мы увидели, как запустить новое действие и как перенести данные из одного действия в другое. В шестой части ( «Настроенное представление списка для представления данных» ) мы создали настраиваемое представление списка, чтобы обеспечить лучшее визуальное представление данных. В этой части мы собираемся создать меню параметров и настраиваемые диалоги, чтобы облегчить взаимодействие с пользователем.

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

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

Неудивительно, что основной класс, вокруг которого строятся меню параметров, называется Menu . На самом деле это интерфейс для управления пунктами меню. По умолчанию каждое действие поддерживает меню параметров действий или параметров. В частности, класс Android Activity предоставляет метод с именем onCreateOptionsMenu, который можно использовать для инициализации содержимого стандартного меню параметров действия. При переопределении этого метода мы обычно используем метод add для добавления новых элементов в меню. Обратите внимание, что метод add и его варианты возвращают объект MenuItem , который является интерфейсом для прямого доступа к ранее созданному пункту меню. Элемент MenuItem может содержать как заголовок, так и значок.

Аргумент itemId метода add позволяет нам выяснить, какие параметры были выбраны. В общем, для обработки таких событий, как выбор пользователем пункта меню, нам необходимо переопределить метод onOptionsItemSelected нашей Activity . Этот метод содержит ссылку на MenuItem, который был выбран, и мы можем использовать метод getItemId , чтобы найти его идентификатор. Таким образом, мы теперь в состоянии узнать, что выбрал пользователь.

В нашем приложении мы будем использовать меню опций, чтобы позволить пользователю либо посетить страницу IMDB определенного фильма, либо просмотреть изображение его плаката в новом окне. Сначала мы определяем идентификаторы доступных элементов, а затем создаем меню, добавляя пункты меню в меню. Обратите внимание, что мы используем заголовок и значок для наших пунктов меню. Наконец, когда выбран вариант, мы проверяем идентификатор пункта меню, чтобы выяснить, какое действие необходимо выполнить. Вот некоторые фрагменты кода для всего вышеперечисленного:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static final int ITEM_VISIT_IMDB = 0;
private static final int ITEM_VIEW_FULL_IMAGE = 1;
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    menu.add(Menu.NONE, ITEM_VISIT_IMDB, 0,
        getString(R.string.visit_imdb)).setIcon(android.R.drawable.ic_menu_set_as);
    menu.add(Menu.NONE, ITEM_VIEW_FULL_IMAGE, 0,
        getString(R.string.view_full_image)).setIcon(android.R.drawable.ic_menu_zoom);
    return super.onCreateOptionsMenu(menu);
}
...
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case ITEM_VISIT_IMDB:
            visitImdbMoviePage();
            return true;
        case ITEM_VIEW_FULL_IMAGE:
            viewFullImagePoster();
            return true;
    }
    return false;
}

Вот изображение того, как будет выглядеть наше меню параметров:

Вторая тема этого руководства — создание и использование диалогов, которые помогают взаимодействовать с пользователями приложения. Самый простой способ сделать это — напрямую использовать класс Dialog . Конструктор Dialog требует в качестве аргумента объект Context , и это будет действие, в рамках которого создается диалог. Как и в случае с Activity , объекты диалога предоставляют метод setContentView , который принимает идентификатор ресурса макета и устанавливает содержимое экрана из этого ресурса макета. Точно так же метод findViewById позволяет нам найти ссылку на представление, которое было идентифицировано атрибутом id из макета XML. Внутренними представлениями можно манипулировать как обычно. В двух словах, это похоже на создание представления для действия .

После того, как мы закончили с созданием диалога, вызов метода show запустит диалог и отобразит его на экране. В этом случае окно размещается на прикладном уровне и непрозрачно. После того, как мы закончили с конкретным диалогом, нам нужно вызвать метод dismiss , чтобы удалить его с экрана. Обратите внимание, что этот метод можно безопасно вызывать из любого потока. И последнее замечание в диалоге : вы можете определить для него заголовок (с помощью метода setTitle ) или указать, что заголовок для него не нужен (с помощью метода requestWindowFeature и функции FEATURE_NO_TITLE ).

В нашем приложении мы собираемся создать два разных типа диалогов: один для показа изображения постера фильма и один для представления обзора фильма. Давайте посмотрим фрагменты кода для тех.

Для диалога изображений постера у нас есть:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
final Dialog dialog = new Dialog(this);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.full_image_layout);
...
final Button closeDialogButton = (Button) dialog.findViewById(R.id.close_full_image_dialog_button);
imageView = (ImageView) dialog.findViewById(R.id.image_view);                               
closeDialogButton.setOnClickListener(new OnClickListener() {           
    @Override
    public void onClick(View v) {
        dialog.dismiss();
    }
});
final ImageDownloaderTask task = new ImageDownloaderTask();
task.execute(imageUrl);
         
dialog.show();

Обратите внимание, что изображение извлекается асинхронно с помощью ImageDownloaderTask (расширяет класс AsyncTask ), а ImageView заполняется в методе onPostExecute . Для этого диалогового окна макет XML называется «full_image_layout.xml» и имеет следующий вид:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="utf-8"?>
 
<LinearLayout
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
 
    <ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="5dip"
        android:layout_marginRight="5dip"
        android:layout_marginTop="5dip"
        android:layout_marginBottom="5dip"
       />
        
       <Button
        android:id="@+id/close_full_image_dialog_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/close"
        android:layout_weight="0.5"
        android:layout_marginLeft="10dip"
        android:layout_marginRight="10dip"
        android:layout_marginTop="5dip"
        android:layout_marginBottom="5dip"
        android:layout_gravity="center"
    />       
 
</LinearLayout>

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

Для диалога обзора фильма у нас есть следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.movie_overview_dialog);
 
dialog.setTitle(title);
 
final TextView overviewTextView = (TextView) dialog.findViewById(R.id.movie_overview_text_view);
overviewTextView.setText(overview);
         
final Button closeButton = (Button) dialog.findViewById(R.id.movie_overview_close_button);
         
closeButton.setOnClickListener(new OnClickListener() {           
    @Override
    public void onClick(View v) {
        dialog.dismiss();
    }
});
         
dialog.show();

Соответственно, файл макета XML называется «movie_overview_dialog.xml» и имеет следующий вид:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   >
    
   <ScrollView
           android:layout_width="fill_parent"
           android:layout_height="fill_parent">
            
           <LinearLayout
               android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:paddingLeft="5px"
            android:paddingRight="5px"
            android:paddingBottom="10px"
           >
    
            <TextView
                android:id="@+id/movie_overview_text_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_marginBottom="5dip"
                android:layout_marginLeft="5dip"
                android:layout_marginRight="5dip"
             />
                 
            <Button
                android:id="@+id/movie_overview_close_button"
                android:text="@string/close"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
            />
         
        </LinearLayout>
     
    </ScrollView>
     
</LinearLayout>

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

В целом класс MoviesListActivity теперь выглядит следующим образом:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package com.javacodegeeks.android.apps.moviesearchapp;
 
import java.io.InputStream;
import java.util.ArrayList;
 
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
 
import com.javacodegeeks.android.apps.moviesearchapp.io.FlushedInputStream;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.services.HttpRetriever;
import com.javacodegeeks.android.apps.moviesearchapp.ui.MoviesAdapter;
import com.javacodegeeks.android.apps.moviesearchapp.util.Utils;
 
public class MoviesListActivity extends ListActivity {
     
    private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
     
    private static final int ITEM_VISIT_IMDB = 0;
    private static final int ITEM_VIEW_FULL_IMAGE = 1;
     
    private ArrayList<Movie> moviesList = new ArrayList<Movie>();
    private MoviesAdapter moviesAdapter;
     
    private HttpRetriever httpRetriever = new HttpRetriever();
     
    private ProgressDialog progressDialog;
    private ImageView imageView;
     
    @SuppressWarnings("unchecked")
    @Override
    public void onCreate(Bundle savedInstanceState) {
         
        super.onCreate(savedInstanceState);
        setContentView(R.layout.movies_layout);
 
        moviesAdapter = new MoviesAdapter(this, R.layout.movie_data_row, moviesList);
        moviesList = (ArrayList<Movie>) getIntent().getSerializableExtra("movies");
         
        setListAdapter(moviesAdapter);
         
        if (moviesList!=null && !moviesList.isEmpty()) {
             
            moviesAdapter.notifyDataSetChanged();
            moviesAdapter.clear();
            for (int i = 0; i < moviesList.size(); i++) {
                moviesAdapter.add(moviesList.get(i));
            }
        }
         
        moviesAdapter.notifyDataSetChanged();
         
    }
     
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(Menu.NONE, ITEM_VISIT_IMDB, 0,
                getString(R.string.visit_imdb)).setIcon(android.R.drawable.ic_menu_set_as);
        menu.add(Menu.NONE, ITEM_VIEW_FULL_IMAGE, 0,
                getString(R.string.view_full_image)).setIcon(android.R.drawable.ic_menu_zoom);
        return super.onCreateOptionsMenu(menu);
    }
     
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case ITEM_VISIT_IMDB:
                visitImdbMoviePage();
                return true;
            case ITEM_VIEW_FULL_IMAGE:
                viewFullImagePoster();
                return true;
        }
        return false;
    }
     
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {       
        super.onListItemClick(l, v, position, id);
        final Movie movie = moviesAdapter.getItem((int)position);
        showMovieOverviewDialog(movie.name, movie.overview);       
    }
     
    private void viewFullImagePoster() {
         
        final Movie movie = retrieveSelectedMovie();
         
        if (movie==null) {
            longToast(getString(R.string.no_movie_selected));
            return;
        }
         
        String imageUrl = movie.retrieveCoverImage();
        if (Utils.isMissing(imageUrl)) {
            longToast(getString(R.string.no_imdb_id_found));
            return;
        }
         
        final Dialog dialog = new Dialog(this);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(R.layout.full_image_layout);
         
        final Button closeDialogButton = (Button) dialog.findViewById(R.id.close_full_image_dialog_button);
        imageView = (ImageView) dialog.findViewById(R.id.image_view);                               
         
        closeDialogButton.setOnClickListener(new OnClickListener() {           
            @Override
            public void onClick(View v) {
                dialog.dismiss();
            }
        });
         
        final ImageDownloaderTask task = new ImageDownloaderTask();
        task.execute(imageUrl);
         
        dialog.show();
         
        progressDialog = ProgressDialog.show(MoviesListActivity.this,
                "Please wait...", "Retrieving data...", true, true);
         
        progressDialog.setOnCancelListener(new OnCancelListener() {               
            @Override
            public void onCancel(DialogInterface dialog) {
                if (task!=null) {
                    task.cancel(true);
                }
            }
        });
         
    }
     
    private void visitImdbMoviePage() {
         
        final Movie movie = retrieveSelectedMovie();
         
        if (movie==null) {
            longToast(getString(R.string.no_movie_selected));
            return;
        }
         
        String imdbId = movie.imdbId;
        if (Utils.isMissing(imdbId)) {
            longToast(getString(R.string.no_imdb_id_found));
            return;
        }
         
        String imdbUrl = IMDB_BASE_URL + movie.imdbId;
        Intent imdbIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(imdbUrl));               
        startActivity(imdbIntent);
         
    }
     
    private void showMovieOverviewDialog(final String title, final String overview) {
         
        final Dialog dialog = new Dialog(this);
        dialog.setContentView(R.layout.movie_overview_dialog);
 
        dialog.setTitle(title);
 
        final TextView overviewTextView = (TextView) dialog.findViewById(R.id.movie_overview_text_view);
        overviewTextView.setText(overview);
         
        final Button closeButton = (Button) dialog.findViewById(R.id.movie_overview_close_button);
         
        closeButton.setOnClickListener(new OnClickListener() {           
            @Override
            public void onClick(View v) {
                dialog.dismiss();
            }
        });
         
        dialog.show();
         
    }
     
    private class ImageDownloaderTask extends AsyncTask<String, Void, Bitmap> {
         
        @Override
        protected Bitmap doInBackground(String... params) {
            String url = params[0];
            InputStream is = httpRetriever.retrieveStream(url);
            if (is==null) {
                return null;
            }
            return BitmapFactory.decodeStream(new FlushedInputStream(is));
        }
         
        @Override
        protected void onPostExecute(final Bitmap result) {           
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (progressDialog!=null) {
                        progressDialog.dismiss();
                        progressDialog = null;
                    }
                    if (result!=null) {
                        imageView.setImageBitmap(result);
                    }                   
                }
            });
        }
         
    }
     
    private Movie retrieveSelectedMovie() {
        int position = getSelectedItemPosition();
        if (position==-1) {
            return null;
        }
        return moviesAdapter.getItem((int)position);
    }
     
    private void longToast(CharSequence message) {
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    }
     
}

Это все, ребята. Вы можете найти здесь проект Eclipse, созданный до сих пор. Ура!

Статьи по Теме :