安德鲁斯 建立与各种听众自己定义的ScrollView
=== 建立与各种听众自己定义的ScrollView ===
尽管安卓5.1已经release, 可是ScrollView的封装和对外API依然少的可怜, 尽管它优化得非常好了.
所以问题来了: ScrollView滑动方向是什么, 何时停止? 所以本文的目标出现了: 解决这些看似小, 可是用起来却非常燃眉的问题!
首先思考: 怎样知道ScrollView是否在滚动, 只是这一点请放心, SDK还是提供了这个功能, 不然SDK也太烂了. 呵呵, 找打了, 首先请继承ScrollView这个父类, 毕竟非常多东西拿来主义是没问题的.
publicclass MyScrollViewextends ScrollView {
privatefinal StringTAG =
this.getClass().getSimpleName();
publicMyScrollView(Contextcontext) {
super(context);
}
publicMyScrollView(Contextcontext,
AttributeSetattrs) {
super(context,attrs);
}
publicMyScrollView(Contextcontext,
AttributeSet attrs, int
defStyle) {
super(context,attrs,defStyle);
}
}
这个我就不多解释了, 自己定义过控件的人肯定知道这三个构造的意义, 多一嘴, 当中的第二个是给XML 来 Render的, 所以一定要写上.
来, 写上刚才说的最关键的:
@Override
protectedvoidonScrollChanged(int l,int t,int oldl,int oldt)
{
super.onScrollChanged(l,t,oldl,
oldt);
}
简单说明一下, API默认的这四个參数的意思非常隐晦, 你能够考虑改写成:
@Override
protectedvoidonScrollChanged(int currentX,int currentY,int oldx,int oldy)
{
super.onScrollChanged(currentX,currentY,oldx,oldy);
}
既然须要进行监听状态改变, 那么使用回调的设计进行监听再好只是(之所以选择抽象类而不是使用接口, 是由于这种话能够更好地让用户进行抉择, 详细须要重写哪一个, 简化代码量和降低流程使用的难度), 这里推荐使用内部类, 当然, 新建一个独立新类也是能够的:
publicabstractclass MyScrollViewListener {
publicvoidonMyScrollChanged(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
publicvoidonMyScrollStart(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
publicvoidonMyScrollStop(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
publicvoidonMyScrollTop(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
publicvoidonMyScrollBottom(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
publicvoidonMyScrollUp(MyScrollView scrollView,intx,inty,intoldx,intoldy)
{
}
publicvoidonMyScrollDown(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
}
简单解释一下, 自上而下的回调监听的意思各自是:
onMyScrollChanged: 当滚动时
onMyScrollStart: 当開始滚动时
onMyScrollStop: 当滚动停止时
onMyScrollTop: 当滚动到到顶部时
onMyScrollBottom: 当滚动究竟部时
onMyScrollUp: 当向上滚动时
onMyScrollDown: 当向下滚动时
这些都是比較经常使用的, 先写好, 我们一个一个来实现. 注意 内部类的话, 回调方法要使用 public的. 不然外部回调的发起者, 是无法重写回调方法的.
哦, 对了, 进一步集成, 方便外部发起者进行调用:
private MyScrollViewListener myScrollViewListener;
public void setMyScrollViewListener(MyScrollViewListener myScrollViewListener)
{
this.myScrollViewListener =myScrollViewListener;
}
非常easy, 一个set方法 来相应刚才的回调抽象类, 不解释了.
前方高能, 核心功能!
@Override
protected void onScrollChanged(int l,int t,int oldl,int oldt)
{
super.onScrollChanged(l,t,oldl,
oldt);
if (myScrollViewListener !=null) {
myScrollViewListener.onMyScrollChanged(this,l,t,
oldl, oldt);
}
}
好理解吧?? 这个就是我之前说的SDK自己提供的监听. 既然继承了 ScrollView, 直接重写, 先显式调用父类的方法, 然后正好, 回调一下我们的onMyScrollChanged监听, 满足题意, 当然必须有发起者才干够, 所以加了一个非空推断. 继续!
@Override
protectedvoidonScrollChanged(intl,intt,intoldl,intoldt)
{
super.onScrollChanged(l,t,oldl,
oldt);
if (myScrollViewListener !=null) {
myScrollViewListener.onMyScrollChanged(this,l,t,
oldl, oldt);
if (t -oldt > 0) {
myScrollViewListener.onMyScrollDown(this,l,t,
oldl, oldt);
Log.i(TAG,"正在向下滚动");
} else {
myScrollViewListener.onMyScrollUp(this,l,t,
oldl, oldt);
Log.i(TAG,"正在向上滚动");
}
}
}
这个也好理解吧, t 之前说过, 是 纵向的偏移量, 你能够用Log看看变化规律, 看代码事实上也能明确. 然后依据条件进行回调, OK, 完毕!
@Override
protectedvoidonScrollChanged(int l,int t,int oldl,int oldt)
{
super.onScrollChanged(l,t,oldl,
oldt);
if (myScrollViewListener !=null) {
myScrollViewListener.onMyScrollChanged(this,l,t,
oldl, oldt);
if (getScrollY() <= 0) {
myScrollViewListener.onMyScrollTop(this,l,t,
oldl, oldt);
Log.i(TAG,"到达了顶部");
}
//====================================================
View view = (View)getChildAt(getChildCount() - 1);//获取
ScrollView最后一个控件
int diff = (view.getBottom() - (getHeight()
+getScrollY()));
if (diff == 0) {
myScrollViewListener.onMyScrollBottom(this,l,t,
oldl, oldt);
Log.i(TAG,"到达了底部");
}
}
}
简单说明一下:
getScrollY能够获取ScrollView顶部位置的像素值, 详细的看API, 不赘述.
还是看图吧, 不多说了, 自己用绘图做的, 比較粗糙,可是顾名思义.
三个各自是滚动到了顶端, 滚动在中间, 滚动到了底部.
至此, 简单的功能都完毕了, 剩下一个最难的了, 立刻攻克之!
思考: 想知道何时停止的话, 能够使用类似于Observer的方式进行监听, 我第一想法是用for, 后来细致想想, Thread.sleep等方式尽量避免, UI都会卡掉. 所以当滚动開始的时候, 通过postDelayed()自身延迟自己调用自己重复复运行(安卓中比較常见的设计).
来, 看代码:
@Override
protected void onScrollChanged(int l,int t,int oldl,int oldt)
{
super.onScrollChanged(l,t,oldl,
oldt);
if (myScrollViewListener !=null) {
myScrollViewListener.onMyScrollChanged(this,l,t,
oldl, oldt);
if (!scrollerTaskRunning) {
startScrollerTask(this,l,t,
oldl, oldt);
}
}
}
private Runnable scrollerTask;
private int initialPosition;
private int newCheck = 50;
private boolean scrollerTaskRunning =false;
private void startScrollerTask(final MyPullableScrollView scrollView,final int x,final int y,final int oldx,final int oldy)
{
if (!scrollerTaskRunning) {
myScrollViewListener.onMyScrollStart(this,x,y,
oldx, oldy);
Log.i(TAG,"滚动開始");
}
scrollerTaskRunning = true;
if (scrollerTask ==null) {
scrollerTask = new
Runnable() {
public void run() {
int newPosition =getScrollY();
if (initialPosition -newPosition == 0) {
if (myScrollViewListener !=null) {
scrollerTaskRunning =false;
myScrollViewListener.onMyScrollStop(scrollView,x,y,
oldx, oldy);
Log.i(TAG,"滚动结束");
}
}
else {
startScrollerTask(scrollView,x,y,
oldx, oldy);
}
}
};
}
initialPosition = getScrollY();
postDelayed(scrollerTask,
newCheck);
}
最后解释一下,
scrollerTask 是一个启动线程的任务, initialPosition
是滚动的初始位置, newCheck 表示postDelayed的推迟频率, 用来实现自身调用的循环.scrollerTaskRunning表示滚动是否開始, 防止重复运行.
这里(还有向上向下滚动)我之前走了弯路, 使用的是网上提供的 重写 view.onTouch() 或者 view.onTouchEvent(), 在 MotionEvent.ACTION_UP 或者 ACTION_MOVE 的时候调用scrollerTask来实现, 可是这样 參数(什么oldX 之类的) 不是非常好传递, 尽管 Event 也能够简单控制,
可是我不想用太多松散的关系类, 所以直接加入 scrollerTaskRunning 这个变量来详细控制 方法内的推断时机.
推断的原理是, 不停地比較当前的currentScrollY和 上一次的 lastScrollY, 假设一样, 则 停止延迟地自身调用自身, 否则,继续延迟地自身调用自身, 再来一次比較, 以此类推. scrollerTaskRunning变量的控制要注意, 这样便能够有效地防止多次无意义的运行.
网上提供的方案非常多是在 构造函数中 初始化
scrollerTask, 可是这样回调拿不到參数, 并且必须用上面说的重写, 还须要额外的成员属性, 重复计算再使用, 麻烦繁琐. 所以我改造了代码, 就能够避免这个问题(怎么感觉语序有一些乱套… 语文渣).
好吧, 贴一下全部的代码, log 都改成了 英文, 既尊重资料提供者, 又能够避免乱码, 加入package就能够直接用了, 自己定义控件怎么用, 我就不说了, 网上有非常多, 祝你好运.
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ScrollView;
public class MyScrollView extends ScrollView {
private final StringTAG =
this.getClass().getSimpleName();
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context,
AttributeSet attrs) {
super(context,attrs);
}
publicMyScrollView(Contextcontext,
AttributeSetattrs,
intdefStyle) {
super(context,attrs,defStyle);
}
public abstract class MyScrollViewListener {
public void onMyScrollChanged(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
public void onMyScrollStart(MyScrollView scrollView,int x,inty,intoldx,intoldy)
{
}
public void onMyScrollStop(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
public void onMyScrollTop(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
public void onMyScrollBottom(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
public void onMyScrollUp(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
public void onMyScrollDown(MyScrollView scrollView,int x,int y,int oldx,int oldy)
{
}
}
private MyScrollViewListener myScrollViewListener;
public void setMyScrollViewListener(MyScrollViewListener myScrollViewListener)
{
this.myScrollViewListener =myScrollViewListener;
}
@Override
protected void onScrollChanged(intl,intt,intoldl,intoldt)
{
super.onScrollChanged(l,t,oldl,
oldt);
if (myScrollViewListener !=null) {
myScrollViewListener.onMyScrollChanged(this,l,t,
oldl, oldt);
//====================================================
if (!scrollerTaskRunning) {
startScrollerTask(this,l,t,
oldl, oldt);
}
// ====================================================
if (t -oldt > 0) {
myScrollViewListener.onMyScrollDown(this,l,t,
oldl, oldt);
Log.i(TAG,"is scrolling down");
}
else {
myScrollViewListener.onMyScrollUp(this,l,t,
oldl, oldt);
Log.i(TAG,"is scrolling up");
}
//====================================================
if (getScrollY() <= 0) {
myScrollViewListener.onMyScrollTop(this,l,t,
oldl, oldt);
Log.i(TAG,"the top has beenreached");
}
// ====================================================
Viewview = (View)getChildAt(getChildCount() - 1);//
We take the last son in thescrollview
intdiff = (view.getBottom() - (getHeight()
+getScrollY()));
if (diff == 0) {
myScrollViewListener.onMyScrollBottom(this,l,t,
oldl, oldt);
Log.i(TAG,"the bottom has beenreached");
}
}
}
private Runnable scrollerTask;
private int initialPosition;
private int newCheck = 50;
private boolean scrollerTaskRunning =false;
private void startScrollerTask(final MyScrollView scrollView,final int x,final int y,final int oldx,final int oldy)
{
if (!scrollerTaskRunning) {
myScrollViewListener.onMyScrollStart(this,x,y,
oldx, oldy);
Log.i(TAG,"scroll start");
}
scrollerTaskRunning = true;
if (scrollerTask ==null) {
scrollerTask = new
Runnable() {
public voidrun() {
int newPosition =
getScrollY();
if (initialPosition -newPosition == 0) {
if (myScrollViewListener !=null)
{
scrollerTaskRunning =false;
myScrollViewListener.onMyScrollStop(scrollView,x,y,
oldx, oldy);
Log.i(TAG,"scroll stop");
return;
}
}else {
startScrollerTask(scrollView,x,y,
oldx, oldy);
}
}
};
}
initialPosition = getScrollY();
postDelayed(scrollerTask,
newCheck);
}
}
======================================================
感谢微软必应, stackoverflow 大神和 Google大神提供的思路. 没有他们, 我, 寸步难行.
======================================================
停止滚动的代码, 參考了这个问题:
http://stackoverflow.com/questions/8181828/android-detect-when-scrollview-stops-scrolling
Presented by imknown
2015-03-19
版权声明:本文博主原创文章,博客,未经同意不得转载。
安德鲁斯 建立与各种听众自己定义的ScrollView的更多相关文章
- (Oracle)已有数据表建立表分区—在线重定义
今天在做数据抽取的时候,发现有一张业务表数据量达到了5000W,所以就想将此表改为分区表.分区表的有点如下: 1.改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索速度.2.增强可用性: ...
- [转载]C++声明和定义的区别
<C++Primer>第四版 2.3.5节中这么说到: ①变量定义:用于为变量分配存储空间,还可为变量指定初始值.程序中,变量有且仅有一个定义. ②变量声明:用于向程序表明变量的类型和名字 ...
- 声明、定义 in C++
序 声明和定义是我们使用的基础,但是对于声明和定义的概念,我们不甚了了,也就是说感觉好像是这样,但是真要详细说明就说不上来. 有博主对于声明和定义有以下描述: 1.需要建立存储空间的 ...
- C语言的声明和定义
在程序设计中,时时刻刻都用到变量的定义和变量的声明,可有些时候我们对这个概念不是很清楚,知道它是怎么用,但却不知是怎么一会事. 下面我就简单的把他们的区别介绍如下: 变量的声明有两种情况: (1)一种 ...
- C++ 变量的声明与定义的区别
变量声明和定义的区别 我们在程序设计中,时时刻刻都用到变量的定义和变量的声明,可有些时候我们对这个概念不是很清楚,知道它是怎么用,但却不知是怎么一会事,下面我就简单的把他们的区别介绍如下:(望我的指点 ...
- C/C++ 定义与声明详解(转)
转自:http://blog.csdn.net/xiaoyusmile/article/details/5420252 1. 变量的定义.声明 变量的声明有两种情况: 一种是需要建立存储空间的.例如: ...
- 谈谈个人网站的建立(二)—— lucene的使用
首先,帮忙点击一下我的网站http://www.wenzhihuai.com/ .谢谢啊,如果可以,GitHub上麻烦给个star,以后面试能讲讲这个项目,GitHub地址https://github ...
- PowerDesigner之PDM(物理概念模型)各种属性建立如PK,AK等
一.PDM概述 PDM(物理数据模型),通俗地理解,就是在PowerDesigner中以图形化的方式展示和设计数据库. PDM中涉及到的基本概念包括: 表: 列: 视图: 主键: 候选键: 外键: 存 ...
- 定义与声明、头文件与extern总结
用#include可以包含其他头文件中变量.函数的声明,为什么还要extern关键字? 如果我想引用一个全局变量或函数a,我只要直接在源文件中包含#include<xxx.h> (xxx ...
随机推荐
- 升级 树莓派的gcc/g++编译器到4.8
首先,更新你的树莓派(避免编译出来版本不对导致不能insert)sudo apt-get update sudo apt-get upgrade sudo apt-get dist-upgrade s ...
- 详细说明XML分解(两)—DOM4J
第一部分关于博客XML三接口,同时也为学习DOM4J该分析工具做准备.一般解析器基本上都实现了DOM和SAX这两组接口,DOM4J自然也不例外..DOM4J仅仅是经常使用解析器的当中一种,只是既然是实 ...
- 让你的字ScrollView、ListView充分伸展
android默认ScrollView.ListView在最顶部的下拉上拉时或底部,未与反弹效应,很僵,让你无法继续拖累,不比iOS至于能否反弹.个人觉得,iOS互动还是略胜一筹.因此,我们已经走在A ...
- cocos2dx 遮罩层 android 手机上 失败
1.CCClippingNode使用(在模拟器上ok,在手机上不行),实现多个剪切区域 local layer=CCLayerColor:create(ccc4(0,0,0,110)) --/ ...
- 文件搜索神器everything 你不知道的技巧总结
everything这个软件用了很久,总结了一些大家可能没注意到的技巧,分享给大家 1.指定文件目录搜索示例: TDDOWNLOAD\ abc 在所有TDDOWNLOAD文件夹下搜索包含 ...
- Codeforces Round #252 (Div. 2) 441B. Valera and Fruits
英语不好就是坑啊.这道题把我坑残了啊.5次WA一次被HACK.第二题得分就比第一题高10分啊. 以后一定要加强英语的学习,要不然就跪了. 题意:有一个果园里有非常多树,上面有非常多果实,为了不然成熟的 ...
- 网络视频播放器插件ckplayer使用-简介
ckplayer插件下载:http://pan.baidu.com/s/12HYH4(假设不见了,下载您自己的地址,下载后添加到站点根文件夹) ******特别提醒:解压后不要忘了把js文件夹也加入到 ...
- cocos2d-x lua 学习笔记(1) -- 环境结构
Cocos2d-x 3.0超过环境的版本号来建立和前Cocos2d-x 2.0 差异较大的版本,从同时Cocos2d-x 3.0项目打包成apkAndroid的应用程序文件,步骤,构建环境有些乏味安德 ...
- 【ASP.NET】判断访问网站的客户端是PC还是手机
原文:[ASP.NET]判断访问网站的客户端是PC还是手机 主要就是通过客户端传递的User-agent来判断访问网站的客户端是PC还是手机,.NET中就是Request.ServerVariable ...
- angularJS socket
工程Controller加载文件Service层socket.js.controller所在页面时连接socket(也可一进入项目就连接,看需求).细节还需继续优化,写下来以防忘了~ Service层 ...