PS:本文系转载文章,阅读原文可读性会更好,文章末尾有原文链接
ps:本篇文章的 demo 是用 AndroidStudio 工具开发的。这里在Android中的自定义View(一)这篇文章的基础上再继续写一下自定义 View 的案例,这里的自定义 ViewGroup 直接继承于 ViewGroup,我们写一个类似 ViewPager 这样的自定义 ViewGroup,可以让它左右滑动。(1)新建一个 Activity ,名叫 DemoActivity;public class DemoActivity extends Activity { private MyViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); initView(); } private void initView() { LayoutInflater inflater = getLayoutInflater(); mViewPager = (MyViewPager) findViewById(R.id.viewPager); //1、 final int screenWidth = getScreenMetrics(this).widthPixels; for (int i = 0; i < 4; i++) { View layout =inflater.inflate( R.layout.content_layout, mViewPager, false); layout.getLayoutParams().width = screenWidth; TextView textView = (TextView) layout.findViewById(R.id.tv); textView.setText("第" +(i+1) + "页面"); //2、 mViewPager.addView(layout); } } public DisplayMetrics getScreenMetrics(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); return dm; }}注释1表示获取手机屏幕的宽度;注释2表示将创建的 View 添加到 MyViewPager 中。(2)新建一个布局文件 activity_demo.xml; (3)新建一个类 MyViewPager并继承于 ViewGroup;public class MyViewPager extends ViewGroup { private int mChildrenSize; private int mChildWidth; private int mChildIndex; private int mLastX = 0; private int mLastY = 0; private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; private VelocityTracker mVelocityTracker; public MyViewPager(Context context) { super(context); init(); } public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MyViewPager(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { if (mScroller == null) { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } } //3、 @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); //4、 intercepted = true; } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (Math.abs(deltaX) > Math.abs(deltaY)) { //5、 intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; } @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(event); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; scrollBy(-deltaX, 0); break; } case MotionEvent.ACTION_UP: { int scrollX = getScrollX(); //6、 mVelocityTracker.computeCurrentVelocity(1000); //7、 float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) >= 50) { mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; } else { mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); //8、 int dx = mChildIndex mChildWidth - scrollX; smoothScrollBy(dx, 0); mVelocityTracker.clear(); break; } default: break; } mLastX = x; mLastY = y; return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int maxChildWidth = 0; int maxChildHeight = 0; //14、 for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getMeasuredHeight() > maxChildHeight) { maxChildHeight = child.getMeasuredHeight(); } if (child.getMeasuredWidth() > maxChildWidth) { maxChildWidth = child.getMeasuredWidth(); } } int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { measuredWidth = maxChildWidth childCount; measuredHeight = maxChildHeight; //15、 setMeasuredDimension(measuredWidth, measuredHeight); } else if (heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredHeight = maxChildHeight; //16、 setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight()); } else if (widthSpecMode == MeasureSpec.AT_MOST) { measuredWidth = maxChildWidth * childCount; //17、 setMeasuredDimension(measuredWidth, heightSpaceSize); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; //18、 childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } private void smoothScrollBy(int dx, int dy) { //9、 mScroller.startScroll(getScrollX(), 0, dx, 0, 500); //10、 invalidate(); } //11、 @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { //12、 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //13、 postInvalidate(); } } @Override protected void onDetachedFromWindow() { mVelocityTracker.recycle(); super.onDetachedFromWindow(); }}注释3的 onInterceptTouchEvent 方法是是否拦截子元素触摸的方法,如果返回值是 true,那么就拦截子元素触摸;如果返回值是 false,那么就不拦截子元素触摸。注释4和注释5的代码都表示拦截子元素触摸;注释6表示1s的时间内运行来多少个像素点;注释7表示获取X轴上的速率;注释8表示获取X轴上移动的距离;注释9中的 mScroller 是一个辅助动画的类,startScroll 方法并没有开始移动,只是将该方法的参数保存到 mScroller 这个对象中。注释10的方法执行后最终调用注释11的方法;注释12的方法最终完成一步步的移动;注释13的代码执行后最终调用注释11的代码,直到注释6代码中的 mVelocityTracker.computeCurrentVelocity(1000) 这个参数1000毫秒过后,就不在调用注释13的代码了,也就是 mScroller.computeScrollOffset() 为 false。注释14的 for 循环是为了获取子 View 的最大宽度和最大高度;注释15,当 MyViewPager 宽和高的模式为 MeasureSpec.AT_MOST 时,将子 View 中最大的宽度乘积子 View 的个数作为 MyViewPager 的宽度,将子 View 中最大的高度作为 MyViewPager 的高度。注释16,当 MyViewPager 高的模式为 MeasureSpec.AT_MOST 时,将子 View 中最大的高度作为 MyViewPager 的高度;注释17,当 MyViewPager 宽的模式为 MeasureSpec.AT_MOST 时,将子 View 中最大的宽度乘积子 View 的个数作为 MyViewPager 的宽度;注释18,执行子 View 的 layout 过程。程序运行结果如下所示;
第一次用手指向左滑动就可以看到效果了。上面的代码并规范,第一点是没有子元素的时候不应该直接把宽和高设为0,而应该根据 LayoutParams 中的宽和高来做相应处理,第二点是在测量 MyViewPager 的宽和高时没有考虑到它的 padding 以及子元素的 margin,因为它的 padding 以及子元素的 margin会影响到 MyViewPager 的宽和高。