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);
复制代码
代码如下(Java奉上,XML代码请各位看官自己实现):

ActSlidingContainer.java

  1. package lab.sodino.sliding;

  2. import lab.sodino.sliding.SlidingContainer.OnSlidingListener;
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.widget.Button;

  8. public class ActSlidingContainer extends Activity implements OnClickListener, OnSlidingListener {
  9. private SlidingContainer slidingContainer;
  10. private SlidingIndicator slidingIndicator;
  11. private Button btnLeft, btnRight, btnMid;

  12. @Override
  13. public void onCreate(Bundle savedInstanceState) {
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.main);
  16. btnLeft = (Button) findViewById(R.id.left);
  17. btnLeft.setOnClickListener(this);
  18. btnRight = (Button) findViewById(R.id.right);
  19. btnRight.setOnClickListener(this);
  20. btnMid = (Button) findViewById(R.id.mid);
  21. btnMid.setOnClickListener(this);

  22. slidingContainer = (SlidingContainer) findViewById(R.id.slidingContainer);
  23. slidingContainer.setOnSlidingListener(this);
  24. slidingIndicator = (SlidingIndicator) findViewById(R.id.slidingIndicator);
  25. slidingIndicator.setPageAmount(slidingContainer.getChildCount());
  26. }

  27. @Override
  28. public void onClick(View v) {
  29. if (v == btnLeft) {
  30. slidingContainer.scroll2page(slidingContainer.getCurrentPage() - 1);
  31. } else if (v == btnRight) {
  32. slidingContainer.scroll2page(slidingContainer.getCurrentPage() + 1);
  33. } else if (v == btnMid) {
  34. slidingContainer.scroll2page(slidingContainer.getChildCount() >> 1);
  35. }
  36. }

  37. @Override
  38. public void onSliding(int scrollX) {
  39. float scale = (float) (slidingContainer.getPageWidth() * slidingContainer.getChildCount())
  40. / (float) slidingIndicator.getWidth();
  41. slidingIndicator.setPosition((int) (scrollX / scale));
  42. }

  43. @Override
  44. public void onSlidingEnd(int pageIdx, int scrollX) {
  45. slidingIndicator.setCurrentPage(pageIdx);
  46. }
  47. }
