HorizontalScrollView扩展总结

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

大家好,又见面了,我是你们的朋友全栈君。

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对象

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/159409.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 纸张与什么是使用喷墨打印机所需的消耗品(打印机打印出来的纸黑乎乎的)

    【PConline杂谈】一直潜心研究打印机的小编,由于长时间周旋于各种打印机,甚是无聊。因此近日研究了点特别的东西。关于打印机耗材方面,一般都是硒鼓、墨盒等。对于打印机要用量最大的纸张耗材,却鲜有人关注。于是,小编就趁着元旦假期去恶补了下相关知识。对于常用的纸张耗材,给人的感觉却是熟悉而又陌生的。因此,因此这篇文章就谈一谈关于纸的知识。纸,四大古代发明之一,在人们的日常生活中发挥着及其重要的作用…

    2022年4月11日
    106
  • 查看TensorFlow版本_查看tensorflow编译版本

    查看TensorFlow版本_查看tensorflow编译版本由于tensorflow版本不同,可能一些函数的调用也有变换,这时候可能需要查看tensorflow版本,可以在终端输入查询命令如下:pythonimporttensorflowastftf.__version__查询tensorflow安装路径为:tf.__path__查询结果如下:

    2022年4月19日
    45
  • 一文掌握图像超分辨率重建(算法原理、Pytorch实现)——含完整代码和数据

    一文掌握图像超分辨率重建(算法原理、Pytorch实现)——含完整代码和数据Photo-RealisticSingleImageSuper-ResolutionUsingaGenerativeAdversarialNetwork

    2022年6月28日
    27
  • ubuntu18.04安装opencv教程_Ubuntu18安装opencv

    ubuntu18.04安装opencv教程_Ubuntu18安装opencv最近opencv3.4.1发布了,想换个新的试试鲜,于是把配置的过程通过博文的方式记录下来,方便查阅。本教程原为3.3.0,但经过博主亲测,3.4.0、3.4.1皆适用1.去官网下载opencv,在本教程中选用的时opencv3.4.1,其他版本的配置方法异曲同工。下载链接http://opencv.org/releases.html,选择sources版本2.解压下…

    2022年9月1日
    5
  • json几种格式_json的格式

    json几种格式_json的格式JSON的三种格式一、JSON的全称JSON的全称是JavaScriptObjectNotation二、为什么需要JSONJSON有三种格式,每一种写法都和JS中的数据类型很像,可以很轻松的和JS中的数据类型互相转换三、JSON的三种格式(一)、简单值的形式:JSON的简单值的格式对应着JS中的基础数据类型:数字字符串布尔值注意事项:JSON中没有undefinedJSON中的字符串必须使用双引号JSON中是不能用注释的(二)、对象形式:对应着JS中的对象注意事项:

    2022年10月12日
    2
  • eplan激活码分享【最新永久激活】

    (eplan激活码分享)好多小伙伴总是说激活码老是失效,太麻烦,关注/收藏全栈君太难教程,2021永久激活的方法等着你。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html70YZDJVTFP-eyJsaWNlbnNlSW…

    2022年3月26日
    3.7K

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号