因为移动端软件开发思维模式或者说是开发的架构其实是不分平台和编程语言的,就拿安卓和IOS来说,他们都是移动前端app开发展示数据和用户交互数据的数据终端,移动架构的几个大模块:UI界面展示、本地数据可持续化存储、网络数据请求、性能优化等等,安卓和IOS开发都要考虑这些架构的模块。所以,熟悉IOS的开发的人,再去学习一下安卓的开发以及安卓的开发模式,你会发现很多技术和思想安卓和IOS是一样的,只是可能说法不一样,由于编程语言比如OC和Java略微的差异性,编码习惯和细节不一样之外,其他都是一样的。

 【声明】 

 【声明】 

 

欢迎转载,但请保留文章原始出处→_→ 

欢迎转载,但请保留文章原始出处→_→ 

本人开始对安卓略有兴趣,开始对安卓粗浅的学习,一方面也会拿IOS和安卓进行对比阐述,如果你会IOS,再学习安卓的,阅读本人的博客也许会对你有很大的帮助。

生命壹号:

生命壹号:

(但是对于安卓开发的语言基础Java、以及android
studio的使用,本人不会详细阐述,作为有情怀有独立能力的程序员,这些基础应该不会难道你们的吧,更何况本人对android
studio的使用一直通过google和百度来学习相关的setting和快捷键)

文章来源:

文章来源:

 

 

联系方式:smyhvae@163.com 

在Android中,异步加载最常用的两种方式:

【其他相关文章】

 效果图:(gif图太大了,有点卡,建议将图片保存到本地查看或者直接本文末尾的源码查看gif图)

  1、多线程\线程池

  • Android
    UI组件—-ListView列表控件详解

图片 1

  2、AsyncTask

 

加载网络图片我们用universal-image-loader,然后实现ListView的上拉下拉刷新我们用PullToRefresh。下面开始写代码。

当然,AsyncTask底层是基于线程池实现的。所以以上两种方法是异曲同工。

 

整个代码的工程文件结构如下:

 

【正文】

图片 2

一、首先,按照在IOS中用UITableView加载数据的思路一样,我们先要创建出自定义的Cell以及Cell的布局:

【引入】

  • libs文件夹下是需要用到的一些库和开源框架(这里有个picasso框架,是用来加载网络图片的,暂时不用哈,留着以后备用,现在这个项目用的是universal-image-loader)。
  • adapter文件夹下是listView的自定义适配器(本文的重点)
  • entities是从网络获取到json数据,解析之后,用来存放这些数据的实体
  • utils文件夹下是url的常量
  • MainActivity.java是主程序的入口
  • MyApplication才是真正的app的第一个入口,这个不多解释,都懂得。

新建item_layout.xml文件:

我们一般编写listView的时候顺序是这样的:

一、开始写代码:

图片 3

  • 需要展示的数据集List<T>
  • 为这个数据集编写一个ListView
  • 为这个ListView编写一个Adapter,一般继承自BaseAdapter
  • 在BaseAdapter内部编写一个ViewHolder类,对应ListView里面的item控件,提高控件的查询效率

(1)activity_main.xml(MainActvity的布局)

源码:

分析:

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent">
 5 
 6     <com.handmark.pulltorefresh.library.PullToRefreshListView
 7         android:id="@+id/lv"
 8         android:layout_width="match_parent"
 9         android:layout_height="match_parent">
10 
11 
12     </com.handmark.pulltorefresh.library.PullToRefreshListView>
13 </RelativeLayout>
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="wrap_content"
 5     android:padding="4dp"
 6     android:orientation="horizontal"
 7     >
 8     <ImageView
 9         android:id="@+id/iv_icon"
10         android:layout_width="64dp"
11         android:layout_height="64dp"
12         android:src="@mipmap/ic_launcher"/>
13 
14 
15     <LinearLayout
16         android:layout_width="match_parent"
17         android:layout_height="match_parent"
18         android:padding="4dp"
19         android:gravity="center"
20         android:orientation="vertical">
21 
22         <TextView
23             android:id="@+id/tv_title"
24             android:layout_width="match_parent"
25             android:layout_height="wrap_content"
26             android:textSize="15sp"
27             android:maxLines="1"
28             android:text="标题标题标题"/>
29 
30 
31         <TextView
32             android:id="@+id/tv_content"
33             android:layout_width="match_parent"
34             android:layout_height="wrap_content"
35             android:textSize="10sp"
36             android:maxLines="3"
37             android:text="内容内容内容"/>
38 
39     </LinearLayout>
40 
41 
42 </LinearLayout>

List<T>:ListView –> Adapter extends BaseAdapter –>
ViewHolder

 

就这样,一个自定义的item就创建好了,就好比我们IOS中xib或者storyboard上的UITableViewCell创建好了。

一般情况下,一个ListView对应一个Adapter类,对应一个ViewHolder类,那如果一个app中有20个ListView,我们岂不是要写20遍?所以的做法是:

这里就放了个PullToRefreshListView,其实本质上还是个ListView

然后接着在activity_main.xml中创建一个ListView,这个ListView就好比我们IOS的UITableView。

  • 抽取ViewHolder,作为公共的类。
  • 将Adapter封装成CommonAdapter,作为公共的类。

(2)item_listview.xml(ListView中单个item的布局)

图片 4

 

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2               xmlns:tools="http://schemas.android.com/tools"
 3               android:layout_width="match_parent"
 4               android:layout_height="match_parent"
 5               android:orientation="vertical"
 6     >
 7 
 8     <LinearLayout
 9         android:layout_width="match_parent"
10         android:layout_height="wrap_content"
11         android:layout_marginBottom="15dp"
12         android:gravity="center_vertical"
13         android:orientation="horizontal"
14         android:paddingTop="28dp">
15 
16         <!--作者头像-->
17         <ImageButton
18             android:id="@+id/mVideoAvatarBtn"
19             android:layout_width="56dp"
20             android:layout_height="56dp"
21             android:background="@mipmap/defaultimg"/>
22 
23         <!--昵称-->
24         <TextView
25             android:id="@+id/mVideoNicknameTv"
26             android:layout_width="match_parent"
27             android:layout_height="wrap_content"
28             android:layout_alignParentTop="true"
29             android:layout_marginLeft="13dp"
30             android:layout_toRightOf="@+id/mVideoAvatarIv"
31             android:singleLine="true"
32             android:text="张三"
33             android:textSize="16sp"/>
34 
35     </LinearLayout>
36 
37 
38     <!--视频缩略图-->
39     <ImageButton
40         android:id="@+id/mVideoImgBtn"
41         android:layout_width="wrap_content"
42         android:layout_height="wrap_content"
43 
44         />
45 
46 
47 </LinearLayout>

源码:

一、传统方式编写适配器:

 

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:id="@+id/activity_main"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     android:paddingBottom="@dimen/activity_vertical_margin"
 8     android:paddingLeft="@dimen/activity_horizontal_margin"
 9     android:paddingRight="@dimen/activity_horizontal_margin"
10     android:paddingTop="@dimen/activity_vertical_margin"
11     tools:context="com.example.heyang.myapplication.MainActivity">
12 
13     <ListView
14         android:id="@+id/lv_main"
15         android:layout_width="match_parent"
16         android:layout_height="match_parent"
17         />
18 
19 
20 </RelativeLayout>

(1)activity_main.xml:

单个item中,一个是头像,一个是文本,一个是图片(包裹内容),其布局效果如下:
图片 5

二、因为每一个item或者类比IOS的每一个Cell都需要一个图片地址、标题Title、内容Content三个数据,所以我们就需要一个模型对象来一一映射对应到item,在安卓或者Java中的说法叫创建一个Bean对象,其实可以类比理解为IOS的model模型对象。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<ListView
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"></ListView>

</RelativeLayout>

(3)MyApplication.java:

图片 6

(2)item_listview.xml:单个item的布局文件

 1 package com.smyhvae.pulltorefreshdemo;
 2 
 3 import android.app.Application;
 4 
 5 import com.nostra13.universalimageloader.core.ImageLoader;
 6 import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
 7 
 8 /**
 9  * Created by smyhvae on 2015/5/8.
10  */
11 public class MyApplication extends Application {
12 
13     @Override
14     public void onCreate() {
15         super.onCreate();
16 
17 
18         //创建默认的ImageLoader配置参数
19         ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
20                 .writeDebugLogs() //打印log信息
21                 .build();
22 
23 
24         //Initialize ImageLoader with configuration.
25         ImageLoader.getInstance().init(configuration);
26     }
27 
28 }

源码:

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:padding="10dp">
 6 
 7     <TextView
 8         android:id="@+id/titleTv"
 9         android:layout_width="wrap_content"
10         android:layout_height="wrap_content"
11         android:singleLine="true"
12         android:text="Android新技能"
13         android:textColor="#444"
14         android:textSize="16sp" />
15 
16     <TextView
17         android:id="@+id/descTv"
18         android:layout_width="match_parent"
19         android:layout_height="wrap_content"
20         android:layout_below="@+id/titleTv"
21         android:layout_marginTop="10dp"
22         android:maxLines="2"
23         android:minLines="1"
24         android:text="Android为ListView和GridView打造万能适配器"
25         android:textColor="#898989"
26         android:textSize="16sp" />
27 
28     <TextView
29         android:id="@+id/timeTv"
30         android:paddingTop="3dp"
31         android:layout_width="wrap_content"
32         android:layout_height="wrap_content"
33         android:layout_below="@+id/descTv"
34         android:layout_marginTop="10dp"
35         android:text="2015-05-04"
36         android:textColor="#898989"
37         android:textSize="12sp" />
38 
39     <TextView
40         android:padding="2dp"
41         android:id="@+id/phoneTv"
42         android:gravity="center"
43         android:layout_width="wrap_content"
44         android:layout_height="wrap_content"
45         android:layout_below="@+id/descTv"
46         android:layout_marginTop="10dp"
47         android:background="#2ED667"
48         android:drawableLeft="@mipmap/phone"
49         android:drawablePadding="5dp"
50         android:text="10086"
51         android:textColor="#ffffff"
52         android:textSize="12sp"
53         android:layout_alignParentRight="true" />
54 
55 
56 </RelativeLayout>

 

 1 package com.example.heyang.myapplication;
 2 
 3 /**
 4  * Created by HeYang on 16/10/5.
 5  */
 6 
 7 public class NewsBean {
 8     // 包含三个属性:1、标题2、内容3、图片的网址
 9     public String newsIconURL;
10     public String newsTitle;
11     public String newsContent;
12 }

其对应的布局效果如下:

主程序一进来,我们就在onCreate()中创建ImageLoader的配置参数,并初始化到ImageLoader中。

 

图片 7 

 

三、接着就是要数据源了,http://www.imooc.com/api/teacher?type=4&num=30,点击这个URL打开网页会看到一大堆数据,然后你可以通过json格式转换工具就可以看到json的数据格式。

(3)Bean.java:ListView的数据集

(4)JavaBean的实体:

图片 8

 1 package com.smyhvae.baseadapter.entities;
 2 
 3 /**
 4  * Created by smyhvae on 2015/5/4.
 5  */
 6 public class Bean {
 7     private String title;
 8     private String desc;
 9     private String time;
10     private String phone;
11 
12     public Bean() {
13     }
14 
15     public Bean(String title, String desc, String time, String phone) {
16         this.title = title;
17         
18         this.desc = desc;
19         this.time = time;
20         this.phone = phone;
21     }
22 
23     public String getTitle() {
24         return title;
25     }
26 
27     public void setTitle(String title) {
28         this.title = title;
29     }
30 
31     public String getDesc() {
32         return desc;
33     }
34 
35     public void setDesc(String desc) {
36         this.desc = desc;
37     }
38 
39     public String getTime() {
40         return time;
41     }
42 
43     public void setTime(String time) {
44         this.time = time;
45     }
46 
47     public String getPhone() {
48         return phone;
49     }
50 
51     public void setPhone(String phone) {
52         this.phone = phone;
53     }
54 }

这个其实就是下面的这些实体:
图片 9

那么接下来就很容易明白了,模型Bean对象中的三个属性就可以来自这个URL下的json数据中的name、picSmall、discription。

(4)MyAdapter.java:自定义适配器,继承自BaseAdapter

这个不需要赘述,代码略,在本文最后有源码下载链接。

那么接下来就是IOS中所谓的字典转模型的步骤。只不过在这之前,先要通过网络请求获取到这些数据才行,这里网络请求获取json数据的做法有点和IOS的不同了。

 1 package com.smyhvae.baseadapter;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 import android.widget.TextView;
 9 
10 import com.smyhvae.baseadapter.entities.Bean;
11 
12 import java.util.List;
13 
14 /**
15  * Created by smyhvae on 2015/5/4.
16  */
17 public class MyAdapter extends BaseAdapter {
18     private LayoutInflater mInflater;
19     private List<Bean> mDatas;
20 
21     //MyAdapter需要一个Context,通过Context获得Layout.inflater,然后通过inflater加载item的布局
22     public MyAdapter(Context context, List<Bean> datas) {
23 
24         mInflater = LayoutInflater.from(context);
25         mDatas = datas;
26     }
27 
28     //返回数据集的长度
29     @Override
30     public int getCount() {
31         return mDatas.size();
32     }
33 
34     @Override
35     public Object getItem(int position) {
36         return mDatas.get(position);
37     }
38 
39     @Override
40     public long getItemId(int position) {
41         return position;
42     }
43 
44     //这个方法才是重点,我们要为它编写一个ViewHolder
45     @Override
46     public View getView(int position, View convertView, ViewGroup parent) {
47         ViewHolder holder = null;
48         if (convertView == null) {
49             convertView = mInflater.inflate(R.layout.item_listview, parent, false); //加载布局
50             holder = new ViewHolder();
51 
52             holder.titleTv = (TextView) convertView.findViewById(R.id.titleTv);
53             holder.descTv = (TextView) convertView.findViewById(R.id.descTv);
54             holder.timeTv = (TextView) convertView.findViewById(R.id.timeTv);
55             holder.phoneTv = (TextView) convertView.findViewById(R.id.phoneTv);
56 
57             convertView.setTag(holder);
58         } else {   //else里面说明,convertView已经被复用了,说明convertView中已经设置过tag了,即holder
59             holder = (ViewHolder) convertView.getTag();
60         }
61 
62         Bean bean = mDatas.get(position);
63         holder.titleTv.setText(bean.getTitle());
64         holder.descTv.setText(bean.getDesc());
65         holder.timeTv.setText(bean.getTime());
66         holder.phoneTv.setText(bean.getPhone());
67 
68         return convertView;
69     }
70 
71     //这个ViewHolder只能服务于当前这个特定的adapter,因为ViewHolder里会指定item的控件,不同的ListView,item可能不同,所以ViewHolder写成一个私有的类
72     private class ViewHolder {
73         TextView titleTv;
74         TextView descTv;
75         TextView timeTv;
76         TextView phoneTv;
77     }
78 
79 }

(5)ViewHolder.java:(ListView的万能模板,嘿嘿)

感觉IOS的网络请求,不管是苹果的NSURLSession还是第三方的AFN框架都已经是封装好的网络请求框架。而安卓的获取json数据的做法好像更接近底层,采用了输入输出流来获取URL的网页数据:

(5)MainActivity.java:

 1 package com.smyhvae.pulltorefreshdemo.adapter;
 2 
 3 import android.content.Context;
 4 import android.util.SparseArray;
 5 import android.view.LayoutInflater;
 6 import android.view.View;
 7 import android.view.ViewGroup;
 8 
 9 /**
10  * Created by smyhvae on 2015/5/4.
11  * 通用的viewHolder类
12  */
13 public class ViewHolder {
14 
15     private SparseArray<View> mViews;
16     private int mPosition;
17     private View mConvertView;
18 
19     public ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
20         this.mPosition = position;
21         this.mViews = new SparseArray<View>();
22 
23         mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
24 
25         mConvertView.setTag(this);
26 
27     }
28 
29     public static ViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
30         if (convertView == null) {
31             return new ViewHolder(context, parent, layoutId, position);
32         } else {
33             ViewHolder holder = (ViewHolder) convertView.getTag();
34             holder.mPosition = position; //即时ViewHolder是复用的,但是position记得更新一下
35             return holder;
36         }
37     }
38 
39     /*
40     通过viewId获取控件
41      */
42     //使用的是泛型T,返回的是View的子类
43     public <T extends View> T getView(int viewId) {
44         View view = mViews.get(viewId);
45 
46         if (view == null) {
47             view = mConvertView.findViewById(viewId);
48             mViews.put(viewId, view);
49         }
50 
51         return (T) view;
52     }
53 
54     public View getConvertView() {
55         return mConvertView;
56     }
57 
58 }
  1 package com.example.heyang.myapplication;
  2 
  3 import android.os.AsyncTask;
  4 import android.support.v7.app.AppCompatActivity;
  5 import android.os.Bundle;
  6 import android.util.Log;
  7 import android.widget.ListView;
  8 
  9 import org.json.JSONArray;
 10 import org.json.JSONException;
 11 import org.json.JSONObject;
 12 
 13 import java.io.BufferedReader;
 14 import java.io.IOException;
 15 import java.io.InputStream;
 16 import java.io.InputStreamReader;
 17 import java.io.UnsupportedEncodingException;
 18 import java.net.URL;
 19 import java.util.ArrayList;
 20 import java.util.List;
 21 
 22 public class MainActivity extends AppCompatActivity {
 23 
 24     private ListView mListView;
 25 
 26     private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30";
 27 
 28     @Override
 29     protected void onCreate(Bundle savedInstanceState) {
 30         super.onCreate(savedInstanceState);
 31         setContentView(R.layout.activity_main);
 32 
 33         // 获取xml上的ListView对象
 34         mListView = (ListView) findViewById(R.id.lv_main);
 35 
 36         new NewsAsyncTask().execute(URL);
 37     }
 38 
 39     // 通过输入输出流获取整个网页格式的字符串数据
 40     private String readStream(InputStream is){
 41         InputStreamReader isr;
 42         String result = "";
 43 
 44         try {
 45             String line = "";
 46             // 1、输入流对象 2、输入流读取对象 3、字节读取对象
 47             isr = new InputStreamReader(is,"utf-8");
 48             BufferedReader br = new BufferedReader(isr);
 49             while ((line = br.readLine()) != null){
 50                 result += line;
 51             }
 52         } catch(UnsupportedEncodingException e){
 53             e.printStackTrace();
 54         } catch (IOException e) {
 55             e.printStackTrace();
 56         }
 57         return result;
 58     }
 59 
 60 
 61     private List<NewsBean> getJsonData(String url){
 62         // 创建存储NewsBean的集合对象
 63         List<NewsBean> newsBeanList = new ArrayList<>();
 64         try {
 65             // 取出网络的json字符串的格式之后
 66             String jsonStr = readStream(new URL(url).openStream());
 67             // 就要用JSONObject对象进行解析
 68             JSONObject jsonObject;
 69             // 然后需要NewsBean,其实相当于IOS的模型对象
 70             NewsBean newsBean;
 71             try {
 72                 // jsonObject的对象,创建该对象的同时传入json字符串格式的对象
 73                 jsonObject = new JSONObject(jsonStr);
 74                 // 拿到jsonObject对象之后,就需要通过key值来拿到数组
 75                 JSONArray jsonArray = jsonObject.getJSONArray("data");
 76                 // 然后开始遍历数组,获取模型数组
 77                 for (int i = 0;i<jsonArray.length();i++){
 78                     // 数组里每一个元素又是jsonObject
 79                     jsonObject = jsonArray.getJSONObject(i);
 80 
 81                     // 开始创建模型对象
 82                     newsBean = new NewsBean();
 83                     newsBean.newsIconURL = jsonObject.getString("picSmall");
 84                     newsBean.newsTitle = jsonObject.getString("name");
 85                     newsBean.newsContent = jsonObject.getString("description");
 86                     
 87                     // 创建的一个模型对象,就要添加到集合当中去
 88                     newsBeanList.add(newsBean);
 89                 }
 90 
 91 
 92             } catch (JSONException e) {
 93                 e.printStackTrace();
 94             }
 95 
 96             Log.d("heyang",jsonStr);
 97         } catch (IOException e) {
 98             e.printStackTrace();
 99         }
100 
101         return newsBeanList;
102     }
103 
104     // 创建一个内部类来实现 ,在实现下面内部类之前,需要自定义的Bean对象来封装处理Josn格式的数据
105     class  NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
106         @Override
107         protected List<NewsBean> doInBackground(String... strings) {
108             return getJsonData(strings[0]);
109         }
110     }
111 }
 1 package com.smyhvae.baseadapter;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.view.Menu;
 6 import android.view.MenuItem;
 7 import android.widget.ListView;
 8 
 9 import com.smyhvae.baseadapter.entities.Bean;
10 
11 import java.util.ArrayList;
12 import java.util.List;
13 
14 public class MainActivity extends Activity {
15 
16     private ListView listView;
17     private List<Bean> mDatas;
18     private MyAdapter mAdapter;
19 
20     @Override
21     protected void onCreate(Bundle savedInstanceState) {
22         super.onCreate(savedInstanceState);
23         setContentView(R.layout.activity_main);
24 
25         initView();
26         initData();
27 
28     }
29 
30     //方法:初始化View
31     private void initView() {
32         listView = (ListView) findViewById(R.id.listView);
33     }
34 
35     //方法;初始化Data
36     private void initData() {
37         mDatas = new ArrayList<Bean>();
38 
39         //将数据装到集合中去
40         Bean bean = new Bean("Android新技能1", "Android为ListView和GridView打造万能适配器", "2015-05-04", "10086");
41         mDatas.add(bean);
42 
43         bean = new Bean("Android新技能2", "Android为ListView和GridView打造万能适配器", "2015-05-04", "10086");
44         mDatas.add(bean);
45 
46         bean = new Bean("Android新技能3", "Android为ListView和GridView打造万能适配器", "2015-05-04", "10086");
47         mDatas.add(bean);
48 
49         bean = new Bean("Android新技能4", "Android为ListView和GridView打造万能适配器", "2015-05-04", "10086");
50         mDatas.add(bean);
51 
52         //为数据绑定适配器
53         mAdapter = new MyAdapter(this,mDatas);
54 
55         listView.setAdapter(mAdapter);
56     }
57 
58 }

 

按照以上的源码,就能顺利的获取到了ListView的数据源数组。

运行效果如下:

(6)ListViewAdapter.java:(同样是ListView的万能模板)

但是在网络请求方面,别忘了要给项目增加Internet访问权限:

图片 10

 1 package com.smyhvae.pulltorefreshdemo.adapter;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 
 9 import java.util.List;
10 
11 /**
12  * Created by smyhvae on 2015/5/4.
13  * 通用的ListView的BaseAdapter,所有的ListView的自定义adapter都可以继承这个类哦
14  */
15 public abstract class ListViewAdapter<T> extends BaseAdapter {
16 
17     //为了让子类访问,于是将属性设置为protected
18     protected Context mContext;
19     protected List<T> mDatas;
20     protected LayoutInflater mInflater;
21     private int layoutId; //不同的ListView的item布局肯能不同,所以要把布局单独提取出来
22 
23     public ListViewAdapter(Context context, List<T> datas, int layoutId) {
24         this.mContext = context;
25         mInflater = LayoutInflater.from(context);
26         this.mDatas = datas;
27         this.layoutId = layoutId;
28     }
29 
30     @Override
31     public int getCount() {
32         return mDatas.size();
33     }
34 
35     @Override
36     public T getItem(int position) {
37         return mDatas.get(position);
38     }
39 
40     @Override
41     public long getItemId(int position) {
42         return position;
43     }
44 
45     @Override
46     public View getView(int position, View convertView, ViewGroup parent) {
47         //初始化ViewHolder,使用通用的ViewHolder,一样代码就搞定ViewHolder的初始化咯
48         ViewHolder holder = ViewHolder.get(mContext, convertView, parent, layoutId, position);//layoutId就是单个item的布局
49 
50         convert(holder, getItem(position));
51         return holder.getConvertView(); //这一行的代码要注意了
52     }
53 
54     //将convert方法公布出去
55     public abstract void convert(ViewHolder holder, T t);
56 
57 }

<!–增加
网络访问权限 –>
<uses-permission
android:name=”android.permission.INTERNET”/>

【工程文件】

 

图片 11

2015-05-04-BaseAdapter的传统写法.rar

(7)【非常非常重要】VideoListViewAdapter.java:

 

 

这个才是我们的这个ListView的自定义适配器哦:

四、创建继承BaseAdapter自定义的Adapter,注意其中利用了匿名内部类来优化处理ListView的每一个view的循环利用:

二、ListView中自定义adapter的封装(万能的写法来编写适配器):

  1 package com.smyhvae.pulltorefreshdemo.adapter;
  2 
  3 
  4 import android.content.Context;
  5 import android.view.ViewGroup;
  6 import android.widget.ImageButton;
  7 import android.widget.LinearLayout;
  8 import android.widget.TextView;
  9 
 10 import com.nostra13.universalimageloader.core.DisplayImageOptions;
 11 import com.nostra13.universalimageloader.core.ImageLoader;
 12 import com.nostra13.universalimageloader.core.assist.ImageScaleType;
 13 import com.smyhvae.pulltorefreshdemo.R;
 14 import com.smyhvae.pulltorefreshdemo.entities.Video;
 15 import com.smyhvae.pulltorefreshdemo.utils.Constants;
 16 
 17 import java.util.List;
 18 
 19 /**
 20  * Created by smyhvae on 2015/5/5.
 21  */
 22 
 23 
 24 public class VideoListViewAdapter extends ListViewAdapter<Video> {
 25 
 26 
 27     DisplayImageOptions options;        // DisplayImageOptions是用于设置图片显示的类
 28 
 29 
 30     //MyAdapter需要一个Context,通过Context获得Layout.inflater,然后通过inflater加载item的布局
 31     public VideoListViewAdapter(Context context, List<Video> datas) {
 32         super(context, datas, R.layout.item_listview);
 33     }
 34 
 35 
 36 
 37   /*  @Override
 38     public void convert(ViewHolder holder, Bean bean) {
 39 
 40         ((TextView) holder.getView(R.id.titleTv)).setText(bean.getTitle());
 41         ((TextView) holder.getView(R.id.descTv)).setText(bean.getDesc());
 42         ((TextView) holder.getView(R.id.timeTv)).setText(bean.getTime());
 43         ((TextView) holder.getView(R.id.phoneTv)).setText(bean.getPhone());
 44 
 45 *//*
 46         TextView tv = holder.getView(R.id.titleTv);
 47         tv.setText(...);
 48 
 49        ImageView view = getView(viewId);
 50        Imageloader.getInstance().loadImag(view.url);
 51 /*//*
 52     }*/
 53 
 54     @Override
 55     public void convert(ViewHolder holder, final Video video) {
 56 
 57         //1、作者的头像
 58         ImageButton mVideoAvatarBtn = holder.getView(R.id.mVideoAvatarBtn);
 59         //如果用的是开源框架Picasso获取网络图片,那就按照下面注释掉这一行代码来做。
 60         // load方法里的参数是请求的图片的url。placeholder方法中的参数是说,加载图片成功之前默认显示的图片
 61         //Picasso.with(mContext).load(Constants.CONTENT_HOST + video.getUserAvatarUrl()).placeholder(R.mipmap.ic_launcher).into(mVideoAvatarBtn);
 62         String imageUrl = Constants.CONTENT_HOST + video.getUserAvatarUrl();
 63 
 64         /*
 65         下面的加载网络图片中,用到了Android-Universal-Image-Loader框架
 66          */
 67         //显示图片的配置
 68         // 使用DisplayImageOptions.Builder()创建DisplayImageOptions
 69         options = new DisplayImageOptions.Builder()
 70                 .showStubImage(R.mipmap.phone)            // 设置图片下载期间显示的图片
 71                 .showImageForEmptyUri(R.mipmap.ic_launcher)    // 设置图片Uri为空或是错误的时候显示的图片
 72                 .showImageOnFail(R.drawable.default_ptr_flip)        // 设置图片加载或解码过程中发生错误显示的图片
 73                 .cacheInMemory(true)                        // 设置下载的图片是否缓存在内存中
 74                 .cacheOnDisc(true)                            // 设置下载的图片是否缓存在SD卡中
 75                 .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)   //图片会缩放到目标大小完全。非常重要,也就是说,这个view有多大,图片就会缩放到多大
 76                 .build();
 77 
 78         ImageLoader.getInstance().displayImage(imageUrl, mVideoAvatarBtn, options);
 79 
 80         //2、作者的昵称
 81         TextView mVideoNicknameTv = holder.getView(R.id.mVideoNicknameTv);
 82         mVideoNicknameTv.setText(video.getUserNickName());
 83 
 84 
 85         //3、视频的缩略图
 86         ImageButton mVideoImgBtn = holder.getView(R.id.mVideoImgBtn);
 87 
 88        //让缩略图的宽度为手机屏幕的宽度,高度为手机屏幕宽度的一半。说白了,就是让 图片的尺寸为2:1
 89         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
 90                 ViewGroup.LayoutParams.MATCH_PARENT,
 91                 (int) (Constants.displayWidth * 0.5f + 0.5f));
 92         mVideoImgBtn.setLayoutParams(params);
 93 
 94         String PicUrl = Constants.CONTENT_HOST + video.getPicUrl();
 95 
 96 
 97         //Picasso.with(mContext).load(Constants.CONTENT_HOST + video.getPicUrl()).placeholder(R.mipmap.defaultimg).into(mVideoImgBtn);
 98         ImageLoader.getInstance().displayImage(PicUrl, mVideoImgBtn, options);
 99 
100 
101         System.out.println("---->" + video.getPicUrl());
102 
103 
104 
105         //跳转到具体是哪一个视频(即item的详情页)
106 /*        mVideoImgBtn.setOnClickListener(new View.OnClickListener() {
107             @Override
108             public void onClick(View v) {
109                 int id = video.getVideoUrl();
110                 mContext.startActivity(new Intent());
111             }
112         });*/
113     }
114 }

这里要补充一下,安卓加载ListView用到了适配器模式,所以需要下面自定义的Adapter对象,这个和我们IOS开发的加载UITableView用到的一些列代理方法的代理模式还是有区别的。不过,如果读者学习了适配器模式,将会对安卓底层用到的适配器模式就会有所理解了。

完整版代码如下:

 

 1 package com.example.heyang.myapplication;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 import android.widget.ImageView;
 9 import android.widget.TextView;
10 
11 import java.util.List;
12 
13 /**
14  * Created by HeYang on 16/10/6.
15  */
16 
17 public class NewsAdapter extends BaseAdapter {
18 
19     // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
20     private List<NewsBean> beanList;
21     // 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
22     private LayoutInflater layoutInflater;
23 
24     // 创建构造方法
25     public NewsAdapter(MainActivity context, List<NewsBean> data){
26         beanList = data;
27         layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
28     }
29 
30     @Override
31     public int getCount() {
32         return beanList.size();
33     }
34 
35     @Override
36     public Object getItem(int i) {
37         // 因为beanList是数组,通过get访问对应index的元素
38         return beanList.get(i);
39     }
40 
41     @Override
42     public long getItemId(int i) {
43         return i;
44     }
45 
46     @Override
47     public View getView(int i, View view, ViewGroup viewGroup) {
48         ViewHolder viewHolder = null;
49         if (view == null){
50             viewHolder = new ViewHolder();
51             // 每一个View都要和layout关联
52             view = layoutInflater.inflate(R.layout.item_layout,null);
53             // 在R.layout.item_layout中有三个控件对象
54             // 现在全部传递给view对象了
55             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
56             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
57             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
58             view.setTag(viewHolder);
59 
60         }else {
61 
62             // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
63             viewHolder = (ViewHolder) view.getTag();
64         }
65         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
66         viewHolder.tvContent.setText(beanList.get(i).newsContent);
67         // 先默认加载系统图片
68         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);
69 
70         return view;
71     }
72 
73     // 最后需要一个匿名内部类来创建一个临时缓存View的对象
74     class ViewHolder{
75         public TextView tvContent,tvTitle;
76         public ImageView ivIcon;
77     }
78 }

(1)activity_main.xml:

尤其要注意第75行的属性哦,这样可以让图片缩放到当前控件的大小。(如果没有这一行,图片大小就是包裹内容;如果加了这一行,图片大小就是匹配当前控件的大小,因为我在第88行设置了这个ImageButton的宽度是手机屏幕的宽度,高度是手机屏幕宽度的一半,这样的话,不管网络上的
图片是多大,都能够保证显示出来的图片比例是2:1)

五、最后优化和完善MainActivity对象

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<ListView
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"></ListView>

</RelativeLayout>

(8)MainActivity.java:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.os.AsyncTask;
  4 import android.support.v7.app.AppCompatActivity;
  5 import android.os.Bundle;
  6 import android.util.Log;
  7 import android.widget.ListView;
  8 
  9 import org.json.JSONArray;
 10 import org.json.JSONException;
 11 import org.json.JSONObject;
 12 
 13 import java.io.BufferedReader;
 14 import java.io.IOException;
 15 import java.io.InputStream;
 16 import java.io.InputStreamReader;
 17 import java.io.UnsupportedEncodingException;
 18 import java.net.URL;
 19 import java.util.ArrayList;
 20 import java.util.List;
 21 
 22 public class MainActivity extends AppCompatActivity {
 23 
 24     private ListView mListView;
 25 
 26     private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30";
 27 
 28     @Override
 29     protected void onCreate(Bundle savedInstanceState) {
 30         super.onCreate(savedInstanceState);
 31         setContentView(R.layout.activity_main);
 32 
 33         // 获取xml上的ListView对象
 34         mListView = (ListView) findViewById(R.id.lv_main);
 35 
 36         new NewsAsyncTask().execute(URL);
 37     }
 38 
 39     // 通过输入输出流获取整个网页格式的字符串数据
 40     private String readStream(InputStream is){
 41         InputStreamReader isr;
 42         String result = "";
 43 
 44         try {
 45             String line = "";
 46             // 1、输入流对象 2、输入流读取对象 3、字节读取对象
 47             isr = new InputStreamReader(is,"utf-8");
 48             BufferedReader br = new BufferedReader(isr);
 49             while ((line = br.readLine()) != null){
 50                 result += line;
 51             }
 52         } catch(UnsupportedEncodingException e){
 53             e.printStackTrace();
 54         } catch (IOException e) {
 55             e.printStackTrace();
 56         }
 57         return result;
 58     }
 59 
 60 
 61     private List<NewsBean> getJsonData(String url){
 62         // 创建存储NewsBean的集合对象
 63         List<NewsBean> newsBeanList = new ArrayList<>();
 64         try {
 65             // 取出网络的json字符串的格式之后
 66             String jsonStr = readStream(new URL(url).openStream());
 67             // 就要用JSONObject对象进行解析
 68             JSONObject jsonObject;
 69             // 然后需要NewsBean,其实相当于IOS的模型对象
 70             NewsBean newsBean;
 71             try {
 72                 // jsonObject的对象,创建该对象的同时传入json字符串格式的对象
 73                 jsonObject = new JSONObject(jsonStr);
 74                 // 拿到jsonObject对象之后,就需要通过key值来拿到数组
 75                 JSONArray jsonArray = jsonObject.getJSONArray("data");
 76                 // 然后开始遍历数组,获取模型数组
 77                 for (int i = 0;i<jsonArray.length();i++){
 78                     // 数组里每一个元素又是jsonObject
 79                     jsonObject = jsonArray.getJSONObject(i);
 80 
 81                     // 开始创建模型对象
 82                     newsBean = new NewsBean();
 83                     newsBean.newsIconURL = jsonObject.getString("picSmall");
 84                     newsBean.newsTitle = jsonObject.getString("name");
 85                     newsBean.newsContent = jsonObject.getString("description");
 86 
 87                     // 创建的一个模型对象,就要添加到集合当中去
 88                     newsBeanList.add(newsBean);
 89                 }
 90 
 91 
 92             } catch (JSONException e) {
 93                 e.printStackTrace();
 94             }
 95 
 96             Log.d("heyang",jsonStr);
 97         } catch (IOException e) {
 98             e.printStackTrace();
 99         }
100 
101         return newsBeanList;
102     }
103 
104     // 创建一个内部类来实现 ,在实现下面内部类之前,需要自定义的Bean对象来封装处理Josn格式的数据
105     class  NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
106         @Override
107         protected List<NewsBean> doInBackground(String... strings) {
108             return getJsonData(strings[0]);
109         }
110 
111         @Override
112         protected void onPostExecute(List<NewsBean> newsBeen) {
113             super.onPostExecute(newsBeen);
114             NewsAdapter newsAdapter = new NewsAdapter(MainActivity.this,newsBeen);
115             mListView.setAdapter(newsAdapter);
116 
117         }
118     }
119 }

(2)item_listview.xml.xml:(ListView中单个item的布局)

  1 package com.smyhvae.pulltorefreshdemo;
  2 
  3 import android.app.Activity;
  4 import android.content.Context;
  5 import android.os.Bundle;
  6 import android.os.Handler;
  7 import android.os.Message;
  8 import android.util.DisplayMetrics;
  9 import android.util.Log;
 10 import android.widget.ListView;
 11 import android.widget.Toast;
 12 
 13 import com.google.gson.Gson;
 14 import com.google.gson.GsonBuilder;
 15 import com.google.gson.reflect.TypeToken;
 16 import com.handmark.pulltorefresh.library.PullToRefreshBase;
 17 import com.handmark.pulltorefresh.library.PullToRefreshListView;
 18 import com.lidroid.xutils.HttpUtils;
 19 import com.lidroid.xutils.exception.HttpException;
 20 import com.lidroid.xutils.http.RequestParams;
 21 import com.lidroid.xutils.http.ResponseInfo;
 22 import com.lidroid.xutils.http.callback.RequestCallBack;
 23 import com.lidroid.xutils.http.client.HttpRequest;
 24 import com.smyhvae.pulltorefreshdemo.adapter.VideoListViewAdapter;
 25 import com.smyhvae.pulltorefreshdemo.entities.ResponseObject;
 26 import com.smyhvae.pulltorefreshdemo.entities.Video;
 27 import com.smyhvae.pulltorefreshdemo.entities.VideoResponse;
 28 import com.smyhvae.pulltorefreshdemo.utils.Constants;
 29 
 30 import java.util.List;
 31 
 32 
 33 public class MainActivity extends Activity {
 34 
 35     private PullToRefreshListView lv;
 36     private Context mContext;
 37 
 38     private List<Video> videoList; //用来存放视频列表的集合
 39 
 40     private int page = 0;  //当前页码
 41     private int size = 10; //每页显示10个
 42     private int count = 0; //当前页面有多少个视频
 43 
 44     private VideoListViewAdapter videoListViewAdapter;
 45 
 46 
 47 
 48 
 49     @Override
 50     protected void onCreate(Bundle savedInstanceState) {
 51         super.onCreate(savedInstanceState);
 52         setContentView(R.layout.activity_main);
 53         //第一个Activity加载进来时,我们就获取屏幕的宽度和高度
 54         DisplayMetrics displayMetrics = new DisplayMetrics();
 55         getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
 56         Constants.displayWidth = displayMetrics.widthPixels;
 57         Constants.displayHeight = displayMetrics.heightPixels;
 58 
 59 
 60 
 61         initView();
 62 
 63     }
 64 
 65     private void initView() {
 66         lv = (PullToRefreshListView) findViewById(R.id.lv);
 67 
 68         /*
 69         设置刷新的模式:
 70         可选值为:disabled(禁用下拉刷新),
 71         pullFromStart(仅支持下拉刷新),
 72         pullFromEnd(仅支持上拉刷新),
 73         both(二者都支持),
 74         manualOnly(只允许手动触发)
 75          */
 76         lv.setMode(PullToRefreshBase.Mode.BOTH);  //让这个Listview支持上拉加载更多,下拉刷新
 77         lv.setScrollingWhileRefreshingEnabled(true);//滚动的时候不允许刷新,要不然么会很乱
 78         //很重要,刷新时做回调
 79         lv.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
 80             @Override
 81             public void onRefresh(PullToRefreshBase<ListView> refreshView) {
 82                 //在这里做数据加载的操作
 83                 loadData(refreshView.getScrollY() < 0);
 84             }
 85         });
 86 
 87         //首次打开页面时,延时200ms后自动加载数据
 88        new Handler (new Handler.Callback(){
 89            @Override
 90             public boolean handleMessage(Message msg) {
 91                lv.setRefreshing();
 92                return true;
 93            }
 94        }).sendEmptyMessageDelayed(0,200);
 95     }
 96 
 97 
 98     //如果是true表示下拉刷新,false表示上拉加载更多(如果y值小于0,说明是下拉操作)
 99     private void loadData(final boolean direction) {
100         //http://172.24.1.49:8081/video/getVideos?apikey=  &typeid=1&page=1
101         RequestParams params = new RequestParams();
102 
103         if (direction) {  //如果是上拉,那应该将page变为第一页
104             page = 1;
105 
106         } else {
107             page++; //如果是下拉,就让page加1
108 
109         }
110 
111         params.addQueryStringParameter("page", String.valueOf(page)); //默认显示第一页
112         // params.addQueryStringParameter("size", "10"); //每页显示10个
113 
114         new HttpUtils().send(HttpRequest.HttpMethod.GET, Constants.VIDEO_LIST + "typeid=1&", params, new RequestCallBack<String>() {
115             @Override
116             public void onSuccess(ResponseInfo<String> responseInfo) {
117                 lv.onRefreshComplete();
118                 Log.d("json", "---video的json数据>" + responseInfo.result);
119 
120                 //解析服务器端的json数据
121                 Gson gson = new GsonBuilder().create();
122                 ResponseObject<VideoResponse> object = gson.fromJson(responseInfo.result, new TypeToken<ResponseObject<VideoResponse>>() {
123                 }.getType());
124 /*                ResponseObject<VideoResponse> object = new GsonBuilder().create().fromJson(responseInfo.result, new TypeToken<VideoResponse>() {
125                 }.getType());*/
126                 page = Integer.parseInt(object.getResult().getPage()); //获取服务器端返回来的当前页码
127                 count = object.getResult().getCnt(); //获取当前页面有多少个视频
128                 Log.d("json","---当前页面的item的个数>"+count);
129                 if (direction) { //下拉刷新
130                     videoList = object.getResult().getVideos();  //获取视频信息的集合,并存放
131 
132                     videoListViewAdapter = new VideoListViewAdapter(MainActivity.this,videoList);
133                     lv.setAdapter(videoListViewAdapter); //为这个listView绑定适配器
134 
135                 } else {//尾部加载更多
136                     videoList.addAll(object.getResult().getVideos());
137 
138                 }
139 
140                 if (count == 0) { //如果当前页面已经没有视频了,那就告诉客户端,不要再拉了,因为后面没有数据了。
141                     lv.setMode(PullToRefreshBase.Mode.PULL_FROM_START);
142                 }
143 
144             }
145 
146             @Override
147             public void onFailure(HttpException e, String s) {
148                 lv.onRefreshComplete();//不管是请求成功还是请求失败,我们都停止加载数据
149                 Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
150 
151             }
152         });
153 
154     }
155 
156 } 

 好,运行一下模拟器看看结果:

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:padding="10dp">
 6 
 7     <TextView
 8         android:id="@+id/titleTv"
 9         android:layout_width="wrap_content"
10         android:layout_height="wrap_content"
11         android:singleLine="true"
12         android:text="Android新技能"
13         android:textColor="#444"
14         android:textSize="16sp" />
15 
16     <TextView
17         android:id="@+id/descTv"
18         android:layout_width="match_parent"
19         android:layout_height="wrap_content"
20         android:layout_below="@+id/titleTv"
21         android:layout_marginTop="10dp"
22         android:maxLines="2"
23         android:minLines="1"
24         android:text="Android为ListView和GridView打造万能适配器"
25         android:textColor="#898989"
26         android:textSize="16sp" />
27 
28     <TextView
29         android:id="@+id/timeTv"
30         android:paddingTop="3dp"
31         android:layout_width="wrap_content"
32         android:layout_height="wrap_content"
33         android:layout_below="@+id/descTv"
34         android:layout_marginTop="10dp"
35         android:text="2015-05-04"
36         android:textColor="#898989"
37         android:textSize="12sp" />
38 
39     <TextView
40         android:padding="2dp"
41         android:id="@+id/phoneTv"
42         android:gravity="center"
43         android:layout_width="wrap_content"
44         android:layout_height="wrap_content"
45         android:layout_below="@+id/descTv"
46         android:layout_marginTop="10dp"
47         android:background="#2ED667"
48         android:drawableLeft="@mipmap/phone"
49         android:drawablePadding="5dp"
50         android:text="10086"
51         android:textColor="#ffffff"
52         android:textSize="12sp"
53         android:layout_alignParentRight="true" />
54 
55 
56 </RelativeLayout>

这个MainActivity中讲到了xUtils怎样获取到网络上的json数据,并用Gson解析,然后用pull
to
refresh处理上拉下拉刷新的逻辑,好吧,确实是快速开发,用到的框架还挺多的好伐~

图片 12

其对应的布局效果如下:

 

六、下面采用两种方法进行加载图片:①多线程加载图片 ②AsyncTask

图片 13

【工程文件】

①多线程加载图片:

(3)Bean.java:数据集

 2015-05-08-PullToRefreshDemo.rar

创建一个普通的Class类:ImageLoader,在内部使用线程run运行执行ImageView加载网络图片的逻辑

 1 package com.smyhvae.baseadapter.entities;
 2 
 3 /**
 4  * Created by smyhvae on 2015/5/4.
 5  */
 6 public class Bean {
 7     private String title;
 8     private String desc;
 9     private String time;
10     private String phone;
11 
12     public Bean() {
13     }
14 
15     public Bean(String title, String desc, String time, String phone) {
16         this.title = title;
17 
18         this.desc = desc;
19         this.time = time;
20         this.phone = phone;
21     }
22 
23     public String getTitle() {
24         return title;
25     }
26 
27     public void setTitle(String title) {
28         this.title = title;
29     }
30 
31     public String getDesc() {
32         return desc;
33     }
34 
35     public void setDesc(String desc) {
36         this.desc = desc;
37     }
38 
39     public String getTime() {
40         return time;
41     }
42 
43     public void setTime(String time) {
44         this.time = time;
45     }
46 
47     public String getPhone() {
48         return phone;
49     }
50 
51     public void setPhone(String phone) {
52         this.phone = phone;
53     }
54 }

 

 1 package com.example.heyang.myapplication;
 2 
 3 import android.graphics.Bitmap;
 4 import android.graphics.BitmapFactory;
 5 import android.os.Handler;
 6 import android.os.Message;
 7 import android.widget.ImageView;
 8 
 9 import java.io.BufferedInputStream;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.net.HttpURLConnection;
13 import java.net.MalformedURLException;
14 import java.net.URL;
15 
16 /**
17  * Created by HeYang on 16/10/6.
18  */
19 
20 public class ImageLoader {
21 
22     private ImageView mImageView;
23     private String mURLStr;
24 
25     private Handler handler = new Handler(){
26         @Override
27         public void handleMessage(Message msg) {
28             super.handleMessage(msg);
29 
30             if (mImageView.getTag().equals(mURLStr)){
31                 mImageView.setImageBitmap((Bitmap) msg.obj);
32             }
33 
34 
35         }
36     };
37 
38     public void showImageByThread(ImageView imageView, final String urlString){
39 
40         mImageView = imageView;
41         mURLStr = urlString;
42 
43         new Thread(){
44             @Override
45             public void run() {
46                 super.run();
47                 Bitmap bitmap = getBitmapFromURL(urlString);
48                 // 当前线程是子线程,并不是UI主线程
49                 // 不是Message message = new Message();
50                 Message message = Message.obtain();
51                 message.obj = bitmap;
52                 handler.sendMessage(message);
53 
54             }
55         }.start();
56     }
57 
58     public Bitmap getBitmapFromURL(String urlString){
59         Bitmap bitmap = null;
60         InputStream is = null;
61 
62         try {
63             URL url = new URL(urlString);
64             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
65             is = new BufferedInputStream(connection.getInputStream());
66             bitmap = BitmapFactory.decodeStream(is);
67             // 最后要关闭http连接
68             connection.disconnect();
69             Thread.sleep(1000);// 睡眠1秒
70         } catch (IOException e) {
71             e.printStackTrace();
72         } catch (InterruptedException e) {
73             e.printStackTrace();
74         } finally {
75 
76             try {
77                 is.close();
78             } catch (IOException e) {
79                 e.printStackTrace();
80             }
81         }
82 
83         return bitmap;
84     }
85 }

(4)【可复用的代码】ViewHolder.java:

然后修改一下之前的NewsAdapter的代码:

 1 package com.smyhvae.baseadapter.utils;
 2 
 3 import android.content.Context;
 4 import android.util.SparseArray;
 5 import android.view.LayoutInflater;
 6 import android.view.View;
 7 import android.view.ViewGroup;
 8 
 9 /**
10  * Created by smyhvae on 2015/5/4.
11  */
12 public class ViewHolder {
13 
14     private SparseArray<View> mViews;
15     private int mPosition;
16     private View mConvertView;
17 
18     public ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
19         this.mPosition = position;
20         this.mViews = new SparseArray<View>();
21 
22         mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
23 
24         mConvertView.setTag(this);
25 
26     }
27 
28     public static ViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
29         if (convertView == null) {
30             return new ViewHolder(context, parent, layoutId, position);
31         } else {
32             ViewHolder holder = (ViewHolder) convertView.getTag();
33             holder.mPosition = position; //即使ViewHolder是复用的,但是position记得更新一下
34             return holder;
35         }
36     }
37 
38     /*
39     通过viewId获取控件
40      */
41     //使用的是泛型T,返回的是View的子类
42     public <T extends View> T getView(int viewId) {
43         View view = mViews.get(viewId);
44 
45         if (view == null) {
46             view = mConvertView.findViewById(viewId);
47             mViews.put(viewId, view);
48         }
49 
50         return (T) view;
51     }
52 
53     public View getConvertView() {
54         return mConvertView;
55     }
56 
57 }
 1 package com.example.heyang.myapplication;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 import android.widget.ImageView;
 9 import android.widget.TextView;
10 
11 import java.util.List;
12 
13 /**
14  * Created by HeYang on 16/10/6.
15  */
16 
17 public class NewsAdapter extends BaseAdapter {
18 
19     // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
20     private List<NewsBean> beanList;
21     // 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
22     private LayoutInflater layoutInflater;
23 
24     // 创建构造方法
25     public NewsAdapter(MainActivity context, List<NewsBean> data){
26         beanList = data;
27         layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
28     }
29 
30     @Override
31     public int getCount() {
32         return beanList.size();
33     }
34 
35     @Override
36     public Object getItem(int i) {
37         // 因为beanList是数组,通过get访问对应index的元素
38         return beanList.get(i);
39     }
40 
41     @Override
42     public long getItemId(int i) {
43         return i;
44     }
45 
46     @Override
47     public View getView(int i, View view, ViewGroup viewGroup) {
48         ViewHolder viewHolder = null;
49         if (view == null){
50             viewHolder = new ViewHolder();
51             // 每一个View都要和layout关联
52             view = layoutInflater.inflate(R.layout.item_layout,null);
53             // 在R.layout.item_layout中有三个控件对象
54             // 现在全部传递给view对象了
55             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
56             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
57             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
58 
59             view.setTag(viewHolder);
60 
61         }else {
62 
63             // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
64             viewHolder = (ViewHolder) view.getTag();
65         }
66         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
67         viewHolder.tvContent.setText(beanList.get(i).newsContent);
68         // 先默认加载系统图片
69         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);  // 类似加载占位图片
70         viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
71         // 将ImageView对象和URLSting对象传入进去
72         new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
73 
74         return view;
75     }
76 
77     // 最后需要一个匿名内部类来创建一个临时缓存View的对象
78     class ViewHolder{
79         public TextView tvContent,tvTitle;
80         public ImageView ivIcon;
81     }
82 }

(5)【可复用的代码】ListViewAdapter.java:自定义的通用适配器,继承自BaseAdapter。以后如果是自定义ListView的adapter,继承它就行了

注意,其中使用了viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);作为唯一标示传递给ImageLoader内部做判断,这样加载ListView不会出现图片加载和缓存冲突了现象。(源码中保留了Thread.sleep(1000)可以查看到效果)。

 1 package com.smyhvae.baseadapter.utils;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 
 9 import java.util.List;
10 
11 /**
12  * Created by smyhvae on 2015/5/4.
13  * 通用的ListView的BaseAdapter,所有的ListView的自定义adapter都可以继承这个类哦
14  */
15 public abstract class ListViewAdapter<T> extends BaseAdapter {
16 
17     //为了让子类访问,于是将属性设置为protected
18     protected Context mContext;
19     protected List<T> mDatas;
20     protected LayoutInflater mInflater;
21     private int layoutId; //不同的ListView的item布局肯能不同,所以要把布局单独提取出来
22 
23     public ListViewAdapter(Context context, List<T> datas, int layoutId) {
24         this.mContext = context;
25         mInflater = LayoutInflater.from(context);
26         this.mDatas = datas;
27         this.layoutId = layoutId;
28     }
29 
30     @Override
31     public int getCount() {
32         return mDatas.size();
33     }
34 
35     @Override
36     public T getItem(int position) {
37         return mDatas.get(position);
38     }
39 
40     @Override
41     public long getItemId(int position) {
42         return position;
43     }
44 
45     @Override
46     public View getView(int position, View convertView, ViewGroup parent) {
47         //初始化ViewHolder,使用通用的ViewHolder,一行代码就搞定ViewHolder的初始化咯
48         ViewHolder holder = ViewHolder.get(mContext, convertView, parent, layoutId, position);//layoutId就是单个item的布局
49 
50         convert(holder, getItem(position));
51         return holder.getConvertView(); //这一行的代码要注意了
52     }
53 
54     //将convert方法公布出去
55     public abstract void convert(ViewHolder holder, T t);
56 
57 }

图片 14

(6)ListViewAdapterWithViewHolder.java:继承自ListViewAdapter

②AsyncTask

 1 package com.smyhvae.baseadapter;
 2 
 3 import android.content.Context;
 4 import android.widget.TextView;
 5 
 6 import com.smyhvae.baseadapter.entities.Bean;
 7 import com.smyhvae.baseadapter.utils.ListViewAdapter;
 8 import com.smyhvae.baseadapter.utils.ViewHolder;
 9 
10 import java.util.List;
11 
12 /**
13  * Created by smyhvae on 2015/5/4.
14  */
15 public class ListViewAdapterWithViewHolder extends ListViewAdapter<Bean> {
16 
17     //MyAdapter需要一个Context,通过Context获得Layout.inflater,然后通过inflater加载item的布局
18     public ListViewAdapterWithViewHolder(Context context, List<Bean> datas) {
19         super(context, datas, R.layout.item_listview);
20     }
21 
22     @Override
23     public void convert(ViewHolder holder, Bean bean) {
24 
25         ((TextView) holder.getView(R.id.titleTv)).setText(bean.getTitle());
26         ((TextView) holder.getView(R.id.descTv)).setText(bean.getDesc());
27         ((TextView) holder.getView(R.id.timeTv)).setText(bean.getTime());
28         ((TextView) holder.getView(R.id.phoneTv)).setText(bean.getPhone());
29 
30 /*
31         TextView tv = holder.getView(R.id.titleTv);
32         tv.setText(...);
33 
34        ImageView view = getView(viewId);
35        Imageloader.getInstance().loadImag(view.url);
36 */
37     }
38 }

接着,使用AsyncTask异步加载,在imageLoader对象的基础上继续:

(7)MainActivity.java:

图片 15

 1 package com.smyhvae.baseadapter;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.widget.ListView;
 6 
 7 import com.smyhvae.baseadapter.entities.Bean;
 8 
 9 import java.util.ArrayList;
10 import java.util.List;
11 
12 public class MainActivity extends Activity {
13 
14     private ListView listView;
15     private List<Bean> mDatas;
16 
17     private ListViewAdapterWithViewHolder listViewAdapterWithViewHolder;
18 
19     @Override
20     protected void onCreate(Bundle savedInstanceState) {
21         super.onCreate(savedInstanceState);
22         setContentView(R.layout.activity_main);
23 
24         initView();
25         initData();
26     }
27 
28 
29     //方法:初始化View
30     private void initView() {
31         listView = (ListView) findViewById(R.id.listView);
32     }
33 
34     //方法;初始化Data
35     private void initData() {
36         mDatas = new ArrayList<Bean>();
37 
38         //将数据装到集合中去
39         Bean bean = new Bean("Android新技能1", "Android为ListView和GridView打造万能适配器", "2015-05-04", "10086");
40         mDatas.add(bean);
41 
42         bean = new Bean("Android新技能2", "Android为ListView和GridView打造万能适配器", "2015-05-04", "10086");
43         mDatas.add(bean);
44 
45         bean = new Bean("Android新技能3", "Android为ListView和GridView打造万能适配器", "2015-05-04", "10086");
46         mDatas.add(bean);
47 
48         bean = new Bean("Android新技能4", "Android为ListView和GridView打造万能适配器", "2015-05-04", "10086");
49         mDatas.add(bean);
50 
51         //为数据绑定适配器
52         listViewAdapterWithViewHolder = new ListViewAdapterWithViewHolder(this, mDatas);
53 
54         listView.setAdapter(listViewAdapterWithViewHolder);
55 
56     }
57 } 

完整源码:

运行效果:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.graphics.Bitmap;
  4 import android.graphics.BitmapFactory;
  5 import android.os.AsyncTask;
  6 import android.os.Handler;
  7 import android.os.Message;
  8 import android.widget.ImageView;
  9 
 10 import java.io.BufferedInputStream;
 11 import java.io.IOException;
 12 import java.io.InputStream;
 13 import java.net.HttpURLConnection;
 14 import java.net.MalformedURLException;
 15 import java.net.URL;
 16 
 17 /**
 18  * Created by HeYang on 16/10/6.
 19  */
 20 
 21 public class ImageLoader {
 22 
 23     private ImageView mImageView;
 24     private String mURLStr;
 25 
 26     private Handler handler = new Handler(){
 27         @Override
 28         public void handleMessage(Message msg) {
 29             super.handleMessage(msg);
 30 
 31             if (mImageView.getTag().equals(mURLStr)){
 32                 mImageView.setImageBitmap((Bitmap) msg.obj);
 33             }
 34 
 35 
 36         }
 37     };
 38 
 39     public void showImageByThread(ImageView imageView, final String urlString){
 40 
 41         mImageView = imageView;
 42         mURLStr = urlString;
 43 
 44         new Thread(){
 45             @Override
 46             public void run() {
 47                 super.run();
 48                 Bitmap bitmap = getBitmapFromURL(urlString);
 49                 // 当前线程是子线程,并不是UI主线程
 50                 // 不是Message message = new Message();
 51                 Message message = Message.obtain();
 52                 message.obj = bitmap;
 53                 handler.sendMessage(message);
 54 
 55             }
 56         }.start();
 57     }
 58 
 59     public Bitmap getBitmapFromURL(String urlString){
 60         Bitmap bitmap = null;
 61         InputStream is = null;
 62 
 63         try {
 64             URL url = new URL(urlString);
 65             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 66             is = new BufferedInputStream(connection.getInputStream());
 67             bitmap = BitmapFactory.decodeStream(is);
 68             // 最后要关闭http连接
 69             connection.disconnect();
 70             Thread.sleep(1000);// 睡眠1秒
 71         } catch (IOException e) {
 72             e.printStackTrace();
 73         } catch (InterruptedException e) {
 74             e.printStackTrace();
 75         } finally {
 76 
 77             try {
 78                 is.close();
 79             } catch (IOException e) {
 80                 e.printStackTrace();
 81             }
 82         }
 83 
 84         return bitmap;
 85     }
 86 
 87 
 88     // ==================使用AsyncTask====================
 89     public void showImageByAsyncTask(ImageView imageView, final String urlString){
 90         new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 这两个参数分别传递的目的地可以理解一下
 91     }
 92     
 93     private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
 94 
 95         // 需要私有的ImageView对象和构造方法来传递ImageView对象
 96         private ImageView mImageView;
 97         private String urlString;
 98 
 99         public NewsAsyncTask(ImageView imageView,String urlString){
100             mImageView = imageView;
101             urlString = urlString;
102         }
103 
104         @Override
105         protected Bitmap doInBackground(String... strings) {
106             // 在这个方法中,完成异步下载的任务
107             getBitmapFromURL(strings[0]);
108             return null;
109         }
110 
111 
112         @Override
113         protected void onPostExecute(Bitmap bitmap) {
114             super.onPostExecute(bitmap);
115             // 然后在这个方法中设置imageViewd
116             if (mImageView.getTag().equals(urlString)){
117                 mImageView.setImageBitmap(bitmap);
118             }
119 
120         }
121     }
122 
123 }

图片 16

然后在NewsAdapter里修改一下:

这样的话,以后每写个ListView,就这么做:直接导入ViewHolder.java和ListViewAdapter,然后写一个自定义adapter继承自ListViewAdapter就行了。

图片 17

【工程文件】

 然后运行效果和上面多线程的效果一样,这里就不再重复展示了。

2015-05-04-BaseAdapter的封装.rar

 

 

七、LruCache缓存处理

三、常见问题:

为了提高用户体验,所以需要使用缓存机制。这里安卓提供了Lru算法处理缓存。

1、item控件抢占焦点:

Lru:Least Recently Used 近期最少使用算法。

假设item里有一个checkbox,那运行程序之后,发现只有checkBox能被点击,而item中的其他位置不能被点击(包括点击整个item也没有反应),这是由于checkbox抢占了整个item的焦点。办法是::

所以,我们需要在ImageLoader中创建LruCache对象:

办法1:为该checkBox设置属性:

图片 18

android:focusable = "false"

完整源码:

办法二:为该item设置属性:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.annotation.TargetApi;
  4 import android.graphics.Bitmap;
  5 import android.graphics.BitmapFactory;
  6 import android.os.AsyncTask;
  7 import android.os.Build;
  8 import android.os.Handler;
  9 import android.os.Message;
 10 import android.support.annotation.RequiresApi;
 11 import android.util.Log;
 12 import android.util.LruCache;
 13 import android.widget.ImageView;
 14 
 15 import java.io.BufferedInputStream;
 16 import java.io.IOException;
 17 import java.io.InputStream;
 18 import java.net.HttpURLConnection;
 19 import java.net.MalformedURLException;
 20 import java.net.URL;
 21 
 22 /**
 23  * Created by HeYang on 16/10/6.
 24  */
 25 
 26 public class ImageLoader {
 27 
 28     private ImageView mImageView;
 29     private String mURLStr;
 30     // 创建缓存对象
 31     // 第一个参数是需要缓存对象的名字或者ID,这里我们传输url作为唯一名字即可
 32     // 第二个参数是Bitmap对象
 33     private LruCache<String,Bitmap> lruCache;
 34 
 35     // 然后我们需要在构造方法中初始化这个缓存对象
 36     // 另外,我们不可能把所有的缓存空间拿来用
 37     @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
 38     public ImageLoader(){
 39         
 40         // 获取最大可用内存
 41         int maxMemory = (int) Runtime.getRuntime().maxMemory();
 42         int cachaSize =  maxMemory / 4;
 43         // 创建LruCache对象,同时用匿名内部类的方式重写方法
 44         lruCache = new LruCache<String,Bitmap>(cachaSize){
 45             @Override
 46             protected int sizeOf(String key, Bitmap value) {
 47                 // 我们需要直接返回Bitmap value的实际大小
 48                 //return super.sizeOf(key, value);
 49                 // 在每次存入缓存的时候调用
 50                 return value.getByteCount();
 51             }
 52         };
 53     }
 54 
 55     // 然后我们要写俩个方法:1、将bitmap存入缓存中 2、从缓存中取出bitmap
 56 
 57     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 58     public void addBitmapToCache(String url, Bitmap bitmap){
 59         if (getBitMapFromCache(url) == null){
 60             lruCache.put(url,bitmap);
 61         }
 62     }
 63 
 64     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 65     public Bitmap getBitMapFromCache(String url){
 66         return lruCache.get(url);
 67     }
 68 
 69 
 70     private Handler handler = new Handler(){
 71         @Override
 72         public void handleMessage(Message msg) {
 73             super.handleMessage(msg);
 74 
 75             if (mImageView.getTag().equals(mURLStr)){
 76                 mImageView.setImageBitmap((Bitmap) msg.obj);
 77             }
 78 
 79 
 80         }
 81     };
 82 
 83     public void showImageByThread(ImageView imageView, final String urlString){
 84 
 85         mImageView = imageView;
 86         mURLStr = urlString;
 87 
 88         new Thread(){
 89             @Override
 90             public void run() {
 91                 super.run();
 92                 Bitmap bitmap = getBitmapFromURL(urlString);
 93                 // 当前线程是子线程,并不是UI主线程
 94                 // 不是Message message = new Message();
 95                 Message message = Message.obtain();
 96                 message.obj = bitmap;
 97                 handler.sendMessage(message);
 98 
 99             }
100         }.start();
101     }
102 
103     public Bitmap getBitmapFromURL(String urlString){
104         Bitmap bitmap = null;
105         InputStream is = null;
106 
107         try {
108             URL url = new URL(urlString);
109             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
110             is = new BufferedInputStream(connection.getInputStream());
111             bitmap = BitmapFactory.decodeStream(is);
112             // 最后要关闭http连接
113             connection.disconnect();
114 //            Thread.sleep(1000);// 睡眠1秒
115         } catch (IOException e) {
116             e.printStackTrace();
117         }
118 //        catch (InterruptedException e) {
119 //            e.printStackTrace();
120 //        }
121         finally {
122 
123             try {
124                 is.close();
125             } catch (IOException e) {
126                 e.printStackTrace();
127             }
128         }
129 
130         return bitmap;
131     }
132 
133 
134     // ==================使用AsyncTask====================
135     public void showImageByAsyncTask(ImageView imageView, final String urlString){
136         // 在异步请求之前,先判断缓存中是否有,有的话就取出直接加载
137         Bitmap bitmap = getBitMapFromCache(urlString);
138         if (bitmap == null){
139             // 如果没有,就异步任务加重
140             new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 这两个参数分别传递的目的地可以理解一下
141         }else{
142             imageView.setImageBitmap(bitmap);
143         }
144 
145     }
146 
147     private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
148 
149         // 需要私有的ImageView对象和构造方法来传递ImageView对象
150         private ImageView mImageView;
151         private String mURlString;
152 
153         public NewsAsyncTask(ImageView imageView,String urlString){
154             mImageView = imageView;
155             mURlString = urlString;
156         }
157 
158         @Override
159         protected Bitmap doInBackground(String... strings) {
160             // 在这个方法中,完成异步下载的任务
161             String url = strings[0];
162             // 从网络中获取图片
163             Bitmap bitmap = getBitmapFromURL(strings[0]);
164             if (bitmap != null){
165                 // 将网络加载出来的图片存储缓存
166                 addBitmapToCache(url,bitmap);
167             }
168             return bitmap;
169         }
170 
171 
172         @Override
173         protected void onPostExecute(Bitmap bitmap) {
174             super.onPostExecute(bitmap);
175             // 然后在这个方法中设置imageViewd
176 
177             if (mImageView.getTag().equals(mURlString)){
178                 mImageView.setImageBitmap(bitmap);
179             }
180 
181         }
182     }
183 }
android:descendantFocusability = "blocksDescendants" 

还需要在NewsAdapter类里做一个小小的优化,因为适配器的getView是个不断被调用的方法,就好比UITableView创建Cell的代理方法,会被不断的被调用。而在getView方法中,ImageLoader的构造方法会被不断的被调用,这样的话,会造成ImageLoader构造方法中的LruCache对象不断的被创建,这样显然是不好的。所以我们需要用一个引用,指向一开始就创建好的ImageLoader对象,然后多次使用。

不让这个item的焦点从上往下传。

图片 19

2、ListView复用导致内容错乱。

然后

 

图片 20

完整源码:

 1 package com.example.heyang.myapplication;
 2 
 3 import android.annotation.TargetApi;
 4 import android.content.Context;
 5 import android.os.Build;
 6 import android.support.annotation.RequiresApi;
 7 import android.view.LayoutInflater;
 8 import android.view.View;
 9 import android.view.ViewGroup;
10 import android.widget.BaseAdapter;
11 import android.widget.ImageView;
12 import android.widget.TextView;
13 
14 import java.util.List;
15 
16 /**
17  * Created by HeYang on 16/10/6.
18  */
19 
20 public class NewsAdapter extends BaseAdapter {
21 
22     // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
23     private List<NewsBean> beanList;
24     // 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
25     private LayoutInflater layoutInflater;
26 
27     private ImageLoader imageLoader;
28 
29     // 创建构造方法
30 
31     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
32     public NewsAdapter(MainActivity context, List<NewsBean> data){
33         beanList = data;
34         layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
35         imageLoader = new ImageLoader();
36     }
37 
38     @Override
39     public int getCount() {
40         return beanList.size();
41     }
42 
43     @Override
44     public Object getItem(int i) {
45         // 因为beanList是数组,通过get访问对应index的元素
46         return beanList.get(i);
47     }
48 
49     @Override
50     public long getItemId(int i) {
51         return i;
52     }
53 
54 
55     @Override
56     public View getView(int i, View view, ViewGroup viewGroup) {
57         ViewHolder viewHolder = null;
58         if (view == null){
59             viewHolder = new ViewHolder();
60             // 每一个View都要和layout关联
61             view = layoutInflater.inflate(R.layout.item_layout,null);
62             // 在R.layout.item_layout中有三个控件对象
63             // 现在全部传递给view对象了
64             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
65             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
66             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
67 
68             view.setTag(viewHolder);
69 
70         }else {
71 
72             // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
73             viewHolder = (ViewHolder) view.getTag();
74         }
75         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
76         viewHolder.tvContent.setText(beanList.get(i).newsContent);
77         // 先默认加载系统图片
78         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);  // 类似加载占位图片
79         viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
80         // 将ImageView对象和URLSting对象传入进去
81 //        new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
82         imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
83         return view;
84     }
85 
86     // 最后需要一个匿名内部类来创建一个临时缓存View的对象
87     class ViewHolder{
88         public TextView tvContent,tvTitle;
89         public ImageView ivIcon;
90     }
91 }

 

八、滚动时的高效优化

前面的代码,虽然顺利的实现了异步加载的过程,但是在实际项目开发中,ListView的每一个item项可能是很复杂的,如果按照前面的代码实现,用户在滚动的过程就可能会出现卡顿的现象。

虽然从网络加载的数据是在子线程中完成,但是更新UI的过程只能在主线程中更新,如果item项很复杂的话,滚动ListView出现卡顿现象也是可以理解的。

但是我们可以优化这个卡顿的问题:

  ① ListView滑动停止后才加载可见项

  ② ListView滑动时,取消所有加载项

优化的原因是:用户在滑动的时候,因为ListView在滑动,所以没必要做更新主线程UI的操作,而更新主线程UI的操作可以放在滑动结束的时候执行,这样也是符合用户交互习惯的,同时也优化了卡顿的问题。

既然我们需要监听滚动的事件,可以直接使用监听滚动的接口:

图片 21

并实现接口的方法:

图片 22

因为我们要实现在结束滚动的时候加载可见项,说的再明白点就是比如有100项需要加载,手机屏幕最多显示8项,用户滚动到第20项范围内结束,这时候就要加载这地20项附近的8项即可。

所以要实现这样的逻辑,就需要滚动结束之前,就要获取可见项的起止位置,还要获取这起止位置之间对应的所有数据。

图片 23

接下来在前面代码进行修改的细节就比较多了,这里就直接上修改的源码,总体思路就是之前的一边滚动一边执行适配器对象的getView对象去加载每一个item,而接下来就是改成

在滚动结束的时候,才加载可见项的图片,当然之前的getView方法里加载String字符串的逻辑仍旧可以保留,因为这个和通过网络加载图片的业务逻辑本质是不同的,读者自己领会,不难。

在NewsAdapter类中:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.annotation.TargetApi;
  4 import android.content.Context;
  5 import android.os.Build;
  6 import android.support.annotation.RequiresApi;
  7 import android.view.LayoutInflater;
  8 import android.view.View;
  9 import android.view.ViewGroup;
 10 import android.widget.AbsListView;
 11 import android.widget.BaseAdapter;
 12 import android.widget.ImageView;
 13 import android.widget.ListView;
 14 import android.widget.TextView;
 15 
 16 import java.util.List;
 17 
 18 /**
 19  * Created by HeYang on 16/10/6.
 20  */
 21 
 22 public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{
 23 
 24     // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
 25     private List<NewsBean> beanList;
 26     // 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
 27     private LayoutInflater layoutInflater;
 28 
 29     private ImageLoader imageLoader;
 30 
 31     // 可见项的起止index
 32     private int mStart,mEnd;
 33     // 因为我们需要存储起止项所有的url地址
 34     public static String[] URLS;
 35 
 36     // 创建构造方法
 37     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 38     public NewsAdapter(MainActivity context, List<NewsBean> data, ListView listView){
 39         beanList = data;
 40         layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
 41         imageLoader = new ImageLoader(listView);
 42 
 43         // 将模型数组中的url字符串单独存储在静态数组中
 44         int dataSize = data.size();
 45         URLS = new String[dataSize];
 46         for (int i = 0; i < dataSize; i++) {
 47             URLS[i] = data.get(i).newsIconURL;
 48         }
 49 
 50         // 已经要记得注册
 51         listView.setOnScrollListener(this);
 52     }
 53 
 54     @Override
 55     public int getCount() {
 56         return beanList.size();
 57     }
 58 
 59     @Override
 60     public Object getItem(int i) {
 61         // 因为beanList是数组,通过get访问对应index的元素
 62         return beanList.get(i);
 63     }
 64 
 65     @Override
 66     public long getItemId(int i) {
 67         return i;
 68     }
 69 
 70 
 71     @Override
 72     public View getView(int i, View view, ViewGroup viewGroup) {
 73         ViewHolder viewHolder = null;
 74         if (view == null){
 75             viewHolder = new ViewHolder();
 76             // 每一个View都要和layout关联
 77             view = layoutInflater.inflate(R.layout.item_layout,null);
 78             // 在R.layout.item_layout中有三个控件对象
 79             // 现在全部传递给view对象了
 80             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
 81             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
 82             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
 83 
 84             view.setTag(viewHolder);
 85 
 86         }else {
 87 
 88             // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
 89             viewHolder = (ViewHolder) view.getTag();
 90         }
 91         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
 92         viewHolder.tvContent.setText(beanList.get(i).newsContent);
 93         // 先默认加载系统图片
 94         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);  // 类似加载占位图片
 95         viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
 96         // 将ImageView对象和URLSting对象传入进去
 97 //        new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
 98         imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
 99         return view;
100     }
101 
102     @Override
103     public void onScrollStateChanged(AbsListView absListView, int i) {
104         // SCROLL_STATE_IDLE :  滚动结束
105         if (i == SCROLL_STATE_IDLE){
106             // 加载可见项
107             imageLoader.loadImages(mStart,mEnd);
108         }else{
109             // 停止所有任务
110             imageLoader.cancelAllTask();
111         }
112     }
113 
114     @Override
115     public void onScroll(AbsListView absListView, int i, int i1, int i2) {
116         // i是第一个可见元素 i1是当前可见元素的长度
117         mStart = i;
118         mEnd = i + i1;
119     }
120 
121     // 最后需要一个匿名内部类来创建一个临时缓存View的对象
122     class ViewHolder{
123         public TextView tvContent,tvTitle;
124         public ImageView ivIcon;
125     }
126 }

在ImageLoader类中:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.annotation.TargetApi;
  4 import android.graphics.Bitmap;
  5 import android.graphics.BitmapFactory;
  6 import android.os.AsyncTask;
  7 import android.os.Build;
  8 import android.os.Handler;
  9 import android.os.Message;
 10 import android.support.annotation.RequiresApi;
 11 import android.util.Log;
 12 import android.util.LruCache;
 13 import android.widget.ImageView;
 14 import android.widget.ListView;
 15 
 16 import java.io.BufferedInputStream;
 17 import java.io.IOException;
 18 import java.io.InputStream;
 19 import java.net.HttpURLConnection;
 20 import java.net.MalformedURLException;
 21 import java.net.URL;
 22 import java.util.HashSet;
 23 import java.util.Set;
 24 
 25 /**
 26  * Created by HeYang on 16/10/6.
 27  */
 28 
 29 public class ImageLoader {
 30 
 31     private ImageView mImageView;
 32     private String mURLStr;
 33     // 创建缓存对象
 34     // 第一个参数是需要缓存对象的名字或者ID,这里我们传输url作为唯一名字即可
 35     // 第二个参数是Bitmap对象
 36     private LruCache<String,Bitmap> lruCache;
 37 
 38     private ListView mListView;
 39     private Set<NewsAsyncTask> mTasks;
 40 
 41     // 然后我们需要在构造方法中初始化这个缓存对象
 42     // 另外,我们不可能把所有的缓存空间拿来用
 43     @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
 44     public ImageLoader(ListView listView){
 45 
 46         mListView = listView;
 47         mTasks = new HashSet<>();
 48 
 49         // 获取最大可用内存
 50         int maxMemory = (int) Runtime.getRuntime().maxMemory();
 51         int cachaSize =  maxMemory / 4;
 52         // 创建LruCache对象,同时用匿名内部类的方式重写方法
 53         lruCache = new LruCache<String,Bitmap>(cachaSize){
 54             @Override
 55             protected int sizeOf(String key, Bitmap value) {
 56                 // 我们需要直接返回Bitmap value的实际大小
 57                 //return super.sizeOf(key, value);
 58                 // 在每次存入缓存的时候调用
 59                 return value.getByteCount();
 60             }
 61         };
 62     }
 63 
 64     // 然后我们要写俩个方法:1、将bitmap存入缓存中 2、从缓存中取出bitmap
 65 
 66     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 67     public void addBitmapToCache(String url, Bitmap bitmap){
 68         if (getBitMapFromCache(url) == null){
 69             lruCache.put(url,bitmap);
 70         }
 71     }
 72 
 73     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 74     public Bitmap getBitMapFromCache(String url){
 75         return lruCache.get(url);
 76     }
 77 
 78 
 79     private Handler handler = new Handler(){
 80         @Override
 81         public void handleMessage(Message msg) {
 82             super.handleMessage(msg);
 83 
 84             if (mImageView.getTag().equals(mURLStr)){
 85                 mImageView.setImageBitmap((Bitmap) msg.obj);
 86             }
 87 
 88 
 89         }
 90     };
 91 
 92     // 根据可见项起止位置加载可见项
 93     public void loadImages(int startIndex,int endIndex){
 94         for (int i = startIndex; i < endIndex; i++) {
 95             String url = NewsAdapter.URLS[i];
 96             // 在异步请求之前,先判断缓存中是否有,有的话就取出直接加载
 97             Bitmap bitmap = getBitMapFromCache(url);
 98             if (bitmap == null){
 99                 // 如果没有,就异步任务加重
100 //                new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 这两个参数分别传递的目的地可以理解一下
101                 NewsAsyncTask task = new NewsAsyncTask(url);
102                 task.execute(url);
103                 mTasks.add(task);
104 
105 
106 
107             }else{
108                 ImageView loadImageView = (ImageView) mListView.findViewWithTag(url);
109                 loadImageView.setImageBitmap(bitmap);
110             }
111         }
112     }
113 
114     public void cancelAllTask(){
115         if (mTasks != null){
116             for (NewsAsyncTask newsTask: mTasks) {
117                 newsTask.cancel(false);
118             }
119         }
120     }
121 
122 
123 
124 
125 
126     public Bitmap getBitmapFromURL(String urlString){
127         Bitmap bitmap = null;
128         InputStream is = null;
129 
130         try {
131             URL url = new URL(urlString);
132             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
133             is = new BufferedInputStream(connection.getInputStream());
134             bitmap = BitmapFactory.decodeStream(is);
135             // 最后要关闭http连接
136             connection.disconnect();
137 //            Thread.sleep(1000);// 睡眠1秒
138         } catch (IOException e) {
139             e.printStackTrace();
140         }
141 //        catch (InterruptedException e) {
142 //            e.printStackTrace();
143 //        }
144         finally {
145 
146             try {
147                 is.close();
148             } catch (IOException e) {
149                 e.printStackTrace();
150             }
151         }
152 
153         return bitmap;
154     }
155 
156 
157     // ==================使用AsyncTask====================
158     public void showImageByAsyncTask(ImageView imageView, final String urlString){
159         // 在异步请求之前,先判断缓存中是否有,有的话就取出直接加载
160         Bitmap bitmap = getBitMapFromCache(urlString);
161         if (bitmap == null){
162             // 如果没有,就异步任务加重
163 //            new NewsAsyncTask( urlString).execute(urlString);// 这两个参数分别传递的目的地可以理解一下
164             imageView.setImageResource(R.mipmap.ic_launcher);
165         }else{
166             imageView.setImageBitmap(bitmap);
167         }
168 
169     }
170 
171     private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
172 
173         // 需要私有的ImageView对象和构造方法来传递ImageView对象
174 //        private ImageView mImageView;
175         private String mURlString;
176 
177 //        public NewsAsyncTask(ImageView imageView,String urlString){
178 //            mImageView = imageView;
179 //            mURlString = urlString;
180 //        }
181 
182         public NewsAsyncTask(String urlString){
183             mURlString = urlString;
184         }
185 
186         @Override
187         protected Bitmap doInBackground(String... strings) {
188             // 在这个方法中,完成异步下载的任务
189             String url = strings[0];
190             // 从网络中获取图片
191             Bitmap bitmap = getBitmapFromURL(strings[0]);
192             if (bitmap != null){
193                 // 将网络加载出来的图片存储缓存
194                 addBitmapToCache(url,bitmap);
195             }
196             return bitmap;
197         }
198 
199 
200         @Override
201         protected void onPostExecute(Bitmap bitmap) {
202             super.onPostExecute(bitmap);
203             // 然后在这个方法中设置imageViewd
204 
205             ImageView executeImageView = (ImageView) mListView.findViewWithTag(mURlString);
206             if (bitmap != null && executeImageView != null){
207                 executeImageView.setImageBitmap(bitmap);
208             }
209             // 执行完当前任务,自然要把当前Task任务remove
210             mTasks.remove(this);
211 
212 //            if (mImageView.getTag().equals(mURlString)){
213 //                mImageView.setImageBitmap(bitmap);
214 //            }
215 
216         }
217     }
218 
219     // ==================使用多线程====================
220     public void showImageByThread(ImageView imageView, final String urlString){
221 
222         mImageView = imageView;
223         mURLStr = urlString;
224 
225         new Thread(){
226             @Override
227             public void run() {
228                 super.run();
229                 Bitmap bitmap = getBitmapFromURL(urlString);
230                 // 当前线程是子线程,并不是UI主线程
231                 // 不是Message message = new Message();
232                 Message message = Message.obtain();
233                 message.obj = bitmap;
234                 handler.sendMessage(message);
235 
236             }
237         }.start();
238     }
239 }

 但是,以上的代码还有一个不足之处,就是最开始启动的时候,因为没有滚动ScorllView,所以默认是没有加载图片的,但是我们可以做一个预加载:

图片 24

图片 25

完整源码:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.annotation.TargetApi;
  4 import android.content.Context;
  5 import android.os.Build;
  6 import android.support.annotation.RequiresApi;
  7 import android.view.LayoutInflater;
  8 import android.view.View;
  9 import android.view.ViewGroup;
 10 import android.widget.AbsListView;
 11 import android.widget.BaseAdapter;
 12 import android.widget.ImageView;
 13 import android.widget.ListView;
 14 import android.widget.TextView;
 15 
 16 import java.util.List;
 17 
 18 /**
 19  * Created by HeYang on 16/10/6.
 20  */
 21 
 22 public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{
 23 
 24     // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
 25     private List<NewsBean> beanList;
 26     // 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
 27     private LayoutInflater layoutInflater;
 28 
 29     private ImageLoader imageLoader;
 30 
 31     private boolean isFirstLoadImage;
 32 
 33     // 可见项的起止index
 34     private int mStart,mEnd;
 35     // 因为我们需要存储起止项所有的url地址
 36     public static String[] URLS;
 37 
 38     // 创建构造方法
 39     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 40     public NewsAdapter(MainActivity context, List<NewsBean> data, ListView listView){
 41 
 42         isFirstLoadImage = true;
 43 
 44         beanList = data;
 45         layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
 46         imageLoader = new ImageLoader(listView);
 47 
 48         // 将模型数组中的url字符串单独存储在静态数组中
 49         int dataSize = data.size();
 50         URLS = new String[dataSize];
 51         for (int i = 0; i < dataSize; i++) {
 52             URLS[i] = data.get(i).newsIconURL;
 53         }
 54 
 55         // 已经要记得注册
 56         listView.setOnScrollListener(this);
 57     }
 58 
 59     @Override
 60     public int getCount() {
 61         return beanList.size();
 62     }
 63 
 64     @Override
 65     public Object getItem(int i) {
 66         // 因为beanList是数组,通过get访问对应index的元素
 67         return beanList.get(i);
 68     }
 69 
 70     @Override
 71     public long getItemId(int i) {
 72         return i;
 73     }
 74 
 75 
 76     @Override
 77     public View getView(int i, View view, ViewGroup viewGroup) {
 78         ViewHolder viewHolder = null;
 79         if (view == null){
 80             viewHolder = new ViewHolder();
 81             // 每一个View都要和layout关联
 82             view = layoutInflater.inflate(R.layout.item_layout,null);
 83             // 在R.layout.item_layout中有三个控件对象
 84             // 现在全部传递给view对象了
 85             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
 86             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
 87             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
 88 
 89             view.setTag(viewHolder);
 90 
 91         }else {
 92 
 93             // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
 94             viewHolder = (ViewHolder) view.getTag();
 95         }
 96         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
 97         viewHolder.tvContent.setText(beanList.get(i).newsContent);
 98         // 先默认加载系统图片
 99         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);  // 类似加载占位图片
100         viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
101         // 将ImageView对象和URLSting对象传入进去
102 //        new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
103         imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
104         return view;
105     }
106 
107     @Override
108     public void onScrollStateChanged(AbsListView absListView, int i) {
109         // SCROLL_STATE_IDLE :  滚动结束
110         if (i == SCROLL_STATE_IDLE){
111             // 加载可见项
112             imageLoader.loadImages(mStart,mEnd);
113         }else{
114             // 停止所有任务
115             imageLoader.cancelAllTask();
116         }
117     }
118 
119     @Override
120     public void onScroll(AbsListView absListView, int i, int i1, int i2) {
121         // i是第一个可见元素 i1是当前可见元素的长度
122         mStart = i;
123         mEnd = i + i1;
124         // 第一次预加载可见项
125         if (isFirstLoadImage && i1 > 0){
126             imageLoader.loadImages(mStart,mEnd);
127             isFirstLoadImage = false;
128         }
129     }
130 
131     // 最后需要一个匿名内部类来创建一个临时缓存View的对象
132     class ViewHolder{
133         public TextView tvContent,tvTitle;
134         public ImageView ivIcon;
135     }
136 }

就这样完成了异步加载的完整最优化的功能:

图片 26

完整源码下载地址:

 

 

相关文章