package com.laotan.easyreader.view; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; /** * Created by laotan on 2017/3/6. */ public class HorizontalScrollView extends ViewGroup { /** * 每一步我们重点关心每一步新添加的变量。 */ //2、准备工作 private int windowWidth; private int halfWindowWidth;//一半的屏幕宽度 private int oneThirdWindowWidth;//三分之一屏幕宽度 //3、重写onLayout排列子View private int childLeftLeft;//左边第一个的left值 private int childRightLeft;//右边第一个的left值 private int childCount;//子View个数 private int childViewHeightDifference = 50;//我们把子View的高度差设为50 private int childHalfCount; //4、ViewGroup滑动处理 private int startX; private int lastX; private int totalX; //5、分别让两边子View动起来 private int touchChildCenterCount;//互动时位于屏幕正中间的View private int leftHigh; private int rightHigh; //7、处理滑动和点击事件冲突和添加VelocityTracker private VelocityTracker mVelocityTracker; private int scllorTime; private int dispatchEndX; private int dispatchEndY; private int childMarginRight = 10; private int visualCount = 3; public HorizontalScrollView(Context context) { this(context, null); } public HorizontalScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { WindowManager wm = (WindowManager) getContext() .getSystemService(Context.WINDOW_SERVICE); windowWidth = wm.getDefaultDisplay().getWidth();//获取屏幕宽度 halfWindowWidth = windowWidth / 2; oneThirdWindowWidth = windowWidth / visualCount; mVelocityTracker = VelocityTracker.obtain(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSizd = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSizd = MeasureSpec.getSize(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = oneThirdWindowWidth * childCount; measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(measuredWidth, measuredHeight); } else if (heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(widthSpecSizd, measuredHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = oneThirdWindowWidth * childCount; setMeasuredDimension(measuredWidth, heightSpecSizd); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { childCount = getChildCount(); // if (childCount < 3) { // try { // throw new Exception("HorizontalScrollView必须有3个或者3个以上的子View"); // } catch (Exception e) { // e.printStackTrace(); // } // } childLeftLeft = halfWindowWidth; childRightLeft = halfWindowWidth; childHalfCount = childCount / 2; leftHigh = childViewHeightDifference * (childHalfCount);//初始化左边for循环的最左边View的高度位置 rightHigh = childViewHeightDifference * (-childHalfCount);//初始化右边for循环的最左边View的高度位置,需要加上i * childViewHeightDifference touchChildCenterCount = childHalfCount; for (int i = childHalfCount; i >= 0; i--) { childMarginRight = 10; final View childView = getChildAt(i); if (childView.getVisibility() != GONE) { final int childWidth = oneThirdWindowWidth; childView.layout(childLeftLeft - childWidth / 2, childViewHeightDifference * (childHalfCount - i), childLeftLeft - childWidth / 2 + childWidth - childMarginRight, childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight()); childLeftLeft -= childWidth; } } for (int i = childHalfCount + 1; i < childCount; i++) { childMarginRight = 10; final View childView = getChildAt(i); if (childView.getVisibility() != GONE) { final int childWidth = oneThirdWindowWidth; if (i == childCount - 1) { childMarginRight = 0; } childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount), childRightLeft + childWidth / 2 + childWidth - childMarginRight, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight()); childRightLeft += childWidth; } } } @Override public boolean onTouchEvent(MotionEvent event) { if (getChildCount()<=3){ return true; } mVelocityTracker.addMovement(event); startX = (int) event.getRawX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: int delatX = 0; //滑动冲突时不能马上获取到滑动的delatX导致突然是一个很大的值没有想到很好的办法,就只能这样子限制delatX了。 if (lastX != 0) { delatX = startX - lastX; if (delatX >= 50) { delatX = 50; } if (delatX <= -50) { delatX = -50; } } totalX += delatX; scroll(); break; case MotionEvent.ACTION_UP: mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) >= childViewHeightDifference) { //以下20和300都是我经过多次测试选取的值,感觉运行代码损失了一些时间所以只能测试找出来不能通过计算。 if (xVelocity > 0) { scllorTime = 20; int count = (int) (xVelocity / 300); int currentTotalX = totalX + count * oneThirdWindowWidth/10; postDelayed(new AutoScllorRunnable(currentTotalX), 1); } if (xVelocity < 0) { scllorTime = 20; int count = (int) (Math.abs(xVelocity) / 300); int currentTotalX = totalX - count * oneThirdWindowWidth/10; postDelayed(new AutoScllorRunnable(currentTotalX), 1); } } break; } lastX = startX; return true; } private void scroll() { childLeftLeft = halfWindowWidth; childRightLeft = halfWindowWidth; if (touchChildCenterCount - childHalfCount >= 1) { childLeftLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth; childRightLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth; } if (touchChildCenterCount - childHalfCount <= -1) { childLeftLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth; childRightLeft = halfWindowWidth + (touchChildCenterCount - childHalfCount) * oneThirdWindowWidth; } for (int i = touchChildCenterCount; i >= 0; i--) { childMarginRight = 10; final View childView = getChildAt(i); if (childView.getVisibility() != GONE) { if (totalX > 0) { if (i == touchChildCenterCount) { final int childWidth = oneThirdWindowWidth; childView.layout(childLeftLeft - childWidth / 2, rightHigh + i * childViewHeightDifference, childLeftLeft + childWidth / 2 - childMarginRight, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight()); } else { final int childWidth = oneThirdWindowWidth; childView.layout(childLeftLeft - 3 * childWidth / 2, leftHigh - i * childViewHeightDifference, childLeftLeft - childWidth / 2 - childMarginRight, leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight()); childLeftLeft -= childWidth; } } else { final int childWidth = oneThirdWindowWidth; childView.layout(childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference, childLeftLeft + childWidth / 2 - childMarginRight, leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight()); childLeftLeft -= childWidth; } } } for (int i = touchChildCenterCount + 1; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != GONE) { final int childWidth = oneThirdWindowWidth; childMarginRight = 10; if (i == childCount - 1) { childMarginRight = 0; } childView.layout(childRightLeft + childWidth / 2, rightHigh + i * childViewHeightDifference, childRightLeft + childWidth / 2 + childWidth - childMarginRight, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight()); childRightLeft += childWidth; } } //控制范围 if ((childCount - visualCount) % 2 == 0) { int totalXRange = windowWidth / visualCount * (childCount - visualCount) / 2; if (totalX <= -totalXRange) { totalX = -totalXRange; } if (totalX >= totalXRange) { totalX = totalXRange; } } else { int totalXRange = (int) (windowWidth / visualCount * ((childCount - visualCount) / 2 + 0.5)); if (totalX >= totalXRange + windowWidth / (visualCount*2)) { totalX = totalXRange + windowWidth / (visualCount*2); } if (totalX <= windowWidth / (visualCount*2) - totalXRange) { totalX = windowWidth / (visualCount*2) - totalXRange; } } if (totalX * oneThirdWindowWidth >= 1 || totalX * oneThirdWindowWidth <= -1) { touchChildCenterCount = childHalfCount - totalX / oneThirdWindowWidth; } if (totalX * oneThirdWindowWidth == 0) { touchChildCenterCount = childHalfCount; } leftHigh = -totalX * childViewHeightDifference / oneThirdWindowWidth + childViewHeightDifference * (childHalfCount); rightHigh = totalX * childViewHeightDifference / oneThirdWindowWidth + childViewHeightDifference * (-childHalfCount); scrollTo(-totalX, 0);//左右滑动实际上是滑动ViewGroup的内容。 } @Override public boolean dispatchTouchEvent(MotionEvent ev) { //打开注释就可以在这个控件得到上下滑动的事件,不过我们这个控件的需求基本是左右滑动打开的话会影响体验。 int dispatchStartX = (int) ev.getX(); int dispatchStartY = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int deltax = dispatchEndX - dispatchStartX; int deltaY = dispatchEndY - dispatchStartY; if (Math.abs(deltaY) <= Math.abs(deltax)) { getParent().requestDisallowInterceptTouchEvent(true); } else { getParent().requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; } dispatchEndX = dispatchStartX; dispatchEndY = dispatchStartY; return super.dispatchTouchEvent(ev); } int InterceptEndX; int InterceptEndY; boolean intercept; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int dispatchStartX = (int) ev.getX(); int dispatchStartY = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: intercept = false; break; case MotionEvent.ACTION_MOVE: int deltax = InterceptEndX - dispatchStartX; int deltaY = InterceptEndY - dispatchStartY; if (Math.abs(deltaY) <= Math.abs(deltax) - 10) { startX = 0; lastX = 0; intercept = true; } else { intercept = false; } break; case MotionEvent.ACTION_UP: intercept = false; break; } InterceptEndX = dispatchStartX; InterceptEndY = dispatchStartY; return intercept; } private class AutoScllorRunnable implements Runnable { int currentTotalx; int totalXValue; public AutoScllorRunnable(int currentTotalx) { this.currentTotalx = currentTotalx; totalXValue = (currentTotalx - totalX) / 10; } @Override public void run() { if (scllorTime > 0) { totalX = totalX + totalXValue; scllorTime--; scroll(); HorizontalScrollView.this.postDelayed(this, 1); } } } }