栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 移动开发 > Android

Android 自定义布局竖向的ViewPager的实现

Android 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android 自定义布局竖向的ViewPager的实现

Android 自定义布局竖向的ViewPager的实现

效果图:

这个自定义控件涉及到的知识点:

自定义ViewGroup中onMeasure和onLayout的写法
弹性滚动Scroller的用法
速度轨迹追踪器VelocityTracker的用法
如何处理滑动事件冲突

dispatchTouchEvent:(外部拦截)告诉此ScrollLayout的父布局,什么时候该拦截触摸事件,什么时候不该拦截触摸事件

onInterceptTouchEvent:(内部拦截)ScrollLayout告诉自己什么时候要拦截内部子View的触摸事件,什么时候不要拦截内部子View的触摸事件

处理触摸滑动的思路:

  1. 先实现布局跟着手指的滑动而滑动 scrollBy
  2. 处理好边界条件(这次的处理边界,仅适用于低速滑动情况下)
  3. 如果是快速滑动VelocityTracker,必须再次考虑边界问题(上面考虑的边界问题不适用于快速滑动情况)
  4. 如果是低速滑动,要根据手指滑动方向和布局滑动的距离一起做判断,来确定,页面该滑动到那个页面,这里用到了弹性滑动Scroller
  5. 难点来了:算法,
