首页 > 代码库 > Android中左右滑屏实现

Android中左右滑屏实现

在网上搜索了下滑屏的实现,自己整理了下,

代码如下:

package kexc.scroll;

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;

/**
 * 仿Launcher中的WorkSapce,可以左右滑动切换屏幕的类
 * 
 */
public class ScrollLayout extends ViewGroup {

/*
  * onMeasure方法在控件的父元素正要放置它的子控件时调用。它会问一个问题,“你想要用多大地方啊?”,然后传入两个参数——
  * widthMeasureSpec和heightMeasureSpec。它们指明控件可获得的空间以及关于这个空间描述的元数据。
  * 比返回一个结果要好的方法是你传递View的高度和宽度到setMeasuredDimension方法里。
  * 一个MeasureSpec包含一个尺寸和模式。
  * 有三种可能的模式: 
  * UNSPECIFIED:父布局没有给子布局任何限制,子布局可以任意大小。
  * EXACTLY:父布局决定子布局的确切大小。不论子布局多大,它都必须限制在这个界限里。
  * AT_MOST:子布局可以根据自己的大小选择任意大小。
  */

 /*
  * VelocityTracker类
  * 
  * 功能: 根据触摸位置计算每像素的移动速率。
  * 
  * 常用方法有:
  * 
  * public void addMovement (MotionEvent ev) 功能:添加触摸对象MotionEvent , 用于计算触摸速率。
  * public void computeCurrentVelocity (int units)
  * 功能:以每像素units单位考核移动速率。额,其实我也不太懂,赋予值1000即可。 参照源码 该units的意思如下: 参数 units :
  * The units you would like the velocity in. A value of 1 provides pixels
  * per millisecond, 1000 provides pixels per second, etc. public float
  * getXVelocity () 功能:获得X轴方向的移动速率。
  */
 /*
  * ViewConfiguration类
  * 
  * 功能: 获得一些关于timeouts(时间)、sizes(大小)、distances(距离)的标准常量值 。
  * 
  * 常用方法:
  * 
  * public int getScaledEdgeSlop()
  * 
  * 说明:获得一个触摸移动的最小像素值。也就是说,只有超过了这个值,才代表我们该滑屏处理了。
  * 
  * public static int getLongPressTimeout()
  * 
  * 说明:获得一个执行长按事件监听(onLongClickListener)的值。也就是说,对某个View按下触摸时,只有超过了
  * 
  * 这个时间值在,才表示我们该对该View回调长按事件了;否则,小于这个时间点松开手指,只执行onClick监听
  */
 private static final String TAG = "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;// change 多少像素算是发生move操作
 private float mLastMotionX;
 private float mLastMotionY;

 public ScrollLayout(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
  // TODO Auto-generated constructor stub
 }

 public ScrollLayout(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  // TODO Auto-generated constructor stub
  mScroller = new Scroller(context);

  mCurScreen = mDefaultScreen;
  //初始化一个最小滑动距离
  mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 }

 /**
  * 生成view
  */
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  // TODO Auto-generated method stub
  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) {
     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.
  */
 public void snapToDestination() {
  // 判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕
  // 这样的一个简单公式意思是:假设当前滑屏偏移值即 scrollCurX 加上每个屏幕一半的宽度,除以每个屏幕的宽度就是
  // 我们目标屏所在位置了。 假如每个屏幕宽度为320dip, 我们滑到了500dip处,很显然我们应该到达第二屏,索引值为1
  // 即(500 + 320/2)/320 = 1
  final int screenWidth = getWidth();
  final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth;
  snapToScreen(destScreen);
 }

 public void snapToScreen(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) * 5);
   mCurScreen = whichScreen;
   onScreenChangeListener.onScreenChange(mCurScreen);
   invalidate(); // Redraw the layout
  }
 }

 public void setToScreen(int whichScreen) {
  whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
  mCurScreen = whichScreen;
  scrollTo(whichScreen * getWidth(), 0);
 }

 public int getCurScreen() {
  return mCurScreen;
 }

 /**
  * 控制view跟随手指滑动 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
  */
 @Override
 public void computeScroll() {
  // 如果返回true,表示动画还没有结束
  // 因为前面startScroll,所以只有在startScroll完成时 才会为false
  if (mScroller.computeScrollOffset()) {
   // 产生了动画效果,根据当前值 每次滚动一点
   scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
   postInvalidate();
  }
 }

 /*
  * 其中:onInterceptTouchEvent()主要功能是控制触摸事件的分发,例如是子视图的点击事件还是滑动事件。
  * 其他所有处理过程均在onTouchEvent()方法里实现了。 1、屏幕的滑动要根据手指的移动而移动 ----
  * 主要实现在onTouchEvent()方法中
  * 
  * 2、当手指松开时,可能我们并没有完全滑动至某个屏幕上,这是我们需要手动判断当前偏移至去计算目标屏(当前屏或者
  * 
  * 前后屏),并且优雅的偏移到目标屏(当然是用Scroller实例咯)。
  * 
  * 3、调用computeScroll ()去实现缓慢移动过程。
  */
 @Override
 public boolean onTouchEvent(MotionEvent event) {

  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;
   break;

  case MotionEvent.ACTION_MOVE:
   int deltaX = (int) (mLastMotionX - x);
   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
    // onScreenChangeListener.onScreenChange(mCurScreen - 1);
    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");
    // onScreenChangeListener.onScreenChange(mCurScreen + 1);
    snapToScreen(mCurScreen + 1);
   }
   //以上为快速移动的 ,强制切换屏幕 
   else {
    //缓慢移动的,因此先判断是保留在本屏幕还是到下一屏幕
    snapToDestination();
   }

   if (mVelocityTracker != null) {
    mVelocityTracker.recycle();
    mVelocityTracker = null;
   }
   // }
   // mTouchState = TOUCH_STATE_REST;
   break;
  }

  return true;
 }

 /**
  * touch 事件是否继续从父控件向子空间传递 true 不传递
  */
 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  // TODO Auto-generated method stub
  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:
   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;
 }

 // 监听页面变动
 public interface OnScreenChangeListener {
  void onScreenChange(int currentIndex);
 }

 private OnScreenChangeListener onScreenChangeListener;

 public void setOnScreenChangeListener(
   OnScreenChangeListener onScreenChangeListener) {
  this.onScreenChangeListener = onScreenChangeListener;
 }

}

 

package kexc.scroll;

import kexc.scroll.ScrollLayout.OnScreenChangeListener;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class PageControlView extends LinearLayout {

 private Context context;
 private int count;

 public PageControlView(Context context) {
  super(context);
  this.context = context;
 }

 public PageControlView(Context context, AttributeSet attrs) {
  super(context, attrs);
  this.context = context;
 }

 public void bindScrollLayout(ScrollLayout scrollLayout) {
  this.count = scrollLayout.getChildCount();
  Log.v("=count==", count + "=");
  // 初始化第一屏
  generatePageControl(0);
  scrollLayout.setOnScreenChangeListener(new OnScreenChangeListener() {

   @Override
   public void onScreenChange(int currentIndex) {

    generatePageControl(currentIndex);
   }
  });
 }

 private void generatePageControl(int currentIndex) {
  this.removeAllViews();
  for (int i = 0; i < this.count; i++) {
   Log.v("=i==", i + "=");
   ImageView imageView = new ImageView(context);
   LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
     LayoutParams.WRAP_CONTENT);
   llp.setMargins(10, 0, 0, 10);
   imageView.setLayoutParams(llp);
   if (i == currentIndex) {
    imageView.setImageResource(R.drawable.page_indicator_focused);
   } else {

    imageView.setImageResource(R.drawable.page_indicator);
   }
   this.addView(imageView);
  }
 }

}

 

package kexc.scroll;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.Button;

public class ScrollLayoutActivity extends Activity {
 private ScrollLayout sl;
 private Button btn1;
 private PageControlView pageControlView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  sl = (ScrollLayout) findViewById(R.id.ScrollLayoutTest);
  pageControlView = (PageControlView) findViewById(R.id.pageControl);
  btn1 = (Button) findViewById(R.id.btn1);
  btn1.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    sl.snapToScreen(1);
   }
  });
  sl.setOnFocusChangeListener(new OnFocusChangeListener() {

   @Override
   public void onFocusChange(View v, boolean hasFocus) {
    int i = v.getVisibility();
    Log.v("=sl.i=", i + "=");
   }
  });
  Log.v("=sl.getCurScreen()=", sl.getCurScreen() + "=");
  pageControlView.bindScrollLayout(sl);
 }
}

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <kexc.scroll.ScrollLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/ScrollLayoutTest"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#FF00" >
        </LinearLayout>

        <FrameLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#F0F0" >
        </FrameLayout>

        <FrameLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#F00F" >
        </FrameLayout>

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#FF00" >

            <Button
                android:id="@+id/btn1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Button1" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Button2" />
        </LinearLayout>
    </kexc.scroll.ScrollLayout>

    <kexc.scroll.PageControlView
        android:id="@+id/pageControl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true" >
    </kexc.scroll.PageControlView>

</RelativeLayout>