23.10 挑战练习:Gson
无论什么平台,把JSON数据转化为Java对象都是应用开发的常见任务,如代码清单23-12所 做的那样。于是,聪明的开发者就创建了一些工具库,希望能简化JSON数据和Java对象的互转。 Gson就是这样的一个工具库(https://github.com/google/gson)。不用写任何解析代码,Gson 就能自动把JSON数据映射为Java对象。因为这个特性,Gson现在是开发者最喜爱的JSON解析库。 挑战自己,在应用中整合Gson库,简化 FlickrFetchr 中的JSON解析代码。
要使用GSON,首先要添加GSON库的依赖。在dependencies闭包中添加如下内容:
compile 'com.google.code.gson:gson:2.7'
GSON可以将一段JSON格式的字符串自动映射成一个对象。
编写GalleryItemBean类,代码如下:
1 public class GalleryItemBean { 2 3 @SerializedName("photos") 4 public Photos mPhotos; 5 6 7 public static class Photos{ 8 @SerializedName("photo") 9 public List<GalleryItem> photo; 10 11 public List<GalleryItem> getPhoto() { 12 return photo; 13 } 14 15 public void setPhoto(List<GalleryItem> photo) { 16 this.photo = photo; 17 } 18 } 19 20 public Photos getPhotos() { 21 return mPhotos; 22 } 23 24 public void setPhotos(Photos photos) { 25 mPhotos = photos; 26 } 27 }
这里没有去重写GalleryItem类,而是在类中使用了GalleryItem类。
GSON要求类属性和JSON字段里面的KEY一模一样,但是可以通过在类属性的上面增加@SerializedName(String)来为属性添加注解。
JSON数据中的photo字段里面的内容是用[]括起来的。所以应在CalleryItemBean类中将photo字段定义为List集合。
接着修改GalleryItem类,为其添加注解:
1 public class GalleryItem { 2 3 @SerializedName("title") 4 private String mCaption; 5 6 @SerializedName("id") 7 private String mId; 8 9 @SerializedName("url_s") 10 private String mUrl; 11 12 @Override 13 public String toString(){ 14 return mCaption; 15 } 16 17 public String getCaption() { 18 return mCaption; 19 } 20 21 public void setCaption(String caption) { 22 mCaption = caption; 23 } 24 25 public String getId() { 26 return mId; 27 } 28 29 public void setId(String id) { 30 mId = id; 31 } 32 33 public String getUrl() { 34 return mUrl; 35 } 36 37 public void setUrl(String url) { 38 mUrl = url; 39 } 40 }
最后修改FlickrFetcher类中的代码,增加如下方法:
1 private void parseItems(List<GalleryItem> items, String jsonData){ 2 Gson gson = new Gson(); 3 GalleryItemBean gib = gson.fromJson(jsonData,GalleryItemBean.class); //GSON解析。将json数据传入到CalleryItemBean类中。 4 List<GalleryItem> photoList = gib.getPhotos().getPhoto(); 5 6 for(int i=0; i<photoList.size(); i++){ 7 GalleryItem photoItem = photoList.get(i); 8 items.add(photoItem); 9 } 10 }
增加了一个通过GSON方式解析json数据的方法。
最后在fetchItems()方法中调用即可。
parseItems(items,jsonString);
23.11 挑战练习:分页
getRecent 方法默认返回一页包含100个结果的数据。不过,该方法还有个叫作 page 的参数,
可以用它返回第二页、第三页等更多页数据。
请实现一个 RecyclerView.OnScrollListener 方法,只要用户看完当前页,就使用下页返
回结果替换当前页。想更有挑战的话,可以尝试把后续结果页添加到当前结果页后面。
修改FlickrFetcher中的fetchItems()方法,为其增加一个int 参数,作为页面的切换数。并且增加一个appenQueryParameter("page",String.valueOf(count))用来切换页面。
public List<GalleryItem> fetchItems(int count){ List<GalleryItem> items = new ArrayList<>(); try{ String url = Uri.parse("https://api.flickr.com/services/rest/") .buildUpon() .appendQueryParameter("page",String.valueOf(count)) //显示传进来的参数的页面。 .appendQueryParameter("method", "flickr.photos.getRecent") .appendQueryParameter("api_key", API_KEY) .appendQueryParameter("format", "json") .appendQueryParameter("nojsoncallback", "1") .appendQueryParameter("extras", "url_s") .build().toString(); ...... }
接下来在PhotoGalleryFragment中增加一个全局变量,用来传入fetchItems()方法。
private int mCount = 1;
修改doInBackground()方法
protected List<GalleryItem> doInBackground(Void... parms){ mCount++; // 页码数加一 return new FlickrFetcher().fetchItems(mCount); }
最后一步设置RecyclerView的滑动监听事件。
在onCreateView()方法中增加一下代码。
//设置滑动监听事件 mPhotoRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { int lastVisibleItem = mGridLayoutManager.findLastCompletelyVisibleItemPosition(); //最后位置 int totalItemCount = mGridLayoutManager.getItemCount(); //总位置 // 判断是否滚动到底部 if (lastVisibleItem == (totalItemCount - 1)) { //开启新的AsyncTask,因为一个AsyncTask只能execute()一次。 new FetchItemsTask().execute(); Log.d(TAG, "滑倒底部了"); } } });
将后续结果页添加到当前结果页后面。
在PhotoGalleryFragment定义一个全局变量,用来添加所有的出现过的数据。
private List<GalleryItem> mGalleryItemList = new ArrayList<>();
修改PhotoAdapter的构造方法
public PhotoAdapter(List<GalleryItem> galleryItems){
for(GalleryItem item : galleryItems){
mGalleryItemList.add(item);
}
}
之后将PhotoAdapter类中的mGalleryItems全部替换为mGalleryItemList即可。
代码如下:
private class PhotoAdapter extends RecyclerView.Adapter<PhotoHolder>{
private List<GalleryItem> mGalleryItems;
public PhotoAdapter(List<GalleryItem> galleryItems){
for(GalleryItem item : galleryItems){
mGalleryItemList.add(item);
}
}
@Override
public PhotoHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TextView textView = new TextView(getActivity());
return new PhotoHolder(textView);
}
@Override
public void onBindViewHolder(PhotoHolder photoHolder, int position) {
GalleryItem galleryItem = mGalleryItemList.get(position);
photoHolder.bindGalleryItem(galleryItem);
}
@Override
public int getItemCount() {
return mGalleryItemList.size();
}
}
23.12 挑战练习:动态调整网格列
当前,显示图片标题的网格固定有3列。编写代码动态调整网格列数,实现在横屏或大屏幕 设备上显示更多的标题列。 实现这个目标有个简单方法:分别为不同的设备配置或屏幕尺寸提供整数修饰资源。这实际 和第17章中为不同尺寸屏幕提供不同布局的方式差不多。整数修饰资源应放置在res/values目录 中。具体实施细节可参阅Android开发者文档。 提供整数修饰资源的方式不太好确定网格列细分粒度(只能凭经验预先定义列数)。下面再 介绍一个颇具挑战的方法:在fragment的视图创建时就计算并设置好网格列数。显然,这种方式 更加灵活实用。基于 RecyclerView 的当前宽度和预定义网格列宽,就可以计算出列数。 实施前还有个问题要解决:我们不能在 onCreateView() 方法中计算网格列数,因为这个时 候 RecyclerView 还没有改变。不过,可以实现 ViewTreeObserver.OnGlobalLayoutListener 监听器方法和计算列数的 onGlobalLayout() 方法,然后使用 addOnGlobalLayoutListener() 把监听器添加给 RecyclerView 视图。
在PhotoGalleryFragment的onCreateView()方法增加如下代码即可:
mPhotoRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { //根据最大的值来计算列数 int width = getActivity().getWindowManager().getDefaultDisplay().getWidth(); int height = getActivity().getWindowManager().getDefaultDisplay().getHeight(); int scale = 0; if(width > height) scale = width; else scale = height; int columns = scale / 200; mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(),columns)); setupAdapter(); mPhotoRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);//移除掉。。 } });
完整代码如下:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ final View v = inflater.inflate(R.layout.fragment_photo_gallery, container, false); mPhotoRecyclerView = (RecyclerView)v.findViewById(R.id.fragment_photo_gallery_recycler_view); mGridLayoutManager = new GridLayoutManager(getActivity(), mRows); mPhotoRecyclerView.setLayoutManager(mGridLayoutManager); //设置滑动监听事件 mPhotoRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { int lastVisibleItem = mGridLayoutManager.findLastCompletelyVisibleItemPosition(); //最后位置 int totalItemCount = mGridLayoutManager.getItemCount(); //总位置 // 判断是否滚动到底部 if (lastVisibleItem == (totalItemCount - 1)) { //开启新的AsyncTask,因为一个AsyncTask只能execute()一次。 new FetchItemsTask().execute(); Log.d(TAG, "滑倒底部了"); } } }); setupAdapter(); //要写在mPhotoRecyclerView实例化之后 mPhotoRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { //根据最大的值来计算列数 int width = getActivity().getWindowManager().getDefaultDisplay().getWidth(); int height = getActivity().getWindowManager().getDefaultDisplay().getHeight(); int scale = 0; if(width > height) scale = width; else scale = height; int columns = scale / 200; mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(),columns)); setupAdapter(); mPhotoRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);//移除掉。。 } }); return v; }