实现view滑动的功能在自定义的ScrollLayout类中实现。该类是一个自定义view,它本身和LinearLayout、ScrollView等等view有异曲同工之妙,就像LinearLayout它能帮助我们把我们包装在里面的控件排列成线性布局,我们自己编写的ScrollLayout能够帮助我们实现屏幕滑屏的效果。
ScrollLayout extendsGroupView
步骤:先把所有的子view的大小都找好,然后为他们分配好位置
之后根据手指的手势判断手势的速度还有移动距离来确定将要移动到哪个curScreen,最后把界面显示成我们的destScreen就OK了。
源码:
main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <com.crte.scrolllayout.ScrollLyout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#ffedfa" >
- </LinearLayout>
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#ceb452" >
- </LinearLayout>
- </com.crte.scrolllayout.ScrollLyout>
ScrollLayout类:
- package com.crte.scrolllayout;
- import android.app.Activity;
- public class ScrollLyout extends ViewGroup {// Terase_ScrollLyoutDemoActivity
- private static final String TAG = "terase_ScrollLayout";
- private Scroller mScroller;
- private VelocityTracker mVelocityTracker;
- private int mCurScreen;
- private int mDefaultScreen = 0;
- private static final int TOUCH_STATE_REST = 0; // 表示触摸状态为空闲
- // 即没有触摸或者手指离开了
- private static final int TOUCH_STATE_SCROLLING = 1; // 表示手指正在移动
- private static final int SNAP_VELOCITY = 600; // 默认的滚动速度
- // 之后用于和手指滑动产生的速度比较
- // 获取屏幕滚动的速度
- private int mTouchState = TOUCH_STATE_REST; // 当前手指的事件状态
- private int mTouchSlop; // 手指移动的最小距离的判断标准
- // =ViewConfiguration.get(getContext()).getScaledTouchSlop();
- // 在viewpapper中就是依赖于这个值来判断用户
- // 手指滑动的距离是否达到界面滑动的标准
- private float mLastMotionX; // 手指移动的时候,或者手指离开屏幕的时候记录下的手指的横坐标
- private float mLastMotionY; // 手指移动的时候,或者手指离开屏幕的时候记录下的手指的纵坐标
- public ScrollLyout(Context context) {
- super(context);
- Logger.e(TAG, "----ScrollLyout1---");
- }
- public ScrollLyout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- Logger.e(TAG, "----ScrollLyout2---");
- // 初始化基本数据
- mScroller = new Scroller(context);
- mCurScreen = mDefaultScreen;
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); // 使用系统默认的值
- }
- public ScrollLyout(Context context, AttributeSet attrs) {
- this(context,attrs,0);
- Logger.e(TAG, "----ScrollLyout3---");
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- Logger.e(TAG, "----onMeasure----");
- // 在onlayout之前执行,获取View申请的大小,把它们保存下来,方面后面使用
- final int width = MeasureSpec.getSize(widthMeasureSpec);
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- if (widthMode != MeasureSpec.EXACTLY) {
- throw new IllegalStateException(
- "ScrollLayout only can run at EXACTLY mode!");
- }
- final int hightModed = MeasureSpec.getMode(heightMeasureSpec);
- if (hightModed != MeasureSpec.EXACTLY) {
- throw new IllegalStateException(
- "ScrollLayout only can run at EXACTLY mode!");
- }
- final int count = getChildCount();
- // 为每一个孩子设置它们的大小为ScrollLayout的大小
- for (int i = 0; i < count; i++) {
- getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
- }
- scrollTo(mCurScreen * width, 0);
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- Logger.e(TAG, "----onLayout----");
- // 为每一个孩子设置它们的位置
- if (changed) {
- int childLeft = 0;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View childView = getChildAt(i);
- if (childView.getVisibility() != View.GONE) {
- // 此处获取到的宽度就是在onMeasure中设置的值
- final int childWidth = childView.getMeasuredWidth();
- // 为每一个子View布局
- childView.layout(childLeft, 0, childLeft + childWidth,
- childView.getMeasuredHeight());
- childLeft = childLeft + childWidth;
- }
- }
- }
- }
- /**
- * 让界面跟着手指移动到手指移动的地点
- */
- public void snapToDestination() {
- Logger.e(TAG, "----snapToDestination---");
- final int screenWidth = getWidth(); // 子view的宽度,此例中为他适配的父view的宽度
- Logger.e(TAG, "screenWidth = "+screenWidth);
- final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth; // 某个算法吧,
- Logger.e(TAG, "[destScreen] : "+destScreen); // 我计算了一下的确是能够准确算出目标view
- // getScroolX()值为
- snap2DestScreen(destScreen);
- }
- /**
- * 滚动到指定screen
- *
- * @param destScreen
- */
- private void snap2DestScreen(int destScreen) {
- Logger.e(TAG, "----snap2DestScreen----");
- Logger.e(TAG, "Math.min(destScreen, getChildCount() - 1) = "+(Math.min(destScreen, getChildCount() - 1)));
- destScreen = Math.max(0, Math.min(destScreen, getChildCount() - 1));// 获取要滚动到的目标screen
- Logger.e(TAG, "whichScreen = "+destScreen);
- if (getScrollX() != (getWidth() * destScreen)) {
- final int delta = destScreen * getWidth() - getScrollX(); // 获取屏幕移到目的view还需要移动多少距离
- Logger.e(TAG, "[getScrollX()] : "+getScrollX() );
- Logger.e(TAG, "[delta] : "+delta);
- Logger.e(TAG, "[getScrollX要走到的位置为] : "+(getScrollX()+delta));
- mScroller.startScroll(getScrollX(), 0, delta, 0,
- Math.abs(delta) * 2);// 使用Scroller辅助滚动,让滚动变得更平滑
- mCurScreen = destScreen;
- invalidate();// 重绘界面
- }
- }
- @Override
- public void computeScroll() {
- Logger.e(TAG, "----computeScroll----");
- if(mScroller.computeScrollOffset()){
- scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
- invalidate();
- }
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Logger.e(TAG, "----onTouchEvent----");
- 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://1,终止滚动2,获取最后一次事件的x值
- Logger.e(TAG, "----ACTION_DOWN----");
- if(!mScroller.isFinished()){
- mScroller.abortAnimation();
- }
- mLastMotionX = x;
- break;
- case MotionEvent.ACTION_MOVE://1,获取最后一次事件的x值2,滚动到指定位置
- Logger.e(TAG, "----ACTION_MOVE----");
- int deltaX = (int) (mLastMotionX - x );
- mLastMotionX = x;
- scrollBy(deltaX, 0);
- break;
- case MotionEvent.ACTION_UP://1,计算手指移动的速度并得出我们需要的速度2,选择不同情况下滚动到哪个 screen
- Logger.e(TAG, "----ACTION_UP----");
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000); // 设置属性为计算1秒运行多少个像素
- // computeCurrentVelocity(int
- // units, float
- // maxVelocity)上面的1000即为此处的units。
- // maxVelocity必须为正,表示当计算出的速率大于maxVelocity时为maxVelocity
- //小于maxVelocity就为计算出的速率
- int velocityX = (int) velocityTracker.getXVelocity();
- Logger.e(TAG, "[velocityX] : "+velocityX);
- if(velocityX > SNAP_VELOCITY && mCurScreen > 0){//如果速度为正,则表示向右滑动。需要指定mCurScreen大于0,才能滑,不然就不准确啦
- Logger.e(TAG, "速度为正且-->:当前mCurScreen = "+mCurScreen);
- Logger.e(TAG, "要走到的:mCurScreen = "+(mCurScreen-1));
- snap2DestScreen(mCurScreen - 1);
- }else if(velocityX < -SNAP_VELOCITY && mCurScreen < (getChildCount() - 1)){//如果速度为负,则表示手指向左滑动。需要指定mCurScreen小于最后一个子view的id,才能滑,不然就不准确啦
- Logger.e(TAG, "速度为fu且《--:当前mCurScreen = "+mCurScreen);
- Logger.e(TAG, "要走到的:mCurScreen = "+(mCurScreen+1));
- snap2DestScreen(mCurScreen + 1);
- }else{ //速度小于我们规定的达标速度,那么就让界面跟着手指滑动显示。最后显示哪个screen再做计算(方法中有计算)
- Logger.e(TAG, "速度的绝对值小于规定速度,走snapToDestination方法");
- snapToDestination();
- }
- if(mVelocityTracker != null){
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mTouchState = TOUCH_STATE_REST; //为什么这里要设置???
- break;
- case MotionEvent.ACTION_CANCEL://1,设置触摸事件状态为空闲
- Logger.e(TAG, "----ACTION_CANCEL----");
- mTouchState = TOUCH_STATE_REST;
- break;
- }
- return true;
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Logger.e(TAG, "----onInterceptTouchEvent----");
- 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_DOWN:// 判断滚动是否停止
- Logger.e(TAG, "----ACTION_DOWN----");
- mLastMotionX = x;
- mLastMotionY = y;
- mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
- : TOUCH_STATE_SCROLLING;
- break;
- case MotionEvent.ACTION_MOVE:// 判断是否达成滚动条件
- Logger.e(TAG, "----ACTION_MOVE----");
- final int xDiff = (int) Math.abs(mLastMotionX - x);
- if (xDiff > mTouchSlop) {// 如果该值大于我们规定的最小移动距离则表示界面在滚动
- mTouchState = TOUCH_STATE_SCROLLING;
- }
- break;
- case MotionEvent.ACTION_UP:// 把状态调整为空闲
- Logger.e(TAG, "----ACTION_UP----");
- mTouchState = TOUCH_STATE_REST;
- break;
- }
- // 如果屏幕没有在滚动那么就不消耗这个touch事件
- return mTouchState != TOUCH_STATE_REST;
- }
- }
Logger是封装了Log的类,负责打印log。
ScrollLayout类中方法运行的流程:
getScrollX()这个方法的值:
有需要的筒子戳下面的连接下载源码。