当前位置: 代码迷 >> Android >> Android开发之自定义View课题(四):自定义ViewGroup
  详细解决方案

Android开发之自定义View课题(四):自定义ViewGroup

热度:55   发布时间:2016-04-28 02:58:08.0
Android开发之自定义View专题(四):自定义ViewGroup

     有时候,我们会有这样的需求,一个activity里面需要有两个或者多个界面切换,就像Viewpager那样。但是在这些界面里面又需要能够有listView,gridview等组件。如果是纵向的,似乎还好,没什么影响,那么如果是横向的,那么就会出事情。因为Viewpager会拦截触摸事件。而如果将Viewpager的触摸事件拦截掉给里面的子控件,那么Viewpager又不能响应滑动事件了。那么如何又能让界面之间能够来回切换,又能让里面的子控件的触摸事件也能毫无影响的响应呢,这个时候,我们需要自定义一个Viewgroup,重写里面的触摸拦截方法即可。

     博主自定义的ViewGroup类似于SlideMenu,包含两个界面的来回切换,博主特意放了一个可以横向滑动item的listView组件在的个界面试验,这个listView控件也是博主之前从网上找出来用的,还不错的一个控件。详细看效果图:




好了,老规矩,完整项目下载地址:

http://download.csdn.net/detail/victorfreedom/8329667

有兴趣的同学可以下载下来研究学习。

自定义ViewGroup详细代码:




package com.freedom.slideviewgroup.ui;import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.view.animation.AccelerateInterpolator;import android.widget.Scroller;import com.freedom.slideviewgroup.FreedomApplication;import com.freedom.slideviewgroup.utils.DptoPxUtil;/** * @ClassName: SlideMenu * @author victor_freedom ([email protected]) * @createddate 2015-1-5 下午8:00:36 * @Description: TODO */public class SlideMenu extends ViewGroup {	private Context mContext;	// 默认第一个	private int currentScreen = 0; // 当前屏	// 移动控制者	private Scroller mScroller = null;	// 判断是否可以移动	private boolean canScroll = false;	// 是否拦截点击事件,false表示拦截,true表示将点击事件传递给子控件	private boolean toChild = false;	// 处理触摸事件的移动速率标准	public static int SNAP_VELOCITY = 600;	// 触发move的最小滑动距离	private int mTouchSlop = 0;	// 最后一点的X坐标	private float mLastionMotionX = 0;	// 处理触摸的速率	private VelocityTracker mVelocityTracker = null;	// 左右子控件的监听器	private LeftListener leftListener;	private RightListener rightListener;	// 触摸状态	private static final int TOUCH_STATE_REST = 0;	private static final int TOUCH_STATE_SCROLLING = 1;	private int mTouchState = TOUCH_STATE_REST;	// 响应触摸事件的边距判定距离(这个根据自定义响应)	public static int TOHCH_LEFT = 140;	public static int TOHCH_RIGHT = FreedomApplication.mScreenWidth;	public static final String TAG = "SlideMenu";	public SlideMenu(Context context) {		super(context);		mContext = context;		init();	}	public SlideMenu(Context context, AttributeSet attrs) {		super(context, attrs);		mContext = context;		init();	}	/**	 * @Title: init	 * @Description: 初始化滑动相关的东西	 * @throws	 */	private void init() {		mScroller = new Scroller(mContext, new AccelerateInterpolator());		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();	}	/**	 * @Title: onMeasure	 * @Description: 设定viewGroup大小	 * @param widthMeasureSpec	 * @param heightMeasureSpec	 * @throws	 */	@Override	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {		super.onMeasure(widthMeasureSpec, heightMeasureSpec);		int width = MeasureSpec.getSize(widthMeasureSpec);		int height = MeasureSpec.getSize(heightMeasureSpec);		setMeasuredDimension(width, height);		for (int i = 0; i < getChildCount(); i++) {			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);		}	}	/**	 * @Title: onLayout	 * @Description: 设置子控件的分布位置	 * @param changed	 * @param l	 *            left	 * @param t	 *            top	 * @param r	 *            right	 * @param b	 *            bottom	 * @throws	 */	@Override	protected void onLayout(boolean changed, int l, int t, int r, int b) {		int startLeft = 0; // 每个子视图的起始布局坐标		int childCount = getChildCount();		for (int i = 0; i < childCount; i++) {			View child = getChildAt(i);			child.layout(startLeft, 0, startLeft + getWidth(), getHeight());			startLeft = startLeft + getWidth(); // 校准每个子View的起始布局位置		}	}	/**	 * @Title: onInterceptTouchEvent	 * @Description:触摸事件拦截判定	 * @param ev	 * @return	 * @throws	 */	@Override	public boolean onInterceptTouchEvent(MotionEvent ev) {		final int action = ev.getAction();		// 如果当前正在滑动状态,则拦截事件		if ((action == MotionEvent.ACTION_MOVE)				&& (mTouchState != TOUCH_STATE_REST)) {			return true;		}		final float x = ev.getX();		switch (action) {		case MotionEvent.ACTION_DOWN:			mLastionMotionX = x;			// 判断当前状态			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST					: TOUCH_STATE_SCROLLING;			// 判断是否能够响应滑动事件			if ((ev.getX() < DptoPxUtil.dip2px(mContext, TOHCH_LEFT) && currentScreen == 1)					|| (ev.getX() > TOHCH_RIGHT							- DptoPxUtil.dip2px(mContext, 200) && currentScreen == 0)) {				canScroll = true;				toChild = false;				return super.onInterceptTouchEvent(ev);			} else {				// 如果不能则不拦截事件				canScroll = false;				toChild = true;				return false;			}		case MotionEvent.ACTION_MOVE:			if (toChild) {				return false;			}			final int differentX = (int) Math.abs(mLastionMotionX - x);			// 超过了最小滑动距离,并且没有传递事件给子控件,则更改状态			if (differentX > mTouchSlop) {				mTouchState = TOUCH_STATE_SCROLLING;			}			break;		case MotionEvent.ACTION_UP:			if (toChild) {				return false;			}			mTouchState = TOUCH_STATE_REST;			break;		}		return super.onInterceptTouchEvent(ev);	}	/**	 * @Title: onTouchEvent	 * @Description: 触摸事件响应	 * @param event	 * @return	 * @throws	 */	public boolean onTouchEvent(MotionEvent event) {		if (mVelocityTracker == null) {			mVelocityTracker = VelocityTracker.obtain();		}		// 获取移动的速率		mVelocityTracker.addMovement(event);		super.onTouchEvent(event);		// 手指位置地点		float x = event.getX();		switch (event.getAction()) {		case MotionEvent.ACTION_DOWN:			// 如果屏幕的动画还没结束,你就按下了,我们就结束该动画			if (mScroller != null) {				if (!mScroller.isFinished()) {					mScroller.abortAnimation();				}			}			mLastionMotionX = x;			break;		case MotionEvent.ACTION_MOVE:			if (canScroll) {				int detaX = (int) (mLastionMotionX - x);				mLastionMotionX = x;				// 移动距离				scrollBy(detaX, 0);			}			break;		case MotionEvent.ACTION_UP:			final VelocityTracker velocityTracker = mVelocityTracker;			velocityTracker.computeCurrentVelocity(1000);			int velocityX = (int) velocityTracker.getXVelocity();			// 滑动速率达到了一个标准(快速向右滑屏,返回上一个屏幕) 马上进行切屏处理			if (velocityX > SNAP_VELOCITY && currentScreen > 0 && canScroll) {				changedScreen(currentScreen - 1);			}			// 快速向左滑屏,返回下一个屏幕)			else if (velocityX < -SNAP_VELOCITY					&& currentScreen < (getChildCount() - 1) && canScroll) {				changedScreen(currentScreen + 1);			}			// 以上为快速移动的 ,强制切换屏幕			else {				// 如果移动缓慢,那么先判断是保留在本屏幕还是到下一屏幕				snapToDestination();			}			if (mVelocityTracker != null) {				mVelocityTracker.recycle();				mVelocityTracker = null;			}			mTouchState = TOUCH_STATE_REST;			break;		case MotionEvent.ACTION_CANCEL:			mTouchState = TOUCH_STATE_REST;			break;		}		return super.onInterceptTouchEvent(event);	}	@Override	public void computeScroll() {		if (mScroller.computeScrollOffset()) {// 如果返回true,则代表正在模拟数据,false表示已经停止模拟数据			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());// 更新偏移量			postInvalidate();		}	}	/**	 * @Title: startMove	 * @Description: 这是从第一个屏幕跳转到第二个屏幕的快捷方法	 * @throws	 */	public void startMove() {		if (currentScreen == 1) {			return;		}		if (currentScreen == 0 && rightListener != null) {			new Thread(new Runnable() {				@Override				public void run() {					rightListener.postNotifyDataChange();				}			}).start();		}		currentScreen++;		mScroller.startScroll((currentScreen - 1) * getWidth(), 0, getWidth(),				0, 600);		// 刷新界面		invalidate();// invalidate -> drawChild -> child.draw -> computeScroll	}	/**	 * @Title: startMoves	 * @Description: 跳转到第一个屏幕	 * @throws	 */	public void startMoves() {		changedScreen(0);	}	/**	 * @Title: snapToDestination	 * @Description: 当缓慢移动的时候,判断跳转屏幕	 * @throws	 */	private void snapToDestination() {		int destScreen = (getScrollX() + getWidth() / 3) / getWidth();		changedScreen(destScreen);	}	/**	 * @Title: changedScreen	 * @Description: 跳转屏幕	 * @param whichScreen	 * @throws	 */	private void changedScreen(int whichScreen) {		currentScreen = whichScreen;		if (currentScreen > getChildCount() - 1) {			currentScreen = getChildCount() - 1;		}		if (currentScreen == 0 && leftListener != null) {			leftListener.notifyDataChange();		}		if (currentScreen == 1 && rightListener != null) {			rightListener.notifyDataChange();		}		// getScrollX得到的是当前视图相对于父控件的偏移量。初始值是0,		int dx = currentScreen * getWidth() - getScrollX();		// dx为正值时,屏幕向右滑动,dx为负值时,屏幕向左滑动		mScroller.startScroll(getScrollX(), 0, dx, 0, 600);		postInvalidate();	}	public interface LeftListener {		public void notifyDataChange();	}	public interface RightListener {		public void notifyDataChange();		public void postNotifyDataChange();	}	public void setLeftListener(LeftListener leftListener) {		this.leftListener = leftListener;	}	public void setRightListener(RightListener rightListener) {		this.rightListener = rightListener;	}}


自此自定义View专题已经讲解完毕,相信所有人都对自定义View有了一个初步的认识,基本上就是那么几个步骤。而自定义ViewGroup相对于自定义View多了一个步骤在于要重写onLayout方法来摆放包含在里面的子控件,其余的,都差不多。

     

好了,老规矩,完整项目下载地址:

http://download.csdn.net/detail/victorfreedom/8329667

有兴趣的同学可以下载下来研究学习。

自定义ViewGroup详细代码:

  相关解决方案