1、概述
这是作者的第一篇文章,如果哪里写的不好请提出来,作者将及时改进。
本文将一步一步带你了解自定义ViewGroup的onLayout方法和layout
还有滑动冲突事件的解决。可以说自定义ViewGroup除了没有深入去研究onMeasure别的都有了。
阅读本文前请先理解View的X轴Y轴原理,还有layout和scrollTo的用法。
效果图
2、准备工作
我们需要先获取屏幕的宽度,然后获取屏幕一半的宽度,用于决定最高点,因为最高点的X轴是在屏幕的正中间。三分之一屏幕的宽度用于子View的宽度。
public class HorizontalScrollView extends ViewGroup {
private int windowWidth;
private int halfWindowWidth;//一半的屏幕宽度
private int oneThirdWindowWidth;//三分之一屏幕宽度
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 / 3;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);//测量子类
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
3、重写onLayout排列子View
public class HorizontalScrollView extends ViewGroup {
private int windowWidth;
private int halfWindowWidth;//一半的屏幕宽度
private int oneThirdWindowWidth;//三分之一屏幕宽度
private int childLeftLeft;//左边第一个的left值
private int childRightLeft;//右边第一个的left值
private int childCount;//子View个数
private int childHalfCount;
private int childViewHeightDifference = 50;//我们把子View的高度差设为50
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 / 3;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
childLeftLeft = halfWindowWidth;
childRightLeft = halfWindowWidth;
childCount = getChildCount();
childHalfCount = childCount / 2;
for (int i = childHalfCount; i >= 0; i--) {
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,
childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());
childLeftLeft -= childWidth;
}
}
for (int i = childHalfCount + 1; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
final int childWidth = oneThirdWindowWidth;
childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
childRightLeft + childWidth / 2 + childWidth, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
childRightLeft += childWidth;
}
}
}
}
这里我们进行子View的排列,把子View的排列分为两部分,一部分为左边,一部分为右边。
如果子View的个数是偶数,那么左边的for循环需要管理的是总个数一半在加一个,而右边是一半还要减一个。这里就是为什么右边循环体要加1。
childLeftLeft -= childWidth;用于排列下一个子View的时候位置向左移动一个子View的宽度。同时他们的高度也要减少一个高度差。
childRightLeft += childWidth;用于排列下一个子View的时候位置向右移动一个子View的宽度。同时他们的高度也要减少一个高度差。
4、ViewGroup滑动处理
public class HorizontalScrollView extends ViewGroup {
private int windowWidth;
private int halfWindowWidth;//一半的屏幕宽度
private int oneThirdWindowWidth;//三分之一屏幕宽度
private int childLeftLeft;//左边第一个的left值
private int childRightLeft;//右边第一个的left值
private int childCount;//子View个数
private int childHalfCount;
private int childViewHeightDifference = 50;//我们把子View的高度差设为50
private int startX;
private int lastX;
private int totalX;
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 / 3;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
childLeftLeft = halfWindowWidth;
childRightLeft = halfWindowWidth;
childCount = getChildCount();
childHalfCount = childCount / 2;
for (int i = childHalfCount; i >= 0; i--) {
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,
childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());
childLeftLeft -= childWidth;
}
}
for (int i = childHalfCount + 1; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
final int childWidth = oneThirdWindowWidth;
childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
childRightLeft + childWidth / 2 + childWidth, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
childRightLeft += childWidth;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
startX = (int) event.getRawX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
int delatX = startX - lastX;
totalX += delatX;
scroll();
break;
case MotionEvent.ACTION_UP:
break;
}
lastX = startX;
return true;
}
private void scroll() {
scrollTo(-totalX, 0);//左右滑动实际上是滑动ViewGroup的内容。
}
}
这里我们添加了一个onTouchEvent然后去算出总共滚动了多少,在去调用自己写的scroll()方法去滚动我们的View。
这个时候我们就可以看看刚刚layout的样子了。
5、分别让两边子View动起来
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 childHalfCount;
private int childViewHeightDifference = 50;//我们把子View的高度差设为50
//4、ViewGroup滑动处理
private int startX;
private int lastX;
private int totalX;
//5、分别让两边子View动起来
private int touchChildCenterCount;//互动时位于屏幕正中间的View
private int leftHigh;
private int rightHigh;
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 / 3;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
childLeftLeft = halfWindowWidth;
childRightLeft = halfWindowWidth;
childCount = getChildCount();
childHalfCount = childCount / 2;
leftHigh = childViewHeightDifference * (childHalfCount);//初始化左边for循环的最左边View的高度位置
rightHigh = childViewHeightDifference * (-childHalfCount);//初始化右边for循环的最左边View的高度位置,需要加上i * childViewHeightDifference
touchChildCenterCount = childHalfCount;
for (int i = childHalfCount; i >= 0; i--) {
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,
childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());
childLeftLeft -= childWidth;
}
}
for (int i = childHalfCount + 1; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
final int childWidth = oneThirdWindowWidth;
childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
childRightLeft + childWidth / 2 + childWidth, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
childRightLeft += childWidth;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
startX = (int) event.getRawX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
int delatX = startX - lastX;
totalX += delatX;
scroll();
break;
case MotionEvent.ACTION_UP:
break;
}
lastX = startX;
return true;
}
private void scroll() {
childLeftLeft = halfWindowWidth;
childRightLeft = halfWindowWidth;
for (int i = touchChildCenterCount; i >= 0; i--) {
final View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference,
childLeftLeft + childWidth / 2, 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 = childView.getMeasuredWidth();
childView.layout(childRightLeft + childWidth / 2, rightHigh + i * childViewHeightDifference,
childRightLeft + childWidth / 2 + childWidth, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
childRightLeft += childWidth;
}
}
leftHigh = -totalX * childViewHeightDifference * oneThirdWindowWidth + childViewHeightDifference * (childHalfCount);
rightHigh = totalX * childViewHeightDifference * oneThirdWindowWidth + childViewHeightDifference * (-childHalfCount);
scrollTo(-totalX, 0);//左右滑动实际上是滑动ViewGroup的内容。
}
}
首先我们定义左边for循环的最左边View的高度位置和右边for循环的最左边View的高度位置,然后在onLayout方法中给他赋值。
然后是scroll()方法中
childLeftLeft = halfWindowWidth;
childRightLeft = halfWindowWidth;
每次调用的时候需要重新赋值防止for循环后改变他们的值,下一步我们也会用到他们。
leftHigh = -totalX * childViewHeightDifference/oneThirdWindowWidth + childViewHeightDifference * (childHalfCount);
rightHigh = totalX* childViewHeightDifference/oneThirdWindowWidth + childViewHeightDifference * (-childHalfCount);
我们先来说说这两行代码,这里加号后面的是初始化的高度,至于+前面的根据x轴大小的改变,来改变y轴的值;也就是说高度的改变是通过左右滑动来获得的。当滑动屏幕的三分之一时高度改变一个高度差,也就是高度改变我们设置的childViewHeightDifference。这是一种函数的思想,可能比较难理解不过大家想想以前的一次函数就明白了,通过X轴的改变来得到Y轴的改变。
然后for循环中我们就不在定死每一个子View的高度值,而是根据高度变化来计算,别的就不变。
接下来我们要处理一下为什么上升到最高点不下降还继续上升了,这个是最难的。
6、处理上升到最高点之后下降
首先我要说怎么处理上升到最高点后,下降的思想。
我们按屏幕的中间的那个子View来划分左右两边的,那么中间那张我始终让他是左边控制的,如果左边上升过程中超过了中间这个View的位置那么他就会成为右边for循环的View。反之在向左滑动的时候,只要超过中间的位置就给左边for循环控制。
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 childHalfCount;
private int childViewHeightDifference = 50;//我们把子View的高度差设为50
//4、ViewGroup滑动处理
private int startX;
private int lastX;
private int totalX;
//5、分别让两边子View动起来
private int touchChildCenterCount;//互动时位于屏幕正中间的View
private int leftHigh;
private int rightHigh;
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 / 3;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
childLeftLeft = halfWindowWidth;
childRightLeft = halfWindowWidth;
childCount = getChildCount();
childHalfCount = childCount / 2;
leftHigh = childViewHeightDifference * (childHalfCount);//初始化左边for循环的最左边View的高度位置
rightHigh = childViewHeightDifference * (-childHalfCount);//初始化右边for循环的最左边View的高度位置,需要加上i * childViewHeightDifference
touchChildCenterCount = childHalfCount;
for (int i = childHalfCount; i >= 0; i--) {
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,
childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());
childLeftLeft -= childWidth;
}
}
for (int i = childHalfCount + 1; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
final int childWidth = oneThirdWindowWidth;
childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
childRightLeft + childWidth / 2 + childWidth, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
childRightLeft += childWidth;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
startX = (int) event.getRawX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
int delatX = startX - lastX;
totalX += delatX;
scroll();
break;
case MotionEvent.ACTION_UP:
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--) {
System.out.println("aaaaai:"+i);
final View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
if (totalX > 0) {
if (i == touchChildCenterCount ) {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeftLeft - childWidth / 2, rightHigh + i * childViewHeightDifference,
childLeftLeft + childWidth / 2 , rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
} else {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeftLeft - 3 * childWidth / 2, leftHigh - i * childViewHeightDifference,
childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight());
childLeftLeft -= childWidth;
}
} else {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference,
childLeftLeft + childWidth / 2 , 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 = childView.getMeasuredWidth();
childView.layout(childRightLeft + childWidth / 2, rightHigh + i * childViewHeightDifference,
childRightLeft + childWidth / 2 + childWidth, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
childRightLeft += childWidth;
}
}
//控制滑动范围
if ((childCount - 3) % 2 == 0) {
int totalXRange = windowWidth / 3 * (childCount - 3) / 2;
if (totalX <= -totalXRange) {
totalX = -totalXRange;
}
if (totalX >= totalXRange) {
totalX = totalXRange;
}
} else {
int totalXRange = (int) (windowWidth / 3 * ((childCount - 3) / 2 + 0.5));
if (totalX >= totalXRange + windowWidth / 6) {
totalX = totalXRange + windowWidth / 6;
}
if (totalX <= windowWidth / 6 - totalXRange) {
totalX = windowWidth / 6 - 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的内容。
}
}
这里左边for循环的代码比较多也比较复杂,我们先去判断totalX的值是否是大于0的就可以知道第一次滑动的时候用户的手势了,如果用户在开始的时候向右滑动,那么这个中间的View高度就要交给右边的高度控制,这样子就能下降。
大家根据这个思想自己理解一下就差不多了。
7、处理滑动和点击事件冲突和添加VelocityTracker
以下直接贴完整代码
添加了滑动冲突和点击冲突事件的解决,对于点击事件的解决是在onInterceptTouchEvent完成的不过不是很好希望有读者能帮忙给出更好的方法。
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 childHalfCount;
private int childViewHeightDifference = 50;//我们把子View的高度差设为50
//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 childMarginLeft = 10;
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 / 3;
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 = childView.getMeasuredWidth() * 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 = childView.getMeasuredWidth() * 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--) {
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 - childMarginLeft,
childViewHeightDifference * (childHalfCount - i) + childView.getMeasuredHeight());
childLeftLeft -= childWidth;
}
}
for (int i = childHalfCount + 1; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
final int childWidth = oneThirdWindowWidth;
if (i == childCount - 1) {
childMarginLeft = 0;
}
childView.layout(childRightLeft + childWidth / 2, childViewHeightDifference * (i - childHalfCount),
childRightLeft + childWidth / 2 + childWidth - childMarginLeft, childViewHeightDifference * (i - childHalfCount) + childView.getMeasuredHeight());
childRightLeft += childWidth;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
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 * windowWidth / 30;
postDelayed(new AutoScllorRunnable(currentTotalX), 1);
}
if (xVelocity < 0) {
scllorTime = 20;
int count = (int) (Math.abs(xVelocity) / 300);
int currentTotalX = totalX - count * windowWidth / 30;
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--) {
final View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
if (totalX > 0) {
if (i == touchChildCenterCount) {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeftLeft - childWidth / 2, rightHigh + i * childViewHeightDifference,
childLeftLeft + childWidth / 2 - childMarginLeft, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
} else {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeftLeft - 3 * childWidth / 2, leftHigh - i * childViewHeightDifference,
childLeftLeft - childWidth / 2 - childMarginLeft, leftHigh - i * childViewHeightDifference + childView.getMeasuredHeight());
childLeftLeft -= childWidth;
}
} else {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeftLeft - childWidth / 2, leftHigh - i * childViewHeightDifference,
childLeftLeft + childWidth / 2 - childMarginLeft, 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 = childView.getMeasuredWidth();
if (i == childCount - 1) {
childMarginLeft = 0;
}
childView.layout(childRightLeft + childWidth / 2, rightHigh + i * childViewHeightDifference,
childRightLeft + childWidth / 2 + childWidth - childMarginLeft, rightHigh + i * childViewHeightDifference + childView.getMeasuredHeight());
childRightLeft += childWidth;
}
}
//控制滑动范围
if ((childCount - 3) % 2 == 0) {
int totalXRange = windowWidth / 3 * (childCount - 3) / 2;
if (totalX <= -totalXRange) {
totalX = -totalXRange;
}
if (totalX >= totalXRange) {
totalX = totalXRange;
}
} else {
int totalXRange = (int) (windowWidth / 3 * ((childCount - 3) / 2 + 0.5));
if (totalX >= totalXRange + windowWidth / 6) {
totalX = totalXRange + windowWidth / 6;
}
if (totalX <= windowWidth / 6 - totalXRange) {
totalX = windowWidth / 6 - 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);
}
}
}
}
项目已经上传到github
随手给个star吧。谢谢。
如果有发现作者哪里写的有问题或者有更好的翻案欢迎指出,可以通过QQ联系作者502325525.