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";
从返回的数据我们可以看出,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