要实现一个功能:当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;
        }
    }
}




android监听ScrollView滑动停止



在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 + "");  
  
            }  
        });