Android开发教程:左右滑屏的实现


先上效果图:



实现“左右滑屏”核心类是Scroller,将View中的内容左右滚动从而实现滑屏效果。关键方法有:
scroller.scrollTo(x,y):
直接将View中的内容滚动到指定的(x,y)位置。
scroller.scrollTo(dx,dy):
直接将View中的内容滚动到相对当前状态的(dx,dy)位置。本例中用于实现手指拖拉移动View的效果。
scroller.startScroll(nowX, nowY, moveX, moveY, duration):
在duration的时间内完成move的位移。配合重写View.computeScroll()不断刷新界面从而实现滑屏动画。

如果当前点击拖拉的组件是按钮等自身可处理手势动作的组件,则重写ViewGroup.onInterceptTouchEvent(MotionEvent)可拦截此事件并将此事件传递至onTouchEvent(MotionEvent)进行处理。从而对如按钮等即可点击亦可拖拉。


左右滑屏的指示器位置为SlidingIndicator。在fadeOut()方法中为本组件的动画设置了延时,体验上更亲近:

[java]
  1. animFadeout.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);  
  2. setAnimation(animFadeout);  

代码如下(Java奉上,XML代码请各位看官自己实现):


ActSlidingContainer.java

[java]
  1. package lab.sodino.sliding;  
  2.   
  3. import lab.sodino.sliding.SlidingContainer.OnSlidingListener;  
  4. import Android.app.Activity;  
  5. import android.os.Bundle;  
  6. import android.view.View;  
  7. import android.view.View.OnClickListener;  
  8. import android.widget.Button;  
  9.   
  10. public class ActSlidingContainer extends Activity implements OnClickListener, OnSlidingListener {  
  11.     private SlidingContainer slidingContainer;  
  12.     private SlidingIndicator slidingIndicator;  
  13.     private Button btnLeft, btnRight, btnMid;  
  14.   
  15.     @Override  
  16.     public void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.main);  
  19.         btnLeft = (Button) findViewById(R.id.left);  
  20.         btnLeft.setOnClickListener(this);  
  21.         btnRight = (Button) findViewById(R.id.right);  
  22.         btnRight.setOnClickListener(this);  
  23.         btnMid = (Button) findViewById(R.id.mid);  
  24.         btnMid.setOnClickListener(this);  
  25.   
  26.         slidingContainer = (SlidingContainer) findViewById(R.id.slidingContainer);  
  27.         slidingContainer.setOnSlidingListener(this);  
  28.         slidingIndicator = (SlidingIndicator) findViewById(R.id.slidingIndicator);  
  29.         slidingIndicator.setPageAmount(slidingContainer.getChildCount());  
  30.     }  
  31.   
  32.     @Override  
  33.     public void onClick(View v) {  
  34.         if (v == btnLeft) {  
  35.             slidingContainer.scroll2page(slidingContainer.getCurrentPage() - 1);  
  36.         } else if (v == btnRight) {  
  37.             slidingContainer.scroll2page(slidingContainer.getCurrentPage() + 1);  
  38.         } else if (v == btnMid) {  
  39.             slidingContainer.scroll2page(slidingContainer.getChildCount() >> 1);  
  40.         }  
  41.     }  
  42.   
  43.     @Override  
  44.     public void onSliding(int scrollX) {  
  45.         float scale = (float) (slidingContainer.getPageWidth() * slidingContainer.getChildCount())  
  46.                 / (float) slidingIndicator.getWidth();  
  47.         slidingIndicator.setPosition((int) (scrollX / scale));  
  48.     }  
  49.   
  50.     @Override  
  51.     public void onSlidingEnd(int pageIdx, int scrollX) {  
  52.         slidingIndicator.setCurrentPage(pageIdx);  
  53.     }  
  54. }  

[java]
  1. <pre name="code" class="java" style="background-color: rgb(255, 255, 255); "><pre>  

SlidingContainer.java

