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()方法中为本组件的动画设置了延时,体验上更亲近:

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


本文内容归优快云博客博主Sodino 所有
转载请注明出处:http://blog.youkuaiyun.com/sodino/article/details/7211049


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


ActSlidingContainer.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. }  


  1. <pre name="code" class="java" style="background-color: rgb(255, 255, 255); "><pre></pre>  
  2. <p></p>  
  3. <p></p>  
  4. <p>SlidingContainer.java</p>  
  5. <pre name="code" class="java">package lab.sodino.sliding;  
  6.   
  7. import java.util.ArrayList;  
  8.   
  9. import android.content.Context;  
  10. import android.content.res.TypedArray;  
  11. import android.graphics.Canvas;  
  12. import android.graphics.Rect;  
  13. import android.util.AttributeSet;  
  14. import android.view.MotionEvent;  
  15. import android.view.View;  
  16. import android.view.ViewConfiguration;  
  17. import android.view.ViewGroup;  
  18. import android.widget.Scroller;  
  19.   
  20. /** 
  21.  * @author Sodino E-mail:sodinoopen@hotmail.com 
  22.  * @version Time:2012-1-18 下午02:55:59 
  23.  */  
  24. public class SlidingContainer extends ViewGroup {  
  25.     private static final int INVALID_SCREEN = -1;  
  26.     public static final int SCROLL_DURATION = 500;  
  27.     public static final int SPEC_UNDEFINED = ViewGroup.LayoutParams.FILL_PARENT;  
  28.     public static final int SNAP_VELOCITY = 500;  
  29.     private static final int STATE_STATIC = 0;  
  30.     private static final int STATE_SCROLLING = 1;  
  31.     private int pageWidth;  
  32.     /** 
  33.      * 标识是否是第一次布局。<br/> 
  34.      * 第一次布局需要将第一页调居中显示在屏幕上。<br/> 
  35.      */  
  36.     private boolean isFirstLayout;  
  37.     private int currentPage, nextPage;  
  38.     private Scroller scroller;  
  39.     /** 手指滑动过程中可理解为拖动的最小长度。 */  
  40.     private int distanceSlop;  
  41.     private int state = STATE_STATIC;  
  42.     private float lastMotionX;  
  43.     private OnSlidingListener slidingListener;  
  44.   
  45.     public SlidingContainer(Context context, AttributeSet attrs, int defStyle) {  
  46.         super(context, attrs, defStyle);  
  47.         LogOut.out(this"SlidingContainer() 3");  
  48.         initialization(context, attrs);  
  49.     }  
  50.   
  51.     public SlidingContainer(Context context, AttributeSet attrs) {  
  52.         super(context, attrs);  
  53.         LogOut.out(this"SlidingContainer() 2");  
  54.         initialization(context, attrs);  
  55.     }  
  56.   
  57.     public SlidingContainer(Context context) {  
  58.         super(context);  
  59.         LogOut.out(this"SlidingContainer() 1");  
  60.         initialization(context, null);  
  61.     }  
  62.   
  63.     private void initialization(Context context, AttributeSet attrs) {  
  64.         if (attrs != null) {  
  65.             TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.sliding_SlidingContainer);  
  66.             pageWidth = typedArr.getDimensionPixelSize(R.styleable.sliding_SlidingContainer_pageWidth, SPEC_UNDEFINED);  
  67.             typedArr.recycle();  
  68.         }  
  69.   
  70.         state = STATE_STATIC;  
  71.         isFirstLayout = true;  
  72.         currentPage = 0;  
  73.         nextPage = INVALID_SCREEN;  
  74.   
  75.         scroller = new Scroller(context);  
  76.   
  77.         final ViewConfiguration configuration = ViewConfiguration.get(context);  
  78.         distanceSlop = configuration.getScaledTouchSlop();  
  79.     }  
  80.   
  81.     public int getCurrentPage() {  
  82.         return currentPage;  
  83.     }  
  84.   
  85.     public int getScrollXByPage(int page) {  
  86.         return (page * pageWidth) - getPagePadding();  
  87.     }  
  88.   
  89.     public int getPagePadding() {  
  90.         return (getMeasuredWidth() - pageWidth) >> 1;  
  91.     }  
  92.   
  93.     public int getPageWidth() {  
  94.         return pageWidth;  
  95.     }  
  96.   
  97.     public boolean scroll2page(int page) {  
  98.         if (page < 0) {  
  99.             return false;  
  100.         } else if (page >= getChildCount()) {  
  101.             return false;  
  102.         } else if (scroller.isFinished() == false) {  
  103.             return false;  
  104.         }  
  105.         enableChildrenCache(true);  
  106.         boolean changingPage = (page != currentPage);  
  107.         nextPage = page;  
  108.   
  109.         View focusedChild = getFocusedChild();  
  110.         if (changingPage && focusedChild != null && focusedChild == getChildAt(currentPage)) {  
  111.             focusedChild.clearFocus();  
  112.         }  
  113.   
  114.         final int nowX = getScrollX();  
  115.         final int newX = getScrollXByPage(nextPage);  
  116.         final int move = newX - nowX;  
  117.         final int absMove = Math.abs(move);  
  118.         int duration = SCROLL_DURATION;  
  119.         if (absMove < pageWidth) {  
  120.             duration = SCROLL_DURATION * absMove / pageWidth;  
  121.         }  
  122.         // 启动左右切屏动画  
  123.         scroller.startScroll(nowX, 0, move, 0, duration);  
  124.         invalidate();  
  125.         return true;  
  126.     }  
  127.   
  128.     private void checkScrolling(float x) {  
  129.         float diff = Math.abs(x - lastMotionX);  
  130.         if (diff > distanceSlop) {  
  131.             state = STATE_SCROLLING;  
  132.             enableChildrenCache(true);  
  133.         }  
  134.     }  
  135.   
  136.     /** 
  137.      * 开始滑动时设置允许使用缓存。<br/> 
  138.      * 结束滑动时设置取消缓存。<br/> 
  139.      */  
  140.     public void enableChildrenCache(boolean enable) {  
  141.         setChildrenDrawingCacheEnabled(enable);  
  142.         setChildrenDrawnWithCacheEnabled(enable);  
  143.     }  
  144.   
  145.     /** 在正式显示之前设置才有效。 */  
  146.     public boolean setPageWidth(int width) {  
  147.         if (isFirstLayout) {  
  148.             pageWidth = width;  
  149.             return true;  
  150.         }  
  151.         return false;  
  152.     }  
  153.   
  154.     public void setOnSlidingListener(OnSlidingListener listener) {  
  155.         slidingListener = listener;  
  156.     }  
  157.   
  158.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  159.         LogOut.out(this"onMeasure()");  
  160.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  161.   
  162.         pageWidth = (pageWidth == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidth;  
  163.         pageWidth = Math.min(Math.max(0, pageWidth), getMeasuredWidth());  
  164.   
  165.         final int count = getChildCount();  
  166.         for (int i = 0; i < count; i++) {  
  167.             int childWidthSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);  
  168.             View view = getChildAt(i);  
  169.             view.measure(childWidthSpec, heightMeasureSpec);  
  170.         }  
  171.     }  
  172.   
  173.     @Override  
  174.     protected void onLayout(boolean changing, int left, int top, int right, int bottom) {  
  175.         LogOut.out(this"onLayout");  
  176.         int childLeft = 0;  
  177.         final int count = getChildCount();  
  178.         for (int i = 0; i < count; i++) {  
  179.             final View view = getChildAt(i);  
  180.             if (view.getVisibility() != View.GONE) {  
  181.                 int childWidth = view.getMeasuredWidth();  
  182.                 view.layout(childLeft, 0, childLeft + childWidth, view.getMeasuredHeight());  
  183.                 childLeft += childWidth;  
  184.             }  
  185.         }  
  186.   
  187.         if (isFirstLayout) {  
  188.             scrollTo(getScrollXByPage(currentPage), 0);  
  189.             isFirstLayout = false;  
  190.         }  
  191.     }  
  192.   
  193.     public boolean onInterceptTouchEvent(MotionEvent event) {  
  194.         LogOut.out(this"onInterceptTouchEvent action=" + event.getAction());  
  195.         final int action = event.getAction();  
  196.         if (action == MotionEvent.ACTION_MOVE && state != STATE_STATIC) {  
  197.             // MOVE及非静止情况下,返回TRUE阻止将此事件传递给子组件,  
  198.             // 而是执行onTouchEvent()来实现滑动  
  199.             return true;  
  200.         }  
  201.         final float x = event.getX();  
  202.         switch (action) {  
  203.         case MotionEvent.ACTION_DOWN:  
  204.             lastMotionX = x;  
  205.             // 点击按钮时,此处设置状态为静止。  
  206.             state = scroller.isFinished() ? STATE_STATIC : STATE_SCROLLING;  
  207.             break;  
  208.         case MotionEvent.ACTION_MOVE:  
  209.             if (state == STATE_STATIC) {  
  210.                 // 由于已静止,在点击按钮后进行拖拉,则根据拖拉位移大小决定是否需要改变状态进而进一步拦截此事件。  
  211.                 checkScrolling(x);  
  212.             }  
  213.             break;  
  214.         case MotionEvent.ACTION_UP:  
  215.         case MotionEvent.ACTION_CANCEL:  
  216.             enableChildrenCache(false);  
  217.             state = STATE_STATIC;  
  218.             break;  
  219.         }  
  220.         // 非静止状态,将此事件交由onTouchEvent()处理。  
  221.         return state != STATE_STATIC;  
  222.     }  
  223.   
  224.     public boolean onTouchEvent(MotionEvent event) {  
  225.         LogOut.out(this"onTouchEvent");  
  226.         super.onTouchEvent(event);  
  227.         final int action = event.getAction();  
  228.         final float x = event.getX();  
  229.         switch (action) {  
  230.         case MotionEvent.ACTION_DOWN:  
  231.             lastMotionX = x;  
  232.             if (scroller.isFinished() == false) {  
  233.                 scroller.abortAnimation();  
  234.             }  
  235.             break;  
  236.         case MotionEvent.ACTION_MOVE:  
  237.             if (state == STATE_STATIC) {  
  238.                 checkScrolling(x);  
  239.             } else if (state == STATE_SCROLLING) {  
  240.                 int moveX = (int) (lastMotionX - x);  
  241.                 lastMotionX = x;  
  242.                 if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {  
  243.                     // 对于越界的拖拉,则将位移减半。  
  244.                     moveX = moveX >> 1;  
  245.                 }  
  246.                 scrollBy(moveX, 0);  
  247.             }  
  248.             break;  
  249.         case MotionEvent.ACTION_UP:  
  250.         case MotionEvent.ACTION_CANCEL:  
  251.             if (state == STATE_SCROLLING) {  
  252.                 final int startX = getScrollXByPage(currentPage);  
  253.                 // 默认选择回到手指滑动之前的当前页  
  254.                 int whichPage = currentPage;  
  255.                 int xSpace = getWidth() / 8;  
  256.                 if (getScrollX() < startX - xSpace) {  
  257.                     whichPage = Math.max(0, whichPage - 1);  
  258.                 } else if (getScrollX() > startX + xSpace) {  
  259.                     whichPage = Math.min(getChildCount() - 1, whichPage + 1);  
  260.                 }  
  261.                 scroll2page(whichPage);  
  262.             }  
  263.             state = STATE_STATIC;  
  264.             break;  
  265.         }  
  266.         return true;  
  267.     }  
  268.   
  269.     /** 让拖拉、动画过程中界面过渡顺滑。 */  
  270.     protected void dispatchDraw(Canvas canvas) {  
  271.         final long drawingTime = getDrawingTime();  
  272.   
  273.         final int count = getChildCount();  
  274.         for (int i = 0; i < count; i++) {  
  275.             drawChild(canvas, getChildAt(i), drawingTime);  
  276.         }  
  277.   
  278.         if (slidingListener != null) {  
  279.             int adjustedScrollX = getScrollX() + getPagePadding();  
  280.             slidingListener.onSliding(adjustedScrollX);  
  281.             if (adjustedScrollX % pageWidth == 0) {  
  282.                 slidingListener.onSlidingEnd(adjustedScrollX / pageWidth, adjustedScrollX);  
  283.             }  
  284.         }  
  285.     }  
  286.   
  287.     /** 与Scroller相匹配,实现动画效果中每一帧的界面更新。 */  
  288.     public void computeScroll() {  
  289.         if (scroller.computeScrollOffset()) {  
  290.             scrollTo(scroller.getCurrX(), scroller.getCurrY());  
  291.             postInvalidate();  
  292.         } else if (nextPage != INVALID_SCREEN) {  
  293.             currentPage = nextPage;  
  294.             nextPage = INVALID_SCREEN;  
  295.             enableChildrenCache(false);  
  296.         }  
  297.     }  
  298.   
  299.   
  300.     public static interface OnSlidingListener {  
  301.         public void onSliding(int scrollX);  
  302.   
  303.         public void onSlidingEnd(int pageIdx, int scrollX);  
  304.     }  
  305. }</pre><br>  
  306. <br>  
  307. SlidingIndicator.java<br>  
  308. <p></p>  
  309. <pre name="code" class="java">package lab.sodino.sliding;  
  310.   
  311. import android.content.Context;  
  312. import android.content.res.TypedArray;  
  313. import android.graphics.Canvas;  
  314. import android.graphics.Paint;  
  315. import android.graphics.RectF;  
  316. import android.util.AttributeSet;  
  317. import android.view.View;  
  318. import android.view.animation.AlphaAnimation;  
  319. import android.view.animation.Animation;  
  320. import android.view.animation.AnimationUtils;  
  321. import android.view.animation.LinearInterpolator;  
  322.   
  323. /** 
  324.  * @author Sodino E-mail:sodinoopen@hotmail.com 
  325.  * @version Time:2012-1-18 下午03:31:08 
  326.  */  
  327. public class SlidingIndicator extends View {  
  328.     public static final int BAR_COLOR = 0xaa777777;  
  329.     public static final int HIGHLIGHT_COLOR = 0xaa999999;  
  330.     public static final int FADE_DELAY = 2000;  
  331.     public static final int FADE_DURATION = 500;  
  332.   
  333.     private int amount, currentPage, position;  
  334.     private Paint barPaint, highlightPaint;  
  335.     private int fadeDelay, fadeDuration;  
  336.     private float ovalRadius;  
  337.     private Animation animFadeout;  
  338.     /** RectF比Rect是精度上更精确。 */  
  339.     private RectF rectFBody, rectFIndicator;  
  340.   
  341.     public SlidingIndicator(Context context, AttributeSet attrs, int defStyle) {  
  342.         super(context, attrs, defStyle);  
  343.         // 预设值。  
  344.         int barColor = BAR_COLOR, highlightColor = HIGHLIGHT_COLOR;  
  345.         fadeDelay = FADE_DELAY;  
  346.         fadeDuration = FADE_DURATION;  
  347.         if (attrs != null) {  
  348.             TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.sliding_SlidingIndicator);  
  349.             barColor = typedArr.getColor(R.styleable.sliding_SlidingIndicator_barColor, BAR_COLOR);  
  350.             highlightColor = typedArr.getColor(R.styleable.sliding_SlidingIndicator_highlightColor, HIGHLIGHT_COLOR);  
  351.             fadeDelay = typedArr.getInteger(R.styleable.sliding_SlidingIndicator_fadeDelay, FADE_DELAY);  
  352.             fadeDuration = typedArr.getInteger(R.styleable.sliding_SlidingIndicator_fadeDuration, FADE_DURATION);  
  353.             ovalRadius = typedArr.getDimension(R.styleable.sliding_SlidingIndicator_roundRectRadius, 0f);  
  354.             typedArr.recycle();  
  355.         }  
  356.         initialization(barColor, highlightColor, fadeDuration);  
  357.     }  
  358.   
  359.     public SlidingIndicator(Context context, AttributeSet attrs) {  
  360.         this(context, attrs, 0);  
  361.     }  
  362.   
  363.     public SlidingIndicator(Context context) {  
  364.         super(context);  
  365.     }  
  366.   
  367.     private void initialization(int barColor, int highlightColor, int fadeDuration) {  
  368.         barPaint = new Paint();  
  369.         barPaint.setColor(barColor);  
  370.   
  371.         highlightPaint = new Paint();  
  372.         highlightPaint.setColor(highlightColor);  
  373.   
  374.         animFadeout = new AlphaAnimation(1f, 0f);  
  375.         animFadeout.setDuration(fadeDuration);  
  376.         animFadeout.setRepeatCount(0);  
  377.         animFadeout.setInterpolator(new LinearInterpolator());  
  378.         // 设置动画结束后,本组件保持动画结束时的最后状态,即全透明不可见。  
  379.         animFadeout.setFillEnabled(true);  
  380.         animFadeout.setFillAfter(true);  
  381.   
  382.         rectFBody = new RectF();  
  383.         rectFIndicator = new RectF();  
  384.     }  
  385.   
  386.     public void setPageAmount(int num) {  
  387.         if (num < 0) {  
  388.             throw new IllegalArgumentException("num must be positive.");  
  389.         }  
  390.         amount = num;  
  391.         invalidate();  
  392.         fadeOut();  
  393.     }  
  394.   
  395.     private void fadeOut() {  
  396.         if (fadeDuration > 0) {  
  397.             clearAnimation();  
  398.             // 设置动画的延时时间,此时间段内正好指示当前页位置。  
  399.             animFadeout.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);  
  400.             setAnimation(animFadeout);  
  401.         }  
  402.     }  
  403.   
  404.     public int getCurrentPage() {  
  405.         return currentPage;  
  406.     }  
  407.   
  408.     public void setCurrentPage(int idx) {  
  409.         if (currentPage < 0 || currentPage >= amount) {  
  410.             throw new IllegalArgumentException("currentPage parameter out of bounds");  
  411.         }  
  412.         if (this.currentPage != idx) {  
  413.             this.currentPage = idx;  
  414.             this.position = currentPage * getPageWidth();  
  415.             invalidate();  
  416.             fadeOut();  
  417.         }  
  418.     }  
  419.   
  420.     public void setPosition(int position) {  
  421.         if (this.position != position) {  
  422.             this.position = position;  
  423.             invalidate();  
  424.             fadeOut();  
  425.         }  
  426.     }  
  427.   
  428.     public int getPageWidth() {  
  429.         return getWidth() / amount;  
  430.     }  
  431.   
  432.     protected void onDraw(Canvas canvas) {  
  433.         rectFBody.set(00, getWidth(), getHeight());  
  434.         canvas.drawRoundRect(rectFBody, ovalRadius, ovalRadius, barPaint);  
  435.         rectFIndicator.set(position, 0, position + getPageWidth(), getHeight());  
  436.         canvas.drawRoundRect(rectFIndicator, ovalRadius, ovalRadius, highlightPaint);  
  437.     }  
  438. }</pre><br>  
  439. 末尾,自己推荐另一个左右滑屏实现方法:ViewPager<br>  
  440. http://my.oschina.net/kzhou/blog/29157<br>  
  441. <pre></pre>  
  442. <pre></pre>  
  443.   
  444. </pre> 
http://blog.youkuaiyun.com/sodino/article/details/7211049
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值