Android中100行代码实现可上下拉动的自定义ListView

转载请注明出处:http://blog.csdn.net/bettarwang/article/details/41634729

之前在网上也看到一些所谓的下拉刷新的例子,但是总感觉是把简单的事情复杂化了,动辄300多行甚至600多行的代码,其实主要就是对触摸事件作出反应嘛,根本用不着这么麻烦。下面先实现一个可上下拉动的ListView,再实现一个带有Header的可下拉刷新的ListView:

可上下拉动的ListView的源码如下:

/**
 * 可上下拉动的ListView
 * @author Bettar
 *
 */
public class RefreshableListView extends ListView
{
	private static final String TAG="RefreshableListView";
	private int touchSlop;
	private int initTopMargin;
	//private int initTopOfFirstChild;
	private boolean hasRecord=false;
	private float startY;
	private boolean isPulling=false;
	//private ViewGroup.LayoutParams params;
	private LinearLayout.LayoutParams params;

	public RefreshableListView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		//这样的话就可以将设置参数读入,从而不会与layout文件的设置产生冲突。
		params=new LinearLayout.LayoutParams(context, attrs);
		initTopMargin=params.topMargin;
		this.setLayoutParams(params);
		touchSlop=ViewConfiguration.get(context).getTouchSlop();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		switch(event.getAction())
		{
		case MotionEvent.ACTION_DOWN:
			if(!hasRecord)
			{
				hasRecord=true;
				startY=event.getY();
				Log.i(TAG,"ACTION_DOWN");
			}
			break;
		case MotionEvent.ACTION_MOVE:
			float distance=event.getY()-startY;
			if(!isPulling)
			{
				if(!couldPull(distance))
			    {
			    	Log.i(TAG,"could not pull  in ACTION_MOVE");
			    	return false;
			    }
			}
		    isPulling=true;
		    Log.i(TAG,"pull in ACTION_MOVE");
		    params.topMargin+=distance;
		    this.setLayoutParams(params);
		    this.setPressed(false);
		    this.setFocusable(false);
		    this.setFocusableInTouchMode(false);
		    return true;
		case MotionEvent.ACTION_UP:
			Log.i(TAG,"ACTION_UP");
			params.topMargin=initTopMargin;
			this.setLayoutParams(params);
			hasRecord=false;
			this.setFocusable(true);
			this.setFocusableInTouchMode(true);
			if(isPulling)
			{
				isPulling=false;
				//注意:拉伸后放起必须返回true,否则这个事件还会被其他的事件处理器读取,从而影响该类的外部操作,如setOnItemClickListener中的操作。
				return true;
			}
			isPulling=false;
			break;
		}
		return super.onTouchEvent(event);
	}

	private boolean couldPull(float distance)
	{
		if(Math.abs(distance)<touchSlop)
		{
			return false;
		}
		if(distance>0)
		{
			Log.i(TAG,"getTop()"+this.getTop());
			if(this.getFirstVisiblePosition()==0&&this.getChildAt(0).getTop()==0)
			{
				return true;
			}
		}
		else
		{
			if(this.getLastVisiblePosition()==this.getCount()-1)
			{
				return true;
			}
		}
		return false;
	}

}

要注意的一个细节是ACTION_UP时的处理,如果是拉伸后放开手指的ACTION_UP,那么要返回true而不是false,否则会影响这个自定义ListView的正常使用,因为如果返回false的话则这整个过程由于有ACTION_DOWN和ACTION_UP,会被当作一次Click,从而影响造成额外的影响。

如果要加上一定的动画,也很简单,使用补间动画或者异步任务去实现,下面的代码使用了两种实现方式:

package com.android.customview;

import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.TranslateAnimation;
import android.widget.AbsListView;
import android.widget.LinearLayout;
import android.widget.ListView;
/**
 * 可上下拉动的ListView
 * @author Bettar
 *
 */
public class RefreshableListView extends ListView
{
	private static final String TAG="RefreshableListView";
	//0.5的话会感觉很粘滞,而1.0的话又感觉太滑,0.8是一个比较好的参数。
	private static final float RATIO=0.8f;
	private static final int ANIM_DURATION=1000;
	private int touchSlop;
	private int initTopMargin;
	private int[]initLocation=new int[2];
	private boolean hasRecord=false;
	private float startY;
	private boolean isPulling=false;
	//private ViewGroup.LayoutParams params;
	private LinearLayout.LayoutParams params;

