Android实战json解析+GridView自适应布局+图片加载

Posted 帥酥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android实战json解析+GridView自适应布局+图片加载相关的知识,希望对你有一定的参考价值。

如何规范化的完成一个需要网络加载数据的页面?

先来看一下效果图


1、Json数据获取解析

查看Json数据,这里推荐使用chrome自带扩展程序JSONView:Validate and view JSON documents,但是可能需要翻墙,如下所示:



准备好工具了,我们接下来看我们如何实现对数据的json解析,假设我们需要的数据来源为:

public class UrlContainer 

    public static final String HOST_URL = "http://search.shouji.baofeng.com/";

    public static final String LEFT_EYE_CINEMA = HOST_URL
            + "lefteye.php?platf=android&mtype=normal&g=23&ver=5.2.10&td=0&s=14D910696A44EC703DBDF57F8486114EAF1AE125";


通过将完整url输入到浏览器当中,我们会加载出如下格式化的数据(url后面的一连串参数不用管)

从返回的数据我们可以看出,result是一个Json数组,包含四个成员,每个成员的items又是一个json数组。

items中的每一个子项:

  
     "cover_url": " http://img.shouji.baofeng.com/img/595/t595_a24070_120*160.4.jpg", 
     "sub_cover_url": " http://img.shouji.baofeng.com/img/595/st595_a24070_272*145.1.jpg", 
     "detail": 
               "id": "24070", 
               "title": "寒战", 
               "type": "1", 
               "year": "2012", 
               "total": "1", 
               "has": "[1]", 
               "sites": "[\\"qiyi\\",\\"m1905\\",\\"tencent\\"]", 
               "cover_v": "\\"file_id\\":\\"33c3d7fc4f15efa2a128cc9e5eebee80\\",\\"size\\":21822,\\"version\\":4,\\"manual\\":1", 
               "cover_h": "\\"file_id\\":\\"141baa8258d57316ee92219f84108bb0\\",\\"size\\":302456,\\"version\\":6,\\"manual\\":1", 
               "score": "8.5", 
               "clicks": 2414594, 
               "finish": "1", 
               "3d": "0", 
               "status": "7", 
               "update_time": "2015-06-11 05:06:01", 
               "merge_id": "0", 
               "duration": "6621", 
               "video_count": "3", 
               "cover_url": " http://img.shouji.baofeng.com/img/070/v24070_255*342.4.jpg", 
               "cover_h_url": " http://img.shouji.baofeng.com/img/070/hh24070_400*225_2.6.jpg", 
               "update_at": "2015-06-11", 
               "area_name": [ 
                            "港台" 
                        ], 
               "actors_name": [ 
                            "刘德华", 
                            "郭富城", 
                            "梁家辉", 
                            "杨采妮" 
                        ], 
               "directors_name": [ 
                            "陆剑青", 
                            "梁乐民" 
                        ], 
               "max_site": "qiyi", 
               "last_seq": "1", 
               "ending": 0, 
               "danmaku": "0" 
 

通过继续查看items,我们会发现也是层层嵌套,对于这种比较复杂的json格式,我们应该如何解析那?其实我们会发现,对于返回的数据,我们并不一定全部获取,我们只要按照我们的需求进行获取就行

接下来按需构建实体类LeftEyeItem和LeftEyeItems

public class LeftEyeItem implements Cloneable, Serializable 
    
    private static final long serialVersionUID = 1L;

    private String cover_url;

    /** 每期的id */
    private String periodId;

    /** 每期里面视频项的id */
    private String id;

    private String title;

    private String desc;

    private String titleDate;

    private String site;

    private String threeD;

    private String has;
    
    ...


public class LeftEyeItems implements Cloneable, Serializable 

    private static final long serialVersionUID = 1L;


    private ArrayList<LeftEyeItem> items = new ArrayList<LeftEyeItem>();


    public ArrayList<LeftEyeItem> getItems() 
        return items;
    

    public void setItems(ArrayList<LeftEyeItem> items) 
        this.items = items;
    

    public void addItem(LeftEyeItem item) 
        items.add(item);
    

这里我们启用了一个线程进行数据的异步加载,具体实现如下:

这里需要注意两点:

1、json数据的按需解析具体如何对应的,大家可以仔细对比一下(根据代码中key值跟json数据的具体字段做对比)

2、就是该段代码的实现将Activity中定义的Handler作为线程构造函数的参数,其实这样并不是很好,

增加了activity中Handler与线程的耦合性,比较好的方法就是使用接口回调的方式,