//即确定当前显示的子控件的位置,
//确定弹性滑动滑向那个位置 
if (Math.abs(velocityY) > criticalVelocityY) {//当手指滑动速度快时,按照速度方向直接翻页 
// 重点二、快速滑动时,如何判断当前显示的是第几个控件,并且再次包含边界判断(必须包含边界判断,因为前面的边界判断,只适用于低速滑动时) 
if (shouZhiXiangXiaHuaDong) { 
if (currentPage > 1) {//★★★★★★★★边界限制,防止滑倒第一个,还继续滑动,注意★(currentPage-2) 
mScroller.startScroll(0, getScrollY(), 0, childHeight * (currentPage - 2) - getScrollY()); 
currentPage--; 
} 
} else { 
if (currentPage < childCount) {//★★★★★★★边界限制,防止滑倒最后一个,还继续滑动,注意★currentPage 
mScroller.startScroll(0, getScrollY(), 0, childHeight * currentPage - getScrollY()); 
currentPage++; 
} 
} 
Log.e("eee", currentPage + "");

总结

当要写一个算法时,不要着急试图一下子写出来,这样往往陷入盲目,应该是一步一步地推导,一步一步实现代码,指导最后找到规律,类似于归纳总结、通项公式的方法。

代码如下:(注释很全)

package beautlful.time.com.beautlfultime.view;

import android.content.Context;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;


public class VerticalViewPager extends ViewGroup {
  int currentPage = 1;

  
  private VelocityTracker mVelocityTracker;

  
  private final int mMaxVelocity;

  
  private int mPointerId;

  
  private float velocityY;

  
  private int criticalVelocityY = 2500;

  
  private Scroller mScroller;

  
  private int mTouchSlop;

  
  private float mYDown;

  
  private float mYMove;

  
  private float mYLastMove;

  
  private int topBorder;

  
  private int bottomBorder;


  
  private int childHeight;


  
  private boolean shouZhiXiangXiaHuaDong;
  private int childCount;


  public VerticalViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    // 第一步,创建Scroller的实例
    mScroller = new Scroller(context);
    ViewConfiguration configuration = ViewConfiguration.get(context);
    // 获取TouchSlop值
    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    //此次计算速度你想要的最大值
    mMaxVelocity = ViewConfiguration.get(context).getMaximumFlingVelocity();
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      View childView = getChildAt(i);
      // 为ScrollerLayout中的每一个子控件测量大小
      measureChild(childView, widthMeasureSpec, heightMeasureSpec);
    }
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (changed) {
      
      int preChildViewTotalHeight = 0;
      for (int i = 0; i < childCount; i++) {
 View childView = getChildAt(i);
 // 为ScrollerLayout中的每一个子控件在竖直方向上进行布局
 if (i == 0) {
   childView.layout(
0,
0,
childView.getMeasuredWidth(),
childView.getMeasuredHeight());

 } else {
   childView.layout(
0,
preChildViewTotalHeight,
childView.getMeasuredWidth(),
preChildViewTotalHeight + childView.getMeasuredHeight());
 }
 preChildViewTotalHeight += childView.getMeasuredHeight();

      }
      // 初始化上下边界值
      topBorder = getChildAt(0).getTop();
      bottomBorder = getChildAt(getChildCount() - 1).getBottom();

      childHeight = getChildAt(0).getMeasuredHeight();


    }
  }


  private int downX;
  private int downY;

  //    告诉此ScrollLayout的父布局,什么时候该拦截触摸事件,什么时候不该拦截触摸事件
  public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
 //让当前ScrollerLayout对应的父控件不要去拦截事件
 getParent().requestDisallowInterceptTouchEvent(true);
 downX = (int) ev.getX();
 downY = (int) ev.getY();
 break;
      case MotionEvent.ACTION_MOVE:
 int moveX = (int) ev.getX();
 int moveY = (int) ev.getY();

 //请求父控件ViewPager拦截触摸事件,ViewPager左右滚动时,不要触发该布局的上下滑动
 if (Math.abs(moveY - downY) < Math.abs(moveX - downX)) {
   getParent().requestDisallowInterceptTouchEvent(false);
 } else {
   //请求父控件ViewPager不要拦截触摸事件,ScrollerLayout自己可以上下滑动
   getParent().requestDisallowInterceptTouchEvent(true);
 }

 break;

      case MotionEvent.ACTION_CANCEL:


 break;
      case MotionEvent.ACTION_UP:


 break;
    }
    return super.dispatchTouchEvent(ev);
  }


  //   ScrollLayout告诉自己什么时候要拦截内部子View的触摸事件,什么时候不要拦截内部子View的触摸事件
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
 //▲▲▲1.求第一个触点的id, 此时可能有多个触点,但至少一个
 mPointerId = ev.getPointerId(0);
 mYDown = ev.getRawY();
 mYLastMove = mYDown;
 break;
      case MotionEvent.ACTION_MOVE:
 mYMove = ev.getRawY();
 float diff = Math.abs(mYMove - mYDown);
 mYLastMove = mYMove;
 // 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
 if (diff > mTouchSlop) {
   return true;
 }
 break;
    }
    return super.onInterceptTouchEvent(ev);
  }


  @Override
  public boolean onTouchEvent(MotionEvent event) {
    //▲▲▲2.向VelocityTracker添加MotionEvent
    acquireVelocityTracker(event);
    switch (event.getAction()) {
      case MotionEvent.ACTION_MOVE:

 //▲▲▲3.求伪瞬时速度
 mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
 velocityY = mVelocityTracker.getYVelocity(mPointerId);

 mYMove = event.getRawY();
 int scrolledY = (int) (mYLastMove - mYMove);//注意取的是负值,因为是整个布局在动,而不是控件在动


 if (getScrollY() + scrolledY < topBorder) {// 如果已经在最上端了,就不让再往上滑动了(重点一、边界判断,直接照着这个模板抄就行)
   scrollTo(0, topBorder);
   return true;//★★★★★★★★★★★★★★★★这里返回true或者false实践证明都可以,但是不能什么都不返回。
 } else if (getScrollY() + getHeight() + scrolledY > bottomBorder) {//如果已经在最底部了,就不让再往底部滑动了
   scrollTo(0, bottomBorder - getHeight());
   return true;//★★★★★★★★★★★★★★★★★这里返回true或者false实践证明都可以,但是不能什么都不返回。
 }

 scrollBy(0, scrolledY);//手指move时,布局跟着滚动
 if (mYDown <= mYMove) {//★★★判断手指向上滑动,还是向下滑动,要用mYDown,而不是mYLastMove
   shouZhiXiangXiaHuaDong = true;//手指往下滑动
 } else {
   shouZhiXiangXiaHuaDong = false;//手指往上滑动
 }
 mYLastMove = mYMove;
 break;
      case MotionEvent.ACTION_UP:
// 4.▲▲▲释放VelocityTracker
 releaseVelocityTracker();

 // 第二步,当手指抬起时,根据当前的滚动值以及滚动方向来判定应该滚动到哪个子控件的界面,并且记得调用invalidate();
 if (Math.abs(velocityY) > criticalVelocityY) {//当手指滑动速度快时,按照速度方向直接翻页
//   重点二、快速滑动时,如何判断当前显示的是第几个控件,并且再次包含边界判断(必须包含边界判断,因为前面的边界判断,只适用于低速滑动时)
   if (shouZhiXiangXiaHuaDong) {
     if (currentPage > 1) {//★★★★★★★★边界限制,防止滑倒第一个,还继续滑动,注意★(currentPage-2)
mScroller.startScroll(0, getScrollY(), 0, childHeight * (currentPage - 2) - getScrollY());
currentPage--;
     }
   } else {
     if (currentPage < childCount) {//★★★★★★★边界限制,防止滑倒最后一个,还继续滑动,注意★currentPage
mScroller.startScroll(0, getScrollY(), 0, childHeight * currentPage - getScrollY());
currentPage++;
     }
   }
   Log.e("eee", currentPage + "");
 } else {//当手指滑动速度不够快时,按照松手时,已经滑动的位置来决定翻页

//重点三、低速滑动时,如何根据位置来判断当前显示的是第几个控件,(这里不必再次进行边界判断,因为第一次的边界判断,在这里会起到作用)
   if ((getScrollY() >= childHeight * (currentPage - 1) + childHeight / 2 && !shouZhiXiangXiaHuaDong)) {
//    手指向上滑动并且,滚动距离过了屏幕一半的距离
     mScroller.startScroll(0, getScrollY(), 0, childHeight * (currentPage) - getScrollY());
     currentPage++;

   } else if ((getScrollY() < childHeight * (currentPage - 1) + childHeight / 2 && !shouZhiXiangXiaHuaDong)) {
//    手指向上滑动并且,滚动距离没有过屏幕一半的距离
     mScroller.startScroll(0, getScrollY(), 0, childHeight * (currentPage - 1) - getScrollY());

   } else if
((getScrollY() <= childHeight * (currentPage - 2) + childHeight / 2
    && shouZhiXiangXiaHuaDong)) {
//     手指向下滑动并且,滚动距离过了屏幕一半的距离
     mScroller.startScroll(0, getScrollY(), 0, childHeight * (currentPage - 2) - getScrollY());
     currentPage--;
   } else if
((getScrollY() > childHeight * (currentPage - 2) + childHeight / 2
    && shouZhiXiangXiaHuaDong)) {
//     手指向下滑动并且,滚动距离没有过屏幕一半的距离
     mScroller.startScroll(0, getScrollY(), 0, childHeight * (currentPage - 1) - getScrollY());
   }

 

 


   
 }
// 必须调用invalidate()重绘
 invalidate();
 break;

      case MotionEvent.ACTION_CANCEL:
//5.▲▲▲释放VelocityTracker
 releaseVelocityTracker();

 break;
    }
    return super.onTouchEvent(event);
  }


  @Override
  public void computeScroll() {
    // 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
      invalidate();
    }
  }

  
  private void acquireVelocityTracker(final MotionEvent event) {
    if (null == mVelocityTracker) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(event);
  }

  
  private void releaseVelocityTracker() {
    if (null != mVelocityTracker) {
      mVelocityTracker.clear();
      mVelocityTracker.recycle();
      mVelocityTracker = null;
    }
  }


   
}

布局文件

 
      

 感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/158697.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号