本篇文章使用一个小程序实现Android之ScrollLayout左右滑动效果


第一步:新建Android工程

添加图片资源,并设置效果

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="true" android:drawable="@drawable/page_indicator_unfocused" />
    <item android:state_enabled="false" android:drawable="@drawable/page_indicator_focused" />
</selector>



第二步:添加实现左右滑动效果的源文件

1.视图改变接口

public interface OnViewChangeListener {
	public void OnViewChange(int view);
}


2.实现左右滑动效果的源文件,以后我们可以直接使用这个文件

public class MyScrollLayout extends ViewGroup
{
	private static final String TAG = "ScrollLayout";
	private VelocityTracker mVelocityTracker;// 用于判断甩动手势
	private Scroller mScroller;// 滑动控制
	private int mCurScreen;
	private int mDefaultScreen = 0;
	private float mLastMotionX; // 手指移动的时候,或者手指离开屏幕的时候记录下的手指的横坐标
	private float mLastMotionY; // 手指移动的时候,或者手指离开屏幕的时候记录下的手指的纵坐标
	private int mTouchSlop;// 手指移动的最小距离的判断标准
							// =ViewConfiguration.get(getContext()).getScaledTouchSlop();
							// 在viewpapper中就是依赖于这个值来判断用户
							// 手指滑动的距离是否达到界面滑动的标准
	private static final int SNAP_VELOCITY = 600;// 默认的滚动速度,之后用于和手指滑动产生的速度比较,获取屏幕滚动的速度
	private static final int TOUCH_STATE_REST = 0;//表示触摸状态为空闲,即没有触摸或者手指离开了
	private static final int TOUCH_STATE_SCROLLING = 1;//表示手指正在移动
	private int mTouchState = TOUCH_STATE_REST;// 当前手指的事件状态
	
	private OnViewChangeListener mOnViewChangeListener;
	
	public MyScrollLayout(Context context)
	{
		super(context);
		Log.i(TAG, "---ScrollLyout1---");
		init(context);
	}
	public MyScrollLayout(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		Log.i(TAG, "---ScrollLyout2---");
		init(context);
	}
	public MyScrollLayout(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		Log.i(TAG, "---ScrollLyout3---");
		init(context);
	}
	private void init(Context context)
	{
		mCurScreen = mDefaultScreen;
		mScroller = new Scroller(context);
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); // 使用系统默认的值
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b)
	{
		Log.i(TAG, "---onLayout---");
		// 为每一个孩子设置它们的位置
		if(changed)
		{
			int childLeft = 0;
			final 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;
				}
			}
		}
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		Log.i(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();
		for(int i=0; i<count; i++)
		{
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}
		scrollTo(mCurScreen*width, 0);
	}
	
	/**
	 * 让界面跟着手指移动到手指移动的地点
	 */
	public void snapToDestination()
	{
		Log.i(TAG, "---snapToDestination---");
		final int screenWidth = getWidth();// 子view的宽度,此例中为他适配的父view的宽度
		Log.i(TAG, "screenWidth = "+screenWidth);
		final int destScreen = (getScrollX()+screenWidth/2)/screenWidth;// 某个算法吧
		Log.i(TAG, "[destScreen] : "+destScreen);// getScroolX()值为
		snapToScreen(destScreen);
	}
	
	/**
	 * 滚动到指定screen
	 */
	public void snapToScreen(int whichScreen)
	{
		Log.i(TAG, "---snapToDestScreen---");
		Log.i(TAG, "Math.min(destScreen, getChildCount() - 1) = "+(Math.min(whichScreen, getChildCount() - 1)));
		whichScreen = Math.max(0,Math.min(whichScreen,getChildCount()-1));// 获取要滚动到的目标screen
		Log.i(TAG, "whichScreen = "+whichScreen);
		if (getScrollX() != (whichScreen*getWidth()))
		{
			final int delta = whichScreen*getWidth()-getScrollX();// 获取屏幕移到目的view还需要移动多少距离
			Log.i(TAG, "[getScrollX()] : "+getScrollX() );
			Log.i(TAG, "[delta] : "+delta);
			Log.i(TAG, "[getScrollX要走到的位置为] : "+(getScrollX()+delta));
			mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta)*2);// 使用Scroller辅助滚动,让滚动变得更平滑
			mCurScreen = whichScreen;
			invalidate();// 重绘界面
			
			if (mOnViewChangeListener != null)
			{
				mOnViewChangeListener.OnViewChange(mCurScreen);
			}
		}
	}
	
	@Override
	public void computeScroll()
	{
		Log.i(TAG, "---computeScroll---");
		if (mScroller.computeScrollOffset())
		{
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
			//invalidate();
		}
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		Log.i(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值
			Log.i(TAG, "onTouchEvent:ACTION_DOWN");
			if (mVelocityTracker == null)
			{
				mVelocityTracker = VelocityTracker.obtain();
				mVelocityTracker.addMovement(event);
			}
			if (!mScroller.isFinished())
			{
				mScroller.abortAnimation();
			}
			mLastMotionX = x;
			break;
		case MotionEvent.ACTION_MOVE://1,获取最后一次事件的x值2,滚动到指定位置
			Log.i(TAG, "onTouchEvent:ACTION_MOVE");
			int deltaX = (int)(mLastMotionX - x);
			if(IsCanMove(deltaX))
			{
				if (mVelocityTracker != null)
				{
					mVelocityTracker.addMovement(event);
				}
				mLastMotionX = x;
				scrollBy(deltaX, 0);
			}
			break;
		case MotionEvent.ACTION_UP://1,计算手指移动的速度并得出我们需要的速度2,选择不同情况下滚动到哪个screen
			Log.i(TAG, "onTouchEvent:ACTION_UP");
			int velocityX = 0;
			if (mVelocityTracker != null)
			{
				mVelocityTracker.addMovement(event);
				mVelocityTracker.computeCurrentVelocity(1000);// 设置属性为计算1秒运行多少个像素
				// computeCurrentVelocity(int units, float maxVelocity)上面的1000即为此处的units。
				// maxVelocity必须为正,表示当计算出的速率大于maxVelocity时为maxVelocity,小于maxVelocity就为计算出的速率
				velocityX = (int)mVelocityTracker.getXVelocity();
				Log.i(TAG, "[velocityX] : "+velocityX);
			}
			if (velocityX > SNAP_VELOCITY && mCurScreen > 0)//如果速度为正,则表示向右滑动。需要指定mCurScreen大于0,才能滑,不然就不准确啦
			{
				// Fling enough to move left
				Log.i(TAG, "snap left");
				Log.i(TAG, "速度为正且-->:当前mCurScreen = "+mCurScreen);
				Log.i(TAG, "要走到的:mCurScreen = "+(mCurScreen-1));
				snapToScreen(mCurScreen - 1);
			}
			else if (velocityX < -SNAP_VELOCITY && mCurScreen < getChildCount() - 1)//如果速度为负,则表示手指向左滑动。需要指定mCurScreen小于最后一个子view的id,才能滑,不然就不准确啦
			{
				// Fling enough to move right
				Log.i(TAG, "snap right");
				Log.i(TAG, "速度为fu且《--:当前mCurScreen = "+mCurScreen);
				Log.i(TAG, "要走到的:mCurScreen = "+(mCurScreen+1));
				snapToScreen(mCurScreen + 1);
			}
			else//速度小于我们规定的达标速度,那么就让界面跟着手指滑动显示。最后显示哪个screen再做计算(方法中有计算)
			{
				Log.i(TAG, "速度的绝对值小于规定速度,走snapToDestination方法");
				snapToDestination();
			}
			if (mVelocityTracker != null)
			{
				mVelocityTracker.recycle();
				mVelocityTracker = null;
			}
			//mTouchState = TOUCH_STATE_REST;		//为什么这里要设置???
			break;
		case MotionEvent.ACTION_CANCEL://1,设置触摸事件状态为空闲
			Log.i(TAG, "onTouchEvent:ACTION_CANCEL");
			mTouchState = TOUCH_STATE_REST;
			break;
		default:
			break;
		}
		return true;
	}
	
	private boolean IsCanMove(int deltaX)
	{
		if (getScrollX() <= 0 && deltaX < 0)
		{
			return false;
		}
		if (getScrollX() >= (getChildCount() - 1)* getWidth() && deltaX > 0)
		{
			return false;
		}
		return true;
	}
	
	public void SetOnViewChangeListener(OnViewChangeListener listener)
	{
		mOnViewChangeListener = listener;
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.i(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:// 判断滚动是否停止
			Log.i(TAG, "onInterceptTouchEvent:ACTION_DOWN");
			mLastMotionX = x;
			mLastMotionY = y;
			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
					: TOUCH_STATE_SCROLLING;

			break;
		case MotionEvent.ACTION_MOVE:// 判断是否达成滚动条件
			Log.i(TAG, "onInterceptTouchEvent:ACTION_MOVE");
			final int xDiff = (int) Math.abs(mLastMotionX - x);
			if (xDiff > mTouchSlop) {// 如果该值大于我们规定的最小移动距离则表示界面在滚动
				mTouchState = TOUCH_STATE_SCROLLING;
			}
			break;
		case MotionEvent.ACTION_UP:// 把状态调整为空闲
			Log.i(TAG, "onInterceptTouchEvent:ACTION_UP");
			mTouchState = TOUCH_STATE_REST;
			break;

		}
		// 如果屏幕没有在滚动那么就不消耗这个touch事件
		return mTouchState != TOUCH_STATE_REST;
	}
}