可以参考http://blog.csdn.net/s003603u/article/details/46813509

                   

public class LeftEyeLoadThread extends Thread 
    private Context context;

    private Handler handler;

    private String url;

    public static final int LOADING_NORMAL = 0;

    public static final int LOADING_FIRT = 1;

    public static final int LOADING_SORT = 2;

    public LeftEyeLoadThread(Context context, Handler handler, String url) 
        super();
        this.context = context;
        this.handler = handler;
        this.url = url;
    

    @Override
    public void run() 
        LeftEyeItems showItems;
        try 
            showItems = doGet();
            if (showItems == null) 
                HandlerMsgUtils.sendMsg(handler, LeftEyeCinemaActivity.MSG_ID_LOADING_FAILED);
             else 
                HandlerMsgUtils.sendMsg(handler, LeftEyeCinemaActivity.MSG_ID_LOADING_SUCCESS, showItems);
            
         catch (Exception e) 
            HandlerMsgUtils.sendMsg(handler, LeftEyeCinemaActivity.MSG_ID_LOADING_FAILED);
        
    

    private LeftEyeItems doGet() throws MalformedURLException, ProtocolException, SocketException,
            IOException, CustomException, JSONException 
        LeftEyeItems items = new LeftEyeItems();
        String jsonString = NetUtils.getJsonStringFrUrl(context, url);
        if (jsonString == null || "".equals(jsonString.trim()) || "[]".equals(jsonString.trim())) 
            throw new JSONException("josn is null");
        

        JSONObject jsonObj;
        JSONArray result;
        String periodId;
        try 
            jsonObj = new JSONObject(jsonString);
            result = jsonObj.getJSONArray("result");
            for (int i = 0; i < result.length(); i++) 
                JSONObject obj1 = result.getJSONObject(i);
                periodId = obj1.getString("id");
                    JSONArray arr2 = obj1.getJSONArray("items");
                    for (int j = 0; j < arr2.length(); j++) 
                        LeftEyeItem item = new LeftEyeItem();
                        JSONObject obj3 = arr2.getJSONObject(j);
                        item.setperiodId(periodId);
                        item.setCover_url(obj3.getString("sub_cover_url"));
                        item.setDesc(obj3.getString("desc"));
                        item.setId(obj3.getString("id"));
                        item.setTitle(obj3.getString("title"));
                        item.setSite(obj3.getJSONObject("detail").getString("max_site"));
                        item.setThreeD(obj3.getJSONObject("detail").getString("3d"));
                        item.setHas(obj3.getJSONObject("detail").getString("has"));
                        items.addItem(item);
                    
                
         catch (Exception e) 
            return null;
        
        return items;
    

2、构建adapter

这次实现我们准备使用自适应的gridview,首先看activity的界面布局

  <!-- 主体内容 -->
    <RelativeLayout
        android:id="@+id/activity_left_eye_cinema_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/activity_left_eye_cinema_title_layout" >
        <GridView
            android:id="@+id/activity_left_eye_cinema_gridview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/web_img_spacing"
            android:layout_marginRight="@dimen/web_img_spacing"
            android:layout_marginTop="@dimen/web_img_spacing"
            android:cacheColorHint="#00000000"
            android:fadingEdge="none"
            android:horizontalSpacing="@dimen/web_img_spacing"
            android:listSelector="@drawable/hide_gridview_yellow_selector"
            android:numColumns="auto_fit"
            android:stretchMode="columnWidth"
            android:verticalSpacing="@dimen/web_img_spacing" >
        </GridView>
    </RelativeLayout>
注意:这里我们设置GridView的numColumns为atuo_fit

LeftEyeAdapter中gridview的子布局其实很简单,就只有一张影片图片和电影名字,就不赘述了,其实自适应最主要的就是根据设置的图片宽度,图片之间的间隔,手机屏幕的宽度来计算gridview将显示几列 ,这里我们让LeftEyeCinemaActivity实现ViewTreeObserver.OnGlobalLayoutListener接口,这个接口的作用是:

Interface definition for a callback to be invoked when the global layout state  or the visibility of views within the view tree changes.

其实就是当一个View的布局加载完成或者布局发生改变时,OnGlobalLayoutListener可以监听到,具体来看

  @Override
    public void onGlobalLayout() 
        if (mAdapter != null && mAdapter.getNumColumns() <= 0) 
            getScreenWidth();
            final int numColumns = (int) Math
                    .floor(screenWidth / (mImageThumbWidth + mImageThumbSpacing));
            int columnWidth; 
            if (numColumns > 0) 
                columnWidth= (screenWidth - (numColumns + 1) * mImageThumbSpacing) / numColumns;
                mAdapter.setNumColumns(numColumns);
             else 
                columnWidth = screenWidth - 2 * mImageThumbSpacing;
                mAdapter.setNumColumns(1);

            
            mAdapter.setItemHeight(columnWidth);
            gridView.setColumnWidth(columnWidth);
        
    
我们首先会获取到手机屏幕的宽度,然后基于事先设置好的图片缩略图宽度和图片间距来计算列数,然后根据列数来计算每一列的宽度,如果我们设置的图片宽度过大的话,我们会基于屏幕宽度只显示一列,在adapter中我们基于imgWidthParam计算出图片的高度

 /**
     * Sets the item height. Useful for when we know the column width so the
     * height can be set to match.
     *
     * @param height
     */
    public void setItemHeight(int width) 
        mImageWidth = width;
        mItemHeight = (int) (mImageWidth * imgWidthParam);
        mImageViewLayoutParams = new RelativeLayout.LayoutParams(mImageWidth, mItemHeight);
        notifyDataSetChanged();
    
3、图片加载

这里面关于图片加载的管理,我们使用的是图片处理开源库Universal-image-loader,这个大家可以去github上去找,这里我只是介绍一下在我们这个项目中怎么简单地使用,首先你要导入jar包,然后就是初始化,这里我们使用Application进行全局统一管理

public class LeftEyeApplication extends Application 
    private static LeftEyeApplication instance;

    @Override
    public void onCreate() 
        instance = this;
        super.onCreate();
        ImageUtil.initImageLoader(this);
    
    
    public static LeftEyeApplication getInstance() 
        return instance;
    

初始化(具体相关的细节和参数设置我就不细说了)

public class ImageUtil 
    /**
     * 初始化图片加载相关
     * 
     * @param context
     */
    public static void initImageLoader(Context context) 
        File cacheDir = StorageUtils.getCacheDirectory(context);
        // int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // // 使用最大可用内存值的1/8作为缓存的大小。
        // int cacheSize = maxMemory / 8;

        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
                // max width, max height,即保存的每个缓存文件的最大长宽
                .diskCacheExtraOptions(720, 1280, null).threadPriority(Thread.NORM_PRIORITY - 2)
                .diskCache(new UnlimitedDiscCache(cacheDir)).denyCacheImageMultipleSizesInMemory()
                .diskCacheSize(50 * 1024 * 1024).diskCacheFileCount(1000)
                .diskCacheFileNameGenerator(new Md5FileNameGenerator())
                .tasksProcessingOrder(QueueProcessingType.LIFO).memoryCacheSizePercentage(15)
                .build();
        ImageLoader.getInstance().init(config);
    

OK!,现在我们就可以在adapter中进行使用了

@Override
    public View getView(int position, View convertView, ViewGroup parent) 
        ViewHolder holder = null;
        if (convertView == null) 
            convertView = mInflater.inflate(R.layout.left_eye_cinema_item, null);
            holder = new ViewHolder();
            holder.image = (ImageView) convertView.findViewById(R.id.web_normal_view_item_img);
          
            holder.title = (TextView) convertView.findViewById(R.id.web_normal_view_item_title);
            
            convertView.setTag(holder);
         else 
            holder = (ViewHolder) convertView.getTag();
        
        if (holder.image.getLayoutParams().width != mImageWidth) 
            holder.image.setLayoutParams(mImageViewLayoutParams);
        
            ImageLoader.getInstance().displayImage(items.get(position).getCover_url(),
                    holder.image, options);

        holder.title.setText(items.get(position).getTitle());
        return convertView;
    
如上使用了displayImage方法,其余用法,朋友们可以自己研究

4、异常处理响应

这里主要就是当页面加载时的loading界面,页面加载异常的异常界面,以及通过广播监听网络状态,加载失败后自动加载的处理

,而对于这些不是经常出现的界面,我们使用了轻量级控件ViewStub,这里就涉及到布局优化的问题,

可以参考http://blog.csdn.net/s003603u/article/details/46841267的大纲进行研究

  <!-- 加载 -->
    <ViewStub
        android:id="@+id/viewstub_left_eye_cinema_loading"
        android:layout_width="match_parent"
    	android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:layout_below="@id/activity_left_eye_cinema_title_layout"
        android:inflatedId="@+id/viewstub_inflate_left_eye_cinema_loading"
        android:layout="@layout/common_loading" />

    <!-- 异常提示 -->
    <!-- 无网络,服务器更新 -->
    <ViewStub
        android:id="@+id/viewstub_left_eye_cinema_tips"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/activity_left_eye_cinema_title_layout"
        android:inflatedId="@+id/viewstub_inflate_left_eye_cinema_tips"
        android:layout="@layout/common_tips_layout" />

这里主要使用了一个netModeManager管理类进行管理,主要包括网络状态的检测,网络模式的刷新与切换,已经通过接口调用对不同网络模式的响应,这里就不贴代码了,大家可以下载demo自行研究,activity中相关

class MyNetModeStatusListener implements OnNetModeChangeListener 

        @Override
        public void onUpdateData() 
            switchTargetView(0, OnTipsListener.TIPS_NONE);
            initData();
        

        @Override
        public void onShowNoNetView() 
            switchTargetView(2, OnTipsListener.TIPS_NO_NETWORK);
        

        @Override
        public void onShowNetModeView() 
            switchTargetView(3, OnTipsListener.TIPS_NONE);
        

        @Override
        public void onHideNetModeView() 
            initData();
        
    

 /**
     * 切换显示页面
     * 
     * @param pageType
     * @param exceptionType
     */
    private void switchTargetView(int pageType, int exceptionType) 
        View loadingView = (View) findViewById(R.id.viewstub_inflate_left_eye_cinema_loading);
        View tipsView = (View) findViewById(R.id.viewstub_inflate_left_eye_cinema_tips);

        switch (pageType) 
            case 0: 
                if (tipsView != null) 
                    tipsView.setVisibility(View.INVISIBLE);
                
                if (loadingView != null) 
                    loadingView.setVisibility(View.INVISIBLE);
                
                if (netModeManager != null) 
                    netModeManager.dismissZeroFlowView();
                
                break;
            
            case 1: 
                // 加载页面
                View view = inflateSubView(R.id.viewstub_left_eye_cinema_loading,
                        R.id.viewstub_inflate_left_eye_cinema_loading);
                if (view != null) 
                    String[] list = getResources().getStringArray(R.array.common_loading_text);
                    int index = (int) (Math.random() * list.length);
                    TextView txt = (TextView) view.findViewById(R.id.lay_progressbar_text);
                    txt.setText(list[index]);
                    view.setVisibility(View.VISIBLE);
                
                if (tipsView != null) 
                    tipsView.setVisibility(View.INVISIBLE);
                
                if (netModeManager != null) 
                    netModeManager.dismissZeroFlowView();
                
                break;
            
            case 2: 
                // 异常页面
                View view = inflateExceptionSubView(R.id.viewstub_left_eye_cinema_tips,
                        R.id.viewstub_inflate_left_eye_cinema_tips, exceptionType, this);
                if (view != null) 
                    view.setVisibility(View.VISIBLE);
                
                if (loadingView != null) 
                    loadingView.setVisibility(View.INVISIBLE);
                
                if (netModeManager != null) 
                    netModeManager.dismissZeroFlowView();
                
                break;
            
            default:
                break;
        
    

5、补充

我们再来说说其它一些细节:

1)在使用Handler时避免activity内存泄露的方法

不使用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理

    /**
     * Handler的处理事件
     */
    private static class MyHandler extends Handler 
        WeakReference<LeftEyeCinemaActivity> thisLayout;

        MyHandler(LeftEyeCinemaActivity layout) 
            thisLayout = new WeakReference<LeftEyeCinemaActivity>(layout);
        

        @Override
        public void handleMessage(Message msg) 
            final LeftEyeCinemaActivity theLayout = thisLayout.get();
            if (theLayout == null) 
                return;
            

            switch (msg.what) 
                case MSG_ID_LOADING_SUCCESS:
                    theLayout.loadingSuccess((LeftEyeItems) msg.obj);
                    break;
                case MSG_ID_LOADING_FAILED:
                    theLayout.loadingFail();
                    break;

                default:
                    break;
            
        
    

网络数据页面的加载的内容暂时就到这了!欢迎点评!

demo地址:https://github.com/feifei003603/LeftEyeDemo.git

以上是关于Android实战json解析+GridView自适应布局+图片加载的主要内容,如果未能解决你的问题,请参考以下文章

Android 开源项目android-open-project解析之 GridView,ImageView,ProgressBar,TextView

Android实战--天气预报(API+JSON解析)

Android实战--天气预报(API+JSON解析)

《Android源代码设计模式解析与实战》读书笔记(二十)

我已经在 Gridview 中解析了 JSON 数据,现在我想让它离线使用

Android基础——JSON数据的全方位解析