首页 > 代码库 > Android开发之自定义View专题(四):自定义ViewGroup

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 (x_freedom_reddevil@126.com)
 * @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详细代码:

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