ListView中类似于微信朋友圈功能刷新的使用

这一篇blog是接着上一篇,上一篇有一些不完美,这里做出改进。

首先我们需要理清思路:使用ListView显示数据是很方便的,ListVIew的数据之间通过适配器adapter去作为桥梁连接起来。当我们需要使用listview显示大量数据的时候,我们需要使用到分页功能,比如我们有一千条数据,那么我们应该分开数据一点一点的显示,比如每次用户刷新我就增加20条数据额、展示给用户,每次都是增加一定量的数据给用户,直到数据没有为止。为了改善用户体验,我们还应该把上一次用户退出的时候显示最新的20条数据保留本地,用户下次点进来就可以直接看到那些数据,用户点击刷新的时候再去加载本次位于服务器的最新20数据给用户,然后把原来的数据覆盖掉。然后用户下拉到20条数据底端的时候再去加载那之后的20条数据,这样往复循环就可以实现了。本人这次只是模拟。

首先,如何判断用户需要加载新的数据,就是需要监听listview的滚动事件,当用户滚动到最底屏的时候,有一个可以唯一确定的是,当前用户屏幕显示的最后一条数据的position+1等于总的数据量,这时候,我们就应该去添加新的数据,开一条新的线程完成该工作。然后利用handler去把数据添加到适配器的data中,然后调用adapter.notifyDataSetChanged()就可以更新ListVIew了。但是这里有一些问题需要注意的

1,我们需要保证我们当期用户点击下拉刷新之后下载好的数据全部加载到适配器的data的时候才可以去加载新的数据,这是因为某些用户可能很急躁,不断的下拉listview,导致不断的是的屏幕最后一条数据+1等于总的数据量,不断去触发线程下载新数据,所以,我们需要有一个flag去控制。

2,所有的数据下载都需要开线程去完成或者是使用异步任务,下载图片的时候建议使用异步任务,以为当用户快速滚动的时候会开很多的线程下载图片,异步任务能控制线程数量,或者使用Imageloder框架

3,缓存图片,每一次我们去加载新的资源的时候我们就需要把最新的20条数据覆盖原来的,保存本地,他应该在每一次去请求数据的时候开一条线程去完成。主要注意的是每一次请求的输入流只能使用一次,所以这里既需要写入本地有需要写入内存,所以需要请求两次来获得两个数据流,这里可以看getData()方法。

详细代码:

MainActivity

public class MainActivity extends Activity
{
	// 每次都保证把最新的数据保存到本地,下次用户点开的时候就可以直接显示这些以前最新的数据,
	// 当用户下拉刷新之后,在把下拉刷新之后的数据读入到该文件中,每次用户点开都是上次最新的不过现在没有刷新的文件
	public static final File saveXMLToSD = new File(Environment.getExternalStorageDirectory(), "list.xml");
	private ListView listview;// listview
	private File cache;// 缓存目录
	public static final int OK = 1;// 成功获得数据
	public static final int YES = 2;// 成功获得最新数据数据
	public static final int ERROR = -1;// 失败获得数据
	private boolean flag = true;
	private View footer;
	private ListAdapter adapter;
	private boolean isFinsh = false;
	// 负责当数据完成下载之后绑定适配器,用户首次使用点击屏幕就不会有异常
	private Handler mHandler = new Handler()
	{
		@SuppressWarnings("unchecked")
		public void handleMessage(android.os.Message msg)
		{
			if (msg.what == OK)
			{
				adapter = new ListAdapter(MainActivity.this, R.layout.list_item, cache, (List<Contacts>) msg.obj);
				System.out.println("(List<Contacts>) msg.obj" + ((List<Contacts>) msg.obj).size());
				listview.addFooterView(footer);// 添加页脚,用于改善用户体验
				listview.setAdapter(adapter);// 绑定适配器
				listview.removeFooterView(footer);// 首次不用显式页脚
			}
			if (msg.what == YES)// 成功加载最新数据
			{
				flag = true;//这时候才可以继续去下载数据
				adapter.setData((List<Contacts>) msg.obj);
				adapter.notifyDataSetChanged();// 通知数据更改成功,更新ListView
				if (listview.getFooterViewsCount() > 0)
					listview.removeFooterView(footer);// 有页脚存在则去除,此时已经完全加载数据
			}
			if (msg.what == ERROR)
			{
				Toast.makeText(getApplicationContext(), "网络连接失败", Toast.LENGTH_SHORT).show();
			}
		};
	};

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		listview = (ListView) findViewById(R.id.listview);

		cache = new File(Environment.getExternalStorageDirectory(), "cahce");
		if (!cache.exists())
			cache.mkdirs();
		footer = getLayoutInflater().inflate(R.layout.footer, null);// 加载页脚
		listview.setOnScrollListener(new ListViewScrollListener());// 监听滚动事件
		new Thread(new Runnable()
		{
			public void run()
			{
				try
				{
					List<Contacts> data = new ArrayList<Contacts>();
					if (!saveXMLToSD.exists())
					{
						// data.addAll(ContactsService.getData());//
						// 首次文件不存在的时候加载数据
						data.addAll(ContactsService.getData());

					} else
					{
						// 首次数据存在的时候从用户的xml文件中读取数据,这样给用户比较快的感觉,每一次登陆都显示上一次登陆的结果
						FileInputStream fis = new FileInputStream(saveXMLToSD);
						// data = ContactsService.parserXML(fis);
						data.addAll(ContactsService.parserXML(fis));
					}
					Message msg = Message.obtain();
					msg.what = OK;
					msg.obj = data;
					mHandler.sendMessage(msg);// 成功发送消息
				} catch (Exception e)
				{
					mHandler.sendEmptyMessage(ERROR);
					e.printStackTrace();
				}
			}
		}).start();

	}

	class ListViewScrollListener implements OnScrollListener
	{
		public void onScrollStateChanged(AbsListView view, int scrollState)
		{

		}

		/**
		 * firstVisibleItem 屏幕中第一个可见的item的position
		 * visibleItemCount 屏幕中可见的item的数量,
		 * totalItemCount 一共有的数据总量
		 */
		public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
		{
			int lastItemId = listview.getLastVisiblePosition();//屏幕中最后的一个可视的item的position
			System.out.println("lastItemId=" + lastItemId + "firstVisibleItem=" + firstVisibleItem + "visibleItemCount=" + visibleItemCount + "totalItemCount=" + totalItemCount);
			// 当可视的item的最后一条达到了总数目则说明用户已经达到了数据的最低部,这时候应该从网络获取最新数据
			if (lastItemId + 1 == totalItemCount && totalItemCount > 0)
			{
				if (flag)
				{
					flag = false;//防止用户不断下拉listview,所以一旦有一次下拉到最低端之后数据下载之后,马上设为false,只有数据下载完成之后才在handlr中设置为true
					// 这时候把标志值为true,因为下拉底部的时候需要把这一次的下拉完全获取玩之前不去再次加载,而获取是使用线程的,需要时间,所以必须先要把该标志值为true
					listview.addFooterView(footer);
					// 开启线程加载最新的数据
					new Thread(new Runnable()
					{
						@Override
						public void run()
						{
							try
							{
								Thread.currentThread().sleep(3000);
								List<Contacts> data = ContactsService.getData();
								Message msg = Message.obtain();
								msg.what = YES;
								msg.obj = data;
								mHandler.sendMessage(msg);
							} catch (Exception e)
							{
								mHandler.sendEmptyMessage(-1);
								e.printStackTrace();
							}
						}
					}).start();
				}
			}
		}
	}

	// 当用户退出当前应用的时候把所有的缓存图片删除
	@Override
	protected void onDestroy()
	{
		if (cache.exists())
		{
			for (File item : cache.listFiles())
			{
				item.delete();
			}
			cache.delete();
		}
		super.onDestroy();// 一定需要这句,否则会失败
	}

}

adapter,下载图片,绑定数据,更新数据

public class ListAdapter extends BaseAdapter
{
	public List<Contacts> data = new ArrayList<Contacts>();
	private int listItem;
	private File cache;
	private LayoutInflater inflater;
	private Contacts contact;
	private static ImageView imageview;

	/**
	 * @return the data
	 */
	public List<Contacts> getData()
	{
		return data;
	}

	/**
	 * @param data the data to set
	 */
	public void setData(List<Contacts> data)
	{
		this.data.addAll(data);
	}

	public ListAdapter (Context mainActivity , int listItem , File cache , List<Contacts> data)
	{
		this.data.addAll(data);
		this.listItem = listItem;
		this.cache = cache;
		inflater = (LayoutInflater) mainActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	}

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

