范例,运用ViewGroup、Scroller、VelocityTracker所创建的MyViewGroup类。ViewGroup与Scroller类为建置View Layout与移动View的相关类,VelocityTracker则是用以追踪User在触控屏幕时的滑动速度。
关于MyViewGroup类:
有两个构造方法:1、在程序里配置之用;2、在Layout里配置ViewGroup时指派属性只用。
onInterceptTouchEvent()方法可以拦截触摸事件
MyViewGroup类的代码如下:
public class MyViewGroup extends ViewGroup { private Scroller mScroller; private int mScaledTouchSlop = 0; private int mCurrentLayoutFlag = 0;// 当前显示页的标识 private int mScrollingX = 0; private static final int FLING_VELOCITY = 1000;// 滑动时的比较数值 private static final int REST_STATE = 1; private static final int SCROLLING_STATE = 2; private static final String TAG = "MyViewGroup"; private int mTouchState = REST_STATE;// 触摸状态标识 private float mLastMovingX;// 记录按下时X的坐标 private VelocityTracker mVelocityTracker; public MyViewGroup(Context context) { super(context); // TODO Auto-generated constructor stub mScroller = new Scroller(context); // 在User触控滑动前预测移动位移 mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); // 配置ViewGroup的宽为WRAP_CONTENT,高为MATCH_PARENT MyViewGroup.this.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub mScroller = new Scroller(context); // 在用户触控之前预测移动位移 mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); // 配置ViewGroup的宽为WRAP_CONTENT,高为MATCH_PARENT MyViewGroup.this.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.SlideStyledAttributes); mCurrentLayoutFlag = mTypedArray.getInteger( R.styleable.SlideStyledAttributes_view_screen, 0); } /** * 复写onMeasure方法,并判断所在Layout Flag */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { throw new IllegalStateException("error mode."); } int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode != MeasureSpec.EXACTLY) { throw new IllegalStateException("error mode."); } int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); } scrollTo(mCurrentLayoutFlag * width, 0); } /** * 继承在ViewGroup必须重写的onLayout()方法 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub int childLeft = 0; int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != View.GONE) { int childWidth = child.getMeasuredWidth(); child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); childLeft += childWidth; } } } /** * 复写computeScroll()方法告诉View已更新 */ @Override public void computeScroll() { // TODO Auto-generated method stub if (mScroller.computeScrollOffset()) { // 取得目前Scroller的X offset mScrollingX = mScroller.getCurrX(); // 移动至scroll移动的position scrollTo(mScrollingX, 0); // 调用invalidate()方法处理来自non-UI thread的移动请求。 postInvalidate(); } } /** * 触摸拦截,每次触摸都会先调用该方法,返回true,则不在进行往下传,这后面的触摸处理事件不能被激发 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 实现onInterceptTouchEvent方法拦截User手指触控屏幕移动事件 if ((ev.getAction() == MotionEvent.ACTION_MOVE) && (mTouchState != REST_STATE)) { return true; } switch (ev.getAction()) { // 按住触控屏幕事件开始 case MotionEvent.ACTION_DOWN: // 记录按下的X坐标 mLastMovingX = ev.getX(); mTouchState = mScroller.isFinished() ? REST_STATE : SCROLLING_STATE; break; // 按住触控屏幕其移动事件 case MotionEvent.ACTION_MOVE: // 判断ACTION_MOVE事件间的移动X坐标间距 int intShiftX = (int) Math.abs(ev.getX() - mLastMovingX); if (intShiftX > mScaledTouchSlop) { mTouchState = SCROLLING_STATE; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // 手指离开屏幕 mTouchState = REST_STATE; break; } return mTouchState != REST_STATE; } @Override public boolean onTouchEvent(MotionEvent event) { if (mVelocityTracker == null) { // 实现flinging事件,通过obtain()取得新的tracking实例 mVelocityTracker = VelocityTracker.obtain(); } // 将User触控的MotionEvent加入Tracker mVelocityTracker.addMovement(event); // 判断user的onTouchEvent屏幕触控事件 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 判断滑动事件是否完成,并停止滑动动画 if (!mScroller.isFinished()) { mScroller.abortAnimation(); } // 当手指按下屏幕触发事件时,记录X的坐标 mLastMovingX = event.getX(); break; case MotionEvent.ACTION_UP: // 当手指离开屏幕时,记录下mVelocityTracker的记录,并取得X轴滑动速度 VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000); float xVelocity = velocityTracker.getXVelocity(); // 当X轴滑动速度大于1000,且mCurrentLayoutFlag>0 if (xVelocity > FLING_VELOCITY && mCurrentLayoutFlag > 0) { // 向左移动画面 snapToScreen(mCurrentLayoutFlag - 1); } else if (xVelocity < -FLING_VELOCITY && mCurrentLayoutFlag < getChildCount() - 1) { // 向右移动画面 snapToScreen(mCurrentLayoutFlag + 1); } else { snapToDestination(); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mTouchState = REST_STATE; break; case MotionEvent.ACTION_CANCEL: mTouchState = REST_STATE; break; } mScrollingX = MyViewGroup.this.getScrollX(); return true; } /** * 当不需要滑动时,会调用该方法 */ private void snapToDestination() { int screenWidth = getWidth(); int whichScreen = (mScrollingX + (screenWidth / 2)) / screenWidth; snapToScreen(whichScreen); } /** * 切换界面时调用的方法 * * @param whichScreen * 需要移至的目标界面的flag */ private void snapToScreen(int whichScreen) { // TODO Auto-generated method stub mCurrentLayoutFlag = whichScreen; int newX = whichScreen * getWidth(); int delta = newX - mScrollingX; mScroller.startScroll(mScrollingX, 0, delta, 0, Math.abs(delta) * 2); // 静止重绘View的画面 invalidate(); } }
在使用这个自定义的控件时,布局文件里面的定义如下:
<com.example.ex_4_33_myviewgroup.MyViewGroup xmlns:app="http://schemas.android.com/apk/res/com.example.ex_4_33_myviewgroup" android:layout_width="match_parent" android:layout_height="match_parent" app:view_screen="1" > <include layout="@layout/layout_left" /> <include layout="@layout/layout_center" /> <include layout="@layout/layout_right" /> </com.example.ex_4_33_myviewgroup.MyViewGroup>
其中,用于标示显示哪一页的样式,定义如下
<declare-styleable name="SlideStyledAttributes"> <attr name="view_screen" format="integer" /> </declare-styleable>
定义在styles.xml文件中。