HorizontalScrollView扩展总结

ScrollView相信大家都已经比较熟悉了,它是支持垂直滚动的,在开发中经常使用到,与垂直滚动相对的就是水平滚动HorizontalScrollView,有时我们在进行页面切换的时候也会用到HorizontalScrollView。通过查看源码比较发现ScrollView和HorizontalScrollView有好多相同的方法。

在说扩展之前,我先说一下HorizontalScrollView的特点

(1) 支持水平滚动

(2) 和ScrollView一样,它只包括一个子View,通常是用LinearLayout作为它的子View,当然还可以用用其它的View

(3) HorizontalScroll内部使用到的OverScroller 缺省滑动的时间为DEFAULT_DURATION = 250 ms

(4) 可以平滑也可以瞬间滑动,平滑则调用smoothScrollBy(int dx,int dy)滑动多少距离)/smoothScrollTo(int x,int y)滑动到x,y位置

     瞬间滑动则调用 scrollBy(int x,int y) scrollTo(int x,int y)


HorizontalScrollView 与滚动有关的常用方法

public final void smoothScrollBy(int dx, int dy)
public final void smoothScrollTo(int x, int y)
public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled)
public void scrollTo(int x, int y) 覆写了父类View的scrollTo

先看 setSmoothScrollingEnabled 这个方法,设置是否有平滑滚动效果,此方法是设置一个标记,此标记会被HorizontalScrollView的
如下方法间接使用到:

public boolean executeKeyEvent(KeyEvent event)
public boolean fullScroll(int direction)
public boolean pageScroll(int direction)
public boolean arrowScroll(int direction)
protected void onSizeChanged(int w, int h, int oldw, int oldh)

如上方法会出发滚动,而滚动是否有平滑效果则取决于setSmoothScrollingEnabled 方法设置的标记。
onSizeChanged方法是当HorizontalScrollView的大小发生改变的时候触发调用的;
标记具体被使用的过程如下:


setSmoothScrollingEnabled(boolean smoothScrollingEnabled)方法 设置的mSmoothScrollingEnabled标记只在doScrollX(int delta)有使用到

在doScrollX内部如果mSmoothScrollingEnabled是true则会调用public smoothScrollBy否则调用scrollBy

而 doScrollX方法被调用关系如下:


public boolean executeKeyEvent(KeyEvent event) -> fullScroll(),pageScroll(),arrowScroll()
public boolean fullScroll(int direction) -> private scrollAndFocus() -->private doScrollX()
public boolean pageScroll(int direction) -> private scrollAndFocus() -->private doScrollX()
public boolean arrowScroll(int direction) -> doScrollX(int delta)
protected void onSizeChanged(int w, int h, int oldw, int oldh) -> doScrollX()

知道了HorizontalScrollView这些特点之后,刚好项目中有这样一个需求:注册模块,

要求:

(1) 划分3个步骤,每个步骤页面是不一样的

(2) 步骤可以回退

(3) 每个页面只初始化一次,

(4) 不使用三个Activity

排除ViewPager,Activity,Fragment,这时可以使用HorizontalScrollView通过滚动来实现,那么就需要扩展HorizontalScrollView了。

此扩展HorizontalScrollView有如下特点:

(1) 可禁用手势滑动,只能通过调用scrollBy,scrollTo,smoothScrollBy, smoothScrollTo来滑动(因为每个步骤切换是通过点击下一步,而不能手势滑动)

(2) 也支持手势滑动

(3) 支持滑动的监听(滑动动作完成后才去更新步骤状态)

主要实现过程:

(1)  继承HorizontalScrollView

(2)  增加自定义方法public void enableTouchScroll(boolean enAbleTouchScroll) 是否允许手势触摸滑动

(3)  覆写public boolean onTouchEvent(MotionEvent ev) 如果支持手势滑动,如果有设置滚动监听则监听滚动,同时调用父类HorizontalScroll的onTouch;

     如果不支持手势滑动,则直接return true直接将touch事件交给子View进行处理

(4) 增加自定义方法public void setOnScrollStateChangedListener(ScrollViewListener listener,Handler handler) 设置滚动监听,这里handler是用于发送消息(每隔多少ms去获取一次滚  动的距离从而知道是否滚动)

(5)增加自定义方法public final void smoothScrollByExt(int dx, int dy)和public final void smoothScrollToExt(int x, int y)支持滚动监听

(6)增加自定义方法public boolean isFinishedScroll() 滚动是否完成(内部是通过调用HorizontalScroll的OverScroll对象的isFinished方法,而OverScroll对象是通过反射得到)

注意以上滚动监听只有设置了滚动监听且调用了smoothScrollByExt或smoothScrollToExt方法或者支持手势滚动才有用。

完整源码如下:

package com.nandudu.engsv.widget;

import java.lang.reflect.Field;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import android.widget.OverScroller;

/**
 * 水平滚动条
 * (1)可设置是否允许手势触摸滚动(默认是支持手势触摸滚动的) <br>
 * (2)支持滚动状态监听
 * 
 * @author Lue
 * 
 */
public class MyHorizontalScrollView extends HorizontalScrollView
{

	/**
	 * 是否允许手势触摸滚动
	 */
	private boolean enAbleTouchScroll = true;

	private int scrollDealy = 50;

	/**
	 * 滚动状态:停止
	 */
	public final static int SCROLL_STATE_IDLE = 0;

	/**
	 * 滚动状态: 手指拖动滚动
	 */
	public final static int SCROLL_STATE_TOUCH_SCROLL = 1;

	/**
	 * 滚动状态: 正在滑动
	 */
	public final static int SCROLL_STATE_FLING = 2;

	/**
	 * 记录当前滚动的距离
	 */
	private int currentX = -9999999;

	private Handler mHandler;

	private ScrollViewListener scrollViewListener;

	private OverScroller parentMScroller;
	
	/**
	 * 当前滚动状态
	 */
	private int scrollState = SCROLL_STATE_IDLE;

	private String TAG = "MyHorizontalScrollView";

	public interface ScrollViewListener
	{
		/**
		 * @param scrollState 滚动状态 
		 * <br>{@link MyHorizontalScrollView#SCROLL_STATE_IDLE}
		 * <br>{@link MyHorizontalScrollView#SCROLL_STATE_TOUCH_SCROLL}
		 * <br>{@link MyHorizontalScrollView#SCROLL_STATE_FLING}
		 */
		void onScrollChanged(int scrollState);
	}

	/**
	 * 滚动监听runnable
	 */
	private Runnable scrollRunnable = new Runnable()
	{
		@Override
		public void run()
		{

			// TODO Auto-generated method stub
			if (getScrollX() == currentX)
			{
				// 滚动停止
				Log.d(TAG, "停止滚动");
				scrollState = SCROLL_STATE_IDLE;
				if (scrollViewListener != null)
				{
					scrollViewListener.onScrollChanged(scrollState);
				}
				// 取消监听线程
				mHandler.removeCallbacks(this);
				return;
			}
			else
			{
				// 手指离开屏幕 view还在滚动的时候
				Log.d(TAG, "Fling...");
				scrollState = SCROLL_STATE_FLING;
				 if(scrollViewListener!=null)
				 {
				 scrollViewListener.onScrollChanged(scrollState);
				 }
			}

			currentX = getScrollX();

			mHandler.postDelayed(this, scrollDealy);
		}
	};

	public MyHorizontalScrollView(Context context)
	{
		super(context);
		init();
	}

	public MyHorizontalScrollView(Context context, AttributeSet attrs,
			int defStyle)
	{
		super(context, attrs, defStyle);
		init();
	}

	public MyHorizontalScrollView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		init();
	}
	
	private void init()
	{
		try
		{
			Class<?> type = HorizontalScrollView.class;
			
			Field f = type.getDeclaredField("mScroller");
			f.setAccessible(true);
			
			parentMScroller = (OverScroller)f.get(this);
		}
		
		catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 是否允许手势触摸滚动
	 * 
	 * @param enAbleTouchScroll
	 *            true 允许 false不允许
	 */
	public void enableTouchScroll(boolean enAbleTouchScroll)
	{
		this.enAbleTouchScroll = enAbleTouchScroll;
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev)
	{
		if (enAbleTouchScroll)
		{
			if (mHandler != null)
			{
				switch (ev.getAction())
				{
				case MotionEvent.ACTION_MOVE:
					
					this.scrollState = SCROLL_STATE_TOUCH_SCROLL;
					if(scrollViewListener != null)
					{
						scrollViewListener.onScrollChanged(scrollState);
					 }
					
					// 手指在上面移动的时候 取消滚动监听线程
					mHandler.removeCallbacks(scrollRunnable);
					break;
				case MotionEvent.ACTION_UP:
					// 手指移动的时候
					mHandler.post(scrollRunnable);
					break;
				}
			}

			return super.onTouchEvent(ev);
		}
		else
		{
			return true;
		}
	}

	/**
	 * 对父类HorizontalScrollView方法smoothScrollBy的扩展,增加了滚动监听
	 * 
	 * @see {@link HorizontalScrollView#smoothScrollBy(int, int)}
	 * @param dx
	 * @param dy
	 */
	public final void smoothScrollByExt(int dx, int dy)
	{
		super.smoothScrollBy(dx, dy);

		// 开始监听滑动
		if (mHandler != null)
		{
			mHandler.postDelayed(scrollRunnable, scrollDealy);
		}
	}

	/**
	 * 滚动是否完成
	 * @return
	 */
	@SuppressLint("NewApi")
	public boolean isFinishedScroll()
	{
		if(parentMScroller != null)
		{
			return parentMScroller.isFinished();
		}
		
		return true;
	}
	
	/**
	 * 对父类HorizontalScrollView方法smoothScrollTo的扩展,增加了滚动监听
	 * 
	 * @see {@link HorizontalScrollView#smoothScrollTo(int, int)}
	 * @param x
	 * @param y
	 */
	public final void smoothScrollToExt(int x, int y)
	{
		super.smoothScrollTo(x, y);

		
		// 开始监听滑动
		if (mHandler != null)
		{
			mHandler.postDelayed(scrollRunnable, scrollDealy);
		}
	}

	/**
	 * 必须先调用这个方法设置Handler
	 * 
	 * @param handler
	 */
	private void setHandler(Handler handler)
	{
		this.mHandler = handler;
	}

	/**
	 * 
	 * 设置滚动监听<br>
	 * 此滚动监听在如下情况有效<br>
	 * (1)支持触摸滚动<br>
	 * (2)调用了 smoothScrollByExt/smoothScrollToExt
	 */
	public void setOnScrollStateChangedListener(ScrollViewListener listener,
			Handler handler)
	{
		this.scrollViewListener = listener;
		setHandler(handler);
	}
}

那么具体使用MyHorizontalScrollView的例子如下:

 
myHorizonScrollView = (MyHorizontalScrollView) findViewById(R.id.myhorslview_calcu);
myHorizonScrollView.enableTouchScroll(false);
		myHorizonScrollView.setOnScrollStateChangedListener(
				new MyHorizontalScrollView.ScrollViewListener()
				{
					@Override
					public void onScrollChanged(int scrollState)
					{
						if (scrollState == MyHorizontalScrollView.SCROLL_STATE_IDLE)
						{//滚动完成后做的事情..Eg:更新步骤状态
							
						}

					}
				}, new Handler());

。。。。
//步骤回退的代码
if (myHorizonScrollView.isFinishedScroll())
{
if (currStep > 0)
			{
				currStep--;
                                //这里每一步的宽度是屏幕宽度,滚动到的位置=当前是哪一步*屏幕宽度(这里currStep是从0开始的)
				myHorizonScrollView.smoothScrollToExt(currStep * displayWidth, 0);
			}
}
。。。。。
 
//切换到下个步骤的代码
if (currStep < 2)
			{// 滚动到下个页面
				currStep++;
				myHorizonScrollView.smoothScrollToExt(currStep * width, 0);

			}

至此HorizontalScroll的扩展就完成了,
还有一个问题:如果觉得HorizontalScrollView 中使用到的 OverScroller 缺省滑动的时间,DEFAULT_DURATION = 250 ms
这250ms时间太长或太短,那么我的实现思路是这样的:
(1) 重写OverScroller(继承OverScroller)

(2) 覆写OverScroller 的public void startScroll(int startX, int startY, int dx, int dy)
     里面调用的是startScroll(startX, startY, dx, dy, DEFAULT_DURATION) 用我们自己的 DEFAULT_DURATION

(3) 通过反射替换HorizontalScrollView 中的OverScroller对象

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值