Android解析JSON数据异步加载新闻图片

参考文章:解析Json并异步加载数据新闻信息(包括新闻图片)
在这里插入图片描述
1.先写出普通的ListView所实现的效果
首先从简单的开始吧,我们先写item项,也就是列表中的一条信息,也就是相当于以后每条模板的作用。实现的效果图是这样的。在这里插入图片描述
设计图是这样的:
在这里插入图片描述
在这里插入图片描述
下面直接上代码:
item_layout.xml

<?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="wrap_content"
    android:orientation="horizontal"
    android:padding="4dp">

    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:src="@mipmap/ic_launcher" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:paddingLeft="4dp">

        <TextView
            android:maxLines="1"
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Title"
            android:textSize="15sp" />

        <TextView
            android:maxLines="3"
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Content"
            android:textSize="10sp" />
    </LinearLayout>
</LinearLayout>

主布局也很简单,就是加一个充满父容器的ListView控件。
翠花,上效果图。
在这里插入图片描述
翠花,再给客官上上等的代码:
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/lv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

是的,就这么简单,觉得太容易了吗?那我下面就循序渐进了~~坐稳扶好。

接下来我们就分析一下JSON数据:
下面是API:http://www.imooc.com/api/teacher?type=4&num=30
凑够下面的图中,我们可以看到,最外层是status,data里是一个列表,列表的子项,分别有id,name,picSmall,picBig, description,还有learner。而我们只需要新闻的图片(picSmall),标题(name),和内容(description)就可以了。
在这里插入图片描述
根据提供的json数据我们就可以先创建我们自己的实体类来存放这些信息,而我们仅需要三条信息。
于是我们创建的实体类就写三个属性就可以,分别是newsTitle,newsContent,newsUrl。
信息如下:
NewsBean.class

package com.iyuba.iyubanews;

public class NewsBean {
    public String newsTitle; //新闻标题
    public String newsUrl; //图片地址
    public String newsIcon; 
}

然后我们还有创建我们自己的适配器,继承自BaseAdapter,同时,并在适配器中创建List存放新闻消息NewsBean,并在构造方法中初始化该List。创建一个ViewHolder类,存放要显示的内容,创建LayoutInflater对象,映射要显示的布局文件。
NewsAdapter .class

package com.iyuba.iyubanews;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;

public class NewsAdapter extends BaseAdapter {
    List<NewsBean> mList;
    LayoutInflater mInflater;

    public NewsAdapter(Context context,List<NewsBean> mList) {
        mInflater = LayoutInflater.from(context);
        this.mList = mList;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public Object getItem(int i) {
        return mList.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {

        ViewHolder holder = null;
        if (view==null){ //如果为空,重新创建
            holder = new ViewHolder();
            view = mInflater.inflate(R.layout.item_layout,null);
            holder.tv_title = view.findViewById(R.id.tv_title);
            holder.tv_content = view.findViewById(R.id.tv_content);
            holder.iv_icon = view.findViewById(R.id.iv_icon);
            view.setTag(holder);
        }else { //如果不为空,从之前创建中获取
            holder = (ViewHolder) view.getTag();
        }
        holder.tv_content.setText(mList.get(i).newsContent);
        holder.tv_title.setText(mList.get(i).newsTitle);
        holder.iv_icon.setImageResource(R.mipmap.ic_launcher);
        return view;
    }
    class ViewHolder{
        public TextView tv_title; //标题
        public TextView tv_content;//内容
        public ImageView iv_icon; //图片
    }
}


重点来了!
异步类
接下来我们需要在主布局中创建一个异步类来加载解析Json数据。创建一个NewsAsyncTask类,继承AsyncTask类,并实现他的doInBackground方法。填写参数,第一个是String 表示传入的url是这个类型,第二个Void表示不关心过程,第三个是List,表示要返回List的数据。
首先,在MainActivity类中创建一个方法readStream(),用来解析网页返回的数据。

/**
         * 解析网页返回数据
         * @param is
         * @return
         */
        public String readStream(InputStream is){
            InputStreamReader isr;
            String result ="";
            try {
                isr = new InputStreamReader(is,"utf-8");

            BufferedReader br = new BufferedReader(isr);
            String line = "";
            while ((line=br.readLine())!=null){
                result += line;
            }
            return result;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return  null;
        }

其次,在MainActivity类中创建一个getJsonData()方法, 将url对应的json数据转化为我们所封装的NewsBean

 private List<NewsBean> getJsonData(String url) {
            List<NewsBean> newsBeans = new ArrayList<>();
            NewsBean newsBean;
            try {
                String objectString = readStream(new URL(url).openStream());
                JSONObject jsonObject = new JSONObject(objectString);
                JSONArray jsonArray = jsonObject.getJSONArray("data");
                for (int i = 0; i < jsonArray.length(); i++) {
                    newsBean = new NewsBean();
                    jsonObject = (JSONObject) jsonArray.get(i);
                    newsBean.newsTitle = jsonObject.getString("name");
                    newsBean.newsContent = jsonObject.getString("description");
                    newsBean.newsUrl = jsonObject.getString("picSmall");
                    newsBeans.add(newsBean);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return newsBeans;
        }

然后在NewsAsyncTask类中onPostExecute()中将适配器与ListView关联

 @Override
        protected void onPostExecute(List<NewsBean> newsBeans) {
            super.onPostExecute(newsBeans);
            NewsAdapter adapter = new NewsAdapter(MainActivity.this,list);
            mListView.setAdapter(adapter);
        }

千万不要忘记增加INTERNET访问权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.iyuba.iyubanews">

    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

先到这里为止,先上传一下项目的运行效果还有MainActivity.java文件
在这里插入图片描述
MainActivity.class

package com.iyuba.iyubanews;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.ListView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ListView mListView;
    private List<NewsBean> list;
    private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = findViewById(R.id.lv_main);
        new NewsAsyncTask().execute(URL);
    }
    /**
     * 解析网页返回数据
     * @param is
     * @return
     */
    private String readStream(InputStream is) {
        InputStreamReader isr;
        String result = "";
        try {
            String line = "";
            isr = new InputStreamReader(is, "utf-8"); //字节流转化为字符流
            BufferedReader br = new BufferedReader(isr);
            while ((line = br.readLine()) != null) {
                result += line;
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private List<NewsBean> getJsonData(String url) {
        List<NewsBean> newsBeanList = new ArrayList<>();
        try {
            String jsonString = readStream(new URL(url).openStream());
            JSONObject jsonObject;
            NewsBean newsBean;
            try {
                jsonObject = new JSONObject(jsonString);
                JSONArray jsonArray = jsonObject.getJSONArray("data");
                for (int i = 0; i < jsonArray.length(); i++) {
                    jsonObject = jsonArray.getJSONObject(i);
                    newsBean = new NewsBean();
                    newsBean.newsUrl = jsonObject.getString("picSmall");
                    newsBean.newsTitle = jsonObject.getString("name");
                    newsBean.newsContent = jsonObject.getString("description");
                    newsBeanList.add(newsBean);
                    Log.d("Tag","newsBeanList.size() is the "+newsBeanList.size());
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }

            Log.d("xys", "jsonString");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return newsBeanList;

    }
    class NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
        @Override
        protected List<NewsBean> doInBackground(String... strings) {
            list = getJsonData(strings[0]);
            return list;
        }

        @Override
        protected void onPostExecute(List<NewsBean> newsBeans) {
            super.onPostExecute(newsBeans);
            NewsAdapter adapter = new NewsAdapter(MainActivity.this,list);
            mListView.setAdapter(adapter);
        }
        
    }
}

-----------------------------------------以上为一般适配器展示界面-------------------------------------------------------
下面开始加入对图片的加载,之前是制订默认的图片。
现在创建一个图片工具类ImageLoader,专门用于下载图片信息
在类中首先创建一个方法getBitmapFromUrl,专门下载图片

/**
     * 下载图片
     * @param url 图片地址
     */
    public Bitmap getBitmapFromUrl(String urlstring){
        Bitmap bitmap;
        InputStream is = null;
        try {
            URL url = new URL(urlstring);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(con.getInputStream());
            bitmap = BitmapFactory.decodeStream(is);
            con.disconnect();
            return bitmap;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

然后写异步方法showImageByAsyncTask(),由于非主线程不能直接在线程中更新UI,所以我们要先创建一个Handler作为一个消息的传递来更新UI,也需要创建一个全局变量ImageView来传递。在showImageByAsyncTask()中建立一个Message发送更新UI请求。那么我们接下来先写主线程

private ImageView mImageView; 
private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mImageView = (ImageView) msg.obj;
        }
    };

然后展示多线程方法showImageByAsyncTask()

   /**
     * 多线程 加载图片
     * @param imageView
     * @param url
     */
   public void showImageByThread(final ImageView imageView, final String url){
       mImageView = imageView;
       new Thread(new Runnable() {
           @Override
           public void run() {
               Bitmap bitmap = getBitmapFromUrl(url);
               Message message = Message.obtain();
               message.obj = bitmap;
               handler.sendMessage(message);
           }
       }).start();
   }

我们还需要一个通过URL下载BitMap的方法

 /**
     * 从url中获取bitmap
     */
    public Bitmap getBitmapFromUrl(String urlstring){
        Bitmap bitmap;
        InputStream is = null;
        try {
            URL url = new URL(urlstring);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(connection.getInputStream());
            bitmap = BitmapFactory.decodeStream(is);
            connection.disconnect();
            return bitmap;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                is.close();
            }catch (Exception e){

            }
        }
        return null;
    }

又是到了翠花上酸菜的时间:
ImageLoader.java

package com.iyuba.mytablayout.utils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class ImageLoader {

    private ImageView mImageView;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mImageView = (ImageView) msg.obj;
        }
    };
    /**
     * 多线程 加载图片
     * @param imageView
     * @param url
     */
   public void showImageByThread(final ImageView imageView, final String url){
       mImageView = imageView;
       new Thread(new Runnable() {
           @Override
           public void run() {
               Bitmap bitmap = getBitmapFromUrl(url);
               Message message = Message.obtain();
               message.obj = bitmap;
               handler.sendMessage(message);
           }
       }).start();
   }
    /**
     * 从url中获取bitmap
     */
    public Bitmap getBitmapFromUrl(String urlstring){
        Bitmap bitmap;
        InputStream is = null;
        try {
            URL url = new URL(urlstring);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(connection.getInputStream());
            bitmap = BitmapFactory.decodeStream(is);
            connection.disconnect();
            return bitmap;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                is.close();
            }catch (Exception e){

            }
        }
        return null;
    }
}

运行效果如图:
在这里插入图片描述
然后呢,我们再实现一下异步下载图片:
首先创建一个NewsAsyncTask类,继承AsyncTask,参数分别是String,代表传入url为string类型,第二个参数Void代表不关心过程,Bitmap表示返回的是这个类型。最后再实现他的方法。
最开始是这个样子。

 private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{

        @Override
        protected Bitmap doInBackground(String... strings) {
            return null;
        }
    }

我们需要在doInBackground()方法中将url(strings[0])传给getBitmapFromUrl()来返回Bitmap对象。代码如下:

        @Override
        protected Bitmap doInBackground(String... strings) {
            return getBitmapFromUrl(strings[0]);
        }

我们还需要一个ImageView对象来与指定的imageview绑定,对imageview设置他的Bitmap对象。在构造方法中将imageview将他绑定。代码如下:

private ImageView mImageView;
        public NewsAsyncTask(ImageView imageView){
            mImageView = imageView;
        }

然后我们再重载他的onPostExecute()方法,在这个方法里面对imageview设置他的Bitmap对象,代码如下:

  @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            mImageView.setImageBitmap(bitmap);
        }

翠花,再来展示一下全部的代码:
ImageLoader.class

package com.iyuba.iyubanews;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class ImageLoader {
    private ImageView mImageView;
    private String mUrl;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mImageView.setImageBitmap((Bitmap) msg.obj);
        }
    };

    /**
     * 通过多线程 下载图片
     * @param imageView
     * @param url
     */
    public void showImageByThread(final ImageView imageView, final String url){
        new Thread(new Runnable() {
            @Override
            public void run() {
                mImageView = imageView;
                Bitmap bitmap = getBitmapFromUrl(url);
                Message message = Message.obtain();
                message.obj = bitmap;
                handler.sendMessage(message);
            }
        }).start();
    }


    /**
     * 下载图片
     *
     * @param urlstring 图片地址
     */
    public Bitmap getBitmapFromUrl(String urlstring) {
        Bitmap bitmap;
        InputStream is = null;
        try {
            URL url = new URL(urlstring);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(con.getInputStream());
            bitmap = BitmapFactory.decodeStream(is);
            con.disconnect();
            return bitmap;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     *
     * @param imageView
     * @param mUrl
     */
    public void showImageByAsyncTask(ImageView imageView,String mUrl){
        new NewsAsyncTask(imageView).execute(mUrl);
    }

    /**
     * 异步加载图片
     */
    private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
        private ImageView mImageView;
        public NewsAsyncTask(ImageView imageView){
            mImageView = imageView;
        }
        @Override
        protected Bitmap doInBackground(String... strings) {
            return getBitmapFromUrl(strings[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            mImageView.setImageBitmap(bitmap);
        }
    }
}

效果图和上图一样,这里我就不上传了,只不过呢,有一些小瑕疵,比如图片错位,浪费网络流量,占用大量存储空间,所以,下面我们要对它进行优化。
方法一:设置tag方法
在NewsAdapter的getView()方法中,在调用ImageLoader方法之前,为图片设置setTag()方法,代码如下:

holder.iv_icon.setTag(mList.get(i).newsUrl);
        new ImageLoader().showImageByAsyncTask(holder.iv_icon,mList.get(i).newsUrl);

在ImageLoader类中的NewsAsyncTask中的构造方法中传入url,并设置全局的String类型的url对象,在onPostExecute方法中调用图片imageView的getTag方法,代码如下:

        private ImageView mImageView;
        private String mUrl;
        public NewsAsyncTask(ImageView imageView,String url){
            mImageView = imageView;
            mUrl = url;
        }
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            if (mImageView.getTag().equals(mUrl)){
                mImageView.setImageBitmap(bitmap);
            }
        }

以上,就是图片防止错乱的代码。


下面我们再来引进关于Cache的LRU算法:

LRU算法:Least Recently Used近期最少使用算法
为Android提供了LruCache类来实现这个缓存方法

使用缓存,将下载图片缓存下来,一方面,让ListView滑动更加流畅。
首先呢,需要的就是翠花,给这位看博客的客官上一盘酸菜:
在ImageLoader 中定义一个全局变量

private LruCache<String, Bitmap> mCaches;//需要保存缓存对象的名字,保存对象  本质是map

String类型的key传入的是图片的地址,Bitmap不用多说,你懂的~

在ImageLoader的构造方法中初始化缓存大小,翠花,上酸菜~
值得一说的是要注意重新加载sizeOf()方法,否则默认返回元素的个数
调用 value.getByteCount()方法,将Bitmap实际大小返回

 public ImageLoader(ListView listView) {
          ......
          ......
          ......
        //获取最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 4; 
        mCaches = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //返回图片大小,每次存入缓存时调用
                return value.getByteCount(); //将bitmap的实际大小传入
            }
        }; //初始化缓存大小
    }

在NewsAdapter中的getView()方法中将new ImageLoader()方法去掉,换成我们定义的全局变量imageLoader,这样每次调用该方法时,就不会重新创建缓存区。

public class NewsAdapter extends BaseAdapter {
    ......
    ......
    private ImageLoader imageLoader;
     @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
    .......
     holder.iv_icon.setTag(mList.get(i).newsUrl);
     imageLoader.showImageByAsyncTask(holder.iv_icon,mList.get(i).newsUrl);
    .......
    }
    }

然后再创建两个方法,一个是将图片保存到缓存中,另一个是将图片从缓存中获取到:
保存到缓存的方法是addBitmapToCache(String url, Bitmap bitmap) ,先判断缓存是否存在,如果存在,就从对应的key(url)返回相应的bitmap

    /**
     * 将内容保存的Cache
     */
    public void addBitmapToCache(String url, Bitmap bitmap) {
        if (getBitmapFromCache(url) == null) { //判断当前缓存是否存在
            mCaches.put(url, bitmap);
        }
    }

第二个就是从缓存中读取内容:

     /**
     * 从Cache中读取内容
     */
    public Bitmap getBitmapFromCache(String url) {
        //从缓存中获取数据
        return mCaches.get(url);
    }

紧接着呢,我们要修改showImageByAsyncTask(ImageView imageView, String url) 方法

 public void showImageByAsyncTask(ImageView imageView, String url) {
        //先判断缓存中是否存在数据,减少下载时间  从缓存中取出对应的图片
        Bitmap bitmap = getBitmapFromCache(url);
        if (bitmap == null) { //如果缓存中没有,必须从网络下载
            //new NewsAsyncTask(url).execute(url);
            imageView.setImageResource(R.mipmap.ic_launcher);
        } else { //直接从内存中获取 并设置
            imageView.setImageBitmap(bitmap);
        }
    }

在NewsAsyncTask 内部类中doInBackground(String… params)方法中加入如下代码:

  @Override
        protected Bitmap doInBackground(String... params) {
            String url = params[0];
            //将下载的图片保存缓存中 从网络获取图片
            Bitmap bitmap = getBitmapFromUrl(url);
            if (bitmap != null) {
                //将不再缓存的图片加入缓存
                addBitmapToCache(url, bitmap);
            }
            return bitmap;
        }

以上,我们就完成了优化图片缓存。


下面我们进行滚动时的优化。
解决ListView滚动时卡顿问题
ListView滑动停止后才会加载可见项
ListView滑动时,取消所有加载项

首先,在NewsAdapter中实现一个接口 AbsListView.OnScrollListener,然后实现他的两个抽象方法onScrollStateChanged(),onScroll(),其中onScrollStateChanged()在状态切换的时候会调用,而onScroll()会在整个调用过程中都会被调用。

public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener {
    @Override
    public void onScrollStateChanged(AbsListView absListView, int i) {

    }

    @Override
    public void onScroll(AbsListView absListView, int i, int i1, int i2) {

    }
}

定义两个全局变量mStart,mEnd,分别记录ListView第一个开始的位置和最后一个item位置

public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener {
     ......
     ......
    private int mStart,mEnd;
    ......
    ......
    }

在onScroll()方法中给mStart,mEnd赋值

    @Override
    public void onScroll(AbsListView absListView, int i, int i1, int i2) {
        mStart = i;
        mEnd = i+i1;
    }

在onScrollStateChanged()方法中判断ListView的状态,如果时停止状态,就加载数据

    @Override
    public void onScrollStateChanged(AbsListView absListView, int i) {
        if (i == SCROLL_STATE_IDLE){
            //加载可见项
        }else{
            //停止加载可见项
        }
    }

在NewsAdapter类添加全局变量存放所有图片的链接的静态数组

 public static String[] URLS; //创建静态数组保存所有图片的链接

在ImageLoader类中创建全局变量

    private ListView mListView;
    private Set<NewsAsyncTask> mTasks;

在构造方法中对他进行初始化,并将构造方法传入一个ListView方法

public ImageLoader(ListView mListView){
        this.mListView = mListView;
        mTasks = new HashSet<>();
        ......
    }

在构造方法中对他进行初始化,并注册一下listview

 public NewsAdapter(Context context,List<NewsBean> mList) {
        ......
        this.mList = mList;
        ......
        URLS = new String[mList.size()];
        for (int i = 0; i < mList.size(); i++) {
            URLS[i] = mList.get(i).newsUrl;
        }
         listView.setOnScrollListener(this);
    }

在ImageLoader类中创建一个加载图片的方法loadImage()

 //用来加载从start到end所有图片
    public void loadImages(int start,int end){
        for (int i = start; i < end; i++) {
            String url  = NewsAdapter.URLS[i];
            //从缓存中取出对应的图片
            Bitmap bitmap = getBitmapFromCache(url);
            if (bitmap == null) { //如果缓存中不存在,就下载
              NewsAsyncTask task = new NewsAsyncTask(url);
              task.execute(url);
              mTasks.add(task);
            }else { //如果存在,就从缓存中下载
                ImageView imageView = mListView.findViewWithTag(url);
                imageView.setImageBitmap(bitmap);
            }
        }

    }

在NewsAdpter类中添加全局变量mFirstIn,用来判别是否时第一次登陆

private boolean mFirstIn; //判断是否第一次登陆

在他的构造方法中,初始化为true

public NewsAdapter(Context context, List<NewsBean> mList, ListView listView) {
        ......
        ......
        mFirstIn = true;
    }

在他的onScroll()方法中进行判断是否时第一次登陆

    @Override
    public void onScroll(AbsListView absListView, int i, int i1, int i2) {
        mStart = i;
        mEnd = i+i1;
        if (mFirstIn && i1>0){
            imageLoader.loadImages(mStart,mEnd);
            mFirstIn = false;
        }
    }

最后了,翠花,这次别上酸菜了,把女儿红上上来吧~~
ImageLoader.class

package com.iyuba.iyubanews;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.LruCache;
import android.widget.ImageView;
import android.widget.ListView;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

public class ImageLoader {
    //全局变量
    private ImageView mImageView;

    //获得图片地址
    private String mUrl;

    //创建Cache
    private LruCache<String,Bitmap> mCaches; //String 传入的url

    //从来注册的listView
    private ListView mListView;

    //将所有下载任务放到集合中
    private Set<NewsAsyncTask> mTasks;

    /**
     * 构造方法
     */
    public ImageLoader(ListView mListView){

        this.mListView = mListView;

        mTasks = new HashSet<>();

        //获取最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 4;
        mCaches = new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //在每次存入缓存的时候调用
                return value.getByteCount();
            }
        }; //指定缓存大小
    }
    //从缓存中获得bitmap
    public Bitmap getBitmapFromCache(String url){
        return mCaches.get(url);
    }
    //增加到缓存
    public void addBitmapToCache(String url,Bitmap bitmap){
        if (getBitmapFromUrl(url) == null){ //校验当前缓存是否存在
            mCaches.put(url,bitmap);
        }
    }

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mImageView.setImageBitmap((Bitmap) msg.obj);
        }
    };




    /**
     * 异步加载图片方法
     * @param imageView
     * @param url
     */
    public void showImageByAsyncTask(ImageView imageView,String url){
        //先判断一下当前缓存是否存在 从缓存中取出相应的图片
        Bitmap bitmap = getBitmapFromCache(url);
        if (bitmap == null) { //如果缓存中不存在,就下载
            imageView.setImageResource(R.mipmap.ic_launcher);
        }else { //如果存在,就从缓存中下载
            imageView.setImageBitmap(bitmap);
        }
    }
    //用来加载从start到end所有图片
    public void loadImages(int start,int end){
        for (int i = start; i < end; i++) {
            String url  = NewsAdapter.URLS[i];
            //从缓存中取出对应的图片
            Bitmap bitmap = getBitmapFromCache(url);
            if (bitmap == null) { //如果缓存中不存在,就下载
              NewsAsyncTask task = new NewsAsyncTask(url);
              task.execute(url);
              mTasks.add(task);
            }else { //如果存在,就从缓存中下载
                ImageView imageView = mListView.findViewWithTag(url);
                imageView.setImageBitmap(bitmap);
            }
        }

    }
    /**
     * 通过多线程方法 下载图片
     * @param imageView
     * @param url
     */
    public void showImageByThread(final ImageView imageView, final String url){
        new Thread(new Runnable() {
            @Override
            public void run() {
                mImageView = imageView;
                Bitmap bitmap = getBitmapFromUrl(url);
                Message message = Message.obtain();
                message.obj = bitmap;
                handler.sendMessage(message);
            }
        }).start();
    }



    /**
     * 下载图片
     *
     * @param urlstring 图片地址
     */
    public Bitmap getBitmapFromUrl(String urlstring) {
        Bitmap bitmap;
        InputStream is = null;
        mUrl = urlstring;
        try {
            URL url = new URL(urlstring);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(con.getInputStream());
            bitmap = BitmapFactory.decodeStream(is);
            con.disconnect();
            return bitmap;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public void cancelAllTasks() {
        if (mTasks != null){
            for (NewsAsyncTask task: mTasks) {
                task.cancel(false);
            }
        }
    }


    /**
     * 异步加载图片类
     */
    private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
        private ImageView mImageView;
        private String mUrl;
        public NewsAsyncTask(String url){
            mUrl = url;
        }
        @Override
        protected Bitmap doInBackground(String... strings) {
            String url = strings[0];
            //从网络获取图片
            Bitmap bitmap = getBitmapFromUrl(strings[0]);
            if (bitmap!=null){//确实下载到了图片
                //将不在缓存的图片加入缓存,从而实现缓存效果
                addBitmapToCache(url,bitmap);
            }
            return bitmap;
        }
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            ImageView imageView = mListView.findViewWithTag(mUrl);
            if (imageView != null && bitmap!=null) { //如果图片存在,并且照片已下载
                imageView.setImageBitmap(bitmap);

            }
            mTasks.remove(this);
        }
    }
}

NewsAdaper.class

package com.iyuba.iyubanews;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;

public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener {
    private List<NewsBean> mList;
    private LayoutInflater mInflater;
    private ImageLoader imageLoader;
    private int mStart,mEnd;
    public static String[] URLS; //创建静态数组保存所有图片的链接
    private boolean mFirstIn; //判断是否第一次登陆

    public NewsAdapter(Context context, List<NewsBean> mList, ListView listView) {
        mInflater = LayoutInflater.from(context);
        this.mList = mList;
        imageLoader = new ImageLoader(listView);
        URLS = new String[mList.size()];
        for (int i = 0; i < mList.size(); i++) {
            URLS[i] = mList.get(i).newsUrl;
        }
        listView.setOnScrollListener(this);
        mFirstIn = true;
    }


    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public Object getItem(int i) {
        return mList.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {

        ViewHolder holder = null;
        if (view==null){ //如果为空,重新创建
            holder = new ViewHolder();
            view = mInflater.inflate(R.layout.item_layout,null);
            holder.tv_title = view.findViewById(R.id.tv_title);
            holder.tv_content = view.findViewById(R.id.tv_content);
            holder.iv_icon = view.findViewById(R.id.iv_icon);
            view.setTag(holder);
        }else { //如果不为空,从之前创建中获取
            holder = (ViewHolder) view.getTag();
        }
        holder.tv_content.setText(mList.get(i).newsContent);
        holder.tv_title.setText(mList.get(i).newsTitle);
        //holder.iv_icon.setImageResource(R.mipmap.ic_launcher);
        holder.iv_icon.setTag(mList.get(i).newsUrl);
        imageLoader.loadImages(mStart,mEnd);
        return view;
    }

    @Override
    public void onScrollStateChanged(AbsListView absListView, int i) {
        if (i == SCROLL_STATE_IDLE){
            //加载可见项
            imageLoader.loadImages(mStart,mEnd);
        }else{
            //停止加载可见项
            imageLoader.cancelAllTasks();
        }
    }

    @Override
    public void onScroll(AbsListView absListView, int i, int i1, int i2) {
        mStart = i;
        mEnd = i+i1;
        if (mFirstIn && i1>0){
            imageLoader.loadImages(mStart,mEnd);
            mFirstIn = false;
        }
    }

    class ViewHolder{
        public TextView tv_title; //标题
        public TextView tv_content;//内容
        public ImageView iv_icon; //图片
    }
}

总结:

通过异步加载,避免堵塞UI线程
通过LruCache,将已下载图片放到内存中
通过判断ListView滑动状态,决定何时加载图片
不仅仅是ListView,任何控件都可以使用异步加载

posted @ 2019-08-16 19:25  Philtell  阅读(529)  评论(0编辑  收藏  举报