安卓 搭建带有多种监听自定义ScrollView

=== 搭建带有多种监听自定义ScrollView ===

虽然安卓5.1已经release, 但是ScrollView的封装和对外API依旧少的可怜, 虽然它优化得很好了.

所以问题来了: ScrollView滑动方向是什么, 何时停止? 所以本文的目标出现了: 解决这些看似小, 但是用起来却很燃眉的问题!

首先思考: 如何知道ScrollView是否在滚动, 不过这一点请放心, SDK还是提供了这个功能, 不然SDK也太烂了. 呵呵, 找打了, 首先请继承ScrollView这个父类, 毕竟很多东西拿来主义是没问题的.

public
class MyScrollView
extends ScrollView
{

private
final String

TAG = this.getClass().getSimpleName();

public
MyScrollView
(Context
context) {

super(context);

}

public
MyScrollView
(Context
context, AttributeSet
attrs) {

super(context,
attrs);

}

public
MyScrollView
(Context
context, AttributeSet
attrs, int
defStyle) {

super(context,
attrs, defStyle);

}

}

这个我就不多解释了, 自定义过控件的人肯定知道这三个构造的意义, 多一嘴, 其中的第二个是给XML 来 Render的, 所以一定要写上.

来, 写上刚才说的最关键的:

@Override

protected
void onScrollChanged
(int
l, int
t, int
oldl, int
oldt) {

super.onScrollChanged(l,
t, oldl,
oldt);

}

简单说明一下, API默认的这四个参数的意思很隐晦, 你可以考虑改写成:

@Override

protected
void onScrollChanged
(int
currentX, int
currentY, int
oldx, int
oldy) {

super.onScrollChanged(currentX,
currentY, oldx,
oldy);

}

既然需要进行监听状态改变, 那么使用回调的设计进行监听再好不过(之所以选择抽象类而不是使用接口, 是因为这样的话可以更好地让用户进行抉择, 具体需要重写哪一个, 简化代码量和减少流程使用的难度), 这里推荐使用内部类, 当然, 新建一个独立新类也是可以的:

public
abstract class MyScrollViewListener
{

public
void onMyScrollChanged
(MyScrollView
scrollView, int
x, int
y, int
oldx, int
oldy) {

}

public
void onMyScrollStart
(MyScrollView
scrollView, int
x, int
y, int
oldx, int
oldy) {

}

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) {

}

}

简单解释一下, 自上而下的回调监听的意思分别是:

onMyScrollChanged
: 当滚动时

onMyScrollStart
: 当开始滚动时

onMyScrollStop
: 当滚动停止时

onMyScrollTop
: 当滚动到到顶部时

onMyScrollBottom
: 当滚动到底部时

onMyScrollUp
: 当向上滚动时

onMyScrollDown
: 当向下滚动时

这些都是比较常用的, 先写好, 我们一个一个来实现. 注意 内部类的话, 回调方法要使用 public的. 不然外部回调的发起者, 是无法重写回调方法的.

哦, 对了, 进一步集成, 方便外部发起者进行调用:

private MyScrollViewListener
myScrollViewListener;

public
void setMyScrollViewListener
(MyScrollViewListener
myScrollViewListener) {

this.myScrollViewListener =
myScrollViewListener;

}

很简单, 一个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

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 (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

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 (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 String

TAG = this.getClass().getSimpleName();

public
MyScrollView
(Context
context) {

super(context);

}

public
MyScrollView
(Context
context, AttributeSet
attrs) {

super(context,
attrs);

}

public
MyScrollView
(Context
context, AttributeSet
attrs, int
defStyle) {

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, int
y, int
oldx, int
oldy) {

}

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
(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);

}

// ====================================================

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");

}

// ====================================================

View
view = (View)
getChildAt(getChildCount() - 1);// We take the last son in the
scrollview

int
diff = (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 void
run
() {

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 byimknown

2015-03-19

时间: 2024-07-31 13:09:56

安卓 搭建带有多种监听自定义ScrollView的相关文章

安卓左右滑动事件监听

import android.os.Bundle;   import android.app.Activity;   import android.content.Context;   import android.util.Log;   import android.widget.RelativeLayout;      public class MainActivity extends Activity {       @Override       protected void onCre

多种监听事件处理方法

5种监听事件处理方法对比总结 1).通过设置UI组件的android:onClick属性,然后代码实现方法:此方法业务逻辑和UI耦合性太高,实际业务一般不用 2).匿名类:一般只应用特定组件的特定业务响应 3).内部类.4).外部类:可作为多个UI共同的事件处理,适用多个UI组件复用:当事件源要传递信息给事件监听器时则不太灵活,必须通过成员变量及构造方法 5).Activity自身类:处理比较灵活,实际中用的最多 6).当同一个UI对同一个事件注册多个监听器时,根据注册的顺序,最后注册的事件监听

java事件响应方法汇总(容器类监听、监听器类、AbstractAction、反射)

Java图形用户界面中,处理事件时所必须的步骤是: 1.创建接受响应的组件(控件)2.实现相关事件监听接口3.注册事件源的动作监听器4.事件触发时的事件处理 相应的可以通过以下的集中方式来作出事件响应. [java] view plaincopyprint? <span style="font-size: 18px;">一.容器类监听 效果:单击窗体中的三个按钮,实现相应的相应时间. </span><pre class="java" n

SpringBoot | 第三十二章:事件的发布和监听

前言 今天去官网查看spring boot资料时,在特性中看见了系统的事件及监听章节.想想,spring的事件应该是在3.x版本就发布的功能了,并越来越完善,其为bean和bean之间的消息通信提供了支持.比如,我们可以在用户注册成功后,发送一份注册成功的邮件至用户邮箱或者发送短信.使用事件其实最大作用,应该还是为了业务解耦,毕竟用户注册成功后,注册服务的事情就做完了,只需要发布一个用户注册成功的事件,让其他监听了此事件的业务系统去做剩下的事件就好了.对于事件发布者而言,不需要关心谁监听了该事件

自定义ScrollView,实现ScrollView滑动监听并记录滑动位置。

Android自带的ScrollView对于滑动监听接口没有开放,然而在许多时候记录ScrollView的滑动位置,实现这个功能比较简单,自己实现一个ObserveScrollView类来继承ScrollView,然后重写里面的onScrollChanged(int l, int t, int oldl, int oldt)方法,本方法就是ScrollView的滑动监听,接着声明一个接口,在重写的方法里利用接口回调,将滑动的数据传出去. onScrollChanged(int l, int t,

ScrollView的滑动监听(以HorizontalScrollView为例)

ScrollView不能像其他组件一样使用onScrollChanged()方法是因为它用protected封装了 protected void onScrollChanged(int x, int y, int oldx, int oldy); 想要实现监听需要简单自定义组件. 1:自定义组件 public class ObservableScrollView extends HorizontalScrollView { private ScrollViewListener scrollVie

【android基础篇】自定义广播和电话监听

I,自定义广播 前面所说的都是接收短信,外拨电话等都是系统所有的广播,而其实我们可以自己自定义一个广播,并且写一个广播接收者来玩玩. 1) 在按钮的点击方法中,发送自定义的广播: 1 public void click(View view){ 2 /** 3 * 发送自定义的广播 4 */ 5 Intent intent=new Intent(); 6 //设置意图的动作,要和自定义的频道要一致 7 intent.setAction("www.wangchengxiang.com");

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

我之前写了一篇关于美团网,大众点评的购买框效果的文章Android对ScrollView滚动监听,实现美团.大众点评的购买悬浮效果,我自己感觉效果并不是很好,如果快速滑动界面,显示悬浮框的时候会出现一卡的现象,有些朋友说有时候会出现两个布局的情况,特别是对ScrollView滚动的Y值得监听,我还使用了Handler来获取,还有朋友给我介绍了Scrolling Tricks这个东西,我下载试了下,确实美团网,大众点评的购买框用的是这种效果,但是Scrolling Tricks只能在API11以上

iPad开发--美团界面的搭建(主要是对Popover的使用,以及监听)

一.主界面的搭建,效果图.设置self.navigationItem.leftBarButtonItems属性. 由于leftBarButtonItem是通过xib文件创建的,通过xib创建的控件默认跟随父控件的大小而变化 解决方法:取消xib的autoLayout,取消xib的高度自动拉伸和宽度自动拉伸 自定义的控件需要外界来更改显示的图片以及文字,所以对外提供以下方法来设置自己显示的图片以及文字 二.leftBarButtonItem点击后是通过Popover来展示的,而且展示的都是一个具有