* 关于Srcoller滑动处理器
* 作用:让控件自动缓慢滑动到目的地
* 注:是通过ScrollTo()实现,即通过目的坐标滑动,是滑动整个父View,从而改变显示位置。[ScrollBy()则是通过偏移量滑动,也是滑动整个父View]
*
* 1.scroller.startScroll(startX,startY,dx,dy);
* startX 水平方向滚动的偏移值,以像素为单位。正值表明滚动将向左滚动
* startY 垂直方向滚动的偏移值,以像素为单位。正值表明滚动将向上滚动
* dx 水平方向滑动的距离,正值会使滚动向左滚动
* dy 垂直方向滑动的距离,正值会使滚动向上滚动
* 2.重写 computeScroll() {}函数
* public void computeScroll() {
if(scroller.computeScrollOffset()){//如果mScroller没有调用startScroll,这里将会返回false。
scrollTo(scroller.getCurrX(),scroller.getCurrY());
invalidate();//刷新界面
}
};
*
*其原理:
* 先通过StartScroll(。。。)获得起始坐标和需要偏移的坐标量
* 再将偏移量用时间分割,然后得到每个时间间隔搜需要到达的位置
* 再通过ScrollTo(..)到达指定位置(由左上角的坐标为原点进行偏移)
*
* 当偏移的位置超出View的边界大小时,动画结束后会弹回边界处
*/
package com.example.View;
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.Scroller;
public class myViewPager extends ViewGroup {
Context context;
private GestureDetector mDetector;
private Scroller mScroller;
int startX, startY, endX, offX;
public myViewPager(Context context, AttributeSet attrs, int defStyleAttr,int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.context = context;
initView();
}
public myViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
initView();
}
public myViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
initView();
}
public myViewPager(Context context) {
super(context);
this.context = context;
initView();
}
public void initView() {
// 定义手势识别器
mDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY) {// 参1:起点动作, 参2:终点动作;参3:x方向滑动距离;参4:y方向滑动距离
scrollBy((int) distanceX, 0);// 页面滑动一定距离;x:水平方向的滑动偏移量;y:竖直方向滑动的偏移量;(相对偏移)
// scrollTo(x, y);//绝对位移;和当前位置无关,以当前左上角坐标为原点滑动到确定好的位置上
return super.onScroll(e1, e2, distanceX, distanceY);
}
});
// 定义滚动器
mScroller = new Scroller(context);
}
// 测量子控件长宽,不然有些子控件不能展示出来
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 不能删,测量viewpager本身的宽高
// 遍历,测量子控件长宽
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
// MeasureSpec.AT_MOST;//相当于wrap
// MeasureSpec.EXACTLY;//相当于match
MeasureSpec.getMode(widthMeasureSpec);// 获取宽高模式
MeasureSpec.getSize(heightMeasureSpec);// 获得具体宽度
}
// 设置子控件位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).layout(getWidth() * i, 0, getWidth() * (i + 1),getHeight());// 设置为一字排开
}
}
// 触摸监听
@Override
public boolean onTouchEvent(MotionEvent event) {
mDetector.onTouchEvent(event);// 委托手势识别器来处理
// 手指起来后子控件画面完整展示
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
int pos,offset,nowX;
nowX = getScrollX();// 返回已经滑动的离左边的距离
pos = nowX / getWidth();// 当前展示的前一副View
offset = nowX - pos * getWidth();
if (offset > getWidth() / 2) {
if (nowX > (getChildCount() - 1) * getWidth()) {// 在最右边
pos = getChildCount() - 1;
setCurrItem(pos);
} else
setCurrItem(pos + 1);
} else {
if (nowX > (getChildCount() - 1) * getWidth()) {// 在最右边
pos = getChildCount() - 1;
setCurrItem(pos);
} else
setCurrItem(pos);
}
invalidate();
break;
default:
break;
}
return true;// 消耗掉
}
// 中断触摸事件监听处理,决定是否下传事件处理给子控件
// ,即该myViewpager类决定是否下传事件处理给他的Child控件
// return true;中断
// return false;不中断
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 在这里判断,如果左右滑动大于上下滑动的距离,则不中断,交给子控件处理
// 否则,则有Viewpager处理
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) ev.getX();
startY = (int) ev.getY();
// 该事件被子控件先执行,在下面虽然返回true,但收拾识别器已丢失Action.Dowm的监听
// 所以,在这里强行调用手势识别器,获得事件监听
mDetector.onTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
int onEndX = (int) ev.getX();
int onEndY = (int) ev.getY();
int offX = onEndX - startX;
int offY = onEndY - startY;
if (Math.abs(offX) > Math.abs(offY)) {
return true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
//事件分发机制
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
public void setCurrItem(int pos) {
// scrollTo(getWidth() * pos, 0);// 滑动到绝对位置,立刻滑动到位置
int posNext = pos * getWidth();// 需要回复到整个图片需要的宽度,目标位置
int distance = posNext - getScrollX();// 移动距离 = 目标位置- 当前位置
// 距离越长,时间越久,可以让时间=距离
// 此处并不会立即启动滑动动画,而是不断回调computeScroll这个方法
mScroller.startScroll(getScrollX(), 0, distance, 0, Math.abs(distance));
// 参1:起始x位置;参2:起始y位置;参3:x方向移动距离;参4:y方向移动距离;参5:移动时间
invalidate();// 刷新界面,不然mScroller.startScroll();不会成功执行
}
// 处理mScroller.startScroll();其中startScroll只是为需要的位移分段,computeScroll则执行处理
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {// 判断滑动是否已经结束,没有结束才处理以下逻辑
int TonextX = mScroller.getCurrX();// 获得下一个需要去到的X坐标
scrollTo(TonextX, 0);
invalidate();
}
}
}
/**
* 事件处理机制:
*
* 首先是最外层控件先获得事件监听
*
* 监听: 由上到下,父到子
*
* 处理: (如果子控件不处理)由下到上,子到父,如果消耗掉,就没了
*
* 事件处理流程:
* dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
* 分发 ->中断->处理
*
*/