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;
    }