先上效果圖:
實現“左右滑屏”核心類是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]
- animFadeout.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);
- setAnimation(animFadeout);
代碼如下(Java奉上,XML代碼請各位看官自己實現):
ActSlidingContainer.java
[java]
- package lab.sodino.sliding;
-
- import lab.sodino.sliding.SlidingContainer.OnSlidingListener;
- import Android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
-
- public class ActSlidingContainer extends Activity implements OnClickListener, OnSlidingListener {
- private SlidingContainer slidingContainer;
- private SlidingIndicator slidingIndicator;
- private Button btnLeft, btnRight, btnMid;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- btnLeft = (Button) findViewById(R.id.left);
- btnLeft.setOnClickListener(this);
- btnRight = (Button) findViewById(R.id.right);
- btnRight.setOnClickListener(this);
- btnMid = (Button) findViewById(R.id.mid);
- btnMid.setOnClickListener(this);
-
- slidingContainer = (SlidingContainer) findViewById(R.id.slidingContainer);
- slidingContainer.setOnSlidingListener(this);
- slidingIndicator = (SlidingIndicator) findViewById(R.id.slidingIndicator);
- slidingIndicator.setPageAmount(slidingContainer.getChildCount());
- }
-
- @Override
- public void onClick(View v) {
- if (v == btnLeft) {
- slidingContainer.scroll2page(slidingContainer.getCurrentPage() - 1);
- } else if (v == btnRight) {
- slidingContainer.scroll2page(slidingContainer.getCurrentPage() + 1);
- } else if (v == btnMid) {
- slidingContainer.scroll2page(slidingContainer.getChildCount() >> 1);
- }
- }
-
- @Override
- public void onSliding(int scrollX) {
- float scale = (float) (slidingContainer.getPageWidth() * slidingContainer.getChildCount())
- / (float) slidingIndicator.getWidth();
- slidingIndicator.setPosition((int) (scrollX / scale));
- }
-
- @Override
- public void onSlidingEnd(int pageIdx, int scrollX) {
- slidingIndicator.setCurrentPage(pageIdx);
- }
- }
[java]
- <pre name="code" class="java" style="background-color: rgb(255, 255, 255); "><pre>
SlidingContainer.java
[java]
- package lab.sodino.sliding;
-
- import java.util.ArrayList;
-
- import android.content.Context;
- import android.content.res.TypedArray;
- import android.graphics.Canvas;
- import android.graphics.Rect;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.ViewConfiguration;
- import android.view.ViewGroup;
- import android.widget.Scroller;
-
- /**
- * @author Sodino E-mail:[email protected]
- * @version Time:2012-1-18 下午02:55:59
- */
- public class SlidingContainer extends ViewGroup {
- private static final int INVALID_SCREEN = -1;
- public static final int SCROLL_DURATION = 500;
- public static final int SPEC_UNDEFINED = ViewGroup.LayoutParams.FILL_PARENT;
- public static final int SNAP_VELOCITY = 500;
- private static final int STATE_STATIC = 0;
- private static final int STATE_SCROLLING = 1;
- private int pageWidth;
- /**
- * 標識是否是第一次布局。<br/>
- * 第一次布局需要將第一頁調居中顯示在屏幕上。<br/>
- */
- private boolean isFirstLayout;
- private int currentPage, nextPage;
- private Scroller scroller;
- /** 手指滑動過程中可理解為拖動的最小長度。 */
- private int distanceSlop;
- private int state = STATE_STATIC;
- private float lastMotionX;
- private OnSlidingListener slidingListener;
-
- public SlidingContainer(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- LogOut.out(this, "SlidingContainer() 3");
- initialization(context, attrs);
- }
-
- public SlidingContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
- LogOut.out(this, "SlidingContainer() 2");
- initialization(context, attrs);
- }
-
- public SlidingContainer(Context context) {
- super(context);
- LogOut.out(this, "SlidingContainer() 1");
- initialization(context, null);
- }
-
- private void initialization(Context context, AttributeSet attrs) {
- if (attrs != null) {
- TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.sliding_SlidingContainer);
- pageWidth = typedArr.getDimensionPixelSize(R.styleable.sliding_SlidingContainer_pageWidth, SPEC_UNDEFINED);
- typedArr.recycle();
- }
-
- state = STATE_STATIC;
- isFirstLayout = true;
- currentPage = 0;
- nextPage = INVALID_SCREEN;
-
- scroller = new Scroller(context);
-
- final ViewConfiguration configuration = ViewConfiguration.get(context);
- distanceSlop = configuration.getScaledTouchSlop();
- }
-
- public int getCurrentPage() {
- return currentPage;
- }
-
- public int getScrollXByPage(int page) {
- return (page * pageWidth) - getPagePadding();
- }
-
- public int getPagePadding() {
- return (getMeasuredWidth() - pageWidth) >> 1;
- }
-
- public int getPageWidth() {
- return pageWidth;
- }
-
- public boolean scroll2page(int page) {
- if (page < 0) {
- return false;
- } else if (page >= getChildCount()) {
- return false;
- } else if (scroller.isFinished() == false) {
- return false;
- }
- enableChildrenCache(true);
- boolean changingPage = (page != currentPage);
- nextPage = page;
-
- View focusedChild = getFocusedChild();
- if (changingPage && focusedChild != null && focusedChild == getChildAt(currentPage)) {
- focusedChild.clearFocus();
- }
-
- final int nowX = getScrollX();
- final int newX = getScrollXByPage(nextPage);
- final int move = newX - nowX;
- final int absMove = Math.abs(move);
- int duration = SCROLL_DURATION;
- if (absMove < pageWidth) {
- duration = SCROLL_DURATION * absMove / pageWidth;
- }
- // 啟動左右切屏動畫
- scroller.startScroll(nowX, 0, move, 0, duration);
- invalidate();
- return true;
- }
-
- private void checkScrolling(float x) {
- float diff = Math.abs(x - lastMotionX);
- if (diff > distanceSlop) {
- state = STATE_SCROLLING;
- enableChildrenCache(true);
- }
- }
-
- /**
- * 開始滑動時設置允許使用緩存。<br/>
- * 結束滑動時設置取消緩存。<br/>
- */
- public void enableChildrenCache(boolean enable) {
- setChildrenDrawingCacheEnabled(enable);
- setChildrenDrawnWithCacheEnabled(enable);
- }
-
- /** 在正式顯示之前設置才有效。 */
- public boolean setPageWidth(int width) {
- if (isFirstLayout) {
- pageWidth = width;
- return true;
- }
- return false;
- }
-
- public void setOnSlidingListener(OnSlidingListener listener) {
- slidingListener = listener;
- }
-
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- LogOut.out(this, "onMeasure()");
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- pageWidth = (pageWidth == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidth;
- pageWidth = Math.min(Math.max(0, pageWidth), getMeasuredWidth());
-
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- int childWidthSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);
- View view = getChildAt(i);
- view.measure(childWidthSpec, heightMeasureSpec);
- }
- }
-
- @Override
- protected void onLayout(boolean changing, int left, int top, int right, int bottom) {
- LogOut.out(this, "onLayout");
- int childLeft = 0;
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View view = getChildAt(i);
- if (view.getVisibility() != View.GONE) {
- int childWidth = view.getMeasuredWidth();
- view.layout(childLeft, 0, childLeft + childWidth, view.getMeasuredHeight());
- childLeft += childWidth;
- }
- }
-
- if (isFirstLayout) {
- scrollTo(getScrollXByPage(currentPage), 0);
- isFirstLayout = false;
- }
- }
-
- public boolean onInterceptTouchEvent(MotionEvent event) {
- LogOut.out(this, "onInterceptTouchEvent action=" + event.getAction());
- final int action = event.getAction();
- if (action == MotionEvent.ACTION_MOVE && state != STATE_STATIC) {
- // MOVE及非靜止情況下,返回TRUE阻止將此事件傳遞給子組件,
- // 而是執行onTouchEvent()來實現滑動
- return true;
- }
- final float x = event.getX();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- lastMotionX = x;
- // 點擊按鈕時,此處設置狀態為靜止。
- state = scroller.isFinished() ? STATE_STATIC : STATE_SCROLLING;
- break;
- case MotionEvent.ACTION_MOVE:
- if (state == STATE_STATIC) {
- // 由於已靜止,在點擊按鈕後進行拖拉,則根據拖拉位移大小決定是否需要改變狀態進而進一步攔截此事件。
- checkScrolling(x);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- enableChildrenCache(false);
- state = STATE_STATIC;
- break;
- }
- // 非靜止狀態,將此事件交由onTouchEvent()處理。
- return state != STATE_STATIC;
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- LogOut.out(this, "onTouchEvent");
- super.onTouchEvent(event);
- final int action = event.getAction();
- final float x = event.getX();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- lastMotionX = x;
- if (scroller.isFinished() == false) {
- scroller.abortAnimation();
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (state == STATE_STATIC) {
- checkScrolling(x);
- } else if (state == STATE_SCROLLING) {
- int moveX = (int) (lastMotionX - x);
- lastMotionX = x;
- if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {
- // 對於越界的拖拉,則將位移減半。
- moveX = moveX >> 1;
- }
- scrollBy(moveX, 0);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (state == STATE_SCROLLING) {
- final int startX = getScrollXByPage(currentPage);
- // 默認選擇回到手指滑動之前的當前頁
- int whichPage = currentPage;
- int xSpace = getWidth() / 8;
- if (getScrollX() < startX - xSpace) {
- whichPage = Math.max(0, whichPage - 1);
- } else if (getScrollX() > startX + xSpace) {
- whichPage = Math.min(getChildCount() - 1, whichPage + 1);
- }
- scroll2page(whichPage);
- }
- state = STATE_STATIC;
- break;
- }
- return true;
- }
-
- /** 讓拖拉、動畫過程中界面過渡順滑。 */
- protected void dispatchDraw(Canvas canvas) {
- final long drawingTime = getDrawingTime();
-
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- drawChild(canvas, getChildAt(i), drawingTime);
- }
-
- if (slidingListener != null) {
- int adjustedScrollX = getScrollX() + getPagePadding();
- slidingListener.onSliding(adjustedScrollX);
- if (adjustedScrollX % pageWidth == 0) {
- slidingListener.onSlidingEnd(adjustedScrollX / pageWidth, adjustedScrollX);
- }
- }
- }
-
- /** 與Scroller相匹配,實現動畫效果中每一幀的界面更新。 */
- public void computeScroll() {
- if (scroller.computeScrollOffset()) {
- scrollTo(scroller.getCurrX(), scroller.getCurrY());
- postInvalidate();
- } else if (nextPage != INVALID_SCREEN) {
- currentPage = nextPage;
- nextPage = INVALID_SCREEN;
- enableChildrenCache(false);
- }
- }
-
-
- public static interface OnSlidingListener {
- public void onSliding(int scrollX);
-
- public void onSlidingEnd(int pageIdx, int scrollX);
- }
- }
SlidingIndicator.java
[java]
- package lab.sodino.sliding;
-
- import android.content.Context;
- import android.content.res.TypedArray;
- import android.graphics.Canvas;
- import android.graphics.Paint;
- import android.graphics.RectF;
- import android.util.AttributeSet;
- import android.view.View;
- import android.view.animation.AlphaAnimation;
- import android.view.animation.Animation;
- import android.view.animation.AnimationUtils;
- import android.view.animation.LinearInterpolator;
-
- /**
- * @author Sodino E-mail:[email protected]
- * @version Time:2012-1-18 下午03:31:08
- */
- public class SlidingIndicator extends View {
- public static final int BAR_COLOR = 0xaa777777;
- public static final int HIGHLIGHT_COLOR = 0xaa999999;
- public static final int FADE_DELAY = 2000;
- public static final int FADE_DURATION = 500;
-
- private int amount, currentPage, position;
- private Paint barPaint, highlightPaint;
- private int fadeDelay, fadeDuration;
- private float ovalRadius;
- private Animation animFadeout;
- /** RectF比Rect是精度上更精確。 */
- private RectF rectFBody, rectFIndicator;
-
- public SlidingIndicator(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- // 預設值。
- int barColor = BAR_COLOR, highlightColor = HIGHLIGHT_COLOR;
- fadeDelay = FADE_DELAY;
- fadeDuration = FADE_DURATION;
- if (attrs != null) {
- TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.sliding_SlidingIndicator);
- barColor = typedArr.getColor(R.styleable.sliding_SlidingIndicator_barColor, BAR_COLOR);
- highlightColor = typedArr.getColor(R.styleable.sliding_SlidingIndicator_highlightColor, HIGHLIGHT_COLOR);
- fadeDelay = typedArr.getInteger(R.styleable.sliding_SlidingIndicator_fadeDelay, FADE_DELAY);
- fadeDuration = typedArr.getInteger(R.styleable.sliding_SlidingIndicator_fadeDuration, FADE_DURATION);
- ovalRadius = typedArr.getDimension(R.styleable.sliding_SlidingIndicator_roundRectRadius, 0f);
- typedArr.recycle();
- }
- initialization(barColor, highlightColor, fadeDuration);
- }
-
- public SlidingIndicator(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SlidingIndicator(Context context) {
- super(context);
- }
-
- private void initialization(int barColor, int highlightColor, int fadeDuration) {
- barPaint = new Paint();
- barPaint.setColor(barColor);
-
- highlightPaint = new Paint();
- highlightPaint.setColor(highlightColor);
-
- animFadeout = new AlphaAnimation(1f, 0f);
- animFadeout.setDuration(fadeDuration);
- animFadeout.setRepeatCount(0);
- animFadeout.setInterpolator(new LinearInterpolator());
- // 設置動畫結束後,本組件保持動畫結束時的最後狀態,即全透明不可見。
- animFadeout.setFillEnabled(true);
- animFadeout.setFillAfter(true);
-
- rectFBody = new RectF();
- rectFIndicator = new RectF();
- }
-
- public void setPageAmount(int num) {
- if (num < 0) {
- throw new IllegalArgumentException("num must be positive.");
- }
- amount = num;
- invalidate();
- fadeOut();
- }
-
- private void fadeOut() {
- if (fadeDuration > 0) {
- clearAnimation();
- // 設置動畫的延時時間,此時間段內正好指示當前頁位置。
- animFadeout.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);
- setAnimation(animFadeout);
- }
- }
-
- public int getCurrentPage() {
- return currentPage;
- }
-
- public void setCurrentPage(int idx) {
- if (currentPage < 0 || currentPage >= amount) {
- throw new IllegalArgumentException("currentPage parameter out of bounds");
- }
- if (this.currentPage != idx) {
- this.currentPage = idx;
- this.position = currentPage * getPageWidth();
- invalidate();
- fadeOut();
- }
- }
-
- public void setPosition(int position) {
- if (this.position != position) {
- this.position = position;
- invalidate();
- fadeOut();
- }
- }
-
- public int getPageWidth() {
- return getWidth() / amount;
- }
-
- protected void onDraw(Canvas canvas) {
- rectFBody.set(0, 0, getWidth(), getHeight());
- canvas.drawRoundRect(rectFBody, ovalRadius, ovalRadius, barPaint);
- rectFIndicator.set(position, 0, position + getPageWidth(), getHeight());
- canvas.drawRoundRect(rectFIndicator, ovalRadius, ovalRadius, highlightPaint);
- }
- }