首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 移动开发 > Android >

android中的上下滑动

2012-07-01 
android中的左右滑动iphone中有很多应用都能够左右滑动,非常cool,关键是实现起来非常简单。android比起来就

android中的左右滑动
iphone中有很多应用都能够左右滑动,非常cool,关键是实现起来非常简单。android比起来就差远了,网上有不少帖子。 我在这边重新分享下自己的经验吧,将实现细节详细解释下。

FlingGallery这个类摘自网上,有少许修改。

package com.nuomi.ui;import java.util.HashSet;import java.util.Set;import android.content.Context;import android.view.GestureDetector;import android.view.KeyEvent;import android.view.MotionEvent;import android.view.View;import android.view.animation.Animation;import android.view.animation.AnimationUtils;import android.view.animation.Interpolator;import android.view.animation.Transformation;import android.widget.Adapter;import android.widget.FrameLayout;import android.widget.LinearLayout;public class FlingGallery extends FrameLayout{private Set<OnGalleryChangeListener> listeners;private final int swipe_min_distance = 120;    private final int swipe_max_off_path = 250;    private final int swipe_threshold_veloicty = 400;private int mViewPaddingWidth = 0;    private int mAnimationDuration = 250;    private float mSnapBorderRatio = 0.5f;    private boolean mIsGalleryCircular = true;    private int mGalleryWidth = 0;    private boolean mIsTouched = false;    private boolean mIsDragging = false;    private float mCurrentOffset = 0.0f;    private long mScrollTimestamp = 0;    private int mFlingDirection = 0;    private int mCurrentPosition = 0;    private int mCurrentViewNumber = 0;    private Context mContext;    private Adapter mAdapter;    private FlingGalleryView[] mViews;    private FlingGalleryAnimation mAnimation;    private GestureDetector mGestureDetector;    private Interpolator mDecelerateInterpolater;    public FlingGallery(Context context){super(context);listeners = new HashSet<OnGalleryChangeListener>();mContext = context;mAdapter = null;        mViews = new FlingGalleryView[3];        mViews[0] = new FlingGalleryView(0, this);        mViews[1] = new FlingGalleryView(1, this);        mViews[2] = new FlingGalleryView(2, this);mAnimation = new FlingGalleryAnimation();mGestureDetector = new GestureDetector(new FlingGestureDetector());mDecelerateInterpolater = AnimationUtils.loadInterpolator(mContext, android.R.anim.decelerate_interpolator);}    public void addGalleryChangeListener(OnGalleryChangeListener listener){    listeners.add(listener);    }    public void setPaddingWidth(int viewPaddingWidth){mViewPaddingWidth = viewPaddingWidth;}public void setAnimationDuration(int animationDuration){mAnimationDuration = animationDuration;}public void setSnapBorderRatio(float snapBorderRatio){mSnapBorderRatio = snapBorderRatio;}public void setIsGalleryCircular(boolean isGalleryCircular) {if (mIsGalleryCircular != isGalleryCircular){mIsGalleryCircular = isGalleryCircular;if (mCurrentPosition == getFirstPosition()){// We need to reload the view immediately to the left to change it to circular view or blank    mViews[getPrevViewNumber(mCurrentViewNumber)].recycleView(getPrevPosition(mCurrentPosition));}if (mCurrentPosition == getLastPosition()){// We need to reload the view immediately to the right to change it to circular view or blank    mViews[getNextViewNumber(mCurrentViewNumber)].recycleView(getNextPosition(mCurrentPosition));}}}public int getGalleryCount(){return (mAdapter == null) ? 0 : mAdapter.getCount();}public int getFirstPosition(){return 0;}public int getLastPosition(){return (getGalleryCount() == 0) ? 0 : getGalleryCount() - 1;}private int getPrevPosition(int relativePosition){int prevPosition = relativePosition - 1;if (prevPosition < getFirstPosition()){prevPosition = getFirstPosition() - 1;if (mIsGalleryCircular == true){prevPosition = getLastPosition();}}NotifyGalleryChange();return prevPosition;}private int getNextPosition(int relativePosition){int nextPosition = relativePosition + 1;if (nextPosition > getLastPosition()){nextPosition = getLastPosition() + 1;if (mIsGalleryCircular == true){nextPosition = getFirstPosition();}}NotifyGalleryChange();return nextPosition;}//private void NotifyGalleryChange() {for (OnGalleryChangeListener listener :listeners) {listener.onGalleryChange(mCurrentPosition);}}private int getPrevViewNumber(int relativeViewNumber){return (relativeViewNumber == 0) ? 2 : relativeViewNumber - 1;}private int getNextViewNumber(int relativeViewNumber){return (relativeViewNumber == 2) ? 0 : relativeViewNumber + 1;}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom){super.onLayout(changed, left, top, right, bottom);// Calculate our view widthmGalleryWidth = right - left;if (changed){    // Position views at correct starting offsets    mViews[0].setOffset(0, 0, mCurrentViewNumber);    mViews[1].setOffset(0, 0, mCurrentViewNumber);    mViews[2].setOffset(0, 0, mCurrentViewNumber);    }}public void setAdapter(Adapter adapter)    {    mAdapter = adapter;    mCurrentPosition = 0;        mCurrentViewNumber = 0;        // Load the initial views from adapter        mViews[0].recycleView(mCurrentPosition);    mViews[1].recycleView(getNextPosition(mCurrentPosition));    mViews[2].recycleView(getPrevPosition(mCurrentPosition));    // Position views at correct starting offsets    mViews[0].setOffset(0, 0, mCurrentViewNumber);    mViews[1].setOffset(0, 0, mCurrentViewNumber);    mViews[2].setOffset(0, 0, mCurrentViewNumber);    }private int getViewOffset(int viewNumber, int relativeViewNumber){// Determine width including configured padding widthint offsetWidth = mGalleryWidth + mViewPaddingWidth;// Position the previous view one measured width to leftif (viewNumber == getPrevViewNumber(relativeViewNumber)){return offsetWidth;}// Position the next view one measured width to the rightif (viewNumber == getNextViewNumber(relativeViewNumber)){return offsetWidth * -1;}return 0;}void movePrevious(){// Slide to previous viewmFlingDirection = 1;processGesture();}void moveNext(){// Slide to next viewmFlingDirection = -1;processGesture();} @Override public boolean onKeyDown(int keyCode, KeyEvent event) {    switch (keyCode)    {    case KeyEvent.KEYCODE_DPAD_LEFT:        movePrevious();        return true;    case KeyEvent.KEYCODE_DPAD_RIGHT:        moveNext();        return true;    case KeyEvent.KEYCODE_DPAD_CENTER:    case KeyEvent.KEYCODE_ENTER:    }    return super.onKeyDown(keyCode, event);}public boolean onGalleryTouchEvent(MotionEvent event){boolean consumed = mGestureDetector.onTouchEvent(event);if (event.getAction() == MotionEvent.ACTION_UP){if (mIsTouched || mIsDragging){processScrollSnap();processGesture();}}        return consumed;    }void processGesture(){int newViewNumber = mCurrentViewNumber;int reloadViewNumber = 0;int reloadPosition = 0;mIsTouched = false;mIsDragging = false;if (mFlingDirection > 0){if (mCurrentPosition > getFirstPosition() || mIsGalleryCircular == true){// Determine previous view and outgoing view to recyclenewViewNumber = getPrevViewNumber(mCurrentViewNumber);mCurrentPosition = getPrevPosition(mCurrentPosition);reloadViewNumber = getNextViewNumber(mCurrentViewNumber); reloadPosition = getPrevPosition(mCurrentPosition);}}if (mFlingDirection < 0){if (mCurrentPosition < getLastPosition() || mIsGalleryCircular == true){// Determine the next view and outgoing view to recyclenewViewNumber = getNextViewNumber(mCurrentViewNumber);mCurrentPosition = getNextPosition(mCurrentPosition);reloadViewNumber = getPrevViewNumber(mCurrentViewNumber);reloadPosition = getNextPosition(mCurrentPosition);}}if (newViewNumber != mCurrentViewNumber){mCurrentViewNumber = newViewNumber; // Reload outgoing view from adapter in new positionmViews[reloadViewNumber].recycleView(reloadPosition);}// Ensure input focus on the current viewmViews[mCurrentViewNumber].requestFocus();// Run the slide animations for view transitionsmAnimation.prepareAnimation(mCurrentViewNumber);this.startAnimation(mAnimation);// Reset fling statemFlingDirection = 0;}void processScrollSnap(){// Snap to next view if scrolled passed snap positionfloat rollEdgeWidth = mGalleryWidth * mSnapBorderRatio;int rollOffset = mGalleryWidth - (int) rollEdgeWidth;int currentOffset = mViews[mCurrentViewNumber].getCurrentOffset();if (currentOffset <= rollOffset * -1){// Snap to previous viewmFlingDirection = 1;}if (currentOffset >= rollOffset){// Snap to next viewmFlingDirection = -1;}}private class FlingGalleryView{private int mViewNumber;private FrameLayout mParentLayout;private FrameLayout mInvalidLayout = null;private LinearLayout mInternalLayout = null;private View mExternalView = null;public FlingGalleryView(int viewNumber, FrameLayout parentLayout){mViewNumber = viewNumber;mParentLayout = parentLayout;// Invalid layout is used when outside gallerymInvalidLayout = new FrameLayout(mContext);mInvalidLayout.setLayoutParams(new LinearLayout.LayoutParams(                 LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));// Internal layout is permanent for durationmInternalLayout = new LinearLayout(mContext);mInternalLayout.setLayoutParams(new LinearLayout.LayoutParams(                 LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));mParentLayout.addView(mInternalLayout);}public void recycleView(int newPosition){if (mExternalView != null){mInternalLayout.removeView(mExternalView);}if (mAdapter != null){if (newPosition >= getFirstPosition() && newPosition <= getLastPosition()){mExternalView = mAdapter.getView(newPosition, mExternalView, mInternalLayout);}else{mExternalView = mInvalidLayout;}}if (mExternalView != null){mInternalLayout.addView(mExternalView, new LinearLayout.LayoutParams(                 LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));}}public void setOffset(int xOffset, int yOffset, int relativeViewNumber){// Scroll the target view relative to its own position relative to currently displayed viewmInternalLayout.scrollTo(getViewOffset(mViewNumber, relativeViewNumber) + xOffset, yOffset);}public int getCurrentOffset(){// Return the current scroll positionreturn mInternalLayout.getScrollX();}public void requestFocus(){mInternalLayout.requestFocus();}}    private class FlingGalleryAnimation extends Animation    {    private boolean mIsAnimationInProgres;    private int mRelativeViewNumber;    private int mInitialOffset;    private int mTargetOffset;    private int mTargetDistance;        public FlingGalleryAnimation()    {    mIsAnimationInProgres = false;    mRelativeViewNumber = 0;        mInitialOffset = 0;        mTargetOffset = 0;        mTargetDistance = 0;    }     public void prepareAnimation(int relativeViewNumber)    {    // If we are animating relative to a new view    if (mRelativeViewNumber != relativeViewNumber)    {if (mIsAnimationInProgres == true){// We only have three views so if requested again to animate in same direction we must snap int newDirection = (relativeViewNumber == getPrevViewNumber(mRelativeViewNumber)) ? 1 : -1;    int animDirection = (mTargetDistance < 0) ? 1 : -1;     // If animation in same direction    if (animDirection == newDirection)    {        // Ran out of time to animate so snap to the target offset        mViews[0].setOffset(mTargetOffset, 0, mRelativeViewNumber);mViews[1].setOffset(mTargetOffset, 0, mRelativeViewNumber);mViews[2].setOffset(mTargetOffset, 0, mRelativeViewNumber);    }}// Set relative view number for animation    mRelativeViewNumber = relativeViewNumber;    }// Note: In this implementation the targetOffset will always be zero    // as we are centering the view; but we include the calculations of// targetOffset and targetDistance for use in future implementationsmInitialOffset = mViews[mRelativeViewNumber].getCurrentOffset();mTargetOffset = getViewOffset(mRelativeViewNumber, mRelativeViewNumber);mTargetDistance = mTargetOffset - mInitialOffset;// Configure base animation propertiesthis.setDuration(mAnimationDuration);this.setInterpolator(mDecelerateInterpolater);// Start/continued animationmIsAnimationInProgres = true;}        @Override        protected void applyTransformation(float interpolatedTime, Transformation transformation)        {        // Ensure interpolatedTime does not over-shoot then calculate new offset        interpolatedTime = (interpolatedTime > 1.0f) ? 1.0f : interpolatedTime;int offset = mInitialOffset + (int) (mTargetDistance * interpolatedTime);for (int viewNumber = 0; viewNumber < 3; viewNumber++){// Only need to animate the visible views as the other view will always be off-screenif ((mTargetDistance > 0 && viewNumber != getNextViewNumber(mRelativeViewNumber)) ||(mTargetDistance < 0 && viewNumber != getPrevViewNumber(mRelativeViewNumber))){mViews[viewNumber].setOffset(offset, 0, mRelativeViewNumber);}}        }        @Override        public boolean getTransformation(long currentTime, Transformation outTransformation)        {        if (super.getTransformation(currentTime, outTransformation) == false)        {        // Perform final adjustment to offsets to cleanup animation        mViews[0].setOffset(mTargetOffset, 0, mRelativeViewNumber);mViews[1].setOffset(mTargetOffset, 0, mRelativeViewNumber);mViews[2].setOffset(mTargetOffset, 0, mRelativeViewNumber);// Reached the animation targetmIsAnimationInProgres = false;return false;        }         // Cancel if the screen touched        if (mIsTouched || mIsDragging)        {        // Note that at this point we still consider ourselves to be animating        // because we have not yet reached the target offset; its just that the        // user has temporarily interrupted the animation with a touch gesture        return false;        }        return true;        }    }private class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener    {    @Override    public boolean onDown(MotionEvent e)    {    // Stop animation    mIsTouched = true;    // Reset fling state    mFlingDirection = 0;            return true;    }    @Override    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)    {    if (e2.getAction() == MotionEvent.ACTION_MOVE)        {    if (mIsDragging == false)    {        // Stop animation    mIsTouched = true;     // Reconfigure scroll    mIsDragging = true;    mFlingDirection = 0;    mScrollTimestamp = System.currentTimeMillis();    mCurrentOffset = mViews[mCurrentViewNumber].getCurrentOffset();    }            float maxVelocity = mGalleryWidth / (mAnimationDuration / 1000.0f);        long timestampDelta = System.currentTimeMillis() - mScrollTimestamp;        float maxScrollDelta = maxVelocity * (timestampDelta / 1000.0f);         float currentScrollDelta = e1.getX() - e2.getX();        if (currentScrollDelta < maxScrollDelta * -1) currentScrollDelta = maxScrollDelta * -1;        if (currentScrollDelta > maxScrollDelta) currentScrollDelta = maxScrollDelta;        int scrollOffset = Math.round(mCurrentOffset + currentScrollDelta);        // We can't scroll more than the width of our own frame layout        if (scrollOffset >= mGalleryWidth) scrollOffset = mGalleryWidth;        if (scrollOffset <= mGalleryWidth * -1) scrollOffset = mGalleryWidth * -1;                mViews[0].setOffset(scrollOffset, 0, mCurrentViewNumber);    mViews[1].setOffset(scrollOffset, 0, mCurrentViewNumber);    mViews[2].setOffset(scrollOffset, 0, mCurrentViewNumber);        }            return false;    }    @Override    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)    {            if (Math.abs(e1.getY() - e2.getY()) <= swipe_max_off_path)            {                if (e2.getX() - e1.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty)                {                movePrevious();                }                if(e1.getX() - e2.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty)                {                moveNext();                }            }            return false;    }    @Override    public void onLongPress(MotionEvent e)    {    // Finalise scrolling    mFlingDirection = 0;            processGesture();    }    @Override    public void onShowPress(MotionEvent e)    {    }    @Override    public boolean onSingleTapUp(MotionEvent e)    {    // Reset fling state    mFlingDirection = 0;            return false;    }        }public GestureDetector getMGestureDetector() {return mGestureDetector;}}


由于我需要在滑动页面时,改动title中的文字,这里采用了观察者模式,加了个OnGalleryChangeListener,有同样需求的同学可以参考下。
public interface OnGalleryChangeListener {public void onGalleryChange(int currentItem);}

在Activity中,
FlingGallery gallery = new FlingGallery(this);gallery.setAdapter(new ArrayAdapter<String>(getApplicationContext(),android.R.layout.simple_list_item_1, new String[xxxx]) {public View getView(int position, View convertView, ViewGroup parent) {                               // 返回滑动的dealreturn dealViews[position];}});gallery.addGalleryChangeListener(new OnGalleryChangeListener() {@Overridepublic void onGalleryChange(int currentItem) {// 干些想干的事件}});

将gallery加到Activity中的最终需要显示的类中。
Adapter中的getView方法中,将需要用来滑动的view添加进来。在GalleryChangeListener中,可以干一些自己想要的滑动后的事情。我在这里改动了标题的文字,进行了我的view中图片的lazyloading。
这里提一下另一个问题。我的这个类,最终嵌入到了tab中,需要在你的tabActivity中dispatchKeyEvent一下,将按键事件分发下去。
最开始,我的滑动的view写得比较通用,所以包含了ScrollView来满足比较长的屏幕,导致手势监听会出一些问题,会出现抖动。当时的解决方案是针对不同屏幕尽量保证一屏能够显示,在res目录下,增加layout-800x480之类的目录,针对每个不同屏幕设计单独的layout,放弃上下滑动,效果也不错。




首先赞一个 楼主的代码写的挺好!!!


看完你的代码后我想这样确实也能实现左右滑动  楼主有没有想到过你的这个类似于gallery的玩意里面要是嵌套了一些类似于listview或者是gridview之类的控件后滑动焦点岂不是废了!!   也不能说废了 你也可以在重写下他们的ontouch事件。。。

另外有一点是这个滑动的算法,我亲自试验过  假如你的这个里面要是嵌套了一个listview  数据不多 就20条滑动就够卡的了!!不过总体来讲 这段代码还是挺棒的  一些不是很复杂的界面用起来很爽  要是像UC首页那种估计要重写了。!

viewgroup 这是一个实现左右滑动最好的控件!!目前QQ浏览器是采用的这种 系统源码 luncher也是用的这种!!有兴趣的同学可以参照luncher下的workplace.java  挺好的!! 首先赞一个 楼主的代码写的挺好!!!


看完你的代码后我想这样确实也能实现左右滑动  楼主有没有想到过你的这个类似于gallery的玩意里面要是嵌套了一些类似于listview或者是gridview之类的控件后滑动焦点岂不是废了!!   也不能说废了 你也可以在重写下他们的ontouch事件。。。

另外有一点是这个滑动的算法,我亲自试验过  假如你的这个里面要是嵌套了一个listview  数据不多 就20条滑动就够卡的了!!不过总体来讲 这段代码还是挺棒的  一些不是很复杂的界面用起来很爽  要是像UC首页那种估计要重写了。!

viewgroup 这是一个实现左右滑动最好的控件!!目前QQ浏览器是采用的这种 系统源码 luncher也是用的这种!!有兴趣的同学可以参照luncher下的workplace.java  挺好的!!
这段代码参考自网上,最终也没有采用这种方法。这种滑动针对能够同时上下滑动的情况比较乏力,不过有重复过50+个比较复杂的view,效果还是可以不错的。你应该是说的workspace吧,最开始的时候就是采用workspace的方案,是很好的办法。 4 楼 chinapengwei_wh 2011-06-28   我在项目中也是用的这个,可是我们的领导是按苹果的滑动来要求,在苹果中实现我想不会太难,可同样的效果要在Android中实现就不是件容易的事了,哎!我在里面要异常下载大图片,要在ListView中实现滚动分页,单个页面中还有多个异常的交互任务,在差机中还不能太卡,反正要求多多,我努力了大半个月才算完成,也累个半死,他们还是不是很满意,真是郁闷!在他们看来苹果能做的Android也能做,什么逻辑!!! 5 楼 Cindy_Lee 2011-06-28   如果是类似Android主屏UI那种3屏幕滑动完全没有这么麻烦把,可以参考下Launcher里的Workspace类源码 6 楼 苹果超人 2011-06-28   关注一下。。。新手。。。 7 楼 dengzhangtao 2011-06-28   发个代码, 看看效果呗。。更容易学习 8 楼 hualang 2011-06-29   此情此景,指的是对楼主表示佩服啊 9 楼 mjl19901003 2011-10-19   3楼的大哥,我用的就是viewGroup实现的左右滑动翻页,但是怎样做才能够实现数据的动态更改,比如说是九宫格页面,在删除一个之后,怎样做才能够使后一个顶上来而不出现闪屏刷新的效果,请指教 10 楼 seamount 2012-03-19   请教一下,您这个dealViews[position]方法在哪呢?

热点排行