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详细代码:

时间: 2024-10-14 13:06:39

Android开发之自定义View专题(四):自定义ViewGroup的相关文章

Android开发之自定义View专题(三):自定义GridView

gridview作为android开发中常用的组件,其功能十分强大.但是,我们有时候有很多特殊的需求,需要在其基础上进行改造.有时候会有移动gridView中item位置的需求,这个网上已经有很多例子,博主就不在描述.今天博主讲的是移动gridView中item中的内容.博主没看过网上那些移动item位置的demo,不知道其原理是不是和博主想的一样.博主思考过,似乎博主的这种实现原理似乎也可以用作实现移动item位置.而之前博主百思不得其解的小米手机的桌面的自定义乱序排放,似乎也可以用这个原理去

Android开发之自定义View专题(二):自定义饼图

在图表里面,常用的图标一般为折线图.柱形图和饼图,上周,博主已经将柱形图分享.在博主的项目里面其实还用到了饼图,但没用到折线图.其实学会了其中一个,再去写其他的,应该都是知道该怎么写的,原理都是自己绘制图形,然后获取触摸位置判定点击事件.好了,废话不多说,直接上今天的饼图的效果图 这次也是博主从项目里面抽离出来的,这次的代码注释会比上次的柱形图更加的详细,更加便于有兴趣的朋友一起学习.图中的那个圆形指向箭头不属于饼图的部分,是在布局文件中为了美化另外添加进去的,有兴趣的朋友可以下载完整的项目下来

android开发最常用例子整理----(1)自定义按钮实现

android开发最常用例子整理----(1)自定义按钮实现 一.Activity MainActivity.java源码: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }

android开发最常用例子整理----(2)自定义ListView(SimpleAdapter实现)

android开发最常用例子整理----(2)自定义ListView(SimpleAdapter实现) 一.Activity MainActivity.java源码: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layou

Android开发之图片处理专题(三):利用ThreadPoolExcutor线程池实现多图片的异步加载

在上一篇专题Android开发之图片处理专题(二):利用AsyncTask和回调接口实现图片的异步加载和压缩中我们实现了listView的图片的大量加载.今天,我们换一种方式,采用线程池的方式来实现. 我们需要准备两个东西: 1.图片下载任务类 2.线程池. 1.图片下载任务类. 图片下载任务类,将需要显示的iamgeView,线程通讯消息管理者handler进行了封装.当图片下载无论成功还是失败,handler发送对应的消息,传入的iamgeView显示对应的图片.这里就不在应用软引用技术,采

Android开发之图片处理专题(二):利用AsyncTask和回调接口实现图片的异步加载和压缩

在上一篇专题Android开发之图片处理专题(一):利用软引用构建图片高速缓存中我们讲述了如何利用软引用技术构建高速缓存.那么想要用到图片,首先得有图片的来源.一般而言,一个应用的图片资源都是从服务器处获得的.今天,我们利用Android开发之网络请求通信专题(二):基于HttpClient的文件上传下载里面封装好的httpUtils来实现图片的下载,然后加载到本地配合软引用缓存使用,以一个listView为例子来说明. 一.准备工作 我们需要准备以下几个类(图片对象和软引用缓存类请参考上一篇专

Android开发之数据存储的四种方式之SharedPreferences

Android项目开发中使用的数据存储方式有:网络存储.sqlite存储.File存储和SharedPreferences存 储,四种存储方式对应的Demo别人是NetworkDemo.SqliteDemo.FileDemo和SharedPreferencesDemo, 根据应用的场景选择其中一种或多种方式,比如在登录界面验证,需要将用户名和密码通过SharedPreferences方式保存,注册信息的时候需要通 过网络将数据存储到后台数据库中.结合一个登录界面的验证,使用SharedPrefe

android自定义View之(四)------一键清除动画

1.前言: 自己也是参考别人的一些自定义view例子,学习了一些基本的自定义view的方法.今天,我参考了一些资料,再结合自已的一些理解,做了一个一键清除的动画.当年,我实现这个是用了几张图片,采用Frame anination的方式来实现,但是这个方法,不灵活,并且占资源,下面,我就采用自定义view的方法来实现这个功能. 2.效果图: 3.具体详细代码 3.1 \res\values\attrs_on_key_clear_circle_view.xml <resources> <de

自定义View(四)——加强版的EditText

1.如何加强? 输入内容后,有面会显示一个图片,用户点击后 可以清空文本框. 2.案例构造步骤 1)在drawable文件中建一个bg_frame_search.xml文件. <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" > <solid android: