要实现一个功能:当Scrollview滑动到最底端的时候需要触发事件加载其他数据。很多人都以为ScrollView可以像ListViev那样setOnScrollListener,其实沒那么简单,因为ScrollView压根就没有该接口,在baidu上兜了一圈没有找到合适的答案,没办法只能google去了,居然一下子解决了这个问题,还是老外比较牛,呵呵,这是我访问的网址:
http://stackoverflow.com/questions/2864563/how-do-i-know-that-the-scrollview-is-already-scrolled-to-the-bottom
注意,如果数据不满一页的话,会执行onBottom方法!通常要使用懒加载的话数据都会超过一页,所以我沒仔细考虑这个问题!
我把ScrollView封装成类了,源码如下:
package com.ql.view;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;
public class LazyScrollView extends ScrollView{
private static final String tag="LazyScrollView";
private Handler handler;
private View view;
public LazyScrollView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public LazyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public LazyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
//这个获得总的高度
public int computeVerticalScrollRange(){
return super.computeHorizontalScrollRange();
}
public int computeVerticalScrollOffset(){
return super.computeVerticalScrollOffset();
}
private void init(){
this.setOnTouchListener(onTouchListener);
handler=new Handler(){
@Override
public void handleMessage(Message msg) {
// process incoming messages here
super.handleMessage(msg);
switch(msg.what){
case 1:
if(view.getMeasuredHeight() <= getScrollY() + getHeight()) {
if(onScrollListener!=null){
onScrollListener.onBottom();
}
}else if(getScrollY()==0){
if(onScrollListener!=null){
onScrollListener.onTop();
}
}
else{
if(onScrollListener!=null){
onScrollListener.onScroll();
}
}
break;
default:
break;
}
}
};
}
OnTouchListener onTouchListener=new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
if(view!=null&&onScrollListener!=null){
handler.sendMessageDelayed(handler.obtainMessage(1), 200);
}
break;
default:
break;
}
return false;
}
};
/**
* 获得参考的View,主要是为了获得它的MeasuredHeight,然后和滚动条的ScrollY+getHeight作比较。
*/
public void getView(){
this.view=getChildAt(0);
if(view!=null){
init();
}
}
/**
* 定义接口
* @author admin
*
*/
public interface OnScrollListener{
void onBottom();
void onTop();
void onScroll();
}
private OnScrollListener onScrollListener;
public void setOnScrollListener(OnScrollListener onScrollListener){
this.onScrollListener=onScrollListener;
}
}
用的时候也很简单,通常这样使用:
scrollView=(LazyScrollView)findViewById(R.id.scrollView);
scrollView.getView();
scrollView.setOnScrollListener(new OnScrollListener() {
@Override
public void onTop() {
// TODO Auto-generated method stub
Log.d(tag,"------滚动到最上方------");
}
@Override
public void onScroll() {
// TODO Auto-generated method stub
Log.d(tag,"没有到最下方,也不是最上方");
}
@Override
public void onBottom() {
// TODO Auto-generated method stub
Log.d(tag,"------滚动到最下方------");
}
});
感激我吧,我呕心沥血才出来了这么个类。呵呵。
重写onScrollChanged()的模式
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;
/**
* BorderScrollView
* <ul>
* <li>onTop and onBottom response ScrollView</li>
* <li>you can {@link #setOnBorderListener(OnBorderListener)} to set your top and bottom response</li>
* </ul>
*
* @author trinea@trinea.cn 2013-5-21
*/
public class BorderScrollView extends ScrollView {
private OnBorderListener onBorderListener;
private View contentView;
public BorderScrollView(Context context) {
super(context);
}
public BorderScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BorderScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
doOnBorderListener();
}
public void setOnBorderListener(final OnBorderListener onBorderListener) {
this.onBorderListener = onBorderListener;
if (onBorderListener == null) {
return;
}
if (contentView == null) {
contentView = getChildAt(0);
}
}
/**
* OnBorderListener, Called when scroll to top or bottom
*
* @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-5-22
*/
public static interface OnBorderListener {
/**
* Called when scroll to bottom
*/
public void onBottom();
/**
* Called when scroll to top
*/
public void onTop();
}
private void doOnBorderListener() {
if (contentView != null && contentView.getMeasuredHeight() <= getScrollY() + getHeight()) {
if (onBorderListener != null) {
onBorderListener.onBottom();
}
} else if (getScrollY() == 0) {
if (onBorderListener != null) {
onBorderListener.onTop();
}
}
}
}
顺便记一下老外使用fullScroll的做法。当然也可以直接fullScroll而不需要放入post()。
scrollView.post(new Runnable() {
@Override
public void run() {
scrollView.fullScroll(View.FOCUS_DOWN);
}
});
只要把fullScroll改成scrollTo就可以做一个书签效果了:
http://yangsongjing.iteye.com/blog/1855063
Android-ObservableScrollView
https://github.com/ksoichiro/Android-ObservableScrollView
在HorizontalScrollView中使用ScrollView相互影响问题的解决办法:
On my ScrollView, I needed to override the onInterceptTouchEvent method to only intercept the touch event if the Y motion is > the X motion. It seems like the default behavior of a ScrollView is to intercept the touch event whenever there is ANY Y motion. So with the fix, the ScrollView will only intercept the event if the user is deliberately scrolling in the Y direction and in that case pass off the ACTION_CANCEL to the children.
Here is the code for my Scroll View class that contains the HorizontalScrollView:
public class CustomScrollView extends ScrollView {
private GestureDetector mGestureDetector;
View.OnTouchListener mGestureListener;
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(new YScrollDetector());
setFadingEdgeLength(0);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
}
// Return false if we're scrolling in the x direction
class YScrollDetector extends SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if(Math.abs(distanceY) > Math.abs(distanceX)) {
return true;
}
return false;
}
}
}
在ScrollView中嵌入GridView
http://fariytale.iteye.com/blog/1420254
做android程序开发的都知道,不能在一个拥有Scrollbar的组件中嵌入另一个拥有Scrollbar的组件,因为这不科学,会混淆滑动事件,导致只显示一到两行数据。那么就换一种思路,首先让子控件的内容全部显示出来,禁用了它的滚动。如果超过了父控件的范围则显示父控件的scrollbar滚动显示内容,思路是这样,一下是代码。
具体的方法是自定义GridView组件,继承自GridView。重载onMeasure方法:
public class MyGridView extends GridView
{
public MyGridView(android.content.Context context,
android.util.AttributeSet attrs)
{
super(context, attrs);
}
/**
* 设置不滚动
*/
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
其中onMeasure函数决定了组件显示的高度与宽度;
makeMeasureSpec函数中第一个函数决定布局空间的大小,第二个参数是布局模式
MeasureSpec.AT_MOST的意思就是子控件需要多大的控件就扩展到多大的空间
之后在ScrollView中添加这个组件就OK了,同样的道理,ListView也适用。
滚动监听的ScrollView
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class NotifyingScrollView extends ScrollView {
public interface OnScrollChangedListener {
void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt);
}
private OnScrollChangedListener mOnScrollChangedListener;
public NotifyingScrollView(Context context) {
super(context);
}
public NotifyingScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NotifyingScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollChangedListener != null) {
mOnScrollChangedListener.onScrollChanged(this, l, t, oldl, oldt);
}
}
public void setOnScrollChangedListener(OnScrollChangedListener listener) {
mOnScrollChangedListener = listener;
}
}
ScrollView也可以实现OnTouchListener来监听是否滑动到最底部
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ScrollView;
import android.app.Activity;
/**
* Demo描述:
* 监听ScrollView滑动到顶端和底部
*
* 注意事项:
* 1 mScrollView.getChildAt(0).getMeasuredHeight()表示:
* ScrollView所占的高度.即ScrollView内容的高度.常常有一
* 部分内容要滑动后才可见,这部分的高度也包含在了
* mScrollView.getChildAt(0).getMeasuredHeight()中
*
* 2 view.getScrollY表示:
* ScrollView顶端已经滑出去的高度
*
* 3 view.getHeight()表示:
* ScrollView的可见高度
*
*/
public class MainActivity extends Activity {
private ScrollView mScrollView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
init();
}
private void init(){
mScrollView=(ScrollView) findViewById(R.id.scrollView);
mScrollView.setOnTouchListener(new TouchListenerImpl());
}
private class TouchListenerImpl implements OnTouchListener{
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
int scrollY=view.getScrollY();
int height=view.getHeight();
int scrollViewMeasuredHeight=mScrollView.getChildAt(0).getMeasuredHeight();
if(scrollY==0){
System.out.println("滑动到了顶端 view.getScrollY()="+scrollY);
}
if((scrollY+height)==scrollViewMeasuredHeight){
System.out.println("滑动到了底部 scrollY="+scrollY);
System.out.println("滑动到了底部 height="+height);
System.out.println("滑动到了底部 scrollViewMeasuredHeight="+scrollViewMeasuredHeight);
}
break;
default:
break;
}
return false;
}
};
}
在滚动的视图观测滚动事件的Android库:Android-ObservableScrollView
http://www.open-open.com/lib/view/open1415854429070.html
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class ObservableScrollView extends ScrollView {
private ScrollViewListener scrollViewListener = null;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
}
网上说的方法乱七八糟,能用的就是自己算高度,其实sdk-9中,ScrollView已经加入了一个方法,能监听到是否已经不能滚动,稍加处理,就可以监听是否滑到底部了。
先上自定义的ScrollView方法:
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class BottomScrollView extends ScrollView {
private OnScrollToBottomListener onScrollToBottom;
public BottomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BottomScrollView(Context context) {
super(context);
}
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
if(scrollY != 0 && null != onScrollToBottom){
onScrollToBottom.onScrollBottomListener(clampedY);
}
}
public void setOnScrollToBottomLintener(OnScrollToBottomListener listener){
onScrollToBottom = listener;
}
public interface OnScrollToBottomListener{
public void onScrollBottomListener(boolean isBottom);
}
}
调用方法:
BottomScrollView scroll = (BottomScrollView)findViewById(R.id.id_scroll);
scroll.setOnScrollToBottomLintener(new OnScrollToBottomListener() {
@Override
public void onScrollBottomListener(boolean isBottom) {
// TODO Auto-generated method stub
Log.e("SCROLLVIEW", isBottom + "");
}
});