Android下拉刷新代码完全解析,完全解读大神代码,解决两个小bug
2016-09-17 14:37
369 查看
本文转载自郭霖的博客转载地址:http://blog.csdn.net/guolin_blog/article/details/9255575,感谢大神的分享!
本文旨在解读代码,让喜欢IT,不断拼搏的程序员同学们能更快的学会大神分享的代码,若有不足之处,请多指教,大神勿喷啊!
不知道从什么时候开始,下拉和侧滑是做App必备的两项装B利器,在没有使用官方原生的侧滑和下拉之前,那都是无数大神用代码构造的啊,看见别人写的第三方,自己也拿来用,用久了,感觉不给力,出bug了不知道如何下手修复,后来就打算去网上找源码看看别人怎么写的,Google搜索排名第一的郭大神的下拉代码就被找出来了,写的原理确实不错啊,逻辑思维也很清晰啊,自己使用的同时也推荐给身边的同事,不过好多同事觉得注释太少看不懂,而且看的也是半懂,小弟怀着为天下苍生谋福利的想法,就把大神的代码全部注释翻译了一遍.
中间发现两个小问题,一个就是下拉刷新后,箭头图片无法隐藏,不过也解决了,就是一句代码的事,另外一个就是listview方法哦fragment时,下拉箭头初始化不会自动隐藏,不过还是一句代码的事,废话不多说,直接上代码,全面解读郭大神的下拉刷新:
差不多每一行都有注释,对于刚进入自绘控件的同学很有帮助.
首先是下拉头的xml文件:其中的箭头自己找一个吧,图片就不展示了
然后开始正题,下拉刷新的代码:
新建一个类继承LinearLayout:
代码注释很详细吧,这里就不啰嗦咯!看下面使用!
先是Xml的引用,直接把listview放进去:
代码解读完毕,注释很多吧,慢慢看吧!中间的下拉箭头图片不能隐藏的解决代码,可别忘记哦!
最后向大神致敬!!!!!
本文旨在解读代码,让喜欢IT,不断拼搏的程序员同学们能更快的学会大神分享的代码,若有不足之处,请多指教,大神勿喷啊!
不知道从什么时候开始,下拉和侧滑是做App必备的两项装B利器,在没有使用官方原生的侧滑和下拉之前,那都是无数大神用代码构造的啊,看见别人写的第三方,自己也拿来用,用久了,感觉不给力,出bug了不知道如何下手修复,后来就打算去网上找源码看看别人怎么写的,Google搜索排名第一的郭大神的下拉代码就被找出来了,写的原理确实不错啊,逻辑思维也很清晰啊,自己使用的同时也推荐给身边的同事,不过好多同事觉得注释太少看不懂,而且看的也是半懂,小弟怀着为天下苍生谋福利的想法,就把大神的代码全部注释翻译了一遍.
中间发现两个小问题,一个就是下拉刷新后,箭头图片无法隐藏,不过也解决了,就是一句代码的事,另外一个就是listview方法哦fragment时,下拉箭头初始化不会自动隐藏,不过还是一句代码的事,废话不多说,直接上代码,全面解读郭大神的下拉刷新:
差不多每一行都有注释,对于刚进入自绘控件的同学很有帮助.
首先是下拉头的xml文件:其中的箭头自己找一个吧,图片就不展示了
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="60dp"> <!--线性布局装载--> <LinearLayout android:layout_width="200dip" android:layout_height="60dip" android:layout_centerInParent="true" android:orientation="horizontal" > <RelativeLayout android:layout_width="0dip" android:layout_height="60dip" android:layout_weight="3" > <!--下拉箭头,图片自己找吧--> <ImageView android:id="@+id/imagejiantou" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="@mipmap/pulltorefresh_down_arrow" /> <!--正在更新时使用的ProgressBar--> <ProgressBar android:id="@+id/progress_bar" android:layout_width="30dip" android:layout_height="30dip" android:layout_centerInParent="true" android:visibility="gone" /> </RelativeLayout> <LinearLayout android:layout_width="0dip" android:layout_height="60dip" android:layout_weight="12" android:orientation="vertical" > //下面这些不用解释咯 <TextView android:id="@+id/description" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:gravity="center_horizontal|bottom" android:text="下拉可以刷新" /> <TextView android:id="@+id/updated_at" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:gravity="center_horizontal|top" android:text="最后更新:暂未更新" /> </LinearLayout> </LinearLayout> </RelativeLayout>
然后开始正题,下拉刷新的代码:
新建一个类继承LinearLayout:
public class PulltoRefresh extends LinearLayout implements View.OnTouchListener { private SharedPreferences preferences; //状态:正在刷新 private int STATE_FRESHING = 0; //状态:下拉刷新 private int STATE_PULL = 1; //状态:刷新完成 private int STATE_FINISH = 2; //状态:释放刷新 private int STATE_RELEASE = 3; //初始状态 private int STATE_CURRENT = STATE_FINISH; //记录上次状态 private int STATE_LAST = STATE_CURRENT; private View header; private ProgressBar progressBar; private ImageView imagejiantou; private TextView description; private TextView updateAt; //在被判定为滚动之前用户手指可以移动的最大值。下面有解释 private int touchSlop; //只能画一次头,下面有解释 private boolean loadOnce = false; //高度 private int hideHeaderHeight; private ViewGroup.MarginLayoutParams headerLayoutParams; private ListView listview; //是否允许下拉 private boolean ableToPull; //listview滚动到顶部的时候,手指点击的坐标 private float yDown; //刷新监听 private OnPullToRefreshListener onPullToRefreshListener; //区分监听的ID private int listenerId; public PulltoRefresh(Context context, AttributeSet attrs) { super(context, attrs); //这个不用说吧 preferences = context.getSharedPreferences("VALUE", Context.MODE_PRIVATE); //需要添加的头 header = LayoutInflater.from(context).inflate(R.layout.activity_pullrefresh, null); //旋转的bar控件 progressBar = (ProgressBar) header.findViewById(R.id.progress_bar); //下拉的图片箭头 imagejiantou = (ImageView) header.findViewById(R.id.imagejiantou); //描述下拉可以刷新的控件 description = (TextView) header.findViewById(R.id.description); //更新时间 updateAt = (TextView) header.findViewById(R.id.updated_at); //getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。 //如果小于这个距离就不触发移动控件,如viewpager就是用这个距离来判断用户是否翻页 touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); //refreshUpdatedAtValue(); //设置方向 setOrientation(VERTICAL); //将view添加到0的位置 addView(header, 0); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); //这里有很多疑问,查了很多资料,说一说本人的理解, //当自绘控件没有发生位置移动时,changed会为false,此时不会绘制 //当自绘控件发生位置移动时,changed会为true,此时系统会调用该方法绘制 //但是我们只需要绘制一次,如果每次都绘制,那么下拉就拉不下来 //所以这里就在引入一个变量loadOnce,使其先为false,然后同时判断,让该方法第一次时开始绘制 //绘制完的时候将loadOnce改为true,这样后面系统就不能再次绘制该控件 if (changed && !loadOnce) { //控件的隐藏高度 hideHeaderHeight = -header.getHeight(); //得到MarginLayoutParams参数 headerLayoutParams = (MarginLayoutParams) header.getLayoutParams(); //设置顶部的margin headerLayoutParams.topMargin = hideHeaderHeight; //此处如果listview实在MainActivity里面,可以不用设置 //如果是在fragment里面必须设置,笔者亲测...之前没有这句,后来加上后搞定 header.setLayoutParams(headerLayoutParams); //得到listview,此处查看xml文件 listview = (ListView) getChildAt(1); //设置listview的监听 listview.setOnTouchListener(this); //改变变量,使系统只绘制一次 loadOnce = true; } } //listview的监听 public boolean onTouch(View v, MotionEvent event) { //判断能否下拉,首先判断这个,然后在进行其他操作 setIsAbleToPull(event); //如果可以下拉 if (ableToPull) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //记录点击时的起始位置 yDown = event.getRawY(); break; case MotionEvent.ACTION_MOVE: //下拉的位置 float ymove = event.getRawY(); //下拉的距离 int absolute = (int) (ymove - yDown); //如果下拉的距离小于等于0,是上拉,上拉的时候肯定不刷新呗 //并且header的topmargin没有超过hideHeaderHeight,说明根本就没有下拉出来 //如果手指是下滑状态,并且下拉头是完全隐藏的,就屏蔽下拉事件 if (absolute <= 0 && headerLayoutParams.topMargin <= hideHeaderHeight) { return false; } //距离小于touchSlop,也应该false if (absolute < touchSlop) { return false; } //如果当前状况不是正在刷新中 if (STATE_CURRENT != STATE_FRESHING) { //判断topMargin>0,是释放状态 if (headerLayoutParams.topMargin > 0) { STATE_CURRENT = STATE_RELEASE; } else { //否则就是下拉状态 STATE_CURRENT = STATE_PULL; } // 通过偏移下拉头的topMargin值,来实现下拉效果 //因为absolute的距离很大,所以除以2,在来与hideHeaderHeight搭配 headerLayoutParams.topMargin = absolute / 2 + hideHeaderHeight; header.setLayoutParams(headerLayoutParams); } break; case MotionEvent.ACTION_UP: default: //手指松开时,判断状态 //手指松开时如果是释放状态,立即调用刷新方法 if (STATE_CURRENT == STATE_RELEASE) { //刷新任务 new PullRefreshing().execute(); //手指松开时如果是下拉状态,就调用下拉隐藏任务方法 } else if (STATE_CURRENT == STATE_PULL) { // 隐藏下拉任务 new hideHeader().execute(); } break; } //时刻记得更新下拉信息 if (STATE_CURRENT == STATE_PULL || STATE_CURRENT == STATE_RELEASE) { //更新header任务 updateHeader(); // 当前正处于下拉或释放状态,要让ListView失去焦点,否则被点击的那一项会一直处于选中状态 listview.setPressed(false); listview.setFocusable(false); listview.setFocusableInTouchMode(false); STATE_LAST = STATE_CURRENT; //当前正处于下拉或释放状态,通过返回true屏蔽掉ListView的滚动事件 return true; } } return false; } //刷新完成方法 public void finishRefresh() { //记录状态 STATE_CURRENT = STATE_FINISH; //将当前时间的毫秒数写入xml文件,方便下次更新是调用计算更新时间 //将传入监听的ListenerId也一起写进去,方便后续判断是哪个listview的更新时间 preferences.edit().putLong("TIME" + listenerId, System.currentTimeMillis()).commit(); //隐藏下拉头 new hideHeader().execute(); } //判断能否下拉 private void setIsAbleToPull(MotionEvent event) { //得到listview的第一个item View firstchild = listview.getChildAt(0); if (firstchild != null) { //得到listview当前屏幕最上方的索引 int firstVisiblePosition = listview.getFirstVisiblePosition(); if (firstVisiblePosition == 0 && firstchild.getTop() == 0) { if (!ableToPull) { //记录坐标 yDown = event.getRawY(); } // 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新 ableToPull = true; } else { //如果listview没有滚动到顶部,判断header的topmargin,超出自身高度后 //设置回自身高度的margin if (headerLayoutParams.topMargin != hideHeaderHeight) { headerLayoutParams.topMargin = hideHeaderHeight; header.setLayoutParams(headerLayoutParams); } //当然此时还是不能下拉 ableToPull = false; } } else { //如果listview中没有内容,应该允许下拉 ableToPull = true; } } //为了防止不同界面的下拉刷新在上次更新时间上互相有冲突, 请不同界面在注册下拉刷新监听器时一定要传入不同的id public void setOnPullToRefreshListener(OnPullToRefreshListener onPullToRefreshListener, int id) { this.onPullToRefreshListener = onPullToRefreshListener; listenerId = id; } public interface OnPullToRefreshListener { void onPullToRefreshListener(); } //隐藏header任务 //隐藏下拉头的任务,当未进行下拉刷新或下拉刷新完成后,此任务将会使下拉头重新隐藏。 class hideHeader extends AsyncTask<Void, Integer, Integer> { @Override protected Integer doInBackground(Void... params) { int topMargin = headerLayoutParams.topMargin; int speed = -20; while (true) { topMargin = topMargin + speed; if (topMargin <= hideHeaderHeight) { topMargin = hideHeaderHeight; break; } publishProgress(topMargin); SystemClock.sleep(10); } return topMargin; } @Override protected void onPostExecute(Integer integer) { super.onPostExecute(integer); headerLayoutParams.topMargin = integer; header.setLayoutParams(headerLayoutParams); STATE_CURRENT = STATE_FINISH; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); headerLayoutParams.topMargin = values[0]; header.setLayoutParams(headerLayoutParams); } } //设置旋转箭头方法 public void rotateImage() { //根据状态分别定义旋转的起始角度 float fromDegrees = 0f; float toDegrees = 0f; if (STATE_CURRENT == STATE_PULL) { fromDegrees = 180f; toDegrees = 360f; } else if (STATE_CURRENT == STATE_RELEASE) { fromDegrees = 0f; toDegrees = 180f; } //此处有改动 //旋转动画需要设置旋转的中心点, //Animation.RELATIVE_TO_SELF意思是相对于自己 //两个0.5f,意思是相对于自己的x和y各一半,也就是控件自身的中心 RotateAnimation rotateAnimation = new RotateAnimation(fromDegrees, toDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotateAnimation.setDuration(100); rotateAnimation.setFillAfter(true); imagejiantou.startAnimation(rotateAnimation); } //设置更新头方法 public void updateHeader() { if (STATE_LAST != STATE_CURRENT) { if (STATE_CURRENT == STATE_PULL) { description.setText("下拉刷新"); imagejiantou.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); rotateImage(); } else if (STATE_CURRENT == STATE_RELEASE) { description.setText("释放刷新"); imagejiantou.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); rotateImage(); } else if (STATE_CURRENT == STATE_FRESHING) { description.setText("正在刷新"); //此处代码必须加上,之前使用时,发现箭头图片不会隐藏 //后来才知道,图片设置过动画,而且设置了rotateAnimation.setFillAfter(true)方法后 //该图片就不会被隐藏掉, //所以隐藏前必须先清除动画 imagejiantou.clearAnimation(); imagejiantou.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); } //更新时间 //此处时间有争议,可以放到点击事件MotionEvent.ACTION_DOWN: //总之看个人喜好,建议放到构造方法或者onLayout方法里面去 updateTime(); } } //更新时间的方法 public void updateTime() { //获得上次的更新时间 long lastTime = preferences.getLong("TIME" + listenerId, -1); //得到当前的更新时间 long currentTime = System.currentTimeMillis(); //计算更新时间的间隔 long updateSpace = currentTime - lastTime; //新建一个文本变量 String timeText = null; //当上次时间等于默认值时,说明还没更新 if (lastTime == -1) { timeText = "最后更新:暂未更新"; //当时间间隔小于0时,显示更新时间错误 } else if (updateSpace < 0) { timeText = "更新时间错误"; //当时间间隔小于2分钟时,显示刚刚更新 } else if (updateSpace < 60 * 1000 ) { timeText = "最后更新:刚刚更新"; //当时间间隔小于1小时,显示最后更新:xx分钟前 } else if (updateSpace < 60 * 1000 * 60) { long updateCount = updateSpace / (60 * 1000); timeText = "最后更新:" + updateCount + "分钟前"; //当时间间隔小于24小时,显示最后更新:xx小时前 } else if (updateSpace < 60 * 1000 * 60 * 24) { long updateCount = updateSpace / (60 * 1000 * 60); timeText = "最后更新:" + updateCount + "小时前"; } else { //最后时间太长了,直接很久以前吧 timeText = "最后更新:很久以前"; } //最后设置给textView updateAt.setText(timeText); } //设置更新任务 class PullRefreshing extends AsyncTask<Void, Integer, Void> { protected Void doInBackground(Void... params) { int topMargin = headerLayoutParams.topMargin; int speed = -20; while (true) { topMargin = topMargin + speed; if (topMargin <= 0) { topMargin = 0; break; } publishProgress(topMargin); SystemClock.sleep(10); } STATE_CURRENT = STATE_FRESHING; publishProgress(0); if (onPullToRefreshListener != null) { onPullToRefreshListener.onPullToRefreshListener(); } return null; } protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); updateHeader(); headerLayoutParams.topMargin = values[0]; header.setLayoutParams(headerLayoutParams); } } }
代码注释很详细吧,这里就不啰嗦咯!看下面使用!
先是Xml的引用,直接把listview放进去:
<?xml version="1.0" encoding="utf-8"?> <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" 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.pulltorefesh.MainActivity"> <!--此处就是自绘的下拉刷新--> <com.example.pulltorefesh.PulltoRefresh android:id="@+id/pulltorefresh" android:layout_width="match_parent" android:layout_height="match_parent"> <!--此处listview--> <ListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="#ff0000" android:dividerHeight="1px" android:entries="@array/city"></ListView> </com.example.pulltorefesh.PulltoRefresh> </RelativeLayout>然后是我们的MainActivity:
public class MainActivity extends Activity { private PulltoRefresh pulltorefresh; private ListView listView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.listview); pulltorefresh = (PulltoRefresh) findViewById(R.id.pulltorefresh); //别忘记设置监听 pulltorefresh.setOnPullToRefreshListener(new PulltoRefresh.OnPullToRefreshListener() { @Override public void onPullToRefreshListener() { //在这里执行刷新任务 //我这里就不执行刷新任务了,让系统睡两秒 SystemClock.sleep(2000); //执行完后调用finish,如果是是异步或者子线程中刷新,记得传回主线程在调用finish pulltorefresh.finishRefresh(); //监听ID别忘记了 } }, 0); } }
代码解读完毕,注释很多吧,慢慢看吧!中间的下拉箭头图片不能隐藏的解决代码,可别忘记哦!
最后向大神致敬!!!!!
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories