Android 自定义ViewPager
短信预约 -IT技能 免费直播动态提醒
项目地址
概述 处理滑动到左边界和右边界时,不允许滑动。 页面滑动一半回弹,滑动一半以上自动切换下一界面。 当页面内存在ScrollView这类子控件,事件要正常分发,不允许自定义ViewPager拦截事件。 回弹与切换动画处理。 源码分析 初始化public ViewPagerY(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
// Scroller设置的是一个匀速插值器
myScroll = new Scroller(context, new LinearInterpolator());
// 初始化ImageLoader
ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(mContext));
}
设置资源,如图片id,布局,图片链接等
public void setRes(ArrayList res) {
for (ResType mResType : res) {
if (mResType.getmType() == ResType.Type.IAMG) {
// 如果是资源图片id,创建ImageView对象
ImageView imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageView.setImageResource((Integer) mResType.getRes());
// 将设置好的ImageView添加进ViewPagerY控件中
this.addView(imageView);
}
if (mResType.getmType() == ResType.Type.URL) {
// 如果资源是图片URL,创建ImageView对象.
ImageView imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
// 使用ImageLoader将图片从网络获取设置到ImageView中.
ImageLoader.getInstance().displayImage((String) mResType.getRes(), imageView);
// 将设置好的ImageView添加进ViewPagerY控件中
this.addView(imageView);
}
if (mResType.getmType() == ResType.Type.LAYOUT) {
// 如果资源是自己写的布局文件,就获取该布局文件对应的View
View view = LayoutInflater.from(mContext).inflate((Integer) mResType.getRes(), null);
// 将获取到的View添加进ViewPagerY控件中
this.addView(view);
}
}
}
// 资源类型类
public class ResType {
public enum Type {
IAMG,
LAYOUT,
URL
}
private T mRes;
private Type mType;
public ResType(T res, Type mType) {
this.mRes = res;
this.mType = mType;
}
public T getRes() {
return mRes;
}
public Type getmType() {
return mType;
}
}
ViewPagerY对子View的测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置ViewPagerY尺寸
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取ViewPagerY宽高
height = getMeasuredHeight();
widht = getMeasuredWidth();
// 创建子View的MeasureSpec, 创建MeasureSpec是有规律的,可以看这个笔记: https://blog.csdn.net/MoLiao2046/article/details/105708819
int wMeasureSpec = MeasureSpec.makeMeasureSpec(widht, MeasureSpec.EXACTLY);
int hMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
for (int i = 0; i < getChildCount(); i++) {
// 遍历子View开始测量子View.
getChildAt(i).measure(wMeasureSpec, hMeasureSpec);
}
}
ViewPagerY对子View进行布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
// 资源集合中每一个资源对应一个页面.
// 分别设置这些View的左上角与右下角坐标.
this.getChildAt(i).layout(i * widht, 0, i * widht + widht, height);
}
}
判断手势,如果手势为水平滑动就拦截, 否则就正常将事件分发给子View处理.
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// 默认交给ViewGroup拦截事件,ViewGroup一般是不会拦截事件.
boolean interceptChildeEvent = super.onInterceptTouchEvent(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// mLastX与mDownX是手指按下时候的坐标,这两个值会在ViewPagerY中onTouchEvent中用到,处理页面滑动用的.
// onTouchEvent()中也能获取ACTION_DOWN事件,但是在onTouchEvent中获取手指按下坐标再进行相应移动处理会出现页面跳动的问题.
mLastX = mDownX = interceptLastX = event.getX();
interceptLastY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
// 获取当前手指坐标
float moveX = event.getX();
float moveY = event.getY();
// 移动后,计算出滑动后与上一个坐标点之间的距离.
float slopX = moveX - interceptLastX;
float slopY = moveY - interceptLastY;
// 得到手指滑动距离绝对值
float slopAbsX = Math.abs(slopX);
float slopAbsY = Math.abs(slopY);
if ((slopAbsX > 0 || slopAbsY > 0) && (slopAbsX - slopAbsY) >= 6) {
// 如果手指移动距离大于0,且横向移动距离减去纵向移动距离大于6像素
// 那么ViewPagerY就将该事件拦截, 不分发给它的子View使用,留给自己使用了.
// 这样会导致mFirstTouchTarget=null,之后子View就再也接收不到事件组的其他事件了.
interceptChildeEvent = true;
}
interceptLastX = moveX;
interceptLastY = moveY;
break;
}
return interceptChildeEvent;
}
处理页面滑动回弹的逻辑
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
float mMoveX = event.getX();
// mLastX:是在onInterceptTouchEvent()中得到的值
float mDiffX = mMoveX - mLastX;
mLastX = mMoveX;
if (event.getPointerId(event.getActionIndex()) == 0 && event.getPointerCount() == 1) {// 这个条件可以控制只追踪屏幕中的一个手指的滑动.
int scrollX = getScrollX();
if (currentIndex == 0) {
// 第一页
if (mDiffX = 0) {
// 内容左边距离控件左边的距离减去向右滑动距离,如果大于0,说明内容左边距离控件左边还有间隔距离,滑动距离取手指移动距离.
ViewPagerY.this.scrollBy((int) -mDiffX, 0);
} else {
// 内容左边距离控件左边的距离减去向右滑动距离,如果小于0,说明内容左边与控件左边需要重合,滑动距离取getScrollX().
ViewPagerY.this.scrollBy(-scrollX, 0);
}
}
}
if (currentIndex == getChildCount() - 1) {
// 最后一页
if (mDiffX > 0) {
// 如果向右滑动
ViewPagerY.this.scrollBy((int) -mDiffX, 0);
} else {
// 处理先向右滑动,然后又向左滑动.
// (((getChildCount() - 1) * widht) - scrollX): 表示内容右边距离控件右边的距离
float mDiffMargin = (((getChildCount() - 1) * widht) - scrollX) + mDiffX;
if (mDiffMargin >= 0) {
// 说明内容右边与控件右边还有距离,滑动距离取手指移动距离.
ViewPagerY.this.scrollBy((int) -mDiffX, 0);
} else {
// 说明内容右边与控件右边需要重合,滑动距离取内容右边与控件右边的距离.
ViewPagerY.this.scrollBy(-(((getChildCount() - 1) * widht) - scrollX), 0);
}
}
}
if (currentIndex != 0 && currentIndex != getChildCount() - 1) {
// scrollBy总是和移动的相反
ViewPagerY.this.scrollBy((int) -mDiffX, 0);
}
}
break;
case MotionEvent.ACTION_UP:
float mUpX = event.getX();
// mDownX:是在onInterceptTouchEvent()中得到的值
if (mUpX - mDownX > getWidth() / 2) {
// 移动到上一个
moveTo(currentIndex - 1);
} else if (mUpX - mDownX < -getWidth() / 2) {
// 移动到下一个
moveTo(currentIndex + 1);
} else {
// 移动到当前页面
moveTo(currentIndex);
}
break;
}
return true;
}
页面切换逻辑
public void moveTo(int index) {
int duration = 0;
if (index getChildCount() - 1) {
index = getChildCount() - 1;
}
// 中间经历几个界面, 每个页面切换时长是固定的.
int count = currentIndex - index;
if (count != 0) {
duration = mDuration * count;
} else {
duration = mDuration;
}
currentIndex = index;
if (onPageChangeListener != null) {
// 页面切换的监听.
onPageChangeListener.onPageSelect(currentIndex);
}
// getScrollX(): 内容左边与ViewPager控件左边距离
// currentIndex * getWidth(): 切换到currentIndex界面时的getScrollX()值.
// 得到需要移动的距离.
int distanceX = currentIndex * getWidth() - getScrollX();
//给MyScroll 计算的类赋初始值
myScroll.startScroll(getScrollX(), 0, distanceX, 0, Math.abs(duration));
invalidate();
}
// 想要缓慢滑动这个也很重要.
@Override
public void computeScroll() {
//如果为true说明移动还没结束
if (myScroll.computeScrollOffset()) {
//得到计算的位置,然后移动
float currX = myScroll.getCurrX();
scrollTo((int) currX, 0);
invalidate();
}
}
效果图
碧云天丶
原创文章 65获赞 14访问量 3万+
关注
私信
展开阅读全文
作者:碧云天丶
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341