Android原理——回弹ScrollView

回弹的ScrollView 网上看到的通常是ElasticScrollView, 有一个Bug:点击子控件滑动时,滑动无效, 所以针对此问题,我对ElasticScrollView做了改进。 原理图 代码 我在注释中做了详细的说明 <span class="hljs-keyword">import</span> android.content.Context; <span class="hljs-keyword">import</span> android.graphics.Rect; <span class="hljs-keyword">import</span> android.util.AttributeSet; <span class="hljs-keyword">import</span> android.view.MotionEvent; <span class="hljs-keyword">import</span> android.view.View; <span class="hljs-keyword">import</span> android.view.animation.Animation; <span class="hljs-keyword">import</span> android.view.animation.DecelerateInterpolator; <span class="hljs-keyword">import</span> android.view.animation.TranslateAnimation; <span class="hljs-keyword">import</span> android.widget.ScrollView; <span class="hljs-javadoc">/** * Created by Tindle Wei. */</span> <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ElasticScrollView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ScrollView</span> {</span> <span class="hljs-javadoc">/** * 手指抖动误差 */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> SHAKE_MOVE_VALUE = <span class="hljs-number">8</span>; <span class="hljs-javadoc">/** * Scrollview内部的view */</span> <span class="hljs-keyword">private</span> View innerView; <span class="hljs-javadoc">/** * 记录innerView最初的Y位置 */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">float</span> startY; <span class="hljs-javadoc">/** * 记录原始innerView的大小位置 */</span> <span class="hljs-keyword">private</span> Rect outRect = <span class="hljs-keyword">new</span> Rect(); <span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> animationFinish = <span class="hljs-keyword">true</span>; <span class="hljs-keyword">public</span> <span class="hljs-title">ElasticScrollView</span>(Context context) { <span class="hljs-keyword">super</span>(context); } <span class="hljs-keyword">public</span> <span class="hljs-title">ElasticScrollView</span>(Context context, AttributeSet attrs) { <span class="hljs-keyword">super</span>(context, attrs); } <span class="hljs-javadoc">/** * 继承自View * 在xml的所有布局加载完之后执行 */</span> <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFinishInflate</span>() { <span class="hljs-keyword">if</span> (getChildCount() > <span class="hljs-number">0</span>) { innerView = getChildAt(<span class="hljs-number">0</span>); } } <span class="hljs-javadoc">/** * 继承自ViewGroup * 返回true, 截取触摸事件 * 返回false, 将事件传递给onTouchEvent()和子控件的dispatchTouchEvent() */</span> <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onInterceptTouchEvent</span>(MotionEvent ev) { <span class="hljs-comment">// 判断 点击子控件 or 按住子控件滑动</span> <span class="hljs-comment">// 如果点击子控件,则返回 false, 子控件响应点击事件</span> <span class="hljs-comment">// 如果按住子控件滑动,则返回 true, 滑动布局</span> <span class="hljs-keyword">switch</span> (ev.getAction()) { <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN: { startY = ev.getY(); <span class="hljs-keyword">break</span>; } <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE: { <span class="hljs-keyword">float</span> currentY = ev.getY(); <span class="hljs-keyword">float</span> scrollY = currentY - startY; <span class="hljs-comment">// 是否返回 true</span> <span class="hljs-keyword">return</span> Math.abs(scrollY) > SHAKE_MOVE_VALUE; } } <span class="hljs-comment">// 默认返回 false</span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.onInterceptTouchEvent(ev); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent ev) { <span class="hljs-keyword">if</span> (innerView == <span class="hljs-keyword">null</span>) { <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.onTouchEvent(ev); } <span class="hljs-keyword">else</span> { myTouchEvent(ev); } <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.onTouchEvent(ev); } <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">myTouchEvent</span>(MotionEvent ev) { <span class="hljs-keyword">if</span> (animationFinish) { <span class="hljs-keyword">switch</span> (ev.getAction()) { <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN: startY = ev.getY(); <span class="hljs-keyword">super</span>.onTouchEvent(ev); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> MotionEvent.ACTION_UP: startY = <span class="hljs-number">0</span>; <span class="hljs-keyword">if</span> (isNeedAnimation()) { animation(); } <span class="hljs-keyword">super</span>.onTouchEvent(ev); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE: <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> preY = startY == <span class="hljs-number">0</span> ? ev.getY() : startY; <span class="hljs-keyword">float</span> nowY = ev.getY(); <span class="hljs-keyword">int</span> deltaY = (<span class="hljs-keyword">int</span>) (preY - nowY); startY = nowY; <span class="hljs-comment">// 当滚动到最上或者最下时就不会再滚动,这时移动布局</span> <span class="hljs-keyword">if</span> (isNeedMove()) { <span class="hljs-keyword">if</span> (outRect.isEmpty()) { <span class="hljs-comment">// 保存正常的布局位置</span> outRect.set(innerView .getLeft(), innerView.getTop(), innerView.getRight(), innerView.getBottom()); } <span class="hljs-comment">// 移动布局</span> <span class="hljs-comment">// 这里 deltaY/2 为了操作体验更好</span> innerView.layout(innerView.getLeft(), innerView.getTop() - deltaY / <span class="hljs-number">2</span>, innerView.getRight(), innerView.getBottom() - deltaY / <span class="hljs-number">2</span>); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">super</span>.onTouchEvent(ev); } <span class="hljs-keyword">break</span>; <span class="hljs-keyword">default</span>: <span class="hljs-keyword">break</span>; } } } <span class="hljs-javadoc">/** * 开启移动动画 */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">animation</span>() { TranslateAnimation ta = <span class="hljs-keyword">new</span> TranslateAnimation(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, outRect.top - innerView.getTop()); ta.setDuration(<span class="hljs-number">400</span>); <span class="hljs-comment">// 减速变化 为了用户体验更好</span> ta.setInterpolator(<span class="hljs-keyword">new</span> DecelerateInterpolator()); ta.setAnimationListener(<span class="hljs-keyword">new</span> Animation.AnimationListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationStart</span>(Animation animation) { animationFinish = <span class="hljs-keyword">false</span>; } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationRepeat</span>(Animation animation) { } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationEnd</span>(Animation animation) { innerView.clearAnimation(); <span class="hljs-comment">// 设置innerView回到正常的布局位置</span> innerView.layout(outRect.left, outRect.top, outRect.right, outRect.bottom); outRect.setEmpty(); animationFinish = <span class="hljs-keyword">true</span>; } }); innerView.startAnimation(ta); } <span class="hljs-javadoc">/** * 是否需要开启动画 */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isNeedAnimation</span>() { <span class="hljs-keyword">return</span> !outRect.isEmpty(); } <span class="hljs-javadoc">/** * 是否需要移动布局 */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isNeedMove</span>() { <span class="hljs-keyword">int</span> offset = innerView.getMeasuredHeight() - getHeight(); offset = (offset < <span class="hljs-number">0</span>) ? <span class="hljs-number">0</span>: offset; <span class="hljs-keyword">int</span> scrollY = getScrollY(); <span class="hljs-keyword">return</span> (offset == <span class="hljs-number">0</span> || scrollY == offset); } } 其他说明 下面是继承关系: ElasticScrollView extends ScrollView ScrollView extends FrameLayout FrameLayout extends ViewGroup ViewGroup extends View 解决子控件 截取滑动监听的代码在onInterceptTouchEvent() , 通过监听Y的变化,来判断是点击子控件还是上拉下拉 getMeasuredHeight()返回的是原始测量高度,与屏幕无关, getHeight()返回的是在屏幕上显示的高度。 实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别。当超出屏幕后,getMeasuredHeight()等于getHeight()加上屏幕之外没有显示的高度。 getScrollY() 返回的是滑动View显示部分的顶部 转自:http://www.hotpost.co/8699.html ...

2015年3月4日 · 3 分钟 · 天边的星星

android源码大放送(实战开发必备)

文件夹 PATH 列表 卷序列号为 000A-8F50 E:. │ javaapk.com文件列表生成工具.bat │ 使用说明.txt │ 免费下载更多源码.url │ 目录列表.txt │ ├─android web应用 │ jqmDemo_static.zip │ jqmMobileDemo-master.zip │ jqmMobileDemo1_1-master.zip │ Location1014.rar │ ├─anko │ anko服务端和客户端源码.rar │ ├─Bmob │ Bmob云后端数据库开发个失物招领的简单案例.zip │ 结合IM_SDK项目源码.zip │ ├─Button按钮 │ 动态添加RadioGroup的RadioButton.zip │ 带有进度条的button.rar │ 自定义组件实现可滑动的ToggleButton的功能..rar │ ├─Dialog对话框 │ android 自定义对话框.rar │ android-styled-dialogs 可自定义样式的dialog.zip │ Android中实现Iphone样式的AlertDialog.zip │ Android实现Windows风格的Dialog.zip │ HerilyAlertDialog完全自定义的Dialog.zip │ 仿QQ的头像选择弹出的对话框,酷似!.zip │ 基本的对话框.rar │ 自定义列表选择Dialog,适用网络请求数据.rar │ 自定义单选、多选对话框及popwindow窗口实例源码.rar │ 自定义彩色Toast.rar │ ├─dlan │ dlna库源代码包.zip │ ├─EditText输入框 │ 前面部分可以编辑后面部分不可编辑的EditText.zip │ ├─Emoji表情 │ 在项目中使用Emoji表情【源代码】.rar ...

2015年3月4日 · 10 分钟 · 天边的星星

github源码分享PinnedSectionListView:分组的listView滑动中固定组标题的实现

在很多应用中,看到这样的listview:listview滑动过程中分组标题固定在上方,当第二个组滑上来时,第一个组才跟着上滑,下一个组固定,直到该组也滑出上边缘。世上无难事只怕有心人,在github上就有人做出来了,而且效果很好(后来发现安卓自带应用中联系人应用就是这样的,估计github的作者也是仿照着联系人做出来的吧)。 先看截图: PinnedSectionListView继承自listview,众所周知listview的每个子view都是按顺序跟着滚动的,要实现联系人listview的效果还真的找不到思路。看了PinnedSectionListView之后,感觉要改造一个现有的控件,一般都是通过重绘子view来实现的。ViewGroup(ListView继承自它)重绘子view的方法是dispatchDraw。 看看PinnedSectionListView在dispatchDraw中有那些特别的处理: 1 <div class="line number2 index1 alt1"> 2 </div> <div class="line number3 index2 alt2"> 3 </div> <div class="line number4 index3 alt1"> 4 </div> <div class="line number5 index4 alt2"> 5 </div> <div class="line number6 index5 alt1"> 6 </div> <div class="line number7 index6 alt2"> 7 </div> <div class="line number8 index7 alt1"> 8 </div> <div class="line number9 index8 alt2"> 9 </div> <div class="line number10 index9 alt1"> 10 </div> <div class="line number11 index10 alt2"> 11 </div> <div class="line number12 index11 alt1"> 12 </div> <div class="line number13 index12 alt2"> 13 </div> <div class="line number14 index13 alt1"> 14 </div> <div class="line number15 index14 alt2"> 15 </div> <div class="line number16 index15 alt1"> 16 </div> <div class="line number17 index16 alt2"> 17 </div> <div class="line number18 index17 alt1"> 18 </div> <div class="line number19 index18 alt2"> 19 </div> <div class="line number20 index19 alt1"> 20 </div> <div class="line number21 index20 alt2"> 21 </div> <div class="line number22 index21 alt1"> 22 </div> <div class="line number23 index22 alt2"> 23 </div> <div class="line number24 index23 alt1"> 24 </div> <div class="line number25 index24 alt2"> 25 </div> </td> <td class="code"> <div class="container"> <div class="line number1 index0 alt2"> `@Override` </div> <div class="line number2 index1 alt1"> `protected void dispatchDraw(Canvas canvas) {` </div> <div class="line number3 index2 alt2"> ` ``super``.dispatchDraw(canvas);` </div> <div class="line number4 index3 alt1"> ` ``if` `(mPinnedSection != ``null``) {` </div> <div class="line number5 index4 alt2"> ` ``// prepare variables` </div> <div class="line number6 index5 alt1"> ` ``int pLeft = getListPaddingLeft();` </div> <div class="line number7 index6 alt2"> ` ``int pTop = getListPaddingTop();` </div> <div class="line number8 index7 alt1"> ` ``View view = mPinnedSection.view;` </div> <div class="line number9 index8 alt2"> ` ``// draw child` </div> <div class="line number10 index9 alt1"> ` ``canvas.save();` </div> <div class="line number11 index10 alt2"> ` ``int clipHeight = view.getHeight() +` </div> <div class="line number12 index11 alt1"> ` ``(mShadowDrawable == ``null` `? 0 : Math.min(mShadowHeight, mSectionsDistanceY));` </div> <div class="line number13 index12 alt2"> ` ``canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight);` </div> <div class="line number14 index13 alt1"> ` ``canvas.translate(pLeft, pTop + mTranslateY);` </div> <div class="line number15 index14 alt2"> ` ``drawChild(canvas, mPinnedSection.view, getDrawingTime());` </div> <div class="line number16 index15 alt1"> ` ``if` `(mShadowDrawable != ``null` `&& mSectionsDistanceY &gt; 0) {` </div> <div class="line number17 index16 alt2"> ` ``mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),` </div> <div class="line number18 index17 alt1"> ` ``mPinnedSection.view.getBottom(),` </div> <div class="line number19 index18 alt2"> ` ``mPinnedSection.view.getRight(),` </div> <div class="line number20 index19 alt1"> ` ``mPinnedSection.view.getBottom() + mShadowHeight);` </div> <div class="line number21 index20 alt2"> ` ``mShadowDrawable.draw(canvas);` </div> <div class="line number22 index21 alt1"> ` ``}` </div> <div class="line number23 index22 alt2"> ` ``canvas.restore();` </div> <div class="line number24 index23 alt1"> ` ``}` </div> <div class="line number25 index24 alt2"> `}` </div> </div> </td> </tr> </table> 关键在于```canvas.translate(pLeft, pTop + mTranslateY);意思是在绘制mPinnedSection的时候,listview滑动了多长的距离,就将canvas移动多少的距离,使mPinnedSection`始终在可见的范围内固定不变。 ...

2015年3月4日 · 4 分钟 · 天边的星星

Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果

转帖请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming),请尊重他人的辛勤劳动成果,谢谢! 随着移动互联网的快速发展,它已经和我们的生活息息相关了,在公交地铁里面都能看到很多人的人低头看着自己的手机屏幕,从此“低头族”一词就产生了,作为一名移动行业的开发人员,我自己也是一名“低头族”,上下班时间在公交地铁上看看新闻来打发下时间,有时候也会看看那些受欢迎的App的一些界面效果,为什么人家的app那么受欢迎?跟用户体验跟UI设计也有直接的关系,最近在美团和大众点评的App看到如下效果,我感觉用户好,很人性化,所以自己也尝试着实现了下,接下来就讲解下实现思路! 如上图(2)我们看到了,当立即抢购布局向上滑动到导航栏布局的时候,立即抢购布局就贴在导航栏布局下面,下面的其他的布局还是可以滑动,当我们向下滑动的时候,立即抢购的布局又随着往下滑动了,看似有点复杂,但是一说思路可能你就顿时恍然大悟了。 当我们向上滑动过程中,我们判断立即抢购的布局是否滑到导航栏布局下面,如果立即抢购的上面顶到了导航栏,我们新建一个立即抢购的悬浮框来显示在导航栏下面,这样子就实现了立即抢购贴在导航栏下面的效果啦,而当我们向下滑动的时候,当立即抢购布局的下面刚好到了刚刚新建的立即抢购悬浮框的下面的时候,我们就移除立即抢购悬浮框,可能说的有点拗口,既然知道了思路,接下来我们就来实现效果。 新建一个Android项目,取名MeiTuanDemo,先看立即抢购(buy_layout.xml)的布局,这里为了方便我直接从美团上面截去了图片 **[html]** [view plain](http://blog.csdn.net/xiaanming/article/details/17374599#)[copy](http://blog.csdn.net/xiaanming/article/details/17374599#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/120124)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/120124/fork) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="tag"><?</span><span class="tag-name">xml</span> <span class="attribute">version</span>=<span class="attribute-value">&#8220;1.0&#8221;</span> <span class="attribute">encoding</span>=<span class="attribute-value">&#8220;UTF-8&#8221;</span><span class="tag">?></span> - <span class="tag"><</span><span class="tag-name">LinearLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span> - <span class="attribute">android:orientation</span>=<span class="attribute-value">&#8220;horizontal&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;fill_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> <span class="tag">></span> - - <span class="tag"><</span><span class="tag-name">ImageView</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/buy_layout&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;fill_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:background</span>=<span class="attribute-value">&#8220;@drawable/buy&#8221;</span> <span class="tag">/></span> - - <span class="tag"></</span><span class="tag-name">LinearLayout</span><span class="tag">></span> 立即抢购的布局实现了,接下来实现主界面的布局,上面是导航栏布局,为了方便还是直接从美团截取的图片,然后下面的ViewPager布局,立即抢购布局,其他布局 放在ScrollView里面,界面还是很简单的 ...

2015年3月4日 · 7 分钟 · 天边的星星

Android 带你从源码的角度解析Scroller的滚动实现原理

转帖请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/17483273),请尊重他人的辛勤劳动成果,谢谢! 今天给大家讲解的是Scroller类的滚动实现原理,可能很多朋友不太了解该类是用来干嘛的,但是研究Launcher的朋友应该对他很熟悉,Scroller类是滚动的一个封装类,可以实现View的平滑滚动效果,什么是实现View的平滑滚动效果呢,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速,可以先减速后加速等等效果,而不是瞬间的移动的效果,所以Scroller可以帮我们实现很多滑动的效果。 在介绍Scroller类之前,我们先去了解View的scrollBy() 和scrollTo()方法的区别,在区分这两个方法的之前,我们要先理解View 里面的两个成员变量mScrollX, mScrollY,X轴方向的偏移量和Y轴方向的偏移量,这个是一个相对距离,相对的不是屏幕的原点,而是View的左边缘,举个通俗易懂的例子,一列火车从吉安到深圳,途中经过赣州,那么原点就是赣州,偏移量就是 负的吉安到赣州的距离,大家从getScrollX()方法中的注释中就能看出答案来 **[java]** [view plain](http://blog.csdn.net/xiaanming/article/details/17483273#)[copy](http://blog.csdn.net/xiaanming/article/details/17483273#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/129807)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/129807/fork) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="comment">/**</span> - <span class="comment"> * Return the scrolled left position of this view. This is the left edge of</span> - <span class="comment"> * the displayed part of your view. You do not need to draw any pixels</span> - <span class="comment"> * farther left, since those are outside of the frame of your view on</span> - <span class="comment"> * screen.</span> - <span class="comment"> *</span> - <span class="comment"> * @return The left edge of the displayed part of your view, in pixels.</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> getScrollX() { - <span class="keyword">return</span> mScrollX; - } 现在我们知道了向右滑动 mScrollX就为负数,向左滑动mScrollX为正数,接下来我们先来看看 scrollTo()方法的源码 ...

2015年3月4日 · 11 分钟 · 天边的星星

Android下拉刷新上拉加载控件,对所有View通用!

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38868463 前面写过一篇关于下拉刷新控件的博客下拉刷新控件终结者:PullToRefreshLayout,后来看到好多人还有上拉加载更多的需求,于是就在前面下拉刷新控件的基础上进行了改进,加了上拉加载的功能。不仅如此,我已经把它改成了对所有View都通用!可以随心所欲使用这两个功能~~ 我做了一个大集合的demo,实现了ListView、GridView、ExpandableListView、ScrollView、WebView、ImageView、TextView的下拉刷新和上拉加载。后面会提供demo的下载地址。(csdn上的demo有小bug,最新代码已上传到github:https://github.com/jingchenUSTC/PullToRefreshAndLoad) 依照惯例,下面将会是一大波效果图: demo首页也是可下拉的ListView,在底下可以加入table: ListView: GridView: ExpandableListView: ScrollView: WebView: ImageView: TextView: 很不错吧?最后的ImageView和TextView是最简单的,直接在下面的接口方法里返回true。 增加上拉加载很简单,和管理下拉头一样,再多管理一个上拉头,也不费事;至于把它改成通用的就需要统一一下View的行为了,为此,我定义了这样一个接口: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38868463#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38868463#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.pulltorefresh.pullableview; - - <span class="keyword">public</span> <span class="keyword">interface</span> Pullable - { - <span class="comment">/**</span> - <span class="comment"> * 判断是否可以下拉,如果不需要下拉功能可以直接return false</span> - <span class="comment"> * </span> - <span class="comment"> * @return true如果可以下拉否则返回false</span> - <span class="comment"> */</span> - <span class="keyword">boolean</span> canPullDown(); - - <span class="comment">/**</span> - <span class="comment"> * 判断是否可以上拉,如果不需要上拉功能可以直接return false</span> - <span class="comment"> * </span> - <span class="comment"> * @return true如果可以上拉否则返回false</span> - <span class="comment"> */</span> - <span class="keyword">boolean</span> canPullUp(); - } 从接口名就可以看出它是一个提供判断是否可拉的方法的接口。这个接口的两个方法,canPullDown()是判断何时可以下拉的方法,canPullUp()则是判断何时可以上拉,我在demo中的判断是滑到顶部的时候可以下拉,滑到底部的时候可以上拉。所有需要上拉和下拉的View都需要实现这个接口。后面会给出一些View的实现。先来看看改进后的自定义的布局PullToRefreshLayout,增加了一个上拉头,下拉头和上拉头之间的View是实现了Pullable接口的pullableView。相比前面的版本,这里有改动的需要注意的地方如下: ...

2015年3月4日 · 17 分钟 · 天边的星星

Android自定义控件实战——仿多看阅读平移翻页

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38728119 之前自己做的一个APP需要用到翻页阅读,网上看过立体翻页效果,不过bug太多了还不兼容。看了一下多看阅读翻页是采用平移翻页的,于是就仿写了一个平移翻页的控件。效果如下: 在翻页时页面右边缘绘制了阴影,效果还不错。要实现这种平移翻页控件并不难,只需要定义一个布局管理页面就可以了。具体实现上有以下难点: 1、循环翻页,页面的重复利用。 2、在翻页时过滤掉多点触碰。 3、采用setAdapter的方式设置页面布局和数据。 下面就来一一解决这几个难点。首先看循环翻页问题,怎么样能采用较少的页面实现这种翻页呢?由于屏幕上每次只能显示一张完整的页面,翻过去的页面也看不到,所以可以把翻过去的页面拿来重复利用,不必每次都new一个页面,所以,我只用了三张页面实现循环翻页。要想重复利用页面,首先要知道页面在布局中序号和对应的层次关系,比如一个父控件的子view的序号越大就位于越上层。循环利用页面的原理图如下: 向右翻页时状态图是这样的,只用了0、1、2三张页面,页面序号为2的位于最上层,我把它隐藏在左边,所以看到的只有页面1,页面0在1下面挡着也看不到,向右翻页时,页面2被滑到屏幕中,这时候把页面0的内容替换成页面2的前一页内容,把它放到之前页面2的位置,这时,状态又回到了初始状态,又可以继续向右翻页了! 向左翻页时是这样的,初始状态还是一样,当页面1被往左翻过时,看到的是页面0,这时候页面0下面已经没有页面了,而页面2已经用不到了,这时候把页面2放到页面0下面,这时候状态又回到了初始状态,就可以继续往左翻页了。 类似于这种循环效果的实现我一直用的解决方案都是将选中的置于最中间,比如原理图中的页面1,每次翻页完成后可见的都是页面1。在滚动选择器PickerView中也是同样的方案。这就解决了页面的重复利用问题了。 解决难点2 翻页时过滤多点触碰这个问题在仿淘宝商品浏览界面中已经解决过了,就是用一个控制变量mEvents过滤掉pointer down或up后到来的第一个move事件。 解决难点3 采用adapter方式设置页面的布局和数据。这个在Android的AdapterView里用到的,但是我没有看它的adapter机制,太复杂了,我就搞了个简单的adapter,如下: PageAdapter.java: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38728119#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38728119#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.pagerdemo; - - <span class="keyword">import</span> android.view.View; - - <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> PageAdapter - { - <span class="comment">/**</span> - <span class="comment"> * @return 页面view</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">abstract</span> View getView(); - - <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">int</span> getCount(); - - <span class="comment">/**</span> - <span class="comment"> * 将内容添加到view中</span> - <span class="comment"> * </span> - <span class="comment"> * @param view</span> - <span class="comment"> * 包含内容的view</span> - <span class="comment"> * @param position</span> - <span class="comment"> * 第position页</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> addContent(View view, <span class="keyword">int</span> position); - } 这是一个抽象类,getView()用于返回页面的布局,getCount()返回数据总共需要多少页,addContent(View view, int position)这个是每翻过一页后将会被调用来请求页面数据的,参数view就是页面,position是表明第几页。待会儿会在自定义布局中定义setAdapter方法设置设配器。 ...

2015年3月4日 · 13 分钟 · 天边的星星

Android自定义控件实战——水流波动效果的实现WaveView

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38556891 水流波动的波形都是三角波,曲线是正余弦曲线,但是Android中没有提供绘制正余弦曲线的API,好在Path类有个绘制贝塞尔曲线的方法quadTo,绘制出来的是2阶的贝塞尔曲线,要想实现波动效果,只能用它来绘制Path曲线。待会儿再讲解2阶的贝塞尔曲线是怎么回事,先来看实现的效果: 这个波长比较短,还看不到起伏,只是荡漾,把波长拉长再看一下: 已经可以看到起伏很明显了,再拉长看一下: 这个的起伏感就比较强了。利用这个波动效果,可以用在绘制水位线的时候使用到,还可以做一个波动的进度条WaveUpProgress,比如这样: 是不是很动感? 那这样的波动效果是怎么做的呢?前面讲到的贝塞尔曲线到底是什么呢?下面一一讲解。想要用好贝塞尔曲线就得先理解它的表达式,为了形象描述,我从网上盗了些动图。 首先看1阶贝塞尔曲线的表达式: 随着t的变化,它实际是一条P0到P1的直线段: Android中Path的quadTo是3点的2阶贝塞尔曲线,那么2阶的表达式是这样的: 看起来很复杂,我把它拆分开来看: 然后再合并成这样: 看到什么了吧?如果看不出来再替换成这样: B0和B1分别是P0到P1和P1到P2的1阶贝塞尔曲线。而2阶贝塞尔曲线B就是B0到B1的1阶贝塞尔曲线。显然,它的动态图表示出来就不难理解了: 红色点的运动轨迹就是B的轨迹,这就是2阶贝塞尔曲线了。当P1位于P0和P2的垂直平分线上时,B就是开口向上或向下的抛物线了。而在WaveView中就是用的开口向上和向下的抛物线模拟水波。在Android里用Path的方法,首先path.moveTo(P0),然后path.quadTo(P1, P2),canvas.drawPath(path, paint)曲线就出来了,如果想要绘制多个贝塞尔曲线就不断的quadTo吧。 讲完贝塞尔曲线后就要开始讲水波动的效果是怎么来的了,首先要理解,机械波的传输就是通过介质的震动把波形往传输方向平移,每震动一个周期波形刚好平移一个波长,所有介质点又回到一个周期前的状态。所以要实现水波动效果只需要把波形平移就可以了。 那么WaveView的实现原理是这样的: 首先在View上根据View宽计算可以容纳几个完整波形,不够一个的算一个,然后在View的不可见处预留一个完整的波形;然后波动开始的时候将所有点同时在x方向上移动相同的距离,这样隐藏的波形就会被平移出来,当平移距离达到一个波长时,这时候将所有点的x坐标又恢复到平移前的值,这样就可以一个波形一个波形地往外传输。用草图表示如下: WaveView的原理在上图很直观的看出来了,P[2n+1],n>=0都是贝塞尔曲线的控制点,红线为水位线。 知道原理以后可以看代码了: WaveView.java: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38556891#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38556891#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.waveview; - - <span class="keyword">import</span> java.util.ArrayList; - <span class="keyword">import</span> java.util.List; - <span class="keyword">import</span> java.util.Timer; - <span class="keyword">import</span> java.util.TimerTask; - - <span class="keyword">import</span> android.content.Context; - <span class="keyword">import</span> android.graphics.Canvas; - <span class="keyword">import</span> android.graphics.Color; - <span class="keyword">import</span> android.graphics.Paint; - <span class="keyword">import</span> android.graphics.Paint.Align; - <span class="keyword">import</span> android.graphics.Paint.Style; - <span class="keyword">import</span> android.graphics.Region.Op; - <span class="keyword">import</span> android.graphics.Path; - <span class="keyword">import</span> android.graphics.RectF; - <span class="keyword">import</span> android.os.Handler; - <span class="keyword">import</span> android.os.Message; - <span class="keyword">import</span> android.util.AttributeSet; - <span class="keyword">import</span> android.view.View; - - <span class="comment">/**</span> - <span class="comment"> * 水流波动控件</span> - <span class="comment"> * </span> - <span class="comment"> * @author chenjing</span> - <span class="comment"> * </span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> WaveView <span class="keyword">extends</span> View - { - - <span class="keyword">private</span> <span class="keyword">int</span> mViewWidth; - <span class="keyword">private</span> <span class="keyword">int</span> mViewHeight; - - <span class="comment">/**</span> - <span class="comment"> * 水位线</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mLevelLine; - - <span class="comment">/**</span> - <span class="comment"> * 波浪起伏幅度</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mWaveHeight = <span class="number">80</span>; - <span class="comment">/**</span> - <span class="comment"> * 波长</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mWaveWidth = <span class="number">200</span>; - <span class="comment">/**</span> - <span class="comment"> * 被隐藏的最左边的波形</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mLeftSide; - - <span class="keyword">private</span> <span class="keyword">float</span> mMoveLen; - <span class="comment">/**</span> - <span class="comment"> * 水波平移速度</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">float</span> SPEED = <span class="number">1</span>.7f; - - <span class="keyword">private</span> List<Point> mPointsList; - <span class="keyword">private</span> Paint mPaint; - <span class="keyword">private</span> Paint mTextPaint; - <span class="keyword">private</span> Path mWavePath; - <span class="keyword">private</span> <span class="keyword">boolean</span> isMeasured = <span class="keyword">false</span>; - - <span class="keyword">private</span> Timer timer; - <span class="keyword">private</span> MyTimerTask mTask; - Handler updateHandler = <span class="keyword">new</span> Handler() - { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> handleMessage(Message msg) - { - <span class="comment">// 记录平移总位移</span> - mMoveLen += SPEED; - <span class="comment">// 水位上升</span> - mLevelLine -= <span class="number"></span>.1f; - <span class="keyword">if</span> (mLevelLine < <span class="number"></span>) - mLevelLine = <span class="number"></span>; - mLeftSide += SPEED; - <span class="comment">// 波形平移</span> - <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < mPointsList.size(); i++) - { - mPointsList.get(i).setX(mPointsList.get(i).getX() + SPEED); - <span class="keyword">switch</span> (i % <span class="number">4</span>) - { - <span class="keyword">case</span> <span class="number"></span>: - <span class="keyword">case</span> <span class="number">2</span>: - mPointsList.get(i).setY(mLevelLine); - <span class="keyword">break</span>; - <span class="keyword">case</span> <span class="number">1</span>: - mPointsList.get(i).setY(mLevelLine + mWaveHeight); - <span class="keyword">break</span>; - <span class="keyword">case</span> <span class="number">3</span>: - mPointsList.get(i).setY(mLevelLine &#8211; mWaveHeight); - <span class="keyword">break</span>; - } - } - <span class="keyword">if</span> (mMoveLen >= mWaveWidth) - { - <span class="comment">// 波形平移超过一个完整波形后复位</span> - mMoveLen = <span class="number"></span>; - resetPoints(); - } - invalidate(); - } - - }; - - <span class="comment">/**</span> - <span class="comment"> * 所有点的x坐标都还原到初始状态,也就是一个周期前的状态</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> resetPoints() - { - mLeftSide = -mWaveWidth; - <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < mPointsList.size(); i++) - { - mPointsList.get(i).setX(i * mWaveWidth / <span class="number">4</span> &#8211; mWaveWidth); - } - } - - <span class="keyword">public</span> WaveView(Context context) - { - <span class="keyword">super</span>(context); - init(); - } - - <span class="keyword">public</span> WaveView(Context context, AttributeSet attrs) - { - <span class="keyword">super</span>(context, attrs); - init(); - } - - <span class="keyword">public</span> WaveView(Context context, AttributeSet attrs, <span class="keyword">int</span> defStyle) - { - <span class="keyword">super</span>(context, attrs, defStyle); - init(); - } - - <span class="keyword">private</span> <span class="keyword">void</span> init() - { - mPointsList = <span class="keyword">new</span> ArrayList<Point>(); - timer = <span class="keyword">new</span> Timer(); - - mPaint = <span class="keyword">new</span> Paint(); - mPaint.setAntiAlias(<span class="keyword">true</span>); - mPaint.setStyle(Style.FILL); - mPaint.setColor(Color.BLUE); - - mTextPaint = <span class="keyword">new</span> Paint(); - mTextPaint.setColor(Color.WHITE); - mTextPaint.setTextAlign(Align.CENTER); - mTextPaint.setTextSize(<span class="number">30</span>); - - mWavePath = <span class="keyword">new</span> Path(); - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onWindowFocusChanged(<span class="keyword">boolean</span> hasWindowFocus) - { - <span class="keyword">super</span>.onWindowFocusChanged(hasWindowFocus); - <span class="comment">// 开始波动</span> - start(); - } - - <span class="keyword">private</span> <span class="keyword">void</span> start() - { - <span class="keyword">if</span> (mTask != <span class="keyword">null</span>) - { - mTask.cancel(); - mTask = <span class="keyword">null</span>; - } - mTask = <span class="keyword">new</span> MyTimerTask(updateHandler); - timer.schedule(mTask, <span class="number"></span>, <span class="number">10</span>); - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) - { - <span class="keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec); - <span class="keyword">if</span> (!isMeasured) - { - isMeasured = <span class="keyword">true</span>; - mViewHeight = getMeasuredHeight(); - mViewWidth = getMeasuredWidth(); - <span class="comment">// 水位线从最底下开始上升</span> - mLevelLine = mViewHeight; - <span class="comment">// 根据View宽度计算波形峰值</span> - mWaveHeight = mViewWidth / <span class="number">2</span>.5f; - <span class="comment">// 波长等于四倍View宽度也就是View中只能看到四分之一个波形,这样可以使起伏更明显</span> - mWaveWidth = mViewWidth * <span class="number">4</span>; - <span class="comment">// 左边隐藏的距离预留一个波形</span> - mLeftSide = -mWaveWidth; - <span class="comment">// 这里计算在可见的View宽度中能容纳几个波形,注意n上取整</span> - <span class="keyword">int</span> n = (<span class="keyword">int</span>) Math.round(mViewWidth / mWaveWidth + <span class="number">0.5</span>); - <span class="comment">// n个波形需要4n+1个点,但是我们要预留一个波形在左边隐藏区域,所以需要4n+5个点</span> - <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < (<span class="number">4</span> * n + <span class="number">5</span>); i++) - { - <span class="comment">// 从P0开始初始化到P4n+4,总共4n+5个点</span> - <span class="keyword">float</span> x = i * mWaveWidth / <span class="number">4</span> &#8211; mWaveWidth; - <span class="keyword">float</span> y = <span class="number"></span>; - <span class="keyword">switch</span> (i % <span class="number">4</span>) - { - <span class="keyword">case</span> <span class="number"></span>: - <span class="keyword">case</span> <span class="number">2</span>: - <span class="comment">// 零点位于水位线上</span> - y = mLevelLine; - <span class="keyword">break</span>; - <span class="keyword">case</span> <span class="number">1</span>: - <span class="comment">// 往下波动的控制点</span> - y = mLevelLine + mWaveHeight; - <span class="keyword">break</span>; - <span class="keyword">case</span> <span class="number">3</span>: - <span class="comment">// 往上波动的控制点</span> - y = mLevelLine &#8211; mWaveHeight; - <span class="keyword">break</span>; - } - mPointsList.add(<span class="keyword">new</span> Point(x, y)); - } - } - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onDraw(Canvas canvas) - { - - mWavePath.reset(); - <span class="keyword">int</span> i = <span class="number"></span>; - mWavePath.moveTo(mPointsList.get(<span class="number"></span>).getX(), mPointsList.get(<span class="number"></span>).getY()); - <span class="keyword">for</span> (; i < mPointsList.size() &#8211; <span class="number">2</span>; i = i + <span class="number">2</span>) - { - mWavePath.quadTo(mPointsList.get(i + <span class="number">1</span>).getX(), - mPointsList.get(i + <span class="number">1</span>).getY(), mPointsList.get(i + <span class="number">2</span>) - .getX(), mPointsList.get(i + <span class="number">2</span>).getY()); - } - mWavePath.lineTo(mPointsList.get(i).getX(), mViewHeight); - mWavePath.lineTo(mLeftSide, mViewHeight); - mWavePath.close(); - - <span class="comment">// mPaint的Style是FILL,会填充整个Path区域</span> - canvas.drawPath(mWavePath, mPaint); - <span class="comment">// 绘制百分比</span> - canvas.drawText(<span class="string">&#8220;&#8221;</span> + ((<span class="keyword">int</span>) ((<span class="number">1</span> &#8211; mLevelLine / mViewHeight) * <span class="number">100</span>)) - + <span class="string">&#8220;%&#8221;</span>, mViewWidth / <span class="number">2</span>, mLevelLine + mWaveHeight - + (mViewHeight &#8211; mLevelLine &#8211; mWaveHeight) / <span class="number">2</span>, mTextPaint); - } - - <span class="keyword">class</span> MyTimerTask <span class="keyword">extends</span> TimerTask - { - Handler handler; - - <span class="keyword">public</span> MyTimerTask(Handler handler) - { - <span class="keyword">this</span>.handler = handler; - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> run() - { - handler.sendMessage(handler.obtainMessage()); - } - - } - - <span class="keyword">class</span> Point - { - <span class="keyword">private</span> <span class="keyword">float</span> x; - <span class="keyword">private</span> <span class="keyword">float</span> y; - - <span class="keyword">public</span> <span class="keyword">float</span> getX() - { - <span class="keyword">return</span> x; - } - - <span class="keyword">public</span> <span class="keyword">void</span> setX(<span class="keyword">float</span> x) - { - <span class="keyword">this</span>.x = x; - } - - <span class="keyword">public</span> <span class="keyword">float</span> getY() - { - <span class="keyword">return</span> y; - } - - <span class="keyword">public</span> <span class="keyword">void</span> setY(<span class="keyword">float</span> y) - { - <span class="keyword">this</span>.y = y; - } - - <span class="keyword">public</span> Point(<span class="keyword">float</span> x, <span class="keyword">float</span> y) - { - <span class="keyword">this</span>.x = x; - <span class="keyword">this</span>.y = y; - } - - } - - } 代码中注释写的很多,不难看懂。 ...

2015年3月4日 · 7 分钟 · 天边的星星

Android自定义控件实战——仿淘宝商品浏览界面

转载出处http://blog.csdn.net/zhongkejingwang/article/details/38656929 用手机淘宝浏览商品详情时,商品图片是放在后面的,在第一个ScrollView滚动到最底下时会有提示,继续拖动才能浏览图片。仿照这个效果写一个出来并不难,只要定义一个Layout管理两个ScrollView就行了,当第一个ScrollView滑到底部时,再次向上滑动进入第二个ScrollView。效果如下: 需要注意的地方是: 1、如果是手动滑到底部需要再次按下才能继续往下滑,自动滚动到底部则不需要 2、在由上一个ScrollView滑动到下一个ScrollView的过程中多只手指相继拖动也不会导致布局的剧变,也就是多个pointer的滑动不会导致move距离的剧变。 这个Layout的实现思路是: 在布局中放置两个ScrollView,并为其设置OnTouchListener,时刻判断ScrollView的滚动距离,一旦第一个ScrollView滚动到底部,则标识改为可向上拖动,此时开始记录滑动距离mMoveLen,根据mMoveLen重新layout两个ScrollView;同理,监听第二个ScrollView是否滚动到顶部,以往下拖动。 OK,明白了原理之后可以看代码了: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38656929#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38656929#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.tbviewer; - - <span class="keyword">import</span> java.util.Timer; - <span class="keyword">import</span> java.util.TimerTask; - - <span class="keyword">import</span> android.content.Context; - <span class="keyword">import</span> android.os.Handler; - <span class="keyword">import</span> android.os.Message; - <span class="keyword">import</span> android.util.AttributeSet; - <span class="keyword">import</span> android.view.MotionEvent; - <span class="keyword">import</span> android.view.VelocityTracker; - <span class="keyword">import</span> android.view.View; - <span class="keyword">import</span> android.widget.RelativeLayout; - <span class="keyword">import</span> android.widget.ScrollView; - - <span class="comment">/**</span> - <span class="comment"> * 包含两个ScrollView的容器</span> - <span class="comment"> * </span> - <span class="comment"> * @author chenjing</span> - <span class="comment"> * </span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> ScrollViewContainer <span class="keyword">extends</span> RelativeLayout { - - <span class="comment">/**</span> - <span class="comment"> * 自动上滑</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> AUTO_UP = <span class="number"></span>; - <span class="comment">/**</span> - <span class="comment"> * 自动下滑</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> AUTO_DOWN = <span class="number">1</span>; - <span class="comment">/**</span> - <span class="comment"> * 动画完成</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DONE = <span class="number">2</span>; - <span class="comment">/**</span> - <span class="comment"> * 动画速度</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">float</span> SPEED = <span class="number">6</span>.5f; - - <span class="keyword">private</span> <span class="keyword">boolean</span> isMeasured = <span class="keyword">false</span>; - - <span class="comment">/**</span> - <span class="comment"> * 用于计算手滑动的速度</span> - <span class="comment"> */</span> - <span class="keyword">private</span> VelocityTracker vt; - - <span class="keyword">private</span> <span class="keyword">int</span> mViewHeight; - <span class="keyword">private</span> <span class="keyword">int</span> mViewWidth; - - <span class="keyword">private</span> View topView; - <span class="keyword">private</span> View bottomView; - - <span class="keyword">private</span> <span class="keyword">boolean</span> canPullDown; - <span class="keyword">private</span> <span class="keyword">boolean</span> canPullUp; - <span class="keyword">private</span> <span class="keyword">int</span> state = DONE; - - <span class="comment">/**</span> - <span class="comment"> * 记录当前展示的是哪个view,0是topView,1是bottomView</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mCurrentViewIndex = <span class="number"></span>; - <span class="comment">/**</span> - <span class="comment"> * 手滑动距离,这个是控制布局的主要变量</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mMoveLen; - <span class="keyword">private</span> MyTimer mTimer; - <span class="keyword">private</span> <span class="keyword">float</span> mLastY; - <span class="comment">/**</span> - <span class="comment"> * 用于控制是否变动布局的另一个条件,mEvents==0时布局可以拖拽了,mEvents==-1时可以舍弃将要到来的第一个move事件,</span> - <span class="comment"> * 这点是去除多点拖动剧变的关键</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mEvents; - - <span class="keyword">private</span> Handler handler = <span class="keyword">new</span> Handler() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> handleMessage(Message msg) { - <span class="keyword">if</span> (mMoveLen != <span class="number"></span>) { - <span class="keyword">if</span> (state == AUTO_UP) { - mMoveLen -= SPEED; - <span class="keyword">if</span> (mMoveLen <= -mViewHeight) { - mMoveLen = -mViewHeight; - state = DONE; - mCurrentViewIndex = <span class="number">1</span>; - } - } <span class="keyword">else</span> <span class="keyword">if</span> (state == AUTO_DOWN) { - mMoveLen += SPEED; - <span class="keyword">if</span> (mMoveLen >= <span class="number"></span>) { - mMoveLen = <span class="number"></span>; - state = DONE; - mCurrentViewIndex = <span class="number"></span>; - } - } <span class="keyword">else</span> { - mTimer.cancel(); - } - } - requestLayout(); - } - - }; - - <span class="keyword">public</span> ScrollViewContainer(Context context) { - <span class="keyword">super</span>(context); - init(); - } - - <span class="keyword">public</span> ScrollViewContainer(Context context, AttributeSet attrs) { - <span class="keyword">super</span>(context, attrs); - init(); - } - - <span class="keyword">public</span> ScrollViewContainer(Context context, AttributeSet attrs, <span class="keyword">int</span> defStyle) { - <span class="keyword">super</span>(context, attrs, defStyle); - init(); - } - - <span class="keyword">private</span> <span class="keyword">void</span> init() { - mTimer = <span class="keyword">new</span> MyTimer(handler); - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> dispatchTouchEvent(MotionEvent ev) { - <span class="keyword">switch</span> (ev.getActionMasked()) { - <span class="keyword">case</span> MotionEvent.ACTION_DOWN: - <span class="keyword">if</span> (vt == <span class="keyword">null</span>) - vt = VelocityTracker.obtain(); - <span class="keyword">else</span> - vt.clear(); - mLastY = ev.getY(); - vt.addMovement(ev); - mEvents = <span class="number"></span>; - <span class="keyword">break</span>; - <span class="keyword">case</span> MotionEvent.ACTION_POINTER_DOWN: - <span class="keyword">case</span> MotionEvent.ACTION_POINTER_UP: - <span class="comment">// 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug</span> - <span class="keyword">break</span>; - <span class="keyword">case</span> MotionEvent.ACTION_MOVE: - vt.addMovement(ev); - <span class="keyword">if</span> (canPullUp && mCurrentViewIndex == <span class="number"></span> && mEvents == <span class="number"></span>) { - <span class="comment">// 防止上下越界</span> - <span class="keyword">if</span> (mMoveLen > <span class="number"></span>) { - mMoveLen = <span class="number"></span>; - mCurrentViewIndex = <span class="number"></span>; - } <span class="keyword">else</span> <span class="keyword">if</span> (mMoveLen < -mViewHeight) { - mMoveLen = -mViewHeight; - mCurrentViewIndex = <span class="number">1</span>; - - } - <span class="comment">// 防止事件冲突</span> - ev.setAction(MotionEvent.ACTION_CANCEL); - } - } <span class="keyword">else</span> <span class="keyword">if</span> (canPullDown && mCurrentViewIndex == <span class="number">1</span> && mEvents == <span class="number"></span>) { - <span class="comment">// 防止上下越界</span> - <span class="keyword">if</span> (mMoveLen < -mViewHeight) { - mMoveLen = -mViewHeight; - mCurrentViewIndex = <span class="number">1</span>; - } <span class="keyword">else</span> <span class="keyword">if</span> (mMoveLen > <span class="number"></span>) { - mMoveLen = <span class="number"></span>; - mCurrentViewIndex = <span class="number"></span>; - } - <span class="comment">// 防止事件冲突</span> - ev.setAction(MotionEvent.ACTION_CANCEL); - } - } <span class="keyword">else</span> - mEvents++; - mLastY = ev.getY(); - requestLayout(); - <span class="keyword">break</span>; - <span class="keyword">case</span> MotionEvent.ACTION_UP: - mLastY = ev.getY(); - vt.addMovement(ev); - vt.computeCurrentVelocity(<span class="number">700</span>); - <span class="comment">// 获取Y方向的速度</span> - <span class="keyword">float</span> mYV = vt.getYVelocity(); - <span class="keyword">if</span> (mMoveLen == <span class="number"></span> || mMoveLen == -mViewHeight) - <span class="keyword">break</span>; - <span class="keyword">if</span> (Math.abs(mYV) < <span class="number">500</span>) { - <span class="comment">// 速度小于一定值的时候当作静止释放,这时候两个View往哪移动取决于滑动的距离</span> - <span class="keyword">if</span> (mMoveLen <= -mViewHeight / <span class="number">2</span>) { - state = AUTO_UP; - } <span class="keyword">else</span> <span class="keyword">if</span> (mMoveLen > -mViewHeight / <span class="number">2</span>) { - state = AUTO_DOWN; - } - } <span class="keyword">else</span> { - <span class="comment">// 抬起手指时速度方向决定两个View往哪移动</span> - <span class="keyword">if</span> (mYV < <span class="number"></span>) - state = AUTO_UP; - <span class="keyword">else</span> - state = AUTO_DOWN; - } - mTimer.schedule(<span class="number">2</span>); - <span class="keyword">try</span> { - vt.recycle(); - } <span class="keyword">catch</span> (Exception e) { - e.printStackTrace(); - } - <span class="keyword">break</span>; - - } - <span class="keyword">super</span>.dispatchTouchEvent(ev); - <span class="keyword">return</span> <span class="keyword">true</span>; - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onLayout(<span class="keyword">boolean</span> changed, <span class="keyword">int</span> l, <span class="keyword">int</span> t, <span class="keyword">int</span> r, <span class="keyword">int</span> b) { - topView.layout(<span class="number"></span>, (<span class="keyword">int</span>) mMoveLen, mViewWidth, - topView.getMeasuredHeight() + (<span class="keyword">int</span>) mMoveLen); - bottomView.layout(<span class="number"></span>, topView.getMeasuredHeight() + (<span class="keyword">int</span>) mMoveLen, - mViewWidth, topView.getMeasuredHeight() + (<span class="keyword">int</span>) mMoveLen - + bottomView.getMeasuredHeight()); - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) { - <span class="keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec); - <span class="keyword">if</span> (!isMeasured) { - isMeasured = <span class="keyword">true</span>; - - mViewHeight = getMeasuredHeight(); - mViewWidth = getMeasuredWidth(); - - topView = getChildAt(<span class="number"></span>); - bottomView = getChildAt(<span class="number">1</span>); - - bottomView.setOnTouchListener(bottomViewTouchListener); - topView.setOnTouchListener(topViewTouchListener); - } - } - - <span class="keyword">private</span> OnTouchListener topViewTouchListener = <span class="keyword">new</span> OnTouchListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> onTouch(View v, MotionEvent event) { - ScrollView sv = (ScrollView) v; - .getMeasuredHeight()) && mCurrentViewIndex == <span class="number"></span>) - canPullUp = <span class="keyword">true</span>; - <span class="keyword">else</span> - canPullUp = <span class="keyword">false</span>; - <span class="keyword">return</span> <span class="keyword">false</span>; - } - }; - <span class="keyword">private</span> OnTouchListener bottomViewTouchListener = <span class="keyword">new</span> OnTouchListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> onTouch(View v, MotionEvent event) { - ScrollView sv = (ScrollView) v; - <span class="keyword">if</span> (sv.getScrollY() == <span class="number"></span> && mCurrentViewIndex == <span class="number">1</span>) - canPullDown = <span class="keyword">true</span>; - <span class="keyword">else</span> - canPullDown = <span class="keyword">false</span>; - <span class="keyword">return</span> <span class="keyword">false</span>; - } - }; - - <span class="keyword">class</span> MyTimer { - <span class="keyword">private</span> Handler handler; - <span class="keyword">private</span> Timer timer; - <span class="keyword">private</span> MyTask mTask; - - <span class="keyword">public</span> MyTimer(Handler handler) { - <span class="keyword">this</span>.handler = handler; - timer = <span class="keyword">new</span> Timer(); - } - - <span class="keyword">public</span> <span class="keyword">void</span> schedule(<span class="keyword">long</span> period) { - <span class="keyword">if</span> (mTask != <span class="keyword">null</span>) { - mTask.cancel(); - mTask = <span class="keyword">null</span>; - } - mTask = <span class="keyword">new</span> MyTask(handler); - timer.schedule(mTask, <span class="number"></span>, period); - } - - <span class="keyword">public</span> <span class="keyword">void</span> cancel() { - <span class="keyword">if</span> (mTask != <span class="keyword">null</span>) { - mTask.cancel(); - mTask = <span class="keyword">null</span>; - } - } - - <span class="keyword">class</span> MyTask <span class="keyword">extends</span> TimerTask { - <span class="keyword">private</span> Handler handler; - - <span class="keyword">public</span> MyTask(Handler handler) { - <span class="keyword">this</span>.handler = handler; - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> run() { - handler.obtainMessage().sendToTarget(); - } - - } - } - - } 注释写的很清楚了,有几个关键点需要讲一下: ...

2015年3月4日 · 9 分钟 · 天边的星星

探索Android 滑动置顶标题的实现

滑动置顶标题在QQ中的好友列表、Android的通讯录中可以见到,用户在滑动的时候可以随时看到当前的内容的标题。 这个效果的实现,思路是在Android的Listview的顶部动态的生成一个与标题等高等宽的View,并且实时监听Listview滑动的状态,当Listview滑动到第一项和第二项的标题不一致的时候,动态的重画View,让它有一种向上滑动的效果,当完全滑出以后再重画生成新的标题。 &nbsp; 话不多说,上干货 - 下载:http://www.etongwl.com/images/2015/03/b69c13c0-75c3-3160-97c8-8691ddfe9501.zip 转自:http://ygydaiaq-gmail-com.iteye.com/blog/1778844

2015年3月4日 · 1 分钟 · 天边的星星