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;<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical" > <com.xiaoer.MyViewPager android:id="@+id/viewPager" android:layout_width="wrap_content" android:layout_height="match_parent" /></LinearLayout>(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 的宽和高。