在我们进行android开发的时候虽然官方提供了形形色色的控件,但是有的时候根据不同业务需求我们找不到官方的控件支持,那么这个时候就需要我们自己去定义控件来适应不同的需求了。众所周知Android自定义为一般分为三类:自定义ViewGroup,自绘View,组合View。本篇将和大家一起探讨自定义ViewGrop 的相关知识.
转载请注明出处: http://blog.csdn.net/unreliable_narrator?viewmode=contents
一.ViewGrop是什么。
首先我们先来看看官方文档是如何进行描述的:
翻译过来的大体意思就是: 一个ViewGroup
是一个可以包含其他子View
特殊的View
。并且是子View
或者布局的父容器。而且ViewGroup
定义了ViewGroup.LayoutParams
类。简单说ViewGroup实际上就是存放一些view的容器,比如官方自带的一些Linerlayout,RelativeLayout等等都是ViewGroup的具体实现
。由于Viewgroup一般是用来作为装子View的容器,所以一般不会去自己绘制(ondraw)内容,viewgroup通过onMeasure方法来确定子View的大小,然后通过Onlayout方法来确定View摆放的位置。
二.ViewGrop的测量。
1.onMeasure()方法作用:
onMeasure()主要的作用就是测量ViewGroup的大小。而viewgroup的作用主要用于管理子view,而在测量的时候可以分两种情况:
1.viewgroup在xml中定义时,宽和高是match_parent或者是固定值,此时说明该viewgroup的宽高是明确的,所以不需要自己去计算。就可以用系统的onMeasure。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
2.viewgroup在xml中定义时,宽和高是wrap_content,此时viewgroup的大小不确定,就需要重写onMeasure,遍历所有的子View,然后对每个子View使用measureChild(childView)进行测量,测量完以后然后根据childView.getMeasuredWidth()获取到子控件的宽高,然后根据子View的排列规则,计算出最终ViewGroup的大小,最后用setMeasuredDimension(int measuredWidth, int measuredHeight)设置进去。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWith = 0;
int measureHeight = 0;
int childCount = getChildCount();//获取子控件的总数
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);//获取子控件
measureChild(childView, widthMeasureSpec, heightMeasureSpec);//测量子控件
measureWith += childView.getMeasuredWidth();
measureHeight += childView.getMeasuredHeight();
}
setMeasuredDimension(measureWith, measureHeight);//设置控件的大小
}
2.onMeasure()方法参数解析:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
调用此方法会传进来的两个参数:int widthMeasureSpec, int heightMeasureSpec 。他们是父类传递过来给当前view的一个建议值,虽然表面上看起来他们是int类型的数字,其实他们是由mode+size两部分组成的。 widthMeasureSpec和heightMeasureSpec转化成二进制数字表示,他们都是30位的。前两位代表mode(测量模式),后面28位才是他们的实际数值(size)。
MeasureSpec.getMode(widthMeasureSpec)//获取测量模式
MeasureSpec.getSize(widthMeasureSpec)//获取测量尺寸
三种不同的测量模式:
EXACTLY:表示设置了精确的值,一般View设置其宽或者高为精确值(也就是我们在布局文件中设定的值如50dp)或者match_parent时,其宽高所对应的值为EXACTLY;
AT_MOST:表示子布局被限制在一个最大值内,一般当View设置其宽或者高为wrap_content时,其宽高所对应的值为AT_MOST;
UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中,布局可以滚动,此种模式比较少见。
3.测量子控件相关的方法:
1. measureChild(subView, int wSpec, int hSpec),调用viewGroup的测量子view的方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();//获取所有的子View个数
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);//获取子View
measureChild(childView, widthMeasureSpec, heightMeasureSpec);//测量子View
}
}
2. measureChildren(int wSpec, int hSpec)测量所有子view 都是 多宽,多高, 内部调用了measureChild方法 ;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
3. measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed),某一个子view,多宽,多高, 内部加上了viewGroup的padding值、margin值和传入的宽高wUsed、hUsed ;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();//获取所有的子View个数
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);//获取子View
measureChildWithMargins(childView, widthMeasureSpec, int widthUsed,
heightMeasureSpec, int heightUsed);//某一个子view,多宽,多高, 内部加上了viewGroup的padding值、margin值和传入的宽高wUsed、hUsed
}
}
4.getMeasuredWidth()与getWidth()的区别.
getMeasuredWidth()与getWidth()的区别。他们的值大部分时间都是相同的,但意义确是根本不一样的,我们就来简单分析一下。
区别主要体现在下面两点:
(1)首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。
(2) getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过layout(left,top,right,bottom)方法设置的。
二.ViewGrop的布局
1.onLayout()方法:
onLayout()是实现所有子控件布局的函数。它的作用就是给每一个子控件设置摆放的位置.
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
View的放置都是根据一个矩形空间放置的,onLayout传下来的l,t,r,b分别是放置父控件的矩形可用空间(除去margin和padding的空间)的左上角的left、top以及右下角right、bottom值参数changed表示view有新的尺寸或位置。需要注意的是如果子view超出了viewgroup所onMeasure(设置好大小)的部分,那部分不会显示出来。
例如:如果我们要实现如下图所示的效果:我们可以看到我们自定义的viewgrop里面的三个子控件并排显示,并且自定义的ViewGrop的高度是和子控件里面最高的View是一样高的.ViewGrop的宽度是和所有子控件的宽度相累加的是一样的.
public class MyViewGroup extends ViewGroup {
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//调用该方法预先对子控件进行测量
measureChildren(widthMeasureSpec, heightMeasureSpec);
//设置控件的宽高
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
/**
* 返回控件的宽
*
* @param widthMeasureSpec
* @return
*/
private int measureWidth(int widthMeasureSpec) {
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
int result = 0;
//判断是否是包裹内容的模式
if (specMode == MeasureSpec.AT_MOST) {
int size = 0;
//将所有的子控件的宽度进行叠加
for (int x = 0; x < getChildCount(); x++) {
View child = getChildAt(x);
int measuredWidth = child.getMeasuredWidth();
size += measuredWidth;
}
result = size;
} else {
result = specSize;
}
return result;
}
/**
* 返回控件的高
*
* @param heightMeasureSpec
* @return
*/
private int measureHeight(int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int result = 0;
//判断是否是包裹内容
if (heightMode == MeasureSpec.AT_MOST) {
for (int x = 0; x < getChildCount(); x++) {
View child = getChildAt(x);
int measuredHeight = child.getMeasuredHeight();
//取子控件最大的高度
int min = Math.max(result, measuredHeight);
result = min;
}
} else {
result = heightSize;
}
return result;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int left = 0; //左边的距离
View child;
//遍历布局子元素
for (int i = 0; i < childCount; i++) {
child = getChildAt(i);
int width = child.getMeasuredWidth();
child.layout(left, 0, left + width, child.getMeasuredHeight());
left += width;
}
}
}
布局文件:
<com.dapeng.viewgropdemo.MyViewGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/black">
<View
android:layout_width="50dip"
android:layout_height="50dip"
android:background="@android:color/holo_green_dark"></View>
<View
android:layout_width="100dip"
android:layout_height="100dip"
android:background="@android:color/holo_red_light"></View>
<View
android:layout_width="200dip"
android:layout_height="200dip"
android:background="@android:color/holo_orange_light"></View>
</com.dapeng.viewgropdemo.MyViewGroup>
三.LayoutParams
1.出现margin等属性无效的原因
比如我们有的时候需要给ViewGrop里面的子控件设置margin等等相关的属性值,直接在布局文件里面进行书写完成以后,仍然是没有效果的。因为测量和布局都是我们自己实现的,我们在onLayout()中没有根据Margin来布局,当然不会出现有关Margin的效果。
需要特别注意的是:如果我们在onLayout()中根据margin来布局的话,那么我们在onMeasure()中计算容器的大小时,也要加上margin,不然会导致容器太小,而控件显示不全的问题。
2.LayoutParams
LayoutParams翻译过来就是布局参数,通俗点说就是能获取到布局一些特定的属性,比如说布局的边距什么的。相当于一个Layout的信息包,它封装了Layout的位置、高、宽等信息。假设在屏幕上一块区域是由一个Layout占据的,如果将一个View添加到一个viewgroup中,最好告诉viewgroup用户期望的布局方式,也就是将一个认可的layoutParams传递进去。
参数 1. AttributeSet attrs xml解析inflate时生成和容器类型匹配的布局LayoutParams
2. ViewGroup.LayoutParams p 传入viewGroupLayoutParams 然后生成和容器类型匹配的布局LayoutParams
childView调用的时候就可以取得相关的参数:
MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams();
int bottomMargin = cParams.bottomMargin;
int topMargin = cParams.topMargin;
int leftMargin = cParams.leftMargin;
int rightMargin = cParams.rightMargin;
View通过LayoutParams类告诉其父视图它想要地大小(即,长度和宽度)。 因此,每个View都包含一个ViewGroup.LayoutParams类或者其派生类,View类依赖于ViewGroup.LayoutParams ViewGroup子类可以实现自定义LayoutParams,自定义LayoutParams提供了更好地扩展性。
LayoutParams类也只是简单的描述了宽高,宽和高都可以设置成三种值:
1,一个确定的值;
2,FILL_PARENT,即填满(和父容器一样大小);
3,WRAP_CONTENT,即包裹住组件就好。
通过继承并且扩展LayoutParams类可以增加更多的属性.
四.Android中视图的坐标系:
- 获取控件在父控件里面的相对坐标(位置).如下图所示.
2.scrollTo(int x,int y)和scrollBy(int x,int y)方法.
在自定义View的时候我们知道我们要重写几个方法,其中有一个方法是onDraw(),在这个方法里面我们可以创建画笔在这个画布上面任意的画图.这里需要特别注意的一点就是画布并没有边界的,,也就是说View视图并没有完全显示.调用scrollto()和scroolBy()实际上View并没有移动,而是View的内容(或者说移动的是View的视图)在水平或者说是垂直方向上面的偏移.如果要使得View的位置移动就需要调用offsetLeftAndRight()左右移动;.offsetTopAndBottom()上下移动;scrollTo 和 scrollBy 移动的是 View 的内容。即:对 TextView 使用的话,则是移动它的文本;对 ViewGroup 使用的话,则移动的是所有的子 View。所以,一般不对 View 使用这两个方法,而是对 ViewGroup 使用。
scrollTo(int x,int y):
调用该方法,这个View的左上角坐标原点会在View视图里进行竖直偏移y,水平偏移x.该方法是针对坐标原点进行偏移.
注意:x,y代表的不是坐标点,而是偏移量。
scrollBy(int x,int y):
该方法的实现是调用的scrollto()方法,区别就是在现有的基础上继续偏移视图的内容.相对于停留的位置进行偏移.
注意:x,y代表的不是坐标点,而是偏移量。
例如:我们自定义控件继承TextView.然后在布局文件里面将这个自定义的视图设置稍大一点.两个按钮对应的点击事件是:
mCustomView.scrollBy(-20, -20);
和:
mCustomView.scrollTo(0, 0);
五.Android中scroller的使用.
1.首先应该明白两个基本的概念
- 什么是scroller呢? Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是辅助UI滑动,反而是单纯地为滑动提供计算。也就是说Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是辅助UI滑动,反而是单纯地为滑动提供计算。
2.Scroller只是提供计算,那谁来调用View是怎么滑动的? 答:真正让View滑动的是scrollTo,scrollBy。
2.scroller常用的方法:
mScroller.getCurrX() //获取mScroller当前水平滚动的位置
mScroller.getCurrY() //获取mScroller当前竖直滚动的位置
mScroller.getFinalX() //获取mScroller最终停止的水平位置
mScroller.getFinalY() //获取mScroller最终停止的竖直位置
mScroller.setFinalX(int newX) //设置mScroller最终停留的水平位置,没有动画效果,直接跳到目标位置
mScroller.setFinalY(int newY) //设置mScroller最终停留的竖直位置,没有动画效果,直接跳到目标位置
//滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间
mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默认完成时间250ms
mScroller.startScroll(int startX, int startY, int dx, int dy, int duration)
mScroller.computeScrollOffset() //返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。这是一个很重要的方法,通常放在View.computeScroll()中,用来判断是否滚动是否结束。
3.例子一枚:
先自定义控件:
public class CustomView extends TextView {
private Scroller mScroller;
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScroller = new Scroller(context);
}
public void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int scrollY = getScrollY();
//在2000ms 内滑动到目标位置
mScroller.startScroll(scrollX, scrollY, destX - scrollX, destY - scrollY, 2000);
//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
invalidate();
}
@Override
public void computeScroll() {
//先判断mScroller滚动是否完成
if (mScroller.computeScrollOffset()) {
//这里调用View的scrollTo()完成实际的滚动
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必须调用该方法,否则不一定能看到滚动效果
invalidate();
}
super.computeScroll();
}
}
在activity中调用:
mCustomView.smoothScrollTo(-100,-100);
六.自定义Viewgroup实现viewpager效果
1.测量和布局.
观察Viewpager所知,Viewpager里面有子View,所以我们需要自定义Viewgroup来实现。onmeasure()测量大小(这里我们暂时固定大小)onLayout()这个方法是对子View的一个位置摆放。
代码:
public class CustomViewPager extends ViewGroup {
public CustomViewPager(Context context) {
this(context, null);
}
public CustomViewPager(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);//测量所有子View的大小.
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
int childCount = getChildCount();
//摆放子View
for (int i4 = 0; i4 < childCount; i4++) {
View childAt = getChildAt(i4);
childAt.layout(childAt.getMeasuredWidth() * i4, i1, (i4 + 1) * childAt.getMeasuredWidth(), i3);
}
}
}
布局文件:
<com.zbht.myapplication.CustomViewPager
android:layout_width="400dp"
android:layout_height="400dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/one"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/two"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/thr"/>
</com.zbht.myapplication.CustomViewPager>
效果:
2.添加滑动效果
由以上效果图,我们看到了第一个子View显示出来了,但是没有滑动的效果。因此我们需要在onTouchEvent对事件进行消费。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = event.getX();//记录x轴的初始位置
break;
case MotionEvent.ACTION_MOVE:
float distanceX = event.getX() - mStartX;//记录x轴滑动的距离
scrollBy(-(int) distanceX, 0);//滑动x轴滑动的距离
break;
case MotionEvent.ACTION_UP:
break;
}
return true;//将事件进行消费
}
效果如下
3.优化滑动效果
使用手势识别器GestureDetector进行优化.
public class CustomViewPager extends ViewGroup {
private GestureDetector mGestureDetector;
...
public CustomViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//相对滑动:X方向滑动多少距离,view就跟着滑动多少距离
scrollBy((int) distanceX, 0);
return super.onScroll(e1, e2, distanceX, distanceY);
}
});
}
...
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);//使用手势识别器接管事件
return true;//消费掉事件
}
}
4.滑动到边界情况的处理。
Viewpager的效果是:手指松开时,当滑动偏移的距离超出子View1/2时,自动切换到下个或者是下一个子View;小于1/2,回弹到初始位置。
public class CustomViewPager extends ViewGroup {
private int currentIndex;
private int startX;
...
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();//获取x轴开始距离
break;
case MotionEvent.ACTION_UP:
float realEndX = event.getX();//x轴滑动结束的坐标
int tempIndex = currentIndex;
//结束x坐标大于开始x坐标,因此是向左滑动,要显示下一个View
if ((startX - realEndX) > getWidth() / 2) {
tempIndex++;
} else if ((realEndX - startX) > getWidth() / 2) {
//结束x坐标小于开始x坐标,因此是向右滑,显示上一张
tempIndex--;
}
//根据下标位置移动到指定的页面
scrollToPage(tempIndex);
break;
}
return true;
}
public void scrollToPage(int tempIndex) {
//屏蔽非法值
if (tempIndex < 0) {
tempIndex = 0;
}
if (tempIndex > getChildCount() - 1) {
tempIndex = getChildCount() - 1;
}
//当前的坐标位置
currentIndex = tempIndex;
//根据位置移动到指定页面
scrollTo(currentIndex * getWidth(), 0);
}
}
效果:
5.优化回弹效果。
使用Scroller 来优化滚动效果.
public class CustomViewPager extends ViewGroup {
private Scroller mScroller;
...
public void scrollToPage(int tempIndex) {
//屏蔽非法值
if (tempIndex < 0) {
tempIndex = 0;
}
if (tempIndex > getChildCount() - 1) {
tempIndex = getChildCount() - 1;
}
//当前的坐标位置
currentIndex = tempIndex;
//根据位置移动到指定页面
// scrollTo(currentIndex * getWidth(), 0);
int backDistanceX = currentIndex * getWidth() - getScrollX();
mScroller.startScroll(getScrollX(), getScrollY(), backDistanceX, 0);
invalidate();//使用invalidate这个方法会有执行一个回调方法computeScroll,需要重写该方法
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
postInvalidate();//必须使用postInvalidate(),这样才会一直回调computeScroll()这个方法,直到滑动结束。
}
}
}
效果
6.解决滑动冲突.
我们自己定义的viewpager如果里面的嵌套的子View为ScrollView,那么ScrollView会将事件吃掉,所以我们自定义的Viewpager就拿不到事件,所以无法左右滑动.
解决方式:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
InterceptStartX = (int) ev.getX();//获取x轴开始距离
InterceptStartY = (int) ev.getY();//获取x轴开始距离
//这个时候还需要把将ACTION_DOWN传递给手势识别器,因为拦截了MOVE的事件后,DOWN的事件还是要给手势识别器处理,否则会丢失事件,滑动的时候会存在bug。
mGestureDetector.onTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
int rangeX = (int) Math.abs(ev.getX() - InterceptStartX);
int rangeY = (int) Math.abs(ev.getY() - InterceptStartY);
if (rangeX > rangeY) {
//横向滑动,拦截事件
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
完整的自定义CustomViewPager代码:
public class CustomViewPager extends ViewGroup {
private GestureDetector mGestureDetector;
private int currentIndex;
private Scroller mScroller;
private int startX;
private int InterceptStartX;
private int InterceptStartY;
public CustomViewPager(Context context) {
this(context, null);
}
public CustomViewPager(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//相对滑动:X方向滑动多少距离,view就跟着滑动多少距离
scrollBy((int) distanceX, 0);
return super.onScroll(e1, e2, distanceX, distanceY);
}
});
mScroller = new Scroller(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);//测量所有子View的大小.
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
int childCount = getChildCount();
//摆放子view
for (int i4 = 0; i4 < childCount; i4++) {
View childAt = getChildAt(i4);
childAt.layout(childAt.getMeasuredWidth() * i4, i1, (i4 + 1) * childAt.getMeasuredWidth(), i3);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
InterceptStartX = (int) ev.getX();//获取x轴开始距离
InterceptStartY = (int) ev.getY();//获取x轴开始距离
//这个时候还需要把将ACTION_DOWN传递给手势识别器,因为拦截了MOVE的事件后,DOWN的事件还是要给手势识别器处理,否则会丢失事件,滑动的时候会存在bug。
mGestureDetector.onTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
int rangeX = (int) Math.abs(ev.getX() - InterceptStartX);
int rangeY = (int) Math.abs(ev.getY() - InterceptStartY);
if (rangeX > rangeY) {
//横向滑动,拦截事件
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();//获取x轴开始距离
break;
case MotionEvent.ACTION_UP:
float realEndX = event.getX();//x轴滑动结束的坐标
int tempIndex = currentIndex;
//结束x坐标大于开始x坐标,因此是向左滑动,要显示下一个View
if ((startX - realEndX) > getWidth() / 2) {
tempIndex++;
} else if ((realEndX - startX) > getWidth() / 2) {
//结束x坐标小于开始x坐标,因此是向右滑,显示上一张
tempIndex--;
}
//根据下标位置移动到指定的页面
scrollToPage(tempIndex);
break;
}
return true;
}
public void scrollToPage(int tempIndex) {
//屏蔽非法值
if (tempIndex < 0) {
tempIndex = 0;
}
if (tempIndex > getChildCount() - 1) {
tempIndex = getChildCount() - 1;
}
//当前的坐标位置
currentIndex = tempIndex;
//根据位置移动到指定页面
// scrollTo(currentIndex * getWidth(), 0);
int backDistanceX = currentIndex * getWidth() - getScrollX();
mScroller.startScroll(getScrollX(), getScrollY(), backDistanceX, 0);
invalidate();//使用invalidate这个方法会有执行一个回调方法computeScroll,需要重写该方法
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
postInvalidate();//必须使用postInvalidate(),这样才会一直回调computeScroll()这个方法,直到滑动结束。
}
}
}