开源中国控件ScrollLayout分析

本文参考http://hi.baidu.com/mysoft_tongmg/item/9b65c898ed7d2030326eeb76和http://www.cnblogs.com/wader2011/archive/2011/10/10/2205142.html
具体思路为:首先,自定义一个控件继承于View,添加一个Scroller类型的成员变量mScroller;
其次,调用mScroller的startScroll()去产生一个偏移控制,手动调用invalid()方法去重新绘制控件;
最后,重写的computeScroll()方法,通过mScroller的computeScrollOffset()方法,根据当前已经逝去的时间,获取当前应该偏移的坐标;调用scrollBy()方法去逐步偏移,调用postInvalidate()方法刷新控件;



package net.oschina.app.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * 左右滑动切换屏幕控件
 * @author Yao.GUET date: 2011-05-04
 * @modify liux (http://my.oschina.net/liux)
 */
public class ScrollLayout extends ViewGroup {
	private static final String TAG = "ScrollLayout";
	private Scroller mScroller;
	 /*
     * 速度追踪器,主要是为了通过当前滑动速度判断当前滑动是否为fling
     */
	private VelocityTracker mVelocityTracker;
	/*
     * 记录当前屏幕下标,取值范围是:0 到 getChildCount()-1
     */
	private int mCurScreen;
	private int mDefaultScreen = 0;
	
	/*
     * Touch状态值 0:静止 1:滑动
     */
	
	private static final int TOUCH_STATE_REST = 0;
	private static final int TOUCH_STATE_SCROLLING = 1;
	private static final int SNAP_VELOCITY = 600;
	//当前touch事件状态
	private int mTouchState = TOUCH_STATE_REST;
	
	/*
     * 记录touch事件中被认为是滑动事件前的最大可滑动距离
     */
	private int mTouchSlop;
	
    /*
     * 记录滑动时上次手指所处的位置
     */
	private float mLastMotionX;
	private float mLastMotionY;
    private OnViewChangeListener mOnViewChangeListener;

    /**
     * 设置是否可左右滑动
     * @author liux
     */
    private boolean isScroll = true;
    public void setIsScroll(boolean b) {
    	this.isScroll = b;
    }
    
	public ScrollLayout(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public ScrollLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mScroller = new Scroller(context);
		mCurScreen = mDefaultScreen;
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int childLeft = 0;
		final int childCount = getChildCount();
		for (int i = 0; i < childCount; i++) {
			final View childView = getChildAt(i);
			if (childView.getVisibility() != View.GONE) {
				final int childWidth = childView.getMeasuredWidth();
				childView.layout(childLeft, 0, childLeft + childWidth,
						childView.getMeasuredHeight());
				childLeft += childWidth;
			}
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		//Log.e(TAG, "onMeasure");
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		final int width = MeasureSpec.getSize(widthMeasureSpec);
		final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		if (widthMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException(
					"ScrollLayout only canmCurScreen run at EXACTLY mode!");
		}
		final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		if (heightMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException(
					"ScrollLayout only can run at EXACTLY mode!");
		}

		// The children are given the same width and height as the scrollLayout
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}
		// Log.e(TAG, "moving to screen "+mCurScreen);
		scrollTo(mCurScreen * width, 0);
	}

	/**
	 * According to the position of current layout scroll to the destination
	 * page.处理当屏幕拖动到一个位置松手后的处理
	 *  根据当前x坐标位置确定切换到第几屏
	 */
	public void snapToDestination() {
		final int screenWidth = getWidth();
		final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth;
		snapToScreen(destScreen);
	}

	public void snapToScreen(int whichScreen) {
		//是否可滑动
		if(!isScroll) {
			this.setToScreen(whichScreen);
			return;
		}
		
		scrollToScreen(whichScreen);
	}

	public void scrollToScreen(int whichScreen) {		
		// get the valid layout page
		whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
		if (getScrollX() != (whichScreen * getWidth())) {
			final int delta = whichScreen * getWidth() - getScrollX();
			mScroller.startScroll(getScrollX(), 0, delta, 0,
					Math.abs(delta) * 1);//持续滚动时间 以毫秒为单位
			mCurScreen = whichScreen;
			invalidate(); // Redraw the layout
            
			if (mOnViewChangeListener != null)
            {
            	mOnViewChangeListener.OnViewChange(mCurScreen);
            }
		}
	}
	
	public void setToScreen(int whichScreen) {
		whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
		mCurScreen = whichScreen;
		scrollTo(whichScreen * getWidth(), 0);
		
        if (mOnViewChangeListener != null)
        {
        	mOnViewChangeListener.OnViewChange(mCurScreen);
        }
	}

	public int getCurScreen() {
		return mCurScreen;
	}

	/**
	 * View绘制流程draw()过程中会调用computeScroll()方法
	 * 即当我们在在mScroller.startScroll()后调用invalidate()后对导致这个方法的执行(即第一次时,我们需要手动去调用一次invalidate才会去调用 )
	 * 而这个方法里再去调postInvalidate,
	 * 这样就可以不断地去调用scrollTo方法了,直到mScroller动画结束。
	 */

	@Override
	public void computeScroll() {
		/**
		 * 当startScroll执行过程中即在duration时间内,computeScrollOffset方法会一直返回true,但当动画执行完成后会返回返加false. 
		 */
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		//是否可滑动
		if(!isScroll) {
			return false;
		}
		
		/**
		 * 当你需要跟踪触摸屏事件的速度的时候,使用obtain()方法来获得VelocityTracker类的一个实例对象
			在onTouchEvent回调函数中,使用addMovement(MotionEvent)函数将当前的移动事件传递给VelocityTracker对象
			使用computeCurrentVelocity  (int units)函数来计算当前的速度,使用 getXVelocity  ()、 getYVelocity  ()函数来获得当前的速度
		 */
		
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(event);
		final int action = event.getAction();
		final float x = event.getX();
		final float y = event.getY();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			//Log.e(TAG, "event down!");
			if (!mScroller.isFinished()) {
				mScroller.abortAnimation();
			}
			mLastMotionX = x;
			
			//---------------New Code----------------------
			mLastMotionY = y;
			//---------------------------------------------
			
			break;
		case MotionEvent.ACTION_MOVE:
			int deltaX = (int) (mLastMotionX - x);
			
			//---------------New Code----------------------
			int deltaY = (int) (mLastMotionY - y);
			if(Math.abs(deltaX) < 200 && Math.abs(deltaY) > 10)
				break;
			mLastMotionY = y;
			//-------------------------------------
			
			mLastMotionX = x;
			scrollBy(deltaX, 0);
			break;
		case MotionEvent.ACTION_UP:
			//Log.e(TAG, "event : up");
			// if (mTouchState == TOUCH_STATE_SCROLLING) {
			final VelocityTracker velocityTracker = mVelocityTracker;
			velocityTracker.computeCurrentVelocity(1000);
			int velocityX = (int) velocityTracker.getXVelocity();
			//Log.e(TAG, "velocityX:" + velocityX);
			if (velocityX > SNAP_VELOCITY && mCurScreen > 0) {
				// Fling enough to move left
				//Log.e(TAG, "snap left");
				snapToScreen(mCurScreen - 1);
			} else if (velocityX < -SNAP_VELOCITY
					&& mCurScreen < getChildCount() - 1) {
				// Fling enough to move right
				//Log.e(TAG, "snap right");
				snapToScreen(mCurScreen + 1);
			} else {
				snapToDestination();
			}
			if (mVelocityTracker != null) {
				mVelocityTracker.recycle();
				mVelocityTracker = null;
			}
			// }
			mTouchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_CANCEL:
			mTouchState = TOUCH_STATE_REST;
			break;
		}
		return true;
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		//Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop);
		
		final int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE)
				&& (mTouchState != TOUCH_STATE_REST)) 
		{
			return true;
		}
		final float x = ev.getX();
		final float y = ev.getY();
		switch (action) {

		case MotionEvent.ACTION_MOVE:
		/**
		 * 我们需在滑动时做一个简单但很重要的判断,即我们需要简单的判断用户的意图——想横向滑动还是想纵向滑动
		 * 只有当用户在横向滑动时才将mTouchState = TOUCH_STATE_SCROLLING
		 */
			final int xDiff = (int) Math.abs(mLastMotionX - x);
			if (xDiff > mTouchSlop) {
				mTouchState = TOUCH_STATE_SCROLLING;
			}
			break;
		case MotionEvent.ACTION_DOWN:
			mLastMotionX = x;
			mLastMotionY = y;
			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
					: TOUCH_STATE_SCROLLING;
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			mTouchState = TOUCH_STATE_REST;
			break;
		}
		return mTouchState != TOUCH_STATE_REST;
	}
	
	/**
	 * 设置屏幕切换监听器
	 * @param listener
	 */
	public void SetOnViewChangeListener(OnViewChangeListener listener)
	{
		mOnViewChangeListener = listener;
	}

	/**
	 * 屏幕切换监听器
	 * @author liux
	 */
	public interface OnViewChangeListener {
		public void OnViewChange(int view);
	}
}














### 回答1: 在Qt中,可以使用QScrollArea来实现在QVBoxLayout内动态添加控件,并使其能够上下滑动而不是堆积在一页。首先,创建一个QVBoxLayout对象,并将其设置为QScrollArea的垂直布局。 ```cpp QScrollArea* scrollArea = new QScrollArea; QWidget* scrollWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(scrollWidget); scrollArea->setWidget(scrollWidget); scrollArea->setWidgetResizable(true); // 动态添加控件 for(int i = 0; i < 控件数量; i++){ QWidget* widget = new QWidget; layout->addWidget(widget); // 为控件设置内容和属性 } scrollArea->show(); ``` 首先,我们创建一个QScrollArea对象和一个QWidget对象(scrollWidget),后者将用作QScrollArea的子部件。然后,再在scrollWidget上创建一个QVBoxLayout对象(layout)作为其窗口布局。这样,我们就可以用这个垂直布局来添加我们想要的控件。 在动态添加控件时,我们创建一个QWidget对象(widget),并将其添加到垂直布局(layout)中。在这里,你可以为控件设置内容和属性。 最后,我们将scrollWidget设置为QScrollArea的窗口部件,使用setWidgetResizable(true)使其能够自动调整大小适应窗口,并调用show()将QScrollArea显示在屏幕上。这样,我们就可以通过滚动来查看添加的控件,并且控件不会堆积在一起。 ### 回答2: 在Qt中,可以使用QScrollArea来实现在Qt VerticalLayout内动态添加控件并实现上下滑动的效果,而不是堆积在一页。 首先,我们需要在UI界面中添加一个QScrollArea,并将其垂直滚动条的策略设置为"AlwaysOn",确保垂直滚动条一直显示。然后,我们将QScrollArea的属性widgetResizable设置为true,以便自动调整内部widget的大小。 接下来,在垂直滚动区内创建一个QWidget,并使用QVBoxLayout作为其布局管理器。这个QWidget将作为QScrollArea的内部widget,所有动态添加的控件都将添加到这个QWidget中。 然后,我们可以按照需要动态创建所需的控件,并使用addWidget()方法将其添加到QVBoxLayout中。在添加完所有控件后,我们还可以使用QSpacerItem来创建一个占位符,以便在需要时调整布局。 最后,将这个QWidget设置为QScrollArea的widget,这样就可以通过滚动条来上下滑动动态添加的控件了。 具体实现代码如下: // 创建滚动区 QScrollArea *scrollArea = new QScrollArea(this); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scrollArea->setWidgetResizable(true); // 创建内部widget和布局管理器 QWidget *scrollWidget = new QWidget(scrollArea); QVBoxLayout *scrollLayout = new QVBoxLayout(scrollWidget); // 创建并添加动态的控件到布局中 for(int i=0; i<10; i++) { QLabel *label = new QLabel(QString("Label %1").arg(i+1)); scrollLayout->addWidget(label); } // 创建一个占位符 QSpacerItem *spacer = new QSpacerItem(10, 10, QSizePolicy::Minimum, QSizePolicy::Expanding); scrollLayout->addItem(spacer); // 设置滚动区的内部widget并显示 scrollArea->setWidget(scrollWidget); scrollArea->show(); 这样,我们就可以在Qt VerticalLayout内动态添加控件,并且通过滚动条来实现上下滑动的效果,避免了控件的堆积。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值