您的位置:首页 > 移动开发 > Android开发

Android 自定义侧滑ViewGroup

2014-03-27 23:42 267 查看
实现思路继承自ViewGroup,通过调用三个主要的ChildView的layout(int l, int t, int r, int b)方法来改变各个子View在ViewGroup中的位置。主要注意的是对触屏事件的拦截处理,不多说,上代码。Demo下载地址http://download.csdn.net/detail/ilioili/7701321





import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.Scroller;

public class SlidingFrame extends ViewGroup{
	private int leftWidthPercentage = 60;
	private int rightWidthPercentage = 60;
	private int animationDuration = 400;
	private View centreView;
	private float firstX,firstY;
	/**
	 * 是否设置了左侧的View
	 */
	private boolean hasLeftView;
	/**
	 * 是否设置了右侧的View
	 */
	private boolean hasRightView;
	private boolean interceptFlag;
	private int lastPosition;
	private View leftShadow;
	
	private int leftShadowWidth;
	private View leftView;
	private FrameLayout leftContiner;
	private FrameLayout centreContiner;
	private FrameLayout rightContiner;
	
	/**
	 * 左边View的宽度
	 */
	private int leftWidth;
	
	/**
	 * 中间View左上角的X坐标
	 */
	private int position = 0;
	private View rightShadow;
	private int rightShadowWidth;
	private View rightView;
	/**
	 * 右边View的宽度
	 */
	private int rightWidth;
	private Scroller scroller;
	final private int STATE_CENTRE_IDLE = 3;
	final private int STATE_CENTRE_TO_LEFT = 4;
	final private int STATE_CENTRE_TO_RIGHT = 6;
	final private int STATE_LEFT_IDLE = 1;
	final private int STATE_LEFT_TO_CENTRE = 5;
	final private int STATE_RIGHT_IDLE = 2;
	final private int STATE_RIGHT_TO_CENTRE = 7;
	private int slidingState = STATE_CENTRE_IDLE;
	public SlidingFrame(Context context) {
		super(context);
		init(context);
	}
	public SlidingFrame(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}
	
	private void addAllViews() {
		addView(leftContiner);
		addView(rightContiner);
		addView(leftShadow);
		addView(rightShadow);
		addView(centreContiner);
	}
	
	@Override
	public void computeScroll() {
		if(scroller.computeScrollOffset()){
			position = scroller.getCurrX();
			if(position>=leftWidth){
				position = leftWidth;
				scroller.forceFinished(true);
				slidingState = STATE_RIGHT_IDLE;
				if(null!=listener) listener.toTheRight();
			}else if(position<=-rightWidth){
				scroller.forceFinished(true);
				position = -rightWidth;
				slidingState = STATE_LEFT_IDLE;
				if(null!=listener) listener.toTheLeft();
			}else if(position==0){
				scroller.forceFinished(true);
				slidingState = STATE_CENTRE_IDLE;
				blockTouchEvent = false;
				if(null!=listener) listener.toTheCentre();
			}
			layoutChildren();
			postInvalidate();//不调用可能看不到滚动效果,不要换成invalidate()
		}else{
			if(position==0){
				blockTouchEvent = false;
				slidingState = STATE_CENTRE_IDLE;
			}
		}
	}
	@SuppressLint("NewApi")
	private void layoutChildren(){
		boolean canAlpha = android.os.Build.VERSION.SDK_INT>10;
		int width = getMeasuredWidth();
		int height = getMeasuredHeight();
		
		if(position<0){
			leftContiner.setVisibility(View.INVISIBLE);
			leftShadow.setVisibility(View.INVISIBLE);
			rightContiner.setVisibility(View.VISIBLE);
			rightShadow.setVisibility(View.VISIBLE);
			int left = width+position/2-rightWidth/2;
			int top = height/4+height*position/rightWidth/4;
			rightContiner.layout(left, top, left+rightWidth, top+height);
			if(rightShadowWidth!=0){
				rightShadow.layout(position+width, 0, position+rightShadowWidth+width, height);
			}
			if(canAlpha) rightContiner.setAlpha((-position*0.9f)/rightWidth+0.1f);
		}else if(position>0){
			rightContiner.setVisibility(View.INVISIBLE);
			rightShadow.setVisibility(View.INVISIBLE);
			leftContiner.setVisibility(View.VISIBLE);
			leftShadow.setVisibility(View.VISIBLE);
			int left = -leftWidth/2+position/2;
			int top = height/4-height*position/leftWidth/4;
			leftContiner.layout(left, top, left+leftWidth, top+height);
			if(leftShadowWidth!=0){
				leftShadow.layout(position-leftShadowWidth, 0, position, height);
			}
			if(canAlpha) leftContiner.setAlpha(position*0.9f/leftWidth+0.1f);
		}else{
			leftContiner.setVisibility(View.INVISIBLE);
			leftShadow.setVisibility(View.INVISIBLE);
			rightContiner.setVisibility(View.INVISIBLE);
			rightShadow.setVisibility(View.INVISIBLE);
			
		}
		centreContiner.layout(position, 0, width+position, height);
	}
	private boolean blockTouchEvent;
	private void init(Context c){
		leftShadow = new View(c);
		rightShadow = new View(c);
		leftContiner = new FrameLayout(c);
		rightContiner = new FrameLayout(c);
		centreContiner = new FrameLayout(c){
			public boolean onInterceptTouchEvent(MotionEvent ev) {
				if(blockTouchEvent){
					return true;
				}
				return super.onInterceptTouchEvent(ev);
			};
		};
		centreContiner.setBackgroundColor(Color.WHITE);
//		leftContiner.setId(View.generateViewId());
//		rightContiner.setId(View.generateViewId());
//		centreContiner.setId(View.generateViewId());
		setBackgroundColor(Color.BLACK);
		addAllViews();
		scroller = new Scroller(c);
	}
	
	@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
		if(slidingState==STATE_CENTRE_TO_LEFT || slidingState==STATE_LEFT_IDLE || slidingState==STATE_LEFT_TO_CENTRE){
			if(ev.getX()<position+getMeasuredWidth()){
				blockTouchEvent = true;
			}
		}else if(slidingState==STATE_CENTRE_TO_RIGHT || slidingState==STATE_RIGHT_TO_CENTRE || slidingState==STATE_RIGHT_IDLE){
			if(ev.getX()>position){
				blockTouchEvent = true;
			}
		}
		switch(ev.getAction()){
		case MotionEvent.ACTION_DOWN:
			firstX = ev.getX();
			firstY = ev.getY();
			lastPosition = position;
			interceptFlag=false;
			break;
		case MotionEvent.ACTION_MOVE:
			if(!interceptFlag){
				float dx = ev.getX()-firstX;
				float dy = ev.getY()-firstY;
				if(Math.abs(dx)+Math.abs(dy)>10 && Math.abs(dx)>Math.abs(dy)){
					interceptFlag=true;
				}
			}
		}
		return interceptFlag;
	}
	
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		layoutChildren();
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int width = MeasureSpec.getSize(widthMeasureSpec);
		int height = MeasureSpec.getSize(heightMeasureSpec);
		widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
		heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
		centreContiner.measure(widthMeasureSpec, heightMeasureSpec);
		if(hasLeftView){
			measureMyChild(leftContiner, width, height, leftWidthPercentage);
			leftWidth = leftContiner.getMeasuredWidth();
		}
		if(hasRightView){
			measureMyChild(rightContiner, width, height, rightWidthPercentage);
			rightWidth = rightContiner.getMeasuredWidth();
		}
		setMeasuredDimension(width, height);
	}
	
	private void measureMyChild(FrameLayout childView, int width, int height, int percentage){
		int childWidthMeasureSpec = 0;
		LayoutParams params = childView.getChildAt(0).getLayoutParams();
		if(params.width == LayoutParams.WRAP_CONTENT){
			childWidthMeasureSpec = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED), 0, LayoutParams.WRAP_CONTENT);
		}else if(params.width==LayoutParams.MATCH_PARENT) {
			childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width*percentage/100, MeasureSpec.EXACTLY);
		}else{
			childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(params.width, MeasureSpec.EXACTLY);
		}
		int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
		childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//		childView.getLayoutParams().width = params.width;