第三步:添加布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#000000"
    android:orientation="vertical" >
    
    <RelativeLayout 
        android:layout_width="fill_parent"
        android:layout_height="45dp"
        android:background="#000000"
        android:gravity="center_vertical">
        <TextView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textSize="20sp"
            android:textColor="#FFFFFF"
            android:text="ScrollLayout"/>
    </RelativeLayout>
    
    <RelativeLayout 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#000000"
        android:gravity="center_vertical">
        <com.scrolllayoutdemo.MyScrollLayout 
	        xmlns:android="http://schemas.android.com/apk/res/android"
	        android:id="@+id/ScrollLayout"
	        android:layout_width="fill_parent"
	        android:layout_height="fill_parent"
	        android:visibility="visible">
	        <RelativeLayout 
	            android:background="#FF0000"
	            android:layout_width="fill_parent"
			    android:layout_height="fill_parent">
	        </RelativeLayout>
	        <RelativeLayout 
	            android:background="#00FF00"
	            android:layout_width="fill_parent"
			    android:layout_height="fill_parent">
	        </RelativeLayout>
	        <RelativeLayout 
	            android:background="#0000FF"
	            android:layout_width="fill_parent"
			    android:layout_height="fill_parent">
	        </RelativeLayout>
	    </com.scrolllayoutdemo.MyScrollLayout>
	    
	    <LinearLayout 
	        android:layout_below="@id/ScrollLayout"
	        android:orientation="horizontal"
	        android:id="@+id/llayout"
	        android:layout_width="wrap_content"
	        android:layout_height="wrap_content"
	        android:layout_centerHorizontal="true"
	        android:layout_marginBottom="50.0dip"
			android:visibility="visible">
	        <ImageView 
	            android:clickable="true" 
	            android:padding="5.0dip" 
	            android:layout_gravity="center_vertical" 
	            android:layout_width="wrap_content" 
	            android:layout_height="wrap_content" 
	            android:src="@drawable/page_indicator_bg" />
	        <ImageView 
	            android:clickable="true" 
	            android:padding="5.0dip" 
	            android:layout_gravity="center_vertical" 
	            android:layout_width="wrap_content" 
	            android:layout_height="wrap_content" 
	            android:src="@drawable/page_indicator_bg" />
	        <ImageView 
	            android:clickable="true" 
	            android:padding="5.0dip" 
	            android:layout_gravity="center_vertical" 
	            android:layout_width="wrap_content" 
	            android:layout_height="wrap_content" 
	            android:src="@drawable/page_indicator_bg" />
	    </LinearLayout>
    </RelativeLayout>
    
</LinearLayout>



第四步:在主界面实现效果

public class MainActivity extends Activity implements OnViewChangeListener
{
	private MyScrollLayout mScrollLayout;
	private LinearLayout pointLLayout;
	
	private ImageView[] imgs;
	private int count;
	private int currentItem;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	
	@Override
	public void onStart()
	{
		super.onStart();
		
		initView();
	}
	
	private void initView()
	{ 
		mScrollLayout = (MyScrollLayout)findViewById(R.id.ScrollLayout);
		pointLLayout = (LinearLayout)findViewById(R.id.llayout);
		count = mScrollLayout.getChildCount();
		imgs = new ImageView[count];
		for(int i = 0; i< count;i++) {
			imgs[i] = (ImageView) pointLLayout.getChildAt(i);
			imgs[i].setEnabled(true);
			imgs[i].setTag(i);
		}
		currentItem = 0;
		imgs[currentItem].setEnabled(false);
		mScrollLayout.SetOnViewChangeListener(this);
	}

	@Override
	public void onResume()
	{
		super.onResume();
	}

	@Override
	public void onPause()
	{
		super.onPause();
	}

	@Override
	public void onStop()
	{
		super.onStop();
	}

	@Override
	public void onDestroy()
	{
		super.onDestroy();
	}

	@Override
	public void OnViewChange(int view)
	{
		setcurrentPoint(view);
	}
	
	private void setcurrentPoint(int position)
	{
		if(position<0 || position>count-1 || currentItem == position)
		{
			return;
		}
		imgs[currentItem].setEnabled(true);
		imgs[position].setEnabled(false);
		currentItem = position;
	}
}



第五步:查看效果

Android view左右滑动动画 安卓滑动效果_ScrollLayout

 

Android view左右滑动动画 安卓滑动效果_ci_02

 

Android view左右滑动动画 安卓滑动效果_Android view左右滑动动画_03