	public RefreshableListView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		//params=this.getLayoutParams();
		//这样的话就可以将设置参数读入,从而不会与layout文件的设置产生冲突。
		params=new LinearLayout.LayoutParams(context, attrs);
		initTopMargin=params.topMargin;
		this.getLocationOnScreen(initLocation);
		//initTopOfFirstChild=this.getChildAt(0).getTop();
		this.setLayoutParams(params);
		touchSlop=ViewConfiguration.get(context).getTouchSlop();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		switch(event.getAction())
		{
		case MotionEvent.ACTION_DOWN:
			if(!hasRecord)
			{
				hasRecord=true;
				startY=event.getY();
				Log.i(TAG,"ACTION_DOWN");
			}
			break;
		case MotionEvent.ACTION_MOVE:
			float distance=event.getY()-startY;
			if(!isPulling)
			{
				if(!couldPull(distance))
			    {
			    	Log.i(TAG,"could not pull  in ACTION_MOVE");
			    	return false;
			    }
			}
		    isPulling=true;
		    Log.i(TAG,"pull in ACTION_MOVE");
		    params.topMargin=initTopMargin+(int)(distance*RATIO);
		    this.setLayoutParams(params);
		    this.setPressed(false);
		    this.setFocusable(false);
		    this.setFocusableInTouchMode(false);
		    return true;
		case MotionEvent.ACTION_UP:
			Log.i(TAG,"ACTION_UP");
			if(isPulling)
			{
				startTranslateAnimation();
				//executeTranslateAnimation();
			}
			//重设参数,注意如果是使用自定义动画,那么要将此处的reset();注释,等到异步任务执行完毕后再执行reset();否则参数会相互干扰。
		    reset();
			if(isPulling)
			{
				isPulling=false;
				//注意:拉伸后放起必须返回true,否则这个事件还会被其他的事件处理器读取,从而影响该类的外部操作,如setOnItemClickListener中的操作。
				return true;
			}
			isPulling=false;
			break;
		}
		return super.onTouchEvent(event);
	}

	private void reset()
	{
		params.topMargin=initTopMargin;
		this.setLayoutParams(params);
		hasRecord=false;
		this.setFocusable(true);
		this.setFocusableInTouchMode(true);
	}

	private void startTranslateAnimation()
	{
		int[]location=new int[2];
		RefreshableListView.this.getLocationOnScreen(location);
		//测试发现location[0]==0而location[1]就是第一个Item上端距离顶部的距离。
		Log.i(TAG,"location[0]="+location[0]+" location[1]="+location[1]);
		TranslateAnimation anim=new TranslateAnimation(location[0],initLocation[0],location[1],initLocation[1]);
		anim.setDuration(ANIM_DURATION);
		RefreshableListView.this.startAnimation(anim);
	}

	/**
	 *这其实就相当于自己去实现动画了。
	 */
	private void executeTranslateAnimation()
	{
		new TranslateTask(20).execute();
	}

	/**
	 * 如果是使用它的话就要将params的参数等放到异步任务执行完之后再完成,否则会现相互干扰的情况。
	 * @author Bettar
	 *
	 */
	private class TranslateTask extends AsyncTask<Void,Integer,Integer>
	{
		//每次线程的睡眠时间
		private int deltaSleepTime;
		private int deltaScrollY;
		public TranslateTask(int deltaSleepTime)
		{
			this.deltaSleepTime=deltaSleepTime;
			if(deltaSleepTime>0)
			{
				deltaScrollY=0-(params.topMargin-initTopMargin)/(ANIM_DURATION/deltaSleepTime);
			}
			else
			{
				deltaScrollY=params.topMargin>initTopMargin?-20:20;
			}
			Log.i(TAG,"deltaScrollY="+deltaScrollY);
		}
		@Override
		protected Integer doInBackground(Void...voidParams) {
			int topMargin=params.topMargin;
			while(true)
			{
				topMargin+=deltaScrollY;
				Log.i(TAG,"topMargin="+topMargin);
				if(deltaScrollY<0)
				{
					if(topMargin<0)
					{
						topMargin=0;
						break;
					}
				}
				else
				{
					if(topMargin>0)
					{
						topMargin=0;
						break;
					}
				}
				publishProgress(topMargin);
				try
				{
					Thread.sleep(deltaSleepTime);
				}
				catch(InterruptedException ex)
				{
					ex.printStackTrace();
				}
			}
			publishProgress(0);
			return topMargin;
		}

		@Override
		protected void onProgressUpdate(Integer... values) {
			//values[0]对应上面publisProgress中的topMargin
			Log.i(TAG,"values[0] i.e topMargin="+values[0]);
			params.topMargin=values[0];
			RefreshableListView.this.setLayoutParams(params);
		}
		@Override
		protected void onPostExecute(Integer result) {
			//执行完异步任务之后就可以进行参数重新设置了
			reset();
		}

	}
	/**
	 * 判断是否可以开始拉动,如果是向下拉动,则要求第一个Item完全可见;如果是向上拉,则要求最后一个Item完全可见。
	 * @param distance
	 * @return
	 */
	private boolean couldPull(float distance)
	{
		if(Math.abs(distance)<touchSlop)
		{
			return false;
		}
		if(distance>0)
		{
			Log.i(TAG,"getTop()"+this.getTop());
			if(this.getFirstVisiblePosition()==0&&this.getChildAt(0).getTop()==0)
			//if(this.getFirstVisiblePosition()==0&&this.getChildAt(0).getTop()==initTopOfFirstChild)
			{
				return true;
			}
		}
		else
		{
			if(this.getLastVisiblePosition()==this.getCount()-1)
			{
				return true;
			}
		}
		return false;
	}

}

相信到了这里,要做一个带下拉刷新头的ListView是极简单的,今天就先到这儿吧,下拉刷新的代码后面补上。

时间: 2024-10-12 16:09:00

Android中100行代码实现可上下拉动的自定义ListView的相关文章

100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】

转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 简介 流程图 simplest_ffmpeg_player标准版代码 simplest_ffmpeg_player_suSU版代码 结果 FFMPEG相关学习资料 补充问题 ===================================================== 最简单的基于FFmp

不到100行代码实现一个推荐系统

似乎咱的产品七,八年前就想做个推荐系统的,就是类似根据用户的喜好,自动的找到用户喜欢的电影或者节目,给用户做推荐.可是这么多年过去了,不知道是领导忘记了还是怎么了,连个影子还没见到. 而市场上各种产品的都有了推荐系统了.比如常见的各种购物网站京东,亚马逊,淘宝之类的商品推荐,视频网站优酷的的类似影片推荐,豆瓣音乐的音乐推荐...... 一个好的推荐系统推荐的精度必然很高,能够真的发现用户的潜在需求或喜好,提高购物网詀的销量,让视频网站发现用户喜欢的收费电影... 可是要实现一个高精度的推荐系统不

100行代码教你教务系统自动抢课!

帮助广大学生解决抢课问题!自动抢课!! 100行代码帮你实现抢课! ? 本项目使用了python中splinter的API接口用来操作页面交互,用了twilio用来给手机发送短信通知抢课成功. ? 欢迎大家来全球最大同性交友网站Github:https://github.com/xubin97 来fork我的菜鸡代码,希望你能来继续增加更多功能,我也会不定期更新功能! ? 其中splinter API文档链接:https://splinter.readthedocs.io/en/latest/m

GameBuilder开发游戏应用系列之100行代码实现贪吃蛇

在线预览:http://osgames.duapp.com/apprun.html?appid=osgames1-801422234293697 在线编辑:http://osgames.duapp.com/gamebuilder.php?appid=osgames1-801422234293697 微信扫描: 运行截图: 除了重力感应游戏,GameBuilder开发传统的游戏也毫不逊色,作为一个怀旧的人,总是对这类游戏情有独钟. 贪吃蛇主要靠一个[UICanvas]来实现,前面一片博客GameB

将文本框内容添加到表格中的行代码实例

将文本框内容添加到表格中的行代码实例:在实际操作中,往往需要将文本框中的内容添加到表格的行中或者类似的情况,下面就通过一个实例介绍一下如何实现此效果,希望能够对需要的朋友有所帮助,代码如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author" content="http://www.softwhy.com/&

用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”

FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua1020/article/details/8652605. 用JavaCV重新实现并使用opencv_highgui进行显示. 1 import com.googlecode.javacpp.IntPointer; 2 import com.googlecode.javacpp.Pointer; 3 im

不到100行代码实现一个简单的推荐系统

似乎咱的产品七,八年前就想做个推荐系统的,就是类似根据用户的喜好,自动的找到用户喜欢的电影或者节目,给用户做推荐.可是这么多年过去了,不知道是领导忘记了还是怎么了,连个影子还没见到. 而市场上各种产品的都有了推荐系统了.比如常见的各种购物网站京东,亚马逊,淘宝之类的商品推荐,视频网站优酷的的类似影片推荐,豆瓣音乐的音乐推荐...... 一个好的推荐系统推荐的精度必然很高,能够真的发现用户的潜在需求或喜好,提高购物网詀的销量,让视频网站发现用户喜欢的收费电影... 可是要实现一个高精度的推荐系统不

100行代码实现简单目录浏览器制作

给大家分享使用Lae软件开发工具开发小应用程序的过程,希望大家喜欢! 界面部分我们用lae软件开发工具实现,无需写代码,业务逻辑部分使用Lae软件开发平台自带的LuaIDE编辑器,使用100行lua代码完成简单目录浏览器的制作. lae软件下载地址: https://github.com/ouloba/laetool.git lae软件下载地址(国内):https://pan.baidu.com/s/1ckMy0Q 相关视频: http://www.tudou.com/listplay/aly7

基于zbus网络通讯模块实现的MySQL透明代理(&lt;100行代码)

项目地址 https://git.oschina.net/rushmore/zbus 我们上次讲到zbus网络通讯的核心API: Dispatcher -- 负责-NIO网络事件Selector引擎的管理,对Selector引擎负载均衡 IoAdaptor -- 网络事件的处理,服务器与客户端共用,负责读写,消息分包组包等 Session -- 代表网络链接,可以读写消息 实际的应用,我们几乎只需要做IoAdaptor的个性化实现就能完成高效的网络通讯服务,今天我们将举例说明如何个性化这个IoA