很实用的关于ScrollView的操作,你知道吗?
来源:互联网 发布:传世源码 编辑:程序博客网 时间:2024/05/04 10:13
文章出自http://blog.sina.com.cn/s/blog_5704bfaf0101iao6.html
玩过微信的朋友想必很熟悉,其实就是界面可以拖拽,会有反弹效果,看起来很炫酷.(总之比拖拽没反应的死板要好.)
下面我来一一讲解如何实现这样效果:
原理:自定义ScrollView对其Touch监听,对布局时时更改.
一:不包含孩子,就是只针对普通的布局:
MyScrollView.java[java] view plaincopyprint?package com.rebound.myscroll; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.TranslateAnimation; import android.widget.ScrollView; public class MyScrollView extends ScrollView { private View inner;// 孩子 private float y;// 坐标 private Rect normal = new Rect();// 矩形空白 public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { if (getChildCount() > 0) { inner = getChildAt(0);// 获取其孩子 } } @Override public boolean onTouchEvent(MotionEvent ev) { if (inner != null) { commOnTouchEvent(ev); } return super.onTouchEvent(ev); } public void commOnTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: y = ev.getY();// 获取点击y坐标 break; case MotionEvent.ACTION_UP: if (isNeedAnimation()) { animation(); } break; case MotionEvent.ACTION_MOVE: final float preY = y; float nowY = ev.getY(); int deltaY = (int) (preY - nowY);// 获取滑动距离 y = nowY; // 当滚动到最上或者最下时就不会再滚动,这时移动布局 if (isNeedMove()) { if (normal.isEmpty()) { // 填充矩形,目的:就是告诉this:我现在已经有了,你松开的时候记得要执行回归动画. normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); } // 移动布局 inner.layout(inner.getLeft(), inner.getTop() - deltaY / 2, inner.getRight(), inner.getBottom() - deltaY / 2); } break; default: break; } } public void animation() { // 开启移动动画 TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top); ta.setDuration(300); inner.startAnimation(ta); // 设置回到正常的布局位置 inner.layout(normal.left, normal.top, normal.right, normal.bottom); normal.setEmpty();// 清空矩形 } public boolean isNeedAnimation() { return !normal.isEmpty(); } public boolean isNeedMove() { int offset = inner.getMeasuredHeight() - getHeight(); int scrollY = getScrollY(); // 0是顶部,后面那个是底部 if (scrollY == 0 || scrollY == offset) { return true; } return false; } }
效果:
下拉效果,松开会自动回缩.
效果图是我随便弄的,这个比较简单,下面我们看下如果自定义的ScrollView里面包含ViewGroup类的控件如何办?
二:自定义ScrollView里面包含ListView.
想必这种效果比较常见,用处也比较广.但是单存的用上面自定义的ScrollView是行不通的,滑动时候相当不灵敏,发生错乱.为何:因为Touch受到影响,因为要继续向下传递嘛.
我们要对上面自定义稍做修改:
<span style="font-size:14px;">MyScrollView.java[java] view plaincopyprint?package com.jj.drag; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.animation.TranslateAnimation; import android.widget.ScrollView; public class MyScrollView extends ScrollView { private View inner;// 孩子View private float y;// 点击时y坐标 private Rect normal = new Rect();// 矩形(这里只是个形式,只是用于判断是否需要动画.) private boolean isCount = false;// 是否开始计算 public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { if (getChildCount() > 0) { inner = getChildAt(0); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (inner != null) { commOnTouchEvent(ev); } return super.onTouchEvent(ev); } public void commOnTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_UP: // 手指松开. if (isNeedAnimation()) { animation(); isCount = false; } break; case MotionEvent.ACTION_MOVE: final float preY = y;// 按下时的y坐标 float nowY = ev.getY();// 时时y坐标 int deltaY = (int) (preY - nowY);// 滑动距离 if (!isCount) { deltaY = 0; // 在这里要归0. } y = nowY; // 当滚动到最上或者最下时就不会再滚动,这时移动布局 if (isNeedMove()) { // 初始化头部矩形 if (normal.isEmpty()) { // 保存正常的布局位置 normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); } Log.e("jj", "矩形:" + inner.getLeft() + "," + inner.getTop() + "," + inner.getRight() + "," + inner.getBottom()); // 移动布局 inner.layout(inner.getLeft(), inner.getTop() - deltaY / 2, inner.getRight(), inner.getBottom() - deltaY / 2); } isCount = true; break; default: break; } } public void animation() { // 开启移动动画 TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top); ta.setDuration(200); inner.startAnimation(ta); // 设置回到正常的布局位置 inner.layout(normal.left, normal.top, normal.right, normal.bottom); Log.e("jj", "回归:" + normal.left + "," + normal.top + "," + normal.right + "," + normal.bottom); normal.setEmpty(); } // 是否需要开启动画 public boolean isNeedAnimation() { return !normal.isEmpty(); } public boolean isNeedMove() { int offset = inner.getMeasuredHeight() - getHeight(); int scrollY = getScrollY(); Log.e("jj", "scrolly=" + scrollY); // 0是顶部,后面那个是底部 if (scrollY == 0 || scrollY == offset) { return true; } return false; } } </span><span style="font-size: 18px;"> </span>
这个与上面那个稍有不同,我简单讲解下不同之处:
我们要排除出第一次ACTION_MOVE移动计算,因为第一次无法得知y坐标, 在MotionEvent.ACTION_DOWN中是获取不到,
哈哈,明白了吧.其实也不难,可是也花了我好些时间.看下效果图吧.
效果图:
看着勉强凑合吧,重要的是实现.
在这里我额外补充一点,不管你是否遇到过,总之掌握了对你没有害处,为之,我可是花费了2个小时才发现错误在哪里,原因:自己很土鳖.呵呵,不瞎扯了.
adapter.notifyDataSetChanged();这句话想必大家都不陌生, 作用:动态更新UI数据用的.
用法简单说明:异步或开线程对数据源BaseAdapter里面的数据进行更新.然后在主线程中执行adapter.notifyDataSetChanged();(其实就是从新执行getView方法,自己可以调试try一下.)
但是我们这里用的不是单纯的ListView,而是自定义的ScrollView包裹着ListView,而ListView和ScrollView本来是冤家,想必现在大家都明白怎么解决,就是我setAdapter();后再次对ListView布局从新布局,这样有效的解决了二者的冲突.
方法如下:(大家都看的明白,这里就不过多解释)
[java] view plaincopyprint? public void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; for (int i = 0; i < listAdapter.getCount(); i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)) + 15; listView.setLayoutParams(params); }
重点:我们在其中执行了adapter.notifyDataSetChanged();UI没有做出任何反映,这个为什么呢?
牛逼人物我想一目了然就知道哪里,但是我却花了2个小时,原因:还是原理没搞懂.
解释:我们首先解释下显示的效果原理:首先我们先把ListView show出来了,因为和ScrollView冲突然后我们又调用setListViewHeightBasedOn
效果图就不展示了,想必大家都知道是what.
上诉有一个小BUG,就是如果当数据超出一屏的话,你下拉的时候不松开,然后慢慢向上移动,你会发现跑得那是相当的快,如果想做成微信那种效果就是下拉的时候刷新,那么相当不好控制,原因其实很简单:当我们下拉的时候用的根本不是ScrollView的滑动.我们是通过对layout布局进行时时更新,此时的getScrollY()始终为0,但是你停止下拉的时候反而向上滑动的时候这个时候getScrollY()就不为0,原因为什么呢,因为我们下拉其实就是让布局向下移动了,抛开这里其实就相当于我滑动了,所以你上滑动的时候会ScrollView滑动会影响你,(自己可以测试看下log.)这就是bug原因,解决办法:我们只需要在滑动中添加:
问题解决了,不过又有一个扯淡的问题,我到底在瞎忙什么,直接用简单的布局文件不就行了,为何要用ScrollView呢,还有ScrollView这个东西和ListView冲突在加上对Touch事件的分发处理不是那么协调,NND,搞了一下午,现在头都大了.
- 很实用的关于ScrollView的操作,你知道吗?
- 你应该知道的 Swift 实用建议
- 关于 fir.im 你可能不知道的实用小工具
- scrollView ListView GridView 嵌套 你要知道的
- 你必须知道的简单实用的 Ubuntu 快捷键
- 你需要知道的实用Python函数和特性
- 金立S9你不知道的最实用安全功能
- [实用]Dev-c++中你所不知道的快捷键
- 你或许不知道的实用 GNOME Shell 快捷键
- cmd最最常用的六个操作命令,你知道吗?
- 你应该知道的Linux操作技巧
- 你所不知道的jdbc操作
- adb那些你应该知道的操作
- Vue中你不知道但却很实用的黑科技
- 你一定要知道的关于Linux文件目录操作的12个常用命令
- 你一定要知道的关于Linux文件目录操作的12个常用命令
- 关于原码,反码,补码的细节,你知道吗?
- 关于字符编码的细节,你知道吗?
- Search for a Range
- Eclipse git图文教程
- MFC消息截获之pretranslatemessage
- VC++实现程序在任务栏中闪烁提示
- varchar 时间转换
- 很实用的关于ScrollView的操作,你知道吗?
- Windows 下 tail 查看日志命令工具分享
- <<C和指针>>第1章之编程练习心得20140702
- 常用SQL语句
- 对于cocos2d-x 3.0版本与之前版本的区别分析-------------cocos2d-x3.0正式版本(7.1)
- 《TCP/IP详解 卷1:协议》读书笔记
- 文件的写入
- 正则表达式整理
- ubuntu12.10 chkconfig安装