先上效果图:
实现“左右滑屏”核心类是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()方法中为本组件的动画设置了延时,体验上更亲近:
animFadeout.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay); setAnimation(animFadeout);
本文内容归CSDN博客博主Sodino 所有
转载请注明出处:http://blog.csdn.net/sodino/article/details/7211049
ActSlidingContainer.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); } }
SlidingContainer.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
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); } }
末尾,自己推荐另一个左右滑屏实现方法:ViewPager
http://my.oschina.net/kzhou/blog/29157