仿照Launcher的Workspace实现左右滑动切换

   对于Launcher的桌面滑动大家应该都比较熟悉了,最好的体验应该是可以随着手指的滑动而显示不同位置的桌面。
    昨天公司要我实现桌面Launcher这种效果,就是顶部布局不变,中间是一个可以滑动类似桌面Launcher的布局,而底部也是不动的布局。难点主要是中间那个可以实现图片滑动功能的ScrollLayout布局,下面我把实现ScrollLayout布局的代码贴出来,代码也是别人的,我只是注释了一下罢了:

package cn.flyaudio.android.lichunan;

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,可以左右滑动切换屏幕的类 
 * @author 李楚男
 * 该类继承ViewGroup容器类以实现自己需要的布局显示
 */
public class ScrollLayout extends ViewGroup{
	
	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;   
    /**横坐标的最终位置*/
    private float mLastMotionX;     
    /**纵坐标的最终位置*/
   private float mLastMotionY;    
    
    public ScrollLayout(Context context, AttributeSet attrs) {       
        this(context, attrs, 0);            
    }   
       
    public ScrollLayout(Context context) {       
        super(context);             
    }       
       
    public ScrollLayout(Context context, AttributeSet attrs, int defStyle) {       
        super(context, attrs, defStyle);  
        
        /**使用缺省的持续时间和动画插入器创建一个Scroller*/
        mScroller = new Scroller(context);       
               
        mCurScreen = mDefaultScreen;       
        
        /**是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件*/
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();    
        
    }       
    /**
     * 为每一个子view指定size和position
     */
    @Override       
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {              
        if (changed) { 
        	/**子view离父view左边的距离*/
            int childLeft = 0;    
            /**获取子view数目*/
            final int childCount = getChildCount();       
                   
            for (int i=0; i<childCount; i++) {   
            	/**获取子view*/
                final View childView = getChildAt(i);    
                
                if (childView.getVisibility() != View.GONE) {//如果子view可见的话     
                	/**获取子view的宽度*/
                    final int childWidth = childView.getMeasuredWidth();  
                    /**为子view设置大小和位置*/
                    childView.layout(childLeft, 0,        
                            childLeft+childWidth, childView.getMeasuredHeight());   
                    /**左边距自加子view宽度,从而得到下一个子view的x坐标*/
                    childLeft += childWidth;                 
                }       
            }       
        }       
    } 
     
    /**
     * 指明控件可获得的空间以及关于这个空间描述的元数据
     */
    @Override         
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          
        Log.e(TAG, "onMeasure");       
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);          
         /**int specMode = MeasureSpec.getMode(measureSpec);
		  *	int specSize = MeasureSpec.getSize(measureSpec);
          * 依据specMode的值,如果是AT_MOST,specSize 代表的是最大可获得的空间;
          * 如果是EXACTLY,specSize 代表的是精确的尺寸;
          * 如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。
          */
        final int widthSize = 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!");       
        }          
         
        //给每一个子view给予相同的空间          
        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 * widthSize, 0);                
    }         
           
    /**    
     * 根据当前布局的位置,滚动到目的页面    
     */       
    public void snapToDestination() {    
    	/**获取view的宽度*/
        final int screenWidth = getWidth();  
        Log.i(TAG, "screenWidth: " + screenWidth + " screenWidth/2: " + screenWidth/2);
        Log.i(TAG, "getScrollX():" + getScrollX());
        /**
         * getScrollX():获得滚动后view的横坐标
         */
        final int destScreen = (getScrollX()+ screenWidth/2)/screenWidth;       
        snapToScreen(destScreen);       
    }       
           
    public void snapToScreen(int whichScreen) {       
        // 获取有效页面       
        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)*2);       
            mCurScreen = whichScreen;       
            invalidate();       // 使view重画       
        }       
    }       
           
    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;       
    }       
    /**
     * 由父视图调用,用于通知子视图在必要时更新 mScrollX 和 mScrollY 的值
     * 该操作主要用于子视图使用 Scroller 进行动画滚动时。       
     */
    @Override       
    public void computeScroll() {            
        if (mScroller.computeScrollOffset()) {//返回true,表示动画仍在进行,还没有停止       
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());// 滚动到目标坐标       
            postInvalidate();  //使view重画     
        }       
    }       
    
    /**
     * 触摸监听事件
     */
    @Override       
    public boolean onTouchEvent(MotionEvent event) {       
             
        if (mVelocityTracker == null) {       
            mVelocityTracker = VelocityTracker.obtain();//获取mVelocityTracker实例对象       
        }      
        
        /**将当前的移动事件传递给mVelocityTracker对象*/
        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();//Scrooller停止动画行为       
            }       
            mLastMotionX = x;       
            break;       
                   
        case MotionEvent.ACTION_MOVE://当手指滑动时    
            int deltaX = (int)(mLastMotionX - x);       
            mLastMotionX = x;       
            /**
             * scrollBy是将view的内容移动多大的距离
             * deltaX:指移动的距离
             */
            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);     
            /**获取当前x方向的速度*/
            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) {//释放VelocityTracker对象        
                mVelocityTracker.recycle();          
                mVelocityTracker = null;          
            }          
            // }          
            mTouchState = TOUCH_STATE_REST;          
            break;       
        case MotionEvent.ACTION_CANCEL:       
            mTouchState = TOUCH_STATE_REST;       
            break;       
        }       
               
        return true;       
    }       
    /**
     * 该方法是用于拦截手势事件的,每个手势事件都会先调用
     * 此方法返回false,则手势事件会向子控件传递
     * 返回true,则调用onTouchEvent方法
     */
    @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:       
            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;       
    }       

	
	
}

                                          
          

 布局文件:main.xml

              

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:background="@drawable/bg"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<cn.flyaudio.android.lichunan.ScrollLayout       
  android:id="@+id/ScrollLayout"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <include layout="@layout/menu_center1"/>
  <include layout="@layout/menu_center2"/>
  <include layout="@layout/menu_center3"/>
</cn.flyaudio.android.lichunan.ScrollLayout>
<ImageButton android:id="@+id/menu_tracklist"
			 android:layout_width="wrap_content"
			 android:layout_height="wrap_content"
			 android:layout_centerVertical="true"
			 android:layout_alignParentRight="true"
			 android:background="@drawable/menu_tracklist_imagebutton"/>
<include layout="@layout/menu_bottom"/>
</RelativeLayout>

 

           

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值