Допустим, вы хотите просмотреть некоторые изображения из альбома Facebook в приложении для Android. В целом достаточно просто позвонить в API Facebook, чтобы получить альбомы и фотографии. Внедрение всего этого в элементы управления пользовательским интерфейсом требует немного больше усилий, но не все так грубо. Для пользователя это выглядит как асинхронный процесс. Все вместе это делает для интересного учебника, тем более, что он содержит такие модные слова, как «Facebook» и «Android».
Темы, включенные в эту статью:
- Вызов API Graph Facebook и анализ результатов
- Использование виджетов Android Spinner и Gallery
- Выполнение UserTasks для загрузки внешних данных без зависания интерфейса
Когда мы все закончим, конечный продукт должен выглядеть примерно так:
Макеты
Мы собираемся создать два макета — один для основного занятия и один для диалога для отображения полноразмерных изображений.
Давайте сначала перейдем к основной деятельности. Нам нужен Spinner для выбора альбомов, TextView будет отображать описание альбома и сообщения для пользователя, и, наконец, Галерея для отображения миниатюр альбомов.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/TextViewSelectAlbum"
android:layout_marginTop="4px"
android:text="@string/selectalbum"
/>
<Spinner android:id="@+id/SpinnerSelectAlbum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/TextViewAlbumDescription"
/>
<Gallery android:id="@+id/GallerySelectedAlbum"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Теперь давайте создадим макет для диалога изображений, который просто содержит ImageView. Недостатком является то, что изображение будет масштабировано до размера экрана устройства. Кто-то умнее меня может вместо этого сделать панорамирование и масштабирование изображения.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ViewImageDialogRoot">
<ImageView
android:id="@+id/ImageViewSourceImage"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/downloading"
android:scaleType="centerInside">
</ImageView>
</LinearLayout>
Следующая часть довольно странная. Нам нужен стиль, который будет использоваться адаптером для галереи. Вместо того, чтобы идти с макетами, это должно быть в файле res / values / attrs.xml. Вы, вероятно, можете назвать это как-то иначе, но в любом случае это должно быть в res / values. Содержимое файла должно включать следующее:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AlbumGallery">
<attr name="android:galleryItemBackground" />
</declare-styleable>
</resources>
Другие задачи настройки
Нам нужно добавить пару строковых значений в res / values / strings.xml для URL-адресов Facebook. Магическое число 226949532466, которое вы видите ниже, должно быть изменено на любую страницу, с которой вы хотите просматривать альбомы. В этой демонстрации альбомы должны быть публичными в Facebook.
Мы будем использовать два API-интерфейса Facebook — один для получения списка альбомов для страницы, а другой для получения содержимого определенного альбома. Если вам интересно, как выглядит исходный код этих API, просто вставьте их в свой веб-браузер.
<!-- CHANGE 226949532466 TO YOUR FACEBOOK PAGE ID -->
<string name="facebook_url_albums">https://graph.facebook.com/226949532466/albums</string>
<string name="facebook_url_photos">https://graph.facebook.com/[ALBUMID]/photos</string>
Рано или поздно нам понадобится доступ к Интернету, поэтому добавьте разрешение в файл манифеста.
<uses-permission android:name="android.permission.INTERNET" />
Объекты данных
Как уже было отмечено, мы будем называть два разных API Facebook. Первый возвращает список альбомов — нам нужно захватить идентификаторы альбомов для последующего вызова, чтобы получить отдельные фотографии. Имя и описание также удобны, потому что они немного более значимы для пользователя, чем 12-значный идентификатор.
public class AlbumItem{
private String id;
private String name;
private String description;
public AlbumItem(String id,String name,String description){
this.id=id;
this.name=name;
this.description=description;
}
public String getId(){
return id;
}
public void setId(String id){
this.id=id;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public String getDescription(){
return description;
}
public void setDescription(String description){
this.description=description;
}
@Override
public String toString(){
//returning name because that is what will be displayed in the Spinner control
return(this.name);
}
}
Второй звонок сносит список фотографий в альбоме. Мы заботимся о двух вещах: URL-адрес изображения, который на самом деле является уменьшенным изображением, и исходный URL-адрес, который является полноразмерным изображением.
public class PhotoItem{
private String pictureUrl;
private String sourceUrl;
public PhotoItem(String pictureUrl,String sourceUrl){
this.pictureUrl=pictureUrl;
this.sourceUrl=sourceUrl;
}
public String getPictureUrl(){
return pictureUrl;
}
public void setPictureUrl(String pictureUrl){
this.pictureUrl=pictureUrl;
}
public String getSourceUrl(){
return sourceUrl;
}
public void setSourceUrl(String sourceUrl){
this.sourceUrl=sourceUrl;
}
}
Утилиты Классы
Мы извлекаем изображения из Интернета, иногда это занимает некоторое время, поэтому здесь есть небольшой кэш для их хранения. Это делает переключение между альбомами намного быстрее.
public abstract class ImageCache{
private static HashMap<String,Bitmap> hashMap;
public static synchronized Bitmap get(String imageUrl){
if(hashMap==null){hashMap=new HashMap<String,Bitmap>();}
return(hashMap.get(imageUrl));
}
public static synchronized void put(String imageUrl,Bitmap bitmap){
if(hashMap==null){hashMap=new HashMap<String,Bitmap>();}
hashMap.put(imageUrl,bitmap);
}
}
Вот метод для вызова службы REST через HTTP.
public abstract class RestInvoke{
public static String invoke(String restUrl) throws Exception{
String result=null;
HttpClient httpClient=new DefaultHttpClient();
HttpGet httpGet=new HttpGet(restUrl);
HttpResponse response=httpClient.execute(httpGet);
HttpEntity httpEntity=response.getEntity();
if(httpEntity!=null){
InputStream in=httpEntity.getContent();
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
StringBuffer temp=new StringBuffer();
String currentLine=null;
while((currentLine=reader.readLine())!=null){
temp.append(currentLine);
}
result=temp.toString();
in.close();
}
return(result);
}
}
Далее следует класс для анализа результатов вызовов API Facebook. Они возвращают все в формате JSON, а структуры данных просты.
public abstract class FacebookJSONParser{
public static String parseLatestStatus(String json){
String latestStatus="";
String startTag="\"message\":\"";
int indexOf=json.indexOf(startTag);
if(indexOf>0){
int start=indexOf+startTag.length();
String endTag="\",";
return(json.substring(start,json.indexOf(endTag,start)));
}
return(latestStatus);
}
public static ArrayList<AlbumItem> parseAlbums(String json) throws JSONException{
ArrayList<AlbumItem> albums=new ArrayList<AlbumItem>();
JSONObject rootObj=new JSONObject(json);
JSONArray itemList=rootObj.getJSONArray("data");
int albumCount=itemList.length();
for(int albumIndex=0;albumIndex<albumCount;albumIndex++){
JSONObject album=itemList.getJSONObject(albumIndex);
String description="";
try{
description=album.getString("description");
}catch(JSONException x){/*not implemented*/}
albums.add(new AlbumItem(album.getString("id"),album.getString("name"),description));
}
return(albums);
}
public static ArrayList<PhotoItem> parsePhotos(String json) throws JSONException{
ArrayList<PhotoItem> photos=new ArrayList<PhotoItem>();
JSONObject rootObj=new JSONObject(json);
JSONArray itemList=rootObj.getJSONArray("data");
int photoCount=itemList.length();
for(int photoIndex=0;photoIndex<photoCount;photoIndex++){
JSONObject photo=itemList.getJSONObject(photoIndex);
photos.add(new PhotoItem(photo.getString("picture"),photo.getString("source")));
}
return(photos);
}
}
Наконец, у нас есть адаптер, который будет хранить элементы в виджете Галерея.
public class AlbumImageAdapter extends BaseAdapter{
private final static String TAG="AlbumImageAdapter";
private ArrayList<PhotoItem> photos;
private Context context;
private int albumGalleryItemBackground;
public AlbumImageAdapter(Context context,ArrayList<PhotoItem> photos){
this.context=context;
this.photos=photos;
TypedArray ta=context.obtainStyledAttributes(R.styleable.AlbumGallery);
albumGalleryItemBackground=ta.getResourceId(R.styleable.AlbumGallery_android_galleryItemBackground,0);
ta.recycle();
}
@Override
public int getCount(){
return(this.photos.size());
}
@Override
public Object getItem(int position){
return(this.photos.get(position));
}
@Override
public long getItemId(int position){
return(position);
}
@Override
public View getView(int position,View convertView,ViewGroup parent){
ImageView imageView=new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setBackgroundResource(albumGalleryItemBackground);
//load the image
String pictureUrl=this.photos.get(position).getPictureUrl();
try{
Bitmap bitmap=ImageCache.get(pictureUrl);
if(bitmap==null){
bitmap=HttpFetch.fetchBitmap(pictureUrl);
ImageCache.put(pictureUrl,bitmap);
}
imageView.setImageBitmap(bitmap);
}catch(Exception x){
Log.e(TAG,"getView",x);
}
return(imageView);
}
}
Активность
Мы собираемся создать только одно действие с метким названием FacebookAlbumDemoActivity.
private final static String TAG="FacebookAlbumDemoActivity";
private ArrayAdapter<AlbumItem> albumArrayAdapter;
private AlbumItem selectedAlbum;
private PhotoItem selectedImage;
//references to ui controls
private Spinner spinnerSelectAlbum;
private TextView textViewAlbumDescription;
private Gallery gallerySelectedAlbum;
private ImageView imageViewSelectedImage;
//In the OnCreate method let's grab references to all the UI widgets.
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.spinnerSelectAlbum=(Spinner)this.findViewById(R.id.SpinnerSelectAlbum);
this.spinnerSelectAlbum.setOnItemSelectedListener(new AlbumSelectedListener());
this.textViewAlbumDescription=(TextView)this.findViewById(R.id.TextViewAlbumDescription);
this.gallerySelectedAlbum=(Gallery)this.findViewById(R.id.GallerySelectedAlbum);
this.gallerySelectedAlbum.setOnItemClickListener(new OnPhotoClickedListener());
}
//In the OnResume method (which also fires after OnCreate is complete) we'll start the UserTask to get the albums. All the UserTasks are at the end of this tutorial, after we wire up the UI we'll tackle them, be patient.
@Override
protected void onResume(){
super.onResume();
//check for albums
if((this.albumArrayAdapter==null)||(this.albumArrayAdapter.getCount()<1)){
new GetAlbumsTask().execute("");
}
}
//Here's the listener for the Spinner used to select an album.
public class AlbumSelectedListener implements OnItemSelectedListener{
public void onItemSelected(AdapterView<?> parent,View view,int pos,long id){
selectedAlbum=(AlbumItem)spinnerSelectAlbum.getItemAtPosition(pos);
updateAlbum();
}
public void onNothingSelected(AdapterView<?> parent){/* not implemented */}
}
//Here's the listener for when an image is clicked in the Gallery.
public class OnPhotoClickedListener implements OnItemClickListener{
@Override
public void onItemClick(AdapterView<?> parent,View view,int position,long id){
selectedImage=(PhotoItem)gallerySelectedAlbum.getItemAtPosition(position);
showImageDialog();
}
}
private void updateAlbum(){
this.gallerySelectedAlbum.setEnabled(false);
this.textViewAlbumDescription.setText("Loading "+this.selectedAlbum.getName()+"...");
new UpdateAlbumGalleryTask().execute("");
}
//Show the full-size image dialog.
private void showImageDialog(){
try{
//create the dialog
LayoutInflater inflater=(LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE);
View layout=inflater.inflate(R.layout.viewimagelayout,(ViewGroup)findViewById(R.id.ViewImageDialogRoot));
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setView(layout);
builder.setTitle("View Image");
builder.setPositiveButton("Close",null);
AlertDialog imageDialog=builder.create();
//show the dialog
imageDialog.show();
this.imageViewSelectedImage=(ImageView)imageDialog.findViewById(R.id.ImageViewSourceImage);
//load the image
new DownloadImageTask().execute("");
}catch(Exception x){
Log.e(TAG,"showImageDialog",x);
}
}
Задачи пользователя
API Facebook очень быстрый, но сетевое время на реальном устройстве может быть нечетким. Итак, давайте поместим все внешние вызовы в UserTasks, чтобы получить данные и изображения в фоновом режиме. Поток тоже может работать, но это сложнее, потому что потоки не могут касаться каких-либо виджетов в Activity. UserTasks, с другой стороны, могут изменять виджеты.
//UserTask to retrieve the albums.
private class GetAlbumsTask extends UserTask<String,String,String>{
public String doInBackground(String... params){
try{
//invoke the API to get albums
long startTime=System.currentTimeMillis();
String albumsJson=RestInvoke.invoke(getResources().getString(R.string.facebook_url_albums));
long endTime=System.currentTimeMillis();
Log.d(TAG,"Time to download albums="+(endTime-startTime)+"ms");
//parse the albums
startTime=endTime;
ArrayList<AlbumItem> albums=FacebookJSONParser.parseAlbums(albumsJson);
endTime=System.currentTimeMillis();
Log.d(TAG,"Time to parse albums="+(endTime-startTime)+"ms");
//update the select album spinner
if(albums.size()>0){
albumArrayAdapter=new ArrayAdapter<AlbumItem>(getApplicationContext(),android.R.layout.simple_spinner_item,albums);
}
return("");
}catch(Exception x){
Log.e(TAG,"GetAlbumsTask",x);
return(null);
}
}
public void onPostExecute(String result){
if((albumArrayAdapter==null)||(albumArrayAdapter.isEmpty())){
ArrayList<String> defaultList=new ArrayList<String>();
defaultList.add("No albums found");
ArrayAdapter<String> defaultAdapter=new ArrayAdapter<String>(getApplicationContext(),android.R.layout.simple_spinner_item,defaultList);
spinnerSelectAlbum.setAdapter(defaultAdapter);
spinnerSelectAlbum.setEnabled(false);
}else{
spinnerSelectAlbum.setAdapter(albumArrayAdapter);
spinnerSelectAlbum.setEnabled(true);
}
}
}
//UserTask to retrieve the images from an album when one is selected.
private class UpdateAlbumGalleryTask extends UserTask<String,String,ArrayList<PhotoItem>>{
public ArrayList<PhotoItem> doInBackground(String... urls){
try{
Resources r=getResources();
//invoke the API to get posts
long startTime=System.currentTimeMillis();
String facebookUrlPhotos=(r.getString(R.string.facebook_url_photos)).replace("[ALBUMID]",selectedAlbum.getId());
String photosJson=RestInvoke.invoke(facebookUrlPhotos);
long endTime=System.currentTimeMillis();
Log.d(TAG,"Time to download photos="+(endTime-startTime)+"ms");
//parse out the latest status
startTime=endTime;
ArrayList<PhotoItem> photos=FacebookJSONParser.parsePhotos(photosJson);
endTime=System.currentTimeMillis();
Log.d(TAG,"Time to parse latest status="+(endTime-startTime)+"ms");
return(photos);
}catch(Exception x){
Log.e(TAG,"UpdateAlbumGalleryTask",x);
return(null);
}
}
public void onPostExecute(ArrayList<PhotoItem> photos){
if((photos==null)||(photos.size()<1)){
textViewAlbumDescription.setText("Unable to download "+selectedAlbum.getName());
}else{
textViewAlbumDescription.setText(selectedAlbum.getDescription());
gallerySelectedAlbum.setAdapter(new AlbumImageAdapter(getApplicationContext(),photos));
}
}
}
//UserTask to download an image.
private class DownloadImageTask extends UserTask<String,String,Bitmap>{
public Bitmap doInBackground(String... params){
try{
Log.d(TAG,"DownloadImageTask.doInBackground");
String imageUrl=selectedImage.getSourceUrl();
Log.d(TAG,"DownloadImageTask.doInBackground, imageUrl="+imageUrl);
Bitmap bitmap=ImageCache.get(imageUrl);
if(bitmap!=null){return(bitmap);}
long startTime=System.currentTimeMillis();
bitmap=HttpFetch.fetchBitmap(imageUrl);
long endTime=System.currentTimeMillis();
Log.d(TAG,"DownloadEpisodeLogoTask.doInBackground, Time to fetch bitmap="+(endTime-startTime)+"ms");
ImageCache.put(imageUrl,bitmap);
return(bitmap);
}catch(IOException iox){
Log.e(TAG,"DownloadImageTask",iox);
return(null);
}
}
public void onPostExecute(Bitmap result){
imageViewSelectedImage.setImageBitmap(result);
}
}
Посмотрите оригинальную статью и загрузите полный исходный код здесь.