[java]
  1. package lab.sodino.sliding;  
  2.   
  3. import java.util.ArrayList;  
  4.   
  5. import android.content.Context;  
  6. import android.content.res.TypedArray;  
  7. import android.graphics.Canvas;  
  8. import android.graphics.Rect;  
  9. import android.util.AttributeSet;  
  10. import android.view.MotionEvent;  
  11. import android.view.View;  
  12. import android.view.ViewConfiguration;  
  13. import android.view.ViewGroup;  
  14. import android.widget.Scroller;  
  15.   
  16. /** 
  17.  * @author Sodino E-mail:sodinoopen@hotmail.com 
  18.  * @version Time:2012-1-18 下午02:55:59 
  19.  */  
  20. public class SlidingContainer extends ViewGroup {  
  21.     private static final int INVALID_SCREEN = -1;  
  22.     public static final int SCROLL_DURATION = 500;  
  23.     public static final int SPEC_UNDEFINED = ViewGroup.LayoutParams.FILL_PARENT;  
  24.     public static final int SNAP_VELOCITY = 500;  
  25.     private static final int STATE_STATIC = 0;  
  26.     private static final int STATE_SCROLLING = 1;  
  27.     private int pageWidth;  
  28.     /** 
  29.      * 标识是否是第一次布局。<br/> 
  30.      * 第一次布局需要将第一页调居中显示在屏幕上。<br/> 
  31.      */  
  32.     private boolean isFirstLayout;  
  33.     private int currentPage, nextPage;  
  34.     private Scroller scroller;  
  35.     /** 手指滑动过程中可理解为拖动的最小长度。 */  
  36.     private int distanceSlop;  
  37.     private int state = STATE_STATIC;  
  38.     private float lastMotionX;  
  39.     private OnSlidingListener slidingListener;  
  40.   
  41.     public SlidingContainer(Context context, AttributeSet attrs, int defStyle) {  
  42.         super(context, attrs, defStyle);  
  43.         LogOut.out(this"SlidingContainer() 3");  
  44.         initialization(context, attrs);  
  45.     }  
  46.   
  47.     public SlidingContainer(Context context, AttributeSet attrs) {  
  48.         super(context, attrs);  
  49.         LogOut.out(this"SlidingContainer() 2");  
  50.         initialization(context, attrs);  
  51.     }  
  52.   
  53.     public SlidingContainer(Context context) {  
  54.         super(context);  
  55.         LogOut.out(this"SlidingContainer() 1");  
  56.         initialization(context, null);  
  57.     }  
  58.   
  59.     private void initialization(Context context, AttributeSet attrs) {  
  60.         if (attrs != null) {  
  61.             TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.sliding_SlidingContainer);  
  62.             pageWidth = typedArr.getDimensionPixelSize(R.styleable.sliding_SlidingContainer_pageWidth, SPEC_UNDEFINED);  
  63.             typedArr.recycle();  
  64.         }  
  65.   
  66.         state = STATE_STATIC;  
  67.         isFirstLayout = true;  
  68.         currentPage = 0;  
  69.         nextPage = INVALID_SCREEN;  
  70.   
  71.         scroller = new Scroller(context);  
  72.   
  73.         final ViewConfiguration configuration = ViewConfiguration.get(context);  
  74.         distanceSlop = configuration.getScaledTouchSlop();  
  75.     }  
  76.   
  77.     public int getCurrentPage() {  
  78.         return currentPage;  
  79.     }  
  80.   
  81.     public int getScrollXByPage(int page) {  
  82.         return (page * pageWidth) - getPagePadding();  
  83.     }  
  84.   
  85.     public int getPagePadding() {  
  86.         return (getMeasuredWidth() - pageWidth) >> 1;  
  87.     }  
  88.   
  89.     public int getPageWidth() {  
  90.         return pageWidth;  
  91.     }  
  92.   
  93.     public boolean scroll2page(int page) {  
  94.         if (page < 0) {  
  95.             return false;  
  96.         } else if (page >= getChildCount()) {  
  97.             return false;  
  98.         } else if (scroller.isFinished() == false) {  
  99.             return false;  
  100.         }  
  101.         enableChildrenCache(true);  
  102.         boolean changingPage = (page != currentPage);  
  103.         nextPage = page;  
  104.   
  105.         View focusedChild = getFocusedChild();  
  106.         if (changingPage && focusedChild != null && focusedChild == getChildAt(currentPage)) {  
  107.             focusedChild.clearFocus();  
  108.         }  
  109.   
  110.         final int nowX = getScrollX();  
  111.         final int newX = getScrollXByPage(nextPage);  
  112.         final int move = newX - nowX;  
  113.         final int absMove = Math.abs(move);  
  114.         int duration = SCROLL_DURATION;  
  115.         if (absMove < pageWidth) {  
  116.             duration = SCROLL_DURATION * absMove / pageWidth;  
  117.         }  
  118.         // 启动左右切屏动画   
  119.         scroller.startScroll(nowX, 0, move, 0, duration);  
  120.         invalidate();  
  121.         return true;  
  122.     }  
  123.   
  124.     private void checkScrolling(float x) {  
  125.         float diff = Math.abs(x - lastMotionX);  
  126.         if (diff > distanceSlop) {  
  127.             state = STATE_SCROLLING;  
  128.             enableChildrenCache(true);  
  129.         }  
  130.     }  
  131.   
  132.     /** 
  133.      * 开始滑动时设置允许使用缓存。<br/> 
  134.      * 结束滑动时设置取消缓存。<br/> 
  135.      */  
  136.     public void enableChildrenCache(boolean enable) {  
  137.         setChildrenDrawingCacheEnabled(enable);  
  138.         setChildrenDrawnWithCacheEnabled(enable);  
  139.     }  
  140.   
  141.     /** 在正式显示之前设置才有效。 */  
  142.     public boolean setPageWidth(int width) {  
  143.         if (isFirstLayout) {  
  144.             pageWidth = width;  
  145.             return true;  
  146.         }  
  147.         return false;  
  148.     }  
  149.   
  150.     public void setOnSlidingListener(OnSlidingListener listener) {  
  151.         slidingListener = listener;  
  152.     }  
  153.   
  154.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  155.         LogOut.out(this"onMeasure()");  
  156.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  157.   
  158.         pageWidth = (pageWidth == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidth;  
  159.         pageWidth = Math.min(Math.max(0, pageWidth), getMeasuredWidth());  
  160.   
  161.         final int count = getChildCount();  
  162.         for (int i = 0; i < count; i++) {  
  163.             int childWidthSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);  
  164.             View view = getChildAt(i);  
  165.             view.measure(childWidthSpec, heightMeasureSpec);  
  166.         }  
  167.     }  
  168.   
  169.     @Override  
  170.     protected void onLayout(boolean changing, int left, int top, int right, int bottom) {  
  171.         LogOut.out(this"onLayout");  
  172.         int childLeft = 0;  
  173.         final int count = getChildCount();  
  174.         for (int i = 0; i < count; i++) {  
  175.             final View view = getChildAt(i);  
  176.             if (view.getVisibility() != View.GONE) {  
  177.                 int childWidth = view.getMeasuredWidth();  
  178.                 view.layout(childLeft, 0, childLeft + childWidth, view.getMeasuredHeight());  
  179.                 childLeft += childWidth;  
  180.             }  
  181.         }  
  182.   
  183.         if (isFirstLayout) {  
  184.             scrollTo(getScrollXByPage(currentPage), 0);  
  185.             isFirstLayout = false;  
  186.         }  
  187.     }  
  188.   
  189.     public boolean onInterceptTouchEvent(MotionEvent event) {  
  190.         LogOut.out(this"onInterceptTouchEvent action=" + event.getAction());  
  191.         final int action = event.getAction();  
  192.         if (action == MotionEvent.ACTION_MOVE && state != STATE_STATIC) {  
  193.             // MOVE及非静止情况下,返回TRUE阻止将此事件传递给子组件,   
  194.             // 而是执行onTouchEvent()来实现滑动   
  195.             return true;  
  196.         }  
  197.         final float x = event.getX();  
  198.         switch (action) {  
  199.         case MotionEvent.ACTION_DOWN:  
  200.             lastMotionX = x;  
  201.             // 点击按钮时,此处设置状态为静止。   
  202.             state = scroller.isFinished() ? STATE_STATIC : STATE_SCROLLING;  
  203.             break;  
  204.         case MotionEvent.ACTION_MOVE:  
  205.             if (state == STATE_STATIC) {  
  206.                 // 由于已静止,在点击按钮后进行拖拉,则根据拖拉位移大小决定是否需要改变状态进而进一步拦截此事件。   
  207.                 checkScrolling(x);  
  208.             }  
  209.             break;  
  210.         case MotionEvent.ACTION_UP:  
  211.         case MotionEvent.ACTION_CANCEL:  
  212.             enableChildrenCache(false);  
  213.             state = STATE_STATIC;  
  214.             break;  
  215.         }  
  216.         // 非静止状态,将此事件交由onTouchEvent()处理。   
  217.         return state != STATE_STATIC;  
  218.     }  
  219.   
  220.     public boolean onTouchEvent(MotionEvent event) {  
  221.         LogOut.out(this"onTouchEvent");  
  222.         super.onTouchEvent(event);  
  223.         final int action = event.getAction();  
  224.         final float x = event.getX();  
  225.         switch (action) {  
  226.         case MotionEvent.ACTION_DOWN:  
  227.             lastMotionX = x;  
  228.             if (scroller.isFinished() == false) {  
  229.                 scroller.abortAnimation();  
  230.             }  
  231.             break;  
  232.         case MotionEvent.ACTION_MOVE:  
  233.             if (state == STATE_STATIC) {  
  234.                 checkScrolling(x);  
  235.             } else if (state == STATE_SCROLLING) {  
  236.                 int moveX = (int) (lastMotionX - x);  
  237.                 lastMotionX = x;  
  238.                 if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {  
  239.                     // 对于越界的拖拉,则将位移减半。   
  240.                     moveX = moveX >> 1;  
  241.                 }  
  242.                 scrollBy(moveX, 0);  
  243.             }  
  244.             break;  
  245.         case MotionEvent.ACTION_UP:  
  246.         case MotionEvent.ACTION_CANCEL:  
  247.             if (state == STATE_SCROLLING) {  
  248.                 final int startX = getScrollXByPage(currentPage);  
  249.                 // 默认选择回到手指滑动之前的当前页   
  250.                 int whichPage = currentPage;  
  251.                 int xSpace = getWidth() / 8;  
  252.                 if (getScrollX() < startX - xSpace) {  
  253.                     whichPage = Math.max(0, whichPage - 1);  
  254.                 } else if (getScrollX() > startX + xSpace) {  
  255.                     whichPage = Math.min(getChildCount() - 1, whichPage + 1);  
  256.                 }  
  257.                 scroll2page(whichPage);  
  258.             }  
  259.             state = STATE_STATIC;  
  260.             break;  
  261.         }  
  262.         return true;  
  263.     }  
  264.   
  265.     /** 让拖拉、动画过程中界面过渡顺滑。 */  
  266.     protected void dispatchDraw(Canvas canvas) {  
  267.         final long drawingTime = getDrawingTime();  
  268.   
  269.         final int count = getChildCount();  
  270.         for (int i = 0; i < count; i++) {  
  271.             drawChild(canvas, getChildAt(i), drawingTime);  
  272.         }  
  273.   
  274.         if (slidingListener != null) {  
  275.             int adjustedScrollX = getScrollX() + getPagePadding();  
  276.             slidingListener.onSliding(adjustedScrollX);  
  277.             if (adjustedScrollX % pageWidth == 0) {  
  278.                 slidingListener.onSlidingEnd(adjustedScrollX / pageWidth, adjustedScrollX);  
  279.             }  
  280.         }  
  281.     }  
  282.   
  283.     /** 与Scroller相匹配,实现动画效果中每一帧的界面更新。 */  
  284.     public void computeScroll() {  
  285.         if (scroller.computeScrollOffset()) {  
  286.             scrollTo(scroller.getCurrX(), scroller.getCurrY());  
  287.             postInvalidate();  
  288.         } else if (nextPage != INVALID_SCREEN) {  
  289.             currentPage = nextPage;  
  290.             nextPage = INVALID_SCREEN;  
  291.             enableChildrenCache(false);  
  292.         }  
  293.     }  
  294.   
  295.   
  296.     public static interface OnSlidingListener {  
  297.         public void onSliding(int scrollX);  
  298.   
  299.         public void onSlidingEnd(int pageIdx, int scrollX);  
  300.     }  
  301. }  
SlidingIndicator.java

[java]
  1. package lab.sodino.sliding;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.graphics.Canvas;  
  6. import android.graphics.Paint;  
  7. import android.graphics.RectF;  
  8. import android.util.AttributeSet;  
  9. import android.view.View;  
  10. import android.view.animation.AlphaAnimation;  
  11. import android.view.animation.Animation;  
  12. import android.view.animation.AnimationUtils;  
  13. import android.view.animation.LinearInterpolator;  
  14.   
  15. /** 
  16.  * @author Sodino E-mail:sodinoopen@hotmail.com 
  17.  * @version Time:2012-1-18 下午03:31:08 
  18.  */  
  19. public class SlidingIndicator extends View {  
  20.     public static final int BAR_COLOR = 0xaa777777;  
  21.     public static final int HIGHLIGHT_COLOR = 0xaa999999;  
  22.     public static final int FADE_DELAY = 2000;  
  23.     public static final int FADE_DURATION = 500;  
  24.   
  25.     private int amount, currentPage, position;  
  26.     private Paint barPaint, highlightPaint;  
  27.     private int fadeDelay, fadeDuration;  
  28.     private float ovalRadius;  
  29.     private Animation animFadeout;  
  30.     /** RectF比Rect是精度上更精确。 */  
  31.     private RectF rectFBody, rectFIndicator;  
  32.   
  33.     public SlidingIndicator(Context context, AttributeSet attrs, int defStyle) {  
  34.         super(context, attrs, defStyle);  
  35.         // 预设值。   
  36.         int barColor = BAR_COLOR, highlightColor = HIGHLIGHT_COLOR;  
  37.         fadeDelay = FADE_DELAY;  
  38.         fadeDuration = FADE_DURATION;  
  39.         if (attrs != null) {  
  40.             TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.sliding_SlidingIndicator);  
  41.             barColor = typedArr.getColor(R.styleable.sliding_SlidingIndicator_barColor, BAR_COLOR);  
  42.             highlightColor = typedArr.getColor(R.styleable.sliding_SlidingIndicator_highlightColor, HIGHLIGHT_COLOR);  
  43.             fadeDelay = typedArr.getInteger(R.styleable.sliding_SlidingIndicator_fadeDelay, FADE_DELAY);  
  44.             fadeDuration = typedArr.getInteger(R.styleable.sliding_SlidingIndicator_fadeDuration, FADE_DURATION);  
  45.             ovalRadius = typedArr.getDimension(R.styleable.sliding_SlidingIndicator_roundRectRadius, 0f);  
  46.             typedArr.recycle();  
  47.         }  
  48.         initialization(barColor, highlightColor, fadeDuration);  
  49.     }  
  50.   
  51.     public SlidingIndicator(Context context, AttributeSet attrs) {  
  52.         this(context, attrs, 0);  
  53.     }  
  54.   
  55.     public SlidingIndicator(Context context) {  
  56.         super(context);  
  57.     }  
  58.   
  59.     private void initialization(int barColor, int highlightColor, int fadeDuration) {  
  60.         barPaint = new Paint();  
  61.         barPaint.setColor(barColor);  
  62.   
  63.         highlightPaint = new Paint();  
  64.         highlightPaint.setColor(highlightColor);  
  65.   
  66.         animFadeout = new AlphaAnimation(1f, 0f);  
  67.         animFadeout.setDuration(fadeDuration);  
  68.         animFadeout.setRepeatCount(0);  
  69.         animFadeout.setInterpolator(new LinearInterpolator());  
  70.         // 设置动画结束后,本组件保持动画结束时的最后状态,即全透明不可见。   
  71.         animFadeout.setFillEnabled(true);  
  72.         animFadeout.setFillAfter(true);  
  73.   
  74.         rectFBody = new RectF();  
  75.         rectFIndicator = new RectF();  
  76.     }  
  77.   
  78.     public void setPageAmount(int num) {  
  79.         if (num < 0) {  
  80.             throw new IllegalArgumentException("num must be positive.");  
  81.         }  
  82.         amount = num;  
  83.         invalidate();  
  84.         fadeOut();  
  85.     }  
  86.   
  87.     private void fadeOut() {  
  88.         if (fadeDuration > 0) {  
  89.             clearAnimation();  
  90.             // 设置动画的延时时间,此时间段内正好指示当前页位置。   
  91.             animFadeout.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);  
  92.             setAnimation(animFadeout);  
  93.         }  
  94.     }  
  95.   
  96.     public int getCurrentPage() {  
  97.         return currentPage;  
  98.     }  
  99.   
  100.     public void setCurrentPage(int idx) {  
  101.         if (currentPage < 0 || currentPage >= amount) {  
  102.             throw new IllegalArgumentException("currentPage parameter out of bounds");  
  103.         }  
  104.         if (this.currentPage != idx) {  
  105.             this.currentPage = idx;  
  106.             this.position = currentPage * getPageWidth();  
  107.             invalidate();  
  108.             fadeOut();  
  109.         }  
  110.     }  
  111.   
  112.     public void setPosition(int position) {  
  113.         if (this.position != position) {  
  114.             this.position = position;  
  115.             invalidate();  
  116.             fadeOut();  
  117.         }  
  118.     }  
  119.   
  120.     public int getPageWidth() {  
  121.         return getWidth() / amount;  
  122.     }  
  123.   
  124.     protected void onDraw(Canvas canvas) {  
  125.         rectFBody.set(00, getWidth(), getHeight());  
  126.         canvas.drawRoundRect(rectFBody, ovalRadius, ovalRadius, barPaint);  
  127.         rectFIndicator.set(position, 0, position + getPageWidth(), getHeight());  
  128.         canvas.drawRoundRect(rectFIndicator, ovalRadius, ovalRadius, highlightPaint);  
  129.     }  
  130. }  

相关内容