//		childView.getLayoutParams().height = params.height;
//		measureChild(childView, childWidthMeasureSpec, childHeightMeasureSpec);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
//		System.out.println("SlidingFrame onTouchEvent()");
		switch(ev.getAction()){
		case MotionEvent.ACTION_UP:
			smoothScroll();
			return false;
		case MotionEvent.ACTION_DOWN://onInterceptTouchEvent 返回true跳到这里
			if(!scroller.isFinished()){
				scroller.forceFinished(true);
			}
			firstX = ev.getX();
			return true;
		case MotionEvent.ACTION_MOVE:
			if(!scroller.isFinished()){
				scroller.forceFinished(true);
			}
			float distanceX = ev.getX()-firstX;
			float dx = position-lastPosition-distanceX;
			if(dx<1&dx>-1) return false;
//			System.out.println("position="+position);
			if(slidingState==STATE_LEFT_IDLE && dx>0){
				return true;
			}else if(slidingState==STATE_RIGHT_IDLE && dx<0){
				return true;
			}
			position = (int) (lastPosition+distanceX);//dx>0 往左滑动
			if(position>leftWidth){
				position = leftWidth;
			}else if(position<-rightWidth){
				position = -rightWidth;
			}
			if(position>0){
				if(dx>0){
					slidingState = STATE_RIGHT_TO_CENTRE;
				}else{
					slidingState = STATE_CENTRE_TO_RIGHT;
				}
			}else if(position<0){
				if(dx>0){
					slidingState = STATE_CENTRE_TO_LEFT;
				}else{
					slidingState = STATE_LEFT_TO_CENTRE;
				}
			}
			layoutChildren();
			postInvalidate();
			return true;
		}
		return false;
	}
	/**
	 * 设置动画持续时间,单位毫秒
	 */
	public void setAnimationDuration(int duration){
		animationDuration = duration;
	}
	/**
	 * 设置中间的View
	 */
	public void setCentreView(View v){
		centreView = v;
		centreContiner.addView(centreView);
	}
	/**
	 * 设置左侧阴影
	 * @param resid 背景资源ID
	 * @param width 阴影宽度,单位像素
	 */
	public void setLeftShadow(int resid, int width){
		leftShadow.setBackgroundResource(resid);
		leftShadowWidth = width;
	}
	/**
	 * 当left的LayoutParams的宽度为MathParent时设置此参数有效
	 * @param percentage 1~100,不调用此方法则根据View的LayoutParams来设置,如果没有设置LayoutParams则高度MatchParent,宽度WrapContent
	 */
	public void setLeftWidth(int percentage){
		leftWidthPercentage = percentage;
		if(leftWidthPercentage>100 || leftWidthPercentage<0){
			throw new IllegalAccessError("percentage 范围 1~100");
		}
	}
	
	/**
	 * 当left的LayoutParams的宽度为MathParent时设置此参数有效
	 * @param percentage 1~100,不调用此方法则根据View的LayoutParams来设置,如果没有设置LayoutParams则高度MatchParent,宽度WrapContent
	 */
	public void setRightWidth(int percentage){
		rightWidthPercentage = percentage;
		if(rightWidthPercentage>100 || rightWidthPercentage<1){
			throw new IllegalAccessError("percentage 范围 1~100");
		}
	}
	
	/**
	 * 设置左侧的View
	 */
	public void setLeftView(View v){
		hasLeftView = true;
		leftView = v;
		leftContiner.addView(leftView);
	}
	/**
	 * 设置右侧阴影
	 * @param resid 背景资源ID
	 * @param width 阴影宽度,单位像素
	 */
	public void setRightShadow(int resid, int width){
		rightShadow.setBackgroundResource(resid);
		rightShadowWidth = width;
	}
	/**
	 * 设置右侧的View
	 */
	public void setRightView(View v){
		hasRightView = true;
		rightView = v;
		rightContiner.addView(rightView);
	}
	/**
	 * 如果不在中间则滑动到中间
	 */
	public void slideToCentre(){
		if(position>0){
			scroller.forceFinished(true);
			slidingState = STATE_RIGHT_TO_CENTRE;
			smoothScroll();
		}else if(position<0){
			scroller.forceFinished(true);
			slidingState = STATE_LEFT_TO_CENTRE;
			smoothScroll();
		}
	}
	/**
	 * 在中间状态时,调用此函数会滑动到左侧。
	 */
	public void slideToLeft(){
		if(hasRightView&&slidingState==STATE_CENTRE_IDLE){
			scroller.forceFinished(true);
			slidingState = STATE_CENTRE_TO_LEFT;
			smoothScroll();
		}
	}
	/**
	 * 在中间状态时,调用此函数会滑动到右侧。
	 */
	public void slideToRight(){
		if(hasLeftView&&slidingState==STATE_CENTRE_IDLE){
			scroller.forceFinished(true);
			slidingState = STATE_CENTRE_TO_RIGHT;
			smoothScroll();
		}
	}
	private void smoothScroll(){
		switch(slidingState){
		case STATE_CENTRE_TO_RIGHT:
			scroller.startScroll(position, 0, leftWidth-position, 0, animationDuration);
			break;
		case STATE_RIGHT_TO_CENTRE:
			scroller.startScroll(position, 0, -position, 0, animationDuration);
			break;
		case STATE_LEFT_TO_CENTRE:
			scroller.startScroll(position, 0, -position, 0, animationDuration);
			break;
		case STATE_CENTRE_TO_LEFT:
			scroller.startScroll(position, 0, -rightWidth-position, 0, animationDuration);
			break;
		}
		postInvalidate();//不调用看不到滚动效果
	}
	
	/**
	 *当滑动停止时的回调,左侧停下,右侧停下,中间停下
	 */
	public interface SlidingStateListener{
		/**
		 * 滑动到左侧停下
		 */
		void toTheLeft();
		/**
		 * 滑动到右侧停下
		 */
		void toTheRight();
		/**
		 * 滑动到中间停下
		 */
		void toTheCentre();
	}
	private SlidingStateListener listener;
	
	/**
	 * @param listener 当滑动停止时的回调,左侧停下,右侧停下,中间停下
	 */
	public void setSlidingStateListener(SlidingStateListener listener){
		this.listener = listener;
	}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: