Ace教你一步一步做Android新闻客户端(五) 优化Listview
2016-01-22 23:32
555 查看
今天写存货了调试一些动画参数花了些时间,嘿嘿存货不多了就没法做教程了,今天来教大家优化listview,等下我把代码编辑下这次代码有些多所以我把条理给大家理清楚。思路就是把加载图片的权利交给OnScrollListener。
1首先来到NewsAdapter这个类,我们给他实现了一个AbsListView.OnScrollListener这个接口,这个接口有两个方法:
这一步我们把加载图片的控制权从adapter的getview方法挪到了我们的滑动状态监听器AbsListView.OnScrollListener上只有在滚动完毕后我们才加载大大节省了内存和不必要消耗的流量,提升了listview的
流畅度哈哈哈哈哈哈这样它就可以流畅的滚了
2视角转入ImageLoader,我们创建一个方法loadImages用来加载start-------end的图片
4修改ImageLoader方法的参数,因为我现在加载的是start-------end的整体所以只传入Imageview单个条目的控件就不太合适了,我们需要加载一整块ListView所以我们先要通过
然后
5最后不要忘记给listview设置监听哟~~~~~~~Ace友情提醒不行了太困了要睡了把整体代码提交给大家!github做好整体代码会放给大家~
MainActivity
NewsAdapter:
ImageLoader
NewsBean
参考:http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html
工作原理:
1.ListView针对List中每个item,要求adapter给我一个视图(getView)
2.一个新的视图被返回并显示
如果我们有上亿个item要显示怎么办?为每个项目创建一个新视图?NO!这不可能~~~Android实际上为你缓存了视图
Android中有个叫做Recycler(反复循环器)的构件,下图是它的工作原理:
1.如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中
2.ListView先请求一个type1视图(getView),然后请求其他可见的项目。conVertView在getView中时null的
3.当item1滚出屏幕,并且一个新的项目从屏幕地段上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1.你只需要设定新的数据返回convertView,不必重新创建一个视图。这样直接使用convertView从而减少了很不不必要view的创建
!!!!!!更快的方式是定义一个ViewHolder,将convertView的tag设置为ViewHolder,不为空是重新使用
ViewHolder只是将需要缓存的那些view封装好,convertView的setTag才是将这些缓存起来供下次调用
当你的listview里布局多样化的时候viewholder的作用就有比较明显的体现了。当然了,单一模式的布局一样有性能优化的作用只是不直观。假如你2种模式的布局当发生回收的时候你会用setTag分别记录是哪两种这两种模式会被封装到viewholder中进行保存方便你下次使用。VH就是个静态类与缓存无关的,下面看我们客户端代码的listview
1首先来到NewsAdapter这个类,我们给他实现了一个AbsListView.OnScrollListener这个接口,这个接口有两个方法:
@Override publicvoidonScrollStateChanged(AbsListViewview,intscrollState){//Listview状态改变完才执行这个方法(比如说滑动-----》停止滑动) if(scrollState==SCROLL_STATE_IDLE){//IDLE是定制flying是滑动 //滚动状态=停止加载可见项 mImageLoader.loadImages(startX,endX); }else{ //其他状态我们就需要停止任务我们的异步线程集合mTask就起到作用了 mImageLoader.cancelAllTask();//给我们的ImageLoader创建一个方法来停止所有的异步加载任务 } } @Override publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){//listview滑动过程中一直执行,传进来的第一个参数是listview,第二个是起始位置,第三个是可见项的数量,第四个是可见元素的总数 startX=firstVisibleItem; endX=firstVisibleItem+visibleItemCount; }
这一步我们把加载图片的控制权从adapter的getview方法挪到了我们的滑动状态监听器AbsListView.OnScrollListener上只有在滚动完毕后我们才加载大大节省了内存和不必要消耗的流量,提升了listview的
流畅度哈哈哈哈哈哈这样它就可以流畅的滚了
2视角转入ImageLoader,我们创建一个方法loadImages用来加载start-------end的图片
publicvoidloadImages(intstart,intend){//通过这个循环我们拿到对应的循环和URL for(inti=start;i<end;i++){ Stringurl=NewsAdapter.URLS[i]; Bitmapbitmap=getBitmapFromCache(url); if(bitmap==null){ MyIconSyncTasktask=newMyIconSyncTask(url);//创建myIconSyncTask对象 task.execute(url); mTask.add(task);//加入到我们创建的mTask集合 }else{ ImageViewimageView=(ImageView)mListView.findViewWithTag(url);//找到url对应的ListView imageView.setImageBitmap(bitmap); } } }
3再创建一个取消所有线程的方法
publicvoidcancelAllTask(){ if(mTask!=null){ for(MyIconSyncTasktask:mTask){//遍历mTask中的任务,并执行cancer方法取消掉 task.cancel(false); } } }
4修改ImageLoader方法的参数,因为我现在加载的是start-------end的整体所以只传入Imageview单个条目的控件就不太合适了,我们需要加载一整块ListView所以我们先要通过
ImageViewimageView=(ImageView)mListView.findViewWithTag(mUrl);//找到url对应的ListView
然后
imageView.setImageBitmap(bitmap);
5最后不要忘记给listview设置监听哟~~~~~~~Ace友情提醒不行了太困了要睡了把整体代码提交给大家!github做好整体代码会放给大家~
MainActivity
packageasynctask.zb.com.asynctask_02; importandroid.os.AsyncTask; importandroid.support.v7.app.AppCompatActivity; importandroid.os.Bundle; importandroid.util.Log; importandroid.widget.ListView; importorg.json.JSONArray; importorg.json.JSONException; importorg.json.JSONObject; importjava.io.BufferedReader; importjava.io.IOException; importjava.io.InputStream; importjava.io.InputStreamReader; importjava.io.UnsupportedEncodingException; importjava.net.URL; importjava.util.ArrayList; importjava.util.List; publicclassMainActivityextendsAppCompatActivity{ //初始化 StringTAG="zbace";//日志TAG privateListViewlistView; privateStringURL="http://www.imooc.com/api/teacher?type=4&num=30";@Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView=(ListView)findViewById(R.id.listview); newNewsAsyncTask().execute(URL); } /**Acein2016/1/20 *创建getJsonData(传入URL地址),把从流中读取的JSON数据封装进NewsBean中放入List集合 *1调用readString方法获取到jason格式的字符串,openStream与url.openConnection().geTinpuStream()一样; *获取到jsonStringLog.d(TAG,jsonString);打印下是否可以获取到JSON数据 *2然后创建JSONObject对象,传入jsonString。 *3getJSONArray("data")方法从中取出JSONArray, *在创建个for循环遍历JSONArray并取出newsicon,title,content,等信息 *最后把信息放入NewsBean,再添加进数组 * */ privateList<NewsBean>getJsonData(Stringurl){ List<NewsBean>nesBeanList=newArrayList<>(); try{ StringjsonString=readStream(newURL(url).openStream()); Log.d(TAG,jsonString); JSONObjectjsonObject; NewsBeannewsBean; try{ jsonObject=newJSONObject(jsonString); JSONArrayjsonArray=jsonObject.getJSONArray("data"); for(inti=0;i<jsonArray.length();i++){ //每个JSONArray的元素都是一个JSONObject jsonObject=jsonArray.getJSONObject(i); //把得到的jsonObject,放入NewsBean newsBean=newNewsBean(); newsBean.newsIconUrl=jsonObject.getString("picSmall"); newsBean.newsTitle=jsonObject.getString("name"); newsBean.newsContent=jsonObject.getString("description"); nesBeanList.add(newsBean); } }catch(JSONExceptione){ e.printStackTrace(); } }catch(IOExceptione){ e.printStackTrace(); } returnnesBeanList;//记得返回list } /**Acein2016/1/20 *readStream方法是为了读取流中的数据从而获得流里的JSONString * **/ privateStringreadStream(InputStreamis){ InputStreamReaderisr; Stringresult=""; try{ Stringline=""; //用把字节流转换为字符流(不转字符流无法显示中文),并设置编码为UTF-8; isr=newInputStreamReader(is,"utf-8"); //套上缓冲流 BufferedReaderbr=newBufferedReader(isr); //创建一个while循环 while((line=br.readLine())!=null){ result+=line;//这就得到了我们需要的JSON字符串,从JSON字符串中就可以得到我们想要数据 } }catch(UnsupportedEncodingExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); } returnresult; } /**Acein2016/1/20 *异步获取JSON数据 * **/ classNewsAsyncTaskextendsAsyncTask<String,Void,List<NewsBean>>{ @Override protectedList<NewsBean>doInBackground(String...params){ returngetJsonData(params[0]);//params就是我们传进来的StringURL网址只传进来了一个就输入[0] } @Override protectedvoidonPostExecute(List<NewsBean>newsBeanList){ super.onPostExecute(newsBeanList); NewsAdapternewsAdapter=newNewsAdapter(MainActivity.this,newsBeanList,listView); listView.setAdapter(newsAdapter); } } }
NewsAdapter:
packageasynctask.zb.com.asynctask_02; importandroid.content.Context; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.ViewGroup; importandroid.widget.AbsListView; importandroid.widget.BaseAdapter; importandroid.widget.ImageView; importandroid.widget.ListView; importandroid.widget.TextView; importjava.net.URL; importjava.util.List; /** *CreatedbyAceon2016/1/20. */ publicclassNewsAdapterextendsBaseAdapterimplementsAbsListView.OnScrollListener{ privateList<NewsBean>mlist; privateLayoutInflatermInflater; privateImageLoadermImageLoader; privateintstartX; privateintendX; publicstaticStringURLS[];//创建一个变量并把权限设置成public publicNewsAdapter(Contextcontext,List<NewsBean>data,ListViewlistView){ //映射下把data传给mlist mlist=data; //从一个上下文中(这里的上下文是MainActivity),获得一个布局填充器,这样你就可以使用这个填充器的inflater.inflate()来把xml布局文件转为View对象了,然后利用view对象,findViewById就可以找到布局中的组件 mInflater=LayoutInflater.from(context); mImageLoader=newImageLoader(listView);//在适配器初始化ImageLoader URLS=newString[data.size()];//初始化URLS数组把data里面的icon的url信息放到里面来,方便取用 for(inti=0;i<data.size();i++){ URLS[i]=data.get(i).newsIconUrl; } listView.setOnScrollListener(this); } @Override publicObjectgetItem(intposition){ returnmlist.get(position); } @Override publicintgetCount(){ returnmlist.size(); } @Override publiclonggetItemId(intposition){ returnposition; } @Override publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ ViewHolderviewHolder=null; if(convertView==null){ viewHolder=newViewHolder(); convertView=mInflater.inflate(R.layout.adapter_item,null); viewHolder.iconimage=(ImageView)convertView.findViewById(R.id.tvimage); viewHolder.title=(TextView)convertView.findViewById(R.id.tvtitle); viewHolder.content=(TextView)convertView.findViewById(R.id.tvcontent); convertView.setTag(viewHolder); }else{ viewHolder=(ViewHolder)convertView.getTag(); viewHolder.iconimage.setImageResource(R.mipmap.ic_launcher); Stringurl=mlist.get(position).newsIconUrl; viewHolder.iconimage.setTag(url);//给imageview设置标签是为了增加一个判断的标准(再imageloader类里),只有URL地址和当前位置的Item的图片相匹配才显示 //newImageLoader().showImageByThread(viewHolder.iconimage,mlist.get(position).newsIconUrl); mImageLoader.showImageByAsyncTask(viewHolder.iconimage,mlist.get(position).newsIconUrl);//不能使用newImageLoader().因为每new一次都创建一个ImageLoader()这样就会有很多的lru viewHolder.title.setText(mlist.get(position).newsTitle); viewHolder.content.setText(mlist.get(position).newsContent); } returnconvertView; } @Override publicvoidonScrollStateChanged(AbsListViewview,intscrollState){//Listview状态改变完才执行这个方法(比如说滑动-----》停止滑动) if(scrollState==SCROLL_STATE_IDLE){//IDLE是定制flying是滑动 //滚动状态=停止加载可见项 mImageLoader.loadImages(startX,endX); }else{ //其他状态我们就需要停止任务我们的异步线程集合mTask就起到作用了 mImageLoader.cancelAllTask();//给我们的ImageLoader创建一个方法来停止所有的异步加载任务 } } @Override publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){//listview滑动过程中一直执行,传进来的第一个参数是listview,第二个是起始位置,第三个是可见项的数量,第四个是可见元素的总数 startX=firstVisibleItem; endX=firstVisibleItem+visibleItemCount; } classViewHolder{ publicTextViewtitle; publicImageViewiconimage; publicTextViewcontent; } }
ImageLoader
packageasynctask.zb.com.asynctask_02; importandroid.graphics.Bitmap; importandroid.graphics.BitmapFactory; importandroid.os.AsyncTask; importandroid.os.Message; importandroid.util.Log; importandroid.util.LruCache; importandroid.widget.ImageView; importandroid.widget.ListView; importjava.io.BufferedInputStream; importjava.io.IOException; importjava.net.HttpURLConnection; importjava.net.MalformedURLException; importjava.net.URL; importjava.util.HashSet; importjava.util.Set; /** *CreatedbyAdministratoron2016/1/20. */ publicclassImageLoader{ privateImageViewmImageView; privateStringmUrl; publicLruCache<String,Bitmap>mCache; privateListViewmListView;//因为我们要加载start-end的所有图片是个整体而不是单个条目,所以创建一个listview通过listview的findViewWithTag方法来找到对应的ImageView privateSet<MyIconSyncTask>mTask;//创建一个Set集合用于装我们所有的AsyncTask publicImageLoader(ListViewlistView){//给构造方法传入listview然后初始化 mListView=listView;//初始化listview mTask=newHashSet<>();//初始化mTask intmaxMemory=(int)Runtime.getRuntime().maxMemory();//获取虚拟机可用内存(内存占用超过该值的时候,将报OOM异常导致程序崩溃) intcacheSzie=maxMemory/4;//使用可用内存的1/4来作为MemoryCache mCache=newLruCache<String,Bitmap>(cacheSzie){ @Override protectedintsizeOf(Stringkey,Bitmapvalue){ returnvalue.getByteCount();//返回Bitmap占用的空间,告诉系统我这张图片要用多少内存 } }; } //把bitmap加入到缓存 publicvoidaddBitmapToCache(StringmUrl,Bitmapbitmap){ if(getBitmapFromCache(mUrl)==null){ mCache.put(mUrl,bitmap); } } //从缓存中获取数据 publicBitmapgetBitmapFromCache(StringmUrl){ returnmCache.get(mUrl); } android.os.HandlermHandler=newandroid.os.Handler(){ @Override publicvoidhandleMessage(Messagemsg){ super.handleMessage(msg); if(mImageView.getTag().equals(mUrl)) mImageView.setImageBitmap((Bitmap)msg.obj); } }; publicvoidshowImageByThread(ImageViewimageView,finalStringurl){ mImageView=imageView; mUrl=url;//对传过来的imageView和url进行缓存(为了避免程序逻辑顺序错误,和viewholder的机制差不多) newThread(){ @Override publicvoidrun(){ super.run(); Bitmapbitmap=getBitmapFromURL(url); Messagemessage=Message.obtain(); message.obj=bitmap; mHandler.sendMessage(message); } }.start(); } publicvoidcancelAllTask(){ if(mTask!=null){ for(MyIconSyncTasktask:mTask){//遍历mTask中的任务,并执行cancer方法取消掉 task.cancel(false); } } } //用来加载start-------end的图片 publicvoidloadImages(intstart,intend){//通过这个循环我们拿到对应的循环和URL for(inti=start;i<end;i++){ Stringurl=NewsAdapter.URLS[i]; Bitmapbitmap=getBitmapFromCache(url); if(bitmap==null){ MyIconSyncTasktask=newMyIconSyncTask(url);//创建myIconSyncTask对象 task.execute(url); mTask.add(task);//加入到我们创建的mTask集合 }else{ ImageViewimageView=(ImageView)mListView.findViewWithTag(url);//找到url对应的ListView imageView.setImageBitmap(bitmap); } } } //创建从URL获取Bitmap的放方法 publicBitmapgetBitmapFromURL(StringstringUrl){ Bitmapbitmap; BufferedInputStreambis=null; URLurl1=null; try{ url1=newURL(stringUrl); }catch(MalformedURLExceptione){ e.printStackTrace(); } HttpURLConnectionconnection=null; try{ connection=(HttpURLConnection)url1.openConnection(); }catch(IOExceptione){ e.printStackTrace(); } try{ bis=newBufferedInputStream(connection.getInputStream()); bitmap=BitmapFactory.decodeStream(bis); connection.disconnect(); returnbitmap; }catch(IOExceptione){ e.printStackTrace(); }finally{ try{ bis.close(); }catch(IOExceptione){ e.printStackTrace(); } }returnnull; } publicvoidshowImageByAsyncTask(ImageViewimageView,Stringurl){ Bitmapbitmap=getBitmapFromCache(url); if(bitmap==null){ imageView.setImageResource(R.mipmap.ic_launcher);//在bitmap=空的时候我们就让它显示默认图片,这样修改后把加载图片的控制权全部放入新建的loadImages方法中了 }else{ imageView.setImageBitmap(bitmap); } } classMyIconSyncTaskextendsAsyncTask<String,Void,Bitmap>{ //privateImageViewmImageView; privateStringmUrl; publicMyIconSyncTask(Stringurl){ mUrl=url; //mImageView=imageView; } @Override protectedBitmapdoInBackground(String...params){ StringmUrl=params[0]; //从网络获取图片,并存入缓存中 Bitmapbitmap=getBitmapFromURL(mUrl); if(bitmap!=null){ addBitmapToCache(mUrl,bitmap); } returnbitmap; } @Override protectedvoidonPostExecute(Bitmapbitmap){ super.onPostExecute(bitmap); //if(mImageView.getTag().equals(mUrl)){ //mImageView.setImageBitmap(bitmap); ImageViewimageView=(ImageView)mListView.findViewWithTag(mUrl);//找到url对应的ListView if(imageView!=null&&bitmap!=null){ imageView.setImageBitmap(bitmap); } mTask.remove(this);//加载位图完毕移除这个异步线程 } } }
NewsBean
packageasynctask.zb.com.asynctask_02; importjava.net.URL; /** *CreatedbyAdministratoron2016/1/20. */ publicclassNewsBean{ publicStringnewsIconUrl; publicStringnewsTitle; publicStringnewsContent; }
还有一个优化就是ViewHolder和concertView,这张图我觉得非常棒 LsitView和Adapter
参考:
工作原理:
1.ListView针对List中每个item,要求adapter给我一个视图(getView)
2.一个新的视图被返回并显示
如果我们有上亿个item要显示怎么办?为每个项目创建一个新视图?NO!这不可能~~~Android实际上为你缓存了视图
Android中有个叫做Recycler(反复循环器)的构件,下图是它的工作原理:
1.如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中
2.ListView先请求一个type1视图(getView),然后请求其他可见的项目。conVertView在getView中时null的
3.当item1滚出屏幕,并且一个新的项目从屏幕地段上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1.你只需要设定新的数据返回convertView,不必重新创建一个视图。这样直接使用convertView从而减少了很不不必要view的创建
!!!!!!更快的方式是定义一个ViewHolder,将convertView的tag设置为ViewHolder,不为空是重新使用
ViewHolder只是将需要缓存的那些view封装好,convertView的setTag才是将这些缓存起来供下次调用
当你的listview里布局多样化的时候viewholder的作用就有比较明显的体现了。当然了,单一模式的布局一样有性能优化的作用只是不直观。假如你2种模式的布局当发生回收的时候你会用setTag分别记录是哪两种这两种模式会被封装到viewholder中进行保存方便你下次使用。VH就是个静态类与缓存无关的,下面看我们客户端代码的listview
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ ViewHolderviewHolder=null; if(convertView==null){ viewHolder=newViewHolder(); convertView=mInflater.inflate(R.layout.adapter_item,null); viewHolder.iconimage=(ImageView)convertView.findViewById(R.id.tvimage); viewHolder.title=(TextView)convertView.findViewById(R.id.tvtitle); viewHolder.content=(TextView)convertView.findViewById(R.id.tvcontent); convertView.setTag(viewHolder); }else{ viewHolder=(ViewHolder)convertView.getTag(); viewHolder.iconimage.setImageResource(R.mipmap.ic_launcher); Stringurl=mlist.get(position).newsIconUrl; viewHolder.iconimage.setTag(url);//给imageview设置标签是为了增加一个判断的标准(再imageloader类里),只有URL地址和当前位置的Item的图片相匹配才显示 //newImageLoader().showImageByThread(viewHolder.iconimage,mlist.get(position).newsIconUrl); mImageLoader.showImageByAsyncTask(viewHolder.iconimage,mlist.get(position).newsIconUrl);//不能使用newImageLoader().因为每new一次都创建一个ImageLoader()这样就会有很多的lru viewHolder.title.setText(mlist.get(position).newsTitle); viewHolder.content.setText(mlist.get(position).newsContent); } returnconvertView; }
classViewHolder{ publicTextViewtitle; publicImageViewiconimage; publicTextViewcontent; } }
相关文章推荐
- Android keystore 的MD5、SHA1值获取方法
- Android ANR
- Android 内容提供器(Content Provider)介绍
- android studio 运行java文件
- Android Studio 2.0 Preview 6 尝鲜记
- Android异步任务AsyncTask
- Android 通过自定义控件方式实现带开关效果的左右切换选择器。
- 手把手带你画一个漂亮蜂窝view Android自定义view
- android failed:EISDIR(Is a directory)错误
- android wav录音,停止和播放
- android 自定义相机
- Android Studio Git插件_分支
- android tv-Managing User Interaction
- 利用 Android Studio 和 Gradle 打包多版本APK
- 利用 Android Studio 和 Gradle 打包多版本APK
- android上开源的酷炫的交互动画和视觉效果
- android常见的颜色代码
- android机型适配终极篇
- android fragment中带有listview,自定义item
- Android checkbox及动态加载控件