	@Override
	public Object getItem(int position)
	{
		return data.get(position);
	}

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent)
	{
		ViewHolder holder = null;
		if (null == convertView)
		{
			convertView = inflater.inflate(listItem, null);
			holder = new ViewHolder();
			holder.imageview = (ImageView) convertView.findViewById(R.id.imageview);
			holder.textView = (TextView) convertView.findViewById(R.id.textview);
			convertView.setTag(holder);

		} else
		{
			holder = (ViewHolder) convertView.getTag();
		}
		contact = data.get(position);
		holder.textView.setText(contact.name);
		imageview = holder.imageview;
		loadImageView(contact.path);
		return convertView;
	}

	static class ViewHolder
	{
		ImageView imageview;
		TextView textView;
	}

	private void loadImageView(String path)
	{

		new MyAsyncTask(imageview).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, path);
	}

	private class MyAsyncTask extends AsyncTask<String, Void, Uri>
	{

		private ImageView imageView;

		public MyAsyncTask (ImageView imageView)
		{
			super();
			this.imageView = imageView;
		}

		//后台下载图片,使用线程池控制,也可以线程对象的重用
		@Override
		protected Uri doInBackground(String... params)
		{
			String path = params[0];
			try
			{
				Uri uri = ContactsService.loadSaveImage(path, cache);
				return uri;
			} catch (Exception e)
			{
				e.printStackTrace();
			}
			return null;
		}

		@Override
		protected void onPostExecute(Uri result)
		{
			if(result!= null && imageView != null)
				imageView.setImageURI(result);//运行在UI线程直接更新ImageView
			else if(imageView != null)
			{
				imageView.setImageResource(R.drawable.ic_launcher);//默认图片
			}
		}

	}

}

service,下载数据,缓存图片,解析XML

public class ContactsService
{
	/**
	 * 返回最新数据 list集合返回
	 *
	 * @return
	 * @throws Exception
	 * @throws IOException
	 */
	public static List<Contacts> getData() throws Exception, IOException
	{

		String pathXML = "http://10.10.117.197:8080/web/list2.xml";
		HttpClient client = new DefaultHttpClient();
		HttpPost post = new HttpPost(pathXML);
		HttpResponse httpResponse = client.execute(post);
		if (200 == httpResponse.getStatusLine().getStatusCode())
		{
			HttpEntity entity = httpResponse.getEntity();
			final InputStream content = entity.getContent();
			new Thread(new Runnable()
			{
				public void run()
				{
					saveToSD(content);// 每一次实现下载最新的数据的同时需要去保存本地
				}
			}).start();
			return parserXML(client.execute(post).getEntity().getContent());// 返回最新的数据给listview显示
		}
		return null;
	}

	/**
	 *  解析XML数据,并以集合返回
	 * @param content
	 * @return
	 * @throws Exception
	 */
	public static List<Contacts> parserXML(InputStream content) throws Exception
	{
		XmlPullParser parser = Xml.newPullParser();
		parser.setInput(content, "UTF-8");
		int event = parser.getEventType();
		List<Contacts> data = new ArrayList<Contacts>();
		Contacts item = null;
		int i = 1;
		while (event != XmlPullParser.END_DOCUMENT)
		{
			switch (event)
			{
			case XmlPullParser.START_TAG:
				if ("contact".equals(parser.getName()))
				{
					item = new Contacts();
					item.id = Integer.valueOf(parser.getAttributeValue(0));
					break;
				}
				if ("name".equals(parser.getName()))
				{
					item.name = parser.nextText()+i++;
					break;
				}
				if ("image".equals(parser.getName()))
				{
					item.path = parser.getAttributeValue(0);
					break;
				}
				break;

			case XmlPullParser.END_TAG:
				if ("contact".equals(parser.getName()))
				{
					data.add(item);
					item = null;
				}
				break;
			}
			event = parser.next();
		}

		return data;
	}

	/**
	 * 保存最新的数据到本地
	 *
	 * @param content
	 *            输入流
	 */
	private static void saveToSD(InputStream content)
	{
		try
		{
			FileOutputStream fos = new FileOutputStream(MainActivity.saveXMLToSD);
			int len;
			byte[] buffer = new byte[1024];
			while ((len = content.read(buffer)) != -1)
			{
				fos.write(buffer, 0, len);
			}
			fos.close();
		} catch (FileNotFoundException e)
		{
			e.printStackTrace();
		} catch (IOException e)
		{
			e.printStackTrace();
		}

	}

	/**
	 * 缓存图片
	 *
	 * @param path
	 *            下载路径
	 * @param cache
	 *            缓存目录
	 * @return
	 * @throws Exception
	 */
	public static Uri loadSaveImage(String path, File cache) throws Exception
	{
		File localFile = new File(cache, MD5.getMD5(path) + path.substring(path.lastIndexOf(".")));
		if (localFile.exists())
		{
			return Uri.fromFile(localFile);
		} else
		{
			BufferedOutputStream localFileBufferedOutputStream = null;
			HttpResponse httpResponse = null;

			FileOutputStream localFileOutputStream = new FileOutputStream(localFile);
			localFileBufferedOutputStream = new BufferedOutputStream(localFileOutputStream);
			HttpClient client = new DefaultHttpClient();
			HttpPost post = new HttpPost(path);
			httpResponse = client.execute(post);

			if (200 == httpResponse.getStatusLine().getStatusCode())
			{
				InputStream content = null;
				try
				{
					HttpEntity entity = httpResponse.getEntity();
					content = entity.getContent();
					int len;
					byte[] buffer = new byte[1024];
					while ((len = content.read(buffer)) != -1)
					{
						localFileBufferedOutputStream.write(buffer, 0, len);
						localFileBufferedOutputStream.flush();
					}
					return Uri.fromFile(localFile);
				} catch (IllegalStateException e)
				{
					e.printStackTrace();
				} catch (IOException e)
				{
					e.printStackTrace();
				} finally
				{
					try
					{
						localFileBufferedOutputStream.close();
					} catch (IOException e)
					{
						e.printStackTrace();
					}
				}

			}

		}
		return null;
	}
}

domain

public class Contacts
{
	public int id;
	public String name;
	public String path;
	public Contacts (){}
	public Contacts (int id , String name , String path)
	{
		super();
		this.id = id;
		this.name = name;
		this.path = path;
	}
	@Override
	public boolean equals(Object o)
	{
		return false;
	}
}

Util类 MD5命名

public class MD5 {

	public static String getMD5(String content) {
		try {
			MessageDigest digest = MessageDigest.getInstance("MD5");
			digest.update(content.getBytes());
			return getHashString(digest);

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return null;
	}

    private static String getHashString(MessageDigest digest) {
        StringBuilder builder = new StringBuilder();
        for (byte b : digest.digest()) {
            builder.append(Integer.toHexString((b >> 4) & 0xf));
            builder.append(Integer.toHexString(b & 0xf));
        }
        return builder.toString();
    }
}

layout文件

main

<LinearLayout 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.asynctasklistdemo.MainActivity" >

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

</LinearLayout>

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:gravity="center"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textColor="#000000"
        android:textSize="40sp" />

</LinearLayout>

页脚footer

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">  

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="数据正在加载。。。"
        android:textSize="20sp"
        android:layout_gravity="center"
        />
</LinearLayout> 

权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

结果截图:

时间: 2024-10-28 22:09:45

ListView中类似于微信朋友圈功能刷新的使用的相关文章

ListView中类似于微信朋友圈功能刷新的使用二

这是对上一篇blog的改进:主要有:增加一个内存缓存,首先ListVIew现在内存缓存中去寻找图片,不存在,再去本地缓存目录去寻找,不存在再去网上下载.然后,对于图片进行压缩,我们从网上或者内存中,读取出来的图片,他的分辨率等会比较高,我们不需要那么高的图片质量就可以了,所以我对图片进行了压缩,然后对缓存目录做了优化,当SD卡不存在的时候,则把缓存图片保存到手机自带的空间,存在则保存把SD卡.然后更新ImageView是利用Ui线程提供的一个方法,runOnUiThread去更新UI,大家可以以

第三方应用分享到微信朋友圈功能

分享自http://blog.csdn.net/qianfu111/article/details/9115303 最权威的学习资料还是要去看官网,以及官网提供的Demo,基本上你是可以直接拿来使用的,这是官网网站:http://open.weixin.qq.com/. 在微信分享中主要碰到了如下问题:第一次可以分享,以后就无法调用出分享对话框.这不是程序问题,而是需要提交审核,只有审核通过,才可以调出分享对话框.不相信?那你可以先将微信退出,再试着点击分享,这时就会弹出登录对话款,登完之后也没

实现类似微信朋友圈功能(一 )只供自己好友可见的点赞和评论

先来看看功能的需求 跟微信朋友圈一样的功能. 我是分步骤做: 1:先查看朋友圈.查看的都是自己的好友.点赞和评论是好友才能看见.不是好友就隐藏看下图.三个不用的用户登录 在展示的接口我先查询出当前好友的点赞和评论的用户集合. doctorId = inputFields.doctorId; //当前医生人 partyIds.add(doctorId); andExprs = FastList.newInstance(); andExprs.add(EntityCondition.makeCond

实现类似微信朋友圈功能(二)点赞推送给自己的好友或关注

需求:评论点赞通知跟自己是好友或关注的评论点赞该条说说的人 这个功能是点赞和评论接口,在点赞的同时会给自己的朋友 (是在该条说说有联系的 评论或点赞)进行消息推送 还是跟上一个一样进行筛选.相同的进行推送.在完成之后感觉有问题.就是出现在推送哪里.微信的是 点赞 推送.取消点赞.但是推送还在.在点赞的情况下.消息不会重复,始终保证数据的唯一性. 而我这个是没有做处理.在取消的时候,消息不会进行删除.重新点赞的情况下,消息会重复添加.也就造成了.其实只有一个推送.却出现很多条数据. 当然解决起来其

如何在App中实现朋友圈功能之一朋友圈实现原理浅析——箭扣科技Arrownock

如何在App中实现朋友圈功能 之一 朋友圈实现原理浅析 微信朋友圈.新浪微博.知乎等知名朋友圈类型功能,大家有没有想过其实现的逻辑呢? 本文以微信朋友圈功能为例,解析实现逻辑. 朋友圈的结构: 朋友圈从总体上来说会分为6块结构,分别是墙.用户.图片.墙贴.评论与点赞. 墙:一块公共的墙,所有的墙贴都位于其上,如果APP只实现朋友圈功能,那么墙贴其实是可以不用的,但是如果APP要实现朋友圈.新闻圈等等其他各种墙贴类型消息的话,那么墙就显得很有必要了,这时候我们需要通过建立不同的墙来展示不同类型的墙

Android&quot;挂逼&quot;修炼之行---微信实现本地视频发布到朋友圈功能

一.前言 前一篇文章已经详细介绍了如何使用Xposed框架编写第一个微信插件:摇骰子和猜拳作弊器  本文继续来介绍如何使用Xposed框架编写第二个微信插件,可以将本地小视频发布到朋友圈的功能.在这之前我们还是要有老套路,准备工作要做好,这里还是使用微信6.3.9版本进行操作,准备工作: 1.使用apktool工具进行反编译,微信没有做加固防护,所以这个版本的微信包反编译是没有任何问题的. 2.借助于可视化反编译工具Jadx打开微信包,后续几乎重要分析都是借助这个工具来操作的. 二.猜想与假设

andriod 实现新浪、QQ空间、微信朋友圈、微信好友分享功能

前言:自己在学习的过程中的一些操作过程,对分享的一些理解.下面就讲解一下: 下载地址:http://download.csdn.net/detail/u014608640/7490357 首先,我们需要去ShareSdk官方网站http://sharesdk.cn/ 去下载ShareSDK ,然后我们会有4个文件: 根据我自己在学习的过程中只用到了第一个文件夹的 libs目录的2个项目,这2个是必须要的,是ShareSdk提供的,然后需要将这2个放入到自己做的项目当中去,在Res目录下有一个 S

iOS开发——项目实战总结&amp;类微信朋友圈发动态功能初步-图片与视频上传

类微信朋友圈发动态功能初步-图片与视频上传 最近在做一个新的项目,涉及到了关于图片和视频上传和显示的功能,研究了一段时间,总结一下. 使用AFNetworking上传图片(可一次上传多张图片,包含不同类型png, jpeg)和视频 1 AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; 2 3 AFHTTPRequestOperation *operation = [manager P

iOS微信朋友圈 评论点击姓名功能 (补充)

如果要做成微信朋友圈的评论效果, 那么评论用一个UITableview去加载,每个UITableviewCell上加载一个PPLabel. 但是这样会导致一个问题,PPLable在响应点击单词的时候,同样UITableviewCell会响应select事件. 有两种处理办法: 1.截取点击事件,这种办法比较复杂,需要了解iOS的事件传递机制.由于PPLable在事件响应的最底层,而我们需要在PPLable中判断了是否点击到单词,才能决定UITableviewCell是否响应点击事件,这样的做法过