复制代码
SlidingContainer.java

  1. package lab.sodino.sliding;

  2. import java.util.ArrayList;

  3. import android.content.Context;
  4. import android.content.res.TypedArray;
  5. import android.graphics.Canvas;
  6. import android.graphics.Rect;
  7. import android.util.AttributeSet;
  8. import android.view.MotionEvent;
  9. import android.view.View;
  10. import android.view.ViewConfiguration;
  11. import android.view.ViewGroup;
  12. import android.widget.Scroller;

  13. /**
  14. * @author Sodino E-mail:sodinoopen@hotmail.com
  15. * @version Time:2012-1-18 下午02:55:59
  16. */
  17. public class SlidingContainer extends ViewGroup {
  18. private static final int INVALID_SCREEN = -1;
  19. public static final int SCROLL_DURATION = 500;
  20. public static final int SPEC_UNDEFINED = ViewGroup.LayoutParams.FILL_PARENT;
  21. public static final int SNAP_VELOCITY = 500;
  22. private static final int STATE_STATIC = 0;
  23. private static final int STATE_SCROLLING = 1;
  24. private int pageWidth;
  25. /**
  26. * 标识是否是第一次布局。

  27. * 第一次布局需要将第一页调居中显示在屏幕上。

  28. */
  29. private boolean isFirstLayout;
  30. private int currentPage, nextPage;
  31. private Scroller scroller;
  32. /** 手指滑动过程中可理解为拖动的最小长度。 */
  33. private int distanceSlop;
  34. private int state = STATE_STATIC;
  35. private float lastMotionX;
  36. private OnSlidingListener slidingListener;

  37. public SlidingContainer(Context context, AttributeSet attrs, int defStyle) {
  38. super(context, attrs, defStyle);
  39. LogOut.out(this, "SlidingContainer() 3");
  40. initialization(context, attrs);
  41. }

  42. public SlidingContainer(Context context, AttributeSet attrs) {
  43. super(context, attrs);
  44. LogOut.out(this, "SlidingContainer() 2");
  45. initialization(context, attrs);
  46. }

  47. public SlidingContainer(Context context) {
  48. super(context);
  49. LogOut.out(this, "SlidingContainer() 1");
  50. initialization(context, null);
  51. }

  52. private void initialization(Context context, AttributeSet attrs) {
  53. if (attrs != null) {
  54. TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.sliding_SlidingContainer);
  55. pageWidth = typedArr.getDimensionPixelSize(R.styleable.sliding_SlidingContainer_pageWidth, SPEC_UNDEFINED);
  56. typedArr.recycle();
  57. }

  58. state = STATE_STATIC;
  59. isFirstLayout = true;
  60. currentPage = 0;
  61. nextPage = INVALID_SCREEN;

  62. scroller = new Scroller(context);

  63. final ViewConfiguration configuration = ViewConfiguration.get(context);
  64. distanceSlop = configuration.getScaledTouchSlop();
  65. }

  66. public int getCurrentPage() {
  67. return currentPage;
  68. }

  69. public int getScrollXByPage(int page) {
  70. return (page * pageWidth) - getPagePadding();
  71. }

  72. public int getPagePadding() {
  73. return (getMeasuredWidth() - pageWidth) >> 1;
  74. }

  75. public int getPageWidth() {
  76. return pageWidth;
  77. }

  78. public boolean scroll2page(int page) {
  79. if (page < 0) {
  80. return false;
  81. } else if (page >= getChildCount()) {
  82. return false;
  83. } else if (scroller.isFinished() == false) {
  84. return false;
  85. }
  86. enableChildrenCache(true);
  87. boolean changingPage = (page != currentPage);
  88. nextPage = page;

  89. View focusedChild = getFocusedChild();
  90. if (changingPage && focusedChild != null && focusedChild == getChildAt(currentPage)) {
  91. focusedChild.clearFocus();
  92. }

  93. final int nowX = getScrollX();
  94. final int newX = getScrollXByPage(nextPage);
  95. final int move = newX - nowX;
  96. final int absMove = Math.abs(move);
  97. int duration = SCROLL_DURATION;
  98. if (absMove < pageWidth) {
  99. duration = SCROLL_DURATION * absMove / pageWidth;
  100. }
  101. // 启动左右切屏动画
  102. scroller.startScroll(nowX, 0, move, 0, duration);
  103. invalidate();
  104. return true;
  105. }

  106. private void checkScrolling(float x) {
  107. float diff = Math.abs(x - lastMotionX);
  108. if (diff > distanceSlop) {
  109. state = STATE_SCROLLING;
  110. enableChildrenCache(true);
  111. }
  112. }

  113. /**
  114. * 开始滑动时设置允许使用缓存。

  115. * 结束滑动时设置取消缓存。

  116. */
  117. public void enableChildrenCache(boolean enable) {
  118. setChildrenDrawingCacheEnabled(enable);
  119. setChildrenDrawnWithCacheEnabled(enable);
  120. }

  121. /** 在正式显示之前设置才有效。 */
  122. public boolean setPageWidth(int width) {
  123. if (isFirstLayout) {
  124. pageWidth = width;
  125. return true;
  126. }
  127. return false;
  128. }

  129. public void setOnSlidingListener(OnSlidingListener listener) {
  130. slidingListener = listener;
  131. }

  132. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  133. LogOut.out(this, "onMeasure()");
  134. super.onMeasure(widthMeasureSpec, heightMeasureSpec);

  135. pageWidth = (pageWidth == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidth;
  136. pageWidth = Math.min(Math.max(0, pageWidth), getMeasuredWidth());

  137. final int count = getChildCount();
  138. for (int i = 0; i < count; i++) {
  139. int childWidthSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);
  140. View view = getChildAt(i);
  141. view.measure(childWidthSpec, heightMeasureSpec);
  142. }
  143. }

  144. @Override
  145. protected void onLayout(boolean changing, int left, int top, int right, int bottom) {
  146. LogOut.out(this, "onLayout");
  147. int childLeft = 0;
  148. final int count = getChildCount();
  149. for (int i = 0; i < count; i++) {
  150. final View view = getChildAt(i);
  151. if (view.getVisibility() != View.GONE) {
  152. int childWidth = view.getMeasuredWidth();
  153. view.layout(childLeft, 0, childLeft + childWidth, view.getMeasuredHeight());
  154. childLeft += childWidth;
  155. }
  156. }

  157. if (isFirstLayout) {
  158. scrollTo(getScrollXByPage(currentPage), 0);
  159. isFirstLayout = false;
  160. }
  161. }

  162. public boolean onInterceptTouchEvent(MotionEvent event) {
  163. LogOut.out(this, "onInterceptTouchEvent action=" + event.getAction());
  164. final int action = event.getAction();
  165. if (action == MotionEvent.ACTION_MOVE && state != STATE_STATIC) {
  166. // MOVE及非静止情况下,返回TRUE阻止将此事件传递给子组件,
  167. // 而是执行onTouchEvent()来实现滑动
  168. return true;
  169. }
  170. final float x = event.getX();
  171. switch (action) {
  172. case MotionEvent.ACTION_DOWN:
  173. lastMotionX = x;
  174. // 点击按钮时,此处设置状态为静止。
  175. state = scroller.isFinished() ? STATE_STATIC : STATE_SCROLLING;
  176. break;
  177. case MotionEvent.ACTION_MOVE:
  178. if (state == STATE_STATIC) {
  179. // 由于已静止,在点击按钮后进行拖拉,则根据拖拉位移大小决定是否需要改变状态进而进一步拦截此事件。
  180. checkScrolling(x);
  181. }
  182. break;
  183. case MotionEvent.ACTION_UP:
  184. case MotionEvent.ACTION_CANCEL:
  185. enableChildrenCache(false);
  186. state = STATE_STATIC;
  187. break;
  188. }
  189. // 非静止状态,将此事件交由onTouchEvent()处理。
  190. return state != STATE_STATIC;
  191. }

  192. public boolean onTouchEvent(MotionEvent event) {
  193. LogOut.out(this, "onTouchEvent");
  194. super.onTouchEvent(event);
  195. final int action = event.getAction();
  196. final float x = event.getX();
  197. switch (action) {
  198. case MotionEvent.ACTION_DOWN:
  199. lastMotionX = x;
  200. if (scroller.isFinished() == false) {
  201. scroller.abortAnimation();
  202. }
  203. break;
  204. case MotionEvent.ACTION_MOVE:
  205. if (state == STATE_STATIC) {
  206. checkScrolling(x);
  207. } else if (state == STATE_SCROLLING) {
  208. int moveX = (int) (lastMotionX - x);
  209. lastMotionX = x;
  210. if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {
  211. // 对于越界的拖拉,则将位移减半。
  212. moveX = moveX >> 1;
  213. }
  214. scrollBy(moveX, 0);
  215. }
  216. break;
  217. case MotionEvent.ACTION_UP:
  218. case MotionEvent.ACTION_CANCEL:
  219. if (state == STATE_SCROLLING) {
  220. final int startX = getScrollXByPage(currentPage);
  221. // 默认选择回到手指滑动之前的当前页
  222. int whichPage = currentPage;
  223. int xSpace = getWidth() / 8;
  224. if (getScrollX() < startX - xSpace) {
  225. whichPage = Math.max(0, whichPage - 1);
  226. } else if (getScrollX() > startX + xSpace) {
  227. whichPage = Math.min(getChildCount() - 1, whichPage + 1);
  228. }
  229. scroll2page(whichPage);
  230. }
  231. state = STATE_STATIC;
  232. break;
  233. }
  234. return true;
  235. }

  236. /** 让拖拉、动画过程中界面过渡顺滑。 */
  237. protected void dispatchDraw(Canvas canvas) {
  238. final long drawingTime = getDrawingTime();

  239. final int count = getChildCount();
  240. for (int i = 0; i < count; i++) {
  241. drawChild(canvas, getChildAt(i), drawingTime);
  242. }

  243. if (slidingListener != null) {
  244. int adjustedScrollX = getScrollX() + getPagePadding();
  245. slidingListener.onSliding(adjustedScrollX);
  246. if (adjustedScrollX % pageWidth == 0) {
  247. slidingListener.onSlidingEnd(adjustedScrollX / pageWidth, adjustedScrollX);
  248. }
  249. }
  250. }

  251. /** 与Scroller相匹配,实现动画效果中每一帧的界面更新。 */
  252. public void computeScroll() {
  253. if (scroller.computeScrollOffset()) {
  254. scrollTo(scroller.getCurrX(), scroller.getCurrY());
  255. postInvalidate();
  256. } else if (nextPage != INVALID_SCREEN) {
  257. currentPage = nextPage;
  258. nextPage = INVALID_SCREEN;
  259. enableChildrenCache(false);
  260. }
  261. }


  262. public static interface OnSlidingListener {
  263. public void onSliding(int scrollX);

  264. public void onSlidingEnd(int pageIdx, int scrollX);
  265. }
  266. }
复制代码
SlidingIndicator.java

  1. package lab.sodino.sliding;

  2. import android.content.Context;
  3. import android.content.res.TypedArray;
  4. import android.graphics.Canvas;
  5. import android.graphics.Paint;
  6. import android.graphics.RectF;
  7. import android.util.AttributeSet;
  8. import android.view.View;
  9. import android.view.animation.AlphaAnimation;
  10. import android.view.animation.Animation;
  11. import android.view.animation.AnimationUtils;
  12. import android.view.animation.LinearInterpolator;

  13. /**
  14. * @author Sodino E-mail:sodinoopen@hotmail.com
  15. * @version Time:2012-1-18 下午03:31:08
  16. */
  17. public class SlidingIndicator extends View {
  18. public static final int BAR_COLOR = 0xaa777777;
  19. public static final int HIGHLIGHT_COLOR = 0xaa999999;
  20. public static final int FADE_DELAY = 2000;
  21. public static final int FADE_DURATION = 500;

  22. private int amount, currentPage, position;
  23. private Paint barPaint, highlightPaint;
  24. private int fadeDelay, fadeDuration;
  25. private float ovalRadius;
  26. private Animation animFadeout;
  27. /** RectF比Rect是精度上更精确。 */
  28. private RectF rectFBody, rectFIndicator;

  29. public SlidingIndicator(Context context, AttributeSet attrs, int defStyle) {
  30. super(context, attrs, defStyle);
  31. // 预设值。
  32. int barColor = BAR_COLOR, highlightColor = HIGHLIGHT_COLOR;
  33. fadeDelay = FADE_DELAY;
  34. fadeDuration = FADE_DURATION;
  35. if (attrs != null) {
  36. TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.sliding_SlidingIndicator);
  37. barColor = typedArr.getColor(R.styleable.sliding_SlidingIndicator_barColor, BAR_COLOR);
  38. highlightColor = typedArr.getColor(R.styleable.sliding_SlidingIndicator_highlightColor, HIGHLIGHT_COLOR);
  39. fadeDelay = typedArr.getInteger(R.styleable.sliding_SlidingIndicator_fadeDelay, FADE_DELAY);
  40. fadeDuration = typedArr.getInteger(R.styleable.sliding_SlidingIndicator_fadeDuration, FADE_DURATION);
  41. ovalRadius = typedArr.getDimension(R.styleable.sliding_SlidingIndicator_roundRectRadius, 0f);
  42. typedArr.recycle();
  43. }
  44. initialization(barColor, highlightColor, fadeDuration);
  45. }

  46. public SlidingIndicator(Context context, AttributeSet attrs) {
  47. this(context, attrs, 0);
  48. }

  49. public SlidingIndicator(Context context) {
  50. super(context);
  51. }

  52. private void initialization(int barColor, int highlightColor, int fadeDuration) {
  53. barPaint = new Paint();
  54. barPaint.setColor(barColor);

  55. highlightPaint = new Paint();
  56. highlightPaint.setColor(highlightColor);

  57. animFadeout = new AlphaAnimation(1f, 0f);
  58. animFadeout.setDuration(fadeDuration);
  59. animFadeout.setRepeatCount(0);
  60. animFadeout.setInterpolator(new LinearInterpolator());
  61. // 设置动画结束后,本组件保持动画结束时的最后状态,即全透明不可见。
  62. animFadeout.setFillEnabled(true);
  63. animFadeout.setFillAfter(true);

  64. rectFBody = new RectF();
  65. rectFIndicator = new RectF();
  66. }

  67. public void setPageAmount(int num) {
  68. if (num < 0) {
  69. throw new IllegalArgumentException("num must be positive.");
  70. }
  71. amount = num;
  72. invalidate();
  73. fadeOut();
  74. }

  75. private void fadeOut() {
  76. if (fadeDuration > 0) {
  77. clearAnimation();
  78. // 设置动画的延时时间,此时间段内正好指示当前页位置。
  79. animFadeout.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);
  80. setAnimation(animFadeout);
  81. }
  82. }

  83. public int getCurrentPage() {
  84. return currentPage;
  85. }

  86. public void setCurrentPage(int idx) {
  87. if (currentPage < 0 || currentPage >= amount) {
  88. throw new IllegalArgumentException("currentPage parameter out of bounds");
  89. }
  90. if (this.currentPage != idx) {
  91. this.currentPage = idx;
  92. this.position = currentPage * getPageWidth();
  93. invalidate();
  94. fadeOut();
  95. }
  96. }

  97. public void setPosition(int position) {
  98. if (this.position != position) {
  99. this.position = position;
  100. invalidate();
  101. fadeOut();
  102. }
  103. }

  104. public int getPageWidth() {
  105. return getWidth() / amount;
  106. }

  107. protected void onDraw(Canvas canvas) {
  108. rectFBody.set(0, 0, getWidth(), getHeight());
  109. canvas.drawRoundRect(rectFBody, ovalRadius, ovalRadius, barPaint);
  110. rectFIndicator.set(position, 0, position + getPageWidth(), getHeight());
  111. canvas.drawRoundRect(rectFIndicator, ovalRadius, ovalRadius, highlightPaint);
  112. }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值