[Android]左右滑屏的实现

先上效果图:

[Android]左右滑屏的实现_第1张图片


实现“左右滑屏”核心类是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


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


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


   

你可能感兴趣的:(android,action,float,button,scroll,initialization)