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详细代码:
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。