Android通过请求网络数据实现ListView,ListView的优化、图片的缓存、子控件的点击事件。

来源:互联网 发布:1688一键传淘宝教程 编辑:程序博客网 时间:2024/05/20 11:25

对Android初学者来说为ListView自定义Adapter和实现item子控件点击事件是一个比较生疏的内容,而且有些地方理解起来也不是太容易。本篇内容介绍下如何实现自定义Adapter并加入了ListView的优化、图片的缓存、防止图片错位以及实现Item子控件的点击事件。
注:程序中的数据来源于网络,格式为json。在真实项目中一般会用第三方框架去加载数据,但对初学者来说尽量少用框架,因此这里使用了HttpURLConnection和AsyncTask来加载数据
源码地址(eclipse):http://download.csdn.net/detail/qq_20521573/9585125
如下图所示,一个item中嵌套了两个图片,当点击不同的图片时会进入不同的页面。
这里写图片描述
点击上图右侧第三张图片后进入下边的WebView页面
这里写图片描述
下面来看一下ListView的具体的实现思路。
ListView可以简单的从以下五个步骤实现:
1.初始化ListView控件
2.初始化List集合
3.为List添加数据
4.创建适配器
5.为ListView匹配适配器
只要记住以上的五个步骤就可以轻松掌握ListView的用法。但是通常情况下我们请求的数据都是来自于服务器,因此ListView中添加的数据势必是从网络上获取后解析到本地并在页面上展示的,因此为ListView添加数据的时候会涉及到请求网络,而通过网络下载数据是一个耗时的过程,我们应该清楚主线程只能更新UI界面,不能进行耗时操作。因此在请求网络数据的时候我们就必须另起一个子线程,当该子线程请求数据完毕后再通知主线程来跟新界面,显示出ListView的内容,这种请求方式在Android中称作异步任务,具体实现可以用使用AsyncTask类。
明白了这个道理后再来看ListView的实现。

package com.example.listview.activity;import java.util.ArrayList;import java.util.List;import android.app.Activity;import android.os.Bundle;import android.widget.ListView;import com.example.listview.R;import com.example.listview.adapter.MyAdapter;import com.example.listview.asynctask.MyAsyncTask;import com.example.listview.bean.PictureBean;import com.example.listview.constant.MyUrl;import com.example.listview.utils.MyJsonUtils;public class MainActivity extends Activity {    ListView mListView;    List<PictureBean> list;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //  1.初始化ListView控件        mListView=(ListView) findViewById(R.id.lv_main);        //  2.初始化List集合        list=new ArrayList<>();        //  3.创建适配器        MyAdapter adapter=new MyAdapter(this,list);        //  4.为ListView匹配适配器        mListView.setAdapter(adapter);        //  5.异步任务为List加载数据        new MyAsyncTask(this, list,new MyJsonUtils(),adapter)        .execute(MyUrl.url);    }}

上面的代码中为List添加数据放在了最后一步,原因是异步任务下载数据,而此时数据并没有办法立即添加到集合,因此把List添加数据放到最后,等异步任务完成后再用adapter.notifyDatatChanged()(此方法的实现见下边MyAsyncTask类中的代码)通知主线程List集合数据发生了改变,此时主线程再去更新界面才能显示出数据。
上边的代码给出了实现ListView的整体思路,那么接下来应该如何自定义一个Adapter?
(以下对应MainActivity中的第3步)
在自定义Adapter之前我们应该先写一个布局文件,在这里把它命名为item_listview.xml,这个布局文件是做什么的的?我们应该清楚,它对应的是ListView的一个条目。我们可以称它为条目布局,布局中两个ImageView和两个TextView分别对应ListView的item中间的两张图片和两个文字描述。代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:layout_marginLeft="10dp"    android:layout_marginRight="10dp"    android:baselineAligned="false"    android:layout_marginTop="10dp"    android:orientation="horizontal" >    <LinearLayout        android:id="@+id/ll_item_left"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_weight="1"        android:layout_marginRight="10dp"        android:orientation="vertical" >        <ImageView            android:id="@+id/iv_item_left"            android:layout_width="wrap_content"            android:layout_height="100dp"            android:scaleType="fitXY"            android:src="@drawable/a" />        <TextView            android:id="@+id/tv_item_left"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginTop="5dp"            android:text="夏日太阳鸟" />    </LinearLayout>    <LinearLayout        android:id="@+id/ll_item_right"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_weight="1"        android:orientation="vertical" >        <ImageView            android:id="@+id/iv_item_right"            android:layout_width="wrap_content"            android:layout_height="100dp"            android:scaleType="fitXY"            android:src="@drawable/a" />        <TextView            android:id="@+id/tv_item_right"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginTop="5dp"            android:text="夏日太阳鸟" />    </LinearLayout></LinearLayout>

有了条目布局之后呢我们就可以去实现自定义的Adapter了,新建一个类MyAdpater并继承BaseAdapter,在这里需要重写四个方法getCount()、getView(int position, View convertView, ViewGroup parent)、getItem(int position)、和getItemId(int position),但是我我们能只需关心前两个方法,后两个可以完全忽略不计。其中getCount()方法返回的是ListView的条目总数,比如如果只想让ListView有10个条目,那么可以直接return 10即可。而getView(int position, View convertView, ViewGroup parent)是自定义Adapter的核心方法,几乎所有的功能都在这个方法中实现,关联条目布局item_listview.xml,convertView的复用,子控件的点击事件均可以在这个方法中实现。
说到convertView的复用我们应该知道,在ListView中下边的条目会复用上边的条目,以此来提高效率。比如前边的第一张图片,一共有五个可见条目,当继续向下滑动的时候下边新出现的条目会复用上边被盖住不见得条目,这样可以减少findViewById的次数,以此提高效率节约资源。这也是ListView优化的一个重要内容。下边来看MyAdapter中的代码。

package com.example.listview.adapter;import java.util.HashMap;import java.util.List;import java.util.Map;import com.example.listview.R;import com.example.listview.activity.WebViewActivity;import com.example.listview.asynctask.ImageAsyncTask;import com.example.listview.bean.PictureBean;import android.content.Context;import android.content.Intent;import android.graphics.Bitmap;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.webkit.WebView;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.TextView;public class MyAdapter extends BaseAdapter implements OnClickListener {    Context context;    List<PictureBean> list;    View leftView;    View rightView;    WebView webView = null;    int position;    // 缓存图片    Map<String, Bitmap> cacheImage = new HashMap<String, Bitmap>();    public MyAdapter(Context context, List<PictureBean> list) {        super();        this.context = context;        this.list = list;    }    @Override    public int getCount() {        /*          * 一共有list.size()条数据,每个条目放两个数据,         * 因此除以2,在ListView中共显示list.size()/2个条目         */        return list.size() / 2;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder holder = null;        // convertView的复用,即下一屏复用本屏的条目        if (convertView == null) {            // 与条目布局相关联            convertView = View.inflate(context, R.layout.item_listview, null);            holder = new ViewHolder();            leftView = convertView.findViewById(R.id.ll_item_left);            rightView = convertView.findViewById(R.id.ll_item_right);            holder.leftImage = (ImageView) convertView                    .findViewById(R.id.iv_item_left);            holder.leftText = (TextView) convertView                    .findViewById(R.id.tv_item_left);            holder.rightImage = (ImageView) convertView                    .findViewById(R.id.iv_item_right);            holder.rightText = (TextView) convertView                    .findViewById(R.id.tv_item_right);            convertView.setTag(holder);        } else {            holder = (ViewHolder) convertView.getTag();        }        // 获取图片对应网页的连接        String leftUrl = list.get(position * 2).getWeb_url();        String rightUrl = list.get(position * 2 + 1).getWeb_url();        //  设置tag,可以在onClick()方法中通过v.getTag()得到对应的url,实现页面跳转;        leftView.setTag(leftUrl);        rightView.setTag(rightUrl);        // 为图片所在的部分设置监听事件        leftView.setOnClickListener(this);        rightView.setOnClickListener(this);        /****** 为条目左右的LinearLayout填充数据 *****/        PictureBean leftPic = list.get(2 * position);        PictureBean rightPic = list.get(2 * position + 1);        // 为左边部分填充数据        holder.leftImage.setTag(leftPic.getPic_url());        holder.leftText.setText(leftPic.getTitle());            /*判断Map集合中是否包含了当前连接对应的图片如果包含直接从map中取出图片显示,            如果不报含则请求网络下载图片,并将url和对应的图片放入map集合            保存以供下次复用*/        if (cacheImage.containsKey(leftPic.getPic_url())) {            holder.leftImage                    .setImageBitmap(cacheImage.get(leftPic.getPic_url()));        } else {            // 设置默认图片,图片下载前显示此图片            holder.leftImage.setImageResource(R.drawable.a);            new ImageAsyncTask(context, holder.leftImage, leftPic.getPic_url(),                    cacheImage).execute(leftPic.getPic_url());        }        // 为右边部分填充数据        holder.rightImage.setTag(rightPic.getPic_url());        holder.rightText.setText(rightPic.getTitle());        if (cacheImage.containsKey(rightPic.getPic_url())) {            holder.rightImage.setImageBitmap(cacheImage.get(rightPic                    .getPic_url()));        } else {            // 设置默认图片,图片下载前显示此图片            holder.rightImage.setImageResource(R.drawable.a);            new ImageAsyncTask(context, holder.rightImage,                    rightPic.getPic_url(), cacheImage).execute(rightPic                    .getPic_url());        }        return convertView;    }    /**     * 图片的点击事件     *      * @Override     */    public void onClick(View v) {        switch (v.getId()) {        case R.id.ll_item_left:            Intent intent1 = new Intent(context, WebViewActivity.class);            intent1.putExtra("Url", v.getTag().toString());            context.startActivity(intent1);            break;        case R.id.ll_item_right:            Intent intent2 = new Intent(context, WebViewActivity.class);            intent2.putExtra("Url", v.getTag().toString());            context.startActivity(intent2);            break;        default:            break;        }    }    @Override    public Object getItem(int position) {        return null;    }    @Override    public long getItemId(int position) {        return 0;    }    class ViewHolder {            ImageView leftImage;            TextView leftText;            ImageView rightImage;            TextView rightText;        }}

上边自定义的Adapter中实现了图片缓存到内存、convertView复用、和子控件点击事件。对于convertView的复用有必要做下解释,在这块代码的最后定义了一个ViewHolder类,里边的四个常量分别对应了条目布局item_listView.xml中的四个控件。在getView()方法中的第一行就声明了一个ViewHolder,接下来判断convertView是否为空,如果为空,我们需要将convertView和item_listview.xml相关联,然后创建ViewHolder对象,并通过convertView.findViewById()分别得到item_listview.xml中的子控件并分别赋值给ViewHolder中对应的常量。全部完成后用convertView.setTag(holder)保存,当下次判断convertView不为空时就可以通过convertView.getTag()获取到ViewHolder对象。之后就可以用list.get(position)获得PictureBean对象(position为当前条目id),然给holder对应的常量设置内容,比如:holder.leftText.setText(leftPic.getTitle());意思为设置左边部分的文字内容。
上边的代码中还用到了ImageAsyncTask类,该类是通过图片的url去下载对应的图片,异步下载,用法为ImageAsyncTask继承AsyncTask类,并重写相应方法,与下边的MyAsyncTask类似,在此不做太多解释,具体可参看下边的MyAsyncTask类。ImageAsyncTask代码如下:

package com.example.listview.asynctask;import java.util.Map;import com.example.listview.utils.HttpUtils;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.widget.ImageView;import android.widget.Toast;//  用图片的连接下载图片public class ImageAsyncTask extends AsyncTask<String, Void, byte[]> {    Context context;    ImageView imageView;    String picUrl;    Map<String, Bitmap> catchImage;    public ImageAsyncTask(Context context, ImageView imageView, String picUrl,            Map<String, Bitmap> catchImage) {        super();        this.context = context;        this.imageView = imageView;        this.picUrl = picUrl;        this.catchImage = catchImage;    }    @Override    protected byte[] doInBackground(String... params) {        // 通过HttpUtils类下载图片        return HttpUtils.getBytesByUrl(params[0]);    }    @Override    protected void onPostExecute(byte[] result) {        super.onPostExecute(result);        if (result != null) {            // 将下载到的图片字节转换成Bitmap            Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0,                    result.length);            /*             * ****防止图片错位****             * imageView.getTag().toString() 当前要现在的图片地址 iconUrl             * 之前没有下载完成的图片的地址             */            if (imageView.getTag().toString().equals(picUrl)) {                // 给ImageView设置图片                imageView.setImageBitmap(bitmap);                catchImage.put(imageView.getTag().toString(), bitmap);            }        } else {            Toast.makeText(context, "网络连接错误,请检查网络", Toast.LENGTH_LONG).show();        }    }}

ImageAsyncTask类中用到了HttpUtils工具类,该类的作用是通过url下载json数据并将下载的json以byte[]数组返回。代码如下:

package com.example.listview.utils;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;public class HttpUtils {    public static byte[] getBytesByUrl(String urlStr) {        URL url = null;        InputStream is = null;        ByteArrayOutputStream baos = null;        try {            url = new URL(urlStr);            HttpURLConnection conn = (HttpURLConnection) url.openConnection();            conn.setRequestMethod("GET");            conn.connect();            if (conn.getResponseCode() == 200) {                is = conn.getInputStream();                byte[] buffer = new byte[1024];                int len = 0;                baos = new ByteArrayOutputStream();                while ((len = is.read(buffer)) != -1) {                    baos.write(buffer, 0, len);                }            }else{                //Toast.makeText(, text, duration)            }        } catch (MalformedURLException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }        return baos.toByteArray();    }}

最后(对应MainActivity中的第5步),应该通过异步任务,来为List集合加载数据。
首先,建一个类MyAsyncTask并继承AsyncTask,然后重写doInBackground()和onPostExecute()方法。
具体看下边代码:

package com.example.listview.asynctask;import java.util.List;import com.example.listview.adapter.MyAdapter;import com.example.listview.bean.PictureBean;import com.example.listview.utils.HttpUtils;import com.example.listview.utils.MyJsonUtils;import android.content.Context;import android.os.AsyncTask;import android.widget.Toast;public class MyAsyncTask extends AsyncTask<String, Void, byte[]> {    Context context;    List<PictureBean> list;    MyJsonUtils utils;    MyAdapter adapter;    public MyAsyncTask(Context context, List<PictureBean> list,            MyJsonUtils utils, MyAdapter adapter) {        super();        this.context = context;        this.list = list;        this.utils = utils;        this.adapter = adapter;    }    @Override    protected byte[] doInBackground(String... params) {        return HttpUtils.getBytesByUrl(params[0]);    }    @Override    protected void onPostExecute(byte[] result) {        super.onPostExecute(result);        if(result!=null){            String jsonStr=new String(result,0,result.length);            System.out.print(jsonStr);            List<PictureBean> list2= (List<PictureBean>) utils.parseJson(jsonStr);            list.addAll(list2);            adapter.notifyDataSetChanged();        }else{            Toast.makeText(context, "数据为空", Toast.LENGTH_SHORT).show();        }    }}

上边代码中doInBackground()方法中通过HttpUtils.getBytesByUrl(params[0])工具类返回json,参数params[0]即为json数据对应的url,即在MainActivity中new MyAsyncTask(this, list,new MyJsonUtils(),adapter)
.execute(MyUrl.url)中的MyUrl.url就是params[0]。而doInBackground()方法返回的json数据传递到了onPostExecute(byte[] result),onPostExecute方法的参数byte[] result即为doInBackground()方法返回的json数据,因此就可以在onPostExecute(byte[] result)方法中解析json并将解析后得到的数据添加到list集合。最后用adapter.notifyDataSetChanged()方法通知主线程list集合发生了变化,主线程更新UI显示数据。

解析接送数据的类为MyJsonUtils,该类用来解析json,将json数据解析成PictureBean,添加到List集合。最后将解析完后的数据以集合形式返回。代码实现如下:

package com.example.listview.utils;import java.util.ArrayList;import java.util.List;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import com.example.listview.bean.PictureBean;public class MyJsonUtils{    public List<PictureBean> parseJson(String jsonStr){        List<PictureBean> list=new ArrayList<PictureBean>();        try {            JSONObject obj1=new JSONObject(jsonStr);            JSONArray array=obj1.getJSONArray("list");            for(int i=0;i<array.length();i++){                PictureBean per=new PictureBean();                JSONObject obj2=array.optJSONObject(i);                String title=obj2.optString("title");                String pic_url=obj2.optString("pic_url");                String web_url=obj2.optString("web_url");                per.setTitle(title);                per.setPic_url(pic_url);                per.setWeb_url(web_url);                list.add(per);            }        } catch (JSONException e) {            e.printStackTrace();        }        return list;    }}
2 0