全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

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;

/**
 * 注意:此自定义viewgroup只适用于每个子控件为match_parent的情况,其实一般情况也都是这种情况
 * 注意:此自定义viewgroup,没有考虑padding的情况,使用者不要在ScrollerLayout里使用任何padding,否则你看到的不是你想要的,
 * 为了实现padding效果,你可以为ScrollerLayout的外层再套一层线性布局(或其他布局),在外层布局里使用padding值
 * 此自定义viewgroup基于郭霖博客改编,想了解具体实现细节,请参照:
 * Android Scroller完全解析,关于Scroller你所需知道的一切
 * http://blog.csdn.net/guolin_blog/article/details/48719871
 */
public class VerticalViewPager extends ViewGroup {
  int currentPage = 1;

  /**
   * 速度轨迹追踪器
   */
  private VelocityTracker mVelocityTracker;

  /**
   * 此次计算速度你想要的最大值
   */
  private final int mMaxVelocity;

  /**
   * 第一个触点的id, 此时可能有多个触点,但至少一个
   */
  private int mPointerId;

  /**
   * 计算出的竖向滚动速率
   */
  private float velocityY;

  /**
   * 手指横向滑动的速率临界值,大于这个值时,不考虑手指滑动的距离,直接滚动到最左边或者最右边
   */
  private int criticalVelocityY = 2500;

  /**
   * 用于完成滚动操作的实例
   */
  private Scroller mScroller;

  /**
   * 判定为拖动的最小移动像素数
   */
  private int mTouchSlop;

  /**
   * 手机按下时的屏幕坐标
   */
  private float mYDown;

  /**
   * 手机当时所处的屏幕坐标
   */
  private float mYMove;

  /**
   * 上次触发ACTION_MOVE事件时的屏幕坐标
   */
  private float mYLastMove;

  /**
   * 界面可滚动的顶部边界
   */
  private int topBorder;

  /**
   * 界面可滚动的底部边界
   */
  private int bottomBorder;


  /**
   * 子控件的高度(这里每个子控件高度都一样,都是match_parent)
   */
  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());
          }

 

 


          /* if ((getScrollY() >= childHeight && !shouZhiXiangXiaHuaDong)//手指往左滑动,并且滑动完全显示第二个控件时,viewgroup滑动到最右端
              || ((getScrollY() >= (totalChildHeight - firstChildHeight - lastChildHeight) && shouZhiXiangXiaHuaDong))) {//手指往右滑动,并且当滑动没有完全隐藏最后一个控件时,viewgroup滑动到最右端
//          当滚动值大于某个数字时(大于第二个控件的宽度,即完全显示第二个控件时)并且是向左滑动,让这个viewgroup滑动到整个Viewgroup的最右侧,
//          因为右侧的所有控件宽度是600,而现在已经滑动的距离是getScrollX,
//          那么,还应该继续滑动的距离是600-getScrollX(),这里正值表示向右滑动
            mScroller.startScroll(0,getScrollY(), 0, (totalChildHeight - firstChildHeight) - getScrollY());
          } else if ((getScrollY() <= (totalChildHeight - firstChildHeight - lastChildHeight) && shouZhiXiangXiaHuaDong)//手指往右滑动,并且当滑动完全隐藏最后一个控件时,viewgroup滑动到最左端
              || (getScrollY() <= childHeight && !shouZhiXiangXiaHuaDong)) {//手指往左滑动,并且滑动没有完全显示第二个控件时,viewgroup滑动到最左端

//          当滚动值小于某个数字时,让这个viewgroup滑动到整个Viewgroup的最左侧,
//          因为滑动到最左侧时,就是让整个viewgroup的滑动量为0,而现在已经滑动的距离是getScrollX,
//          那么,还应该继续滑动的距离是0-getScrollX(),这里负值表示向左滑动
            mScroller.startScroll(0,getScrollY(), 0, 0 - 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();
    }
  }

  /**
   * @param event 向VelocityTracker添加MotionEvent
   * @see VelocityTracker#obtain()
   * @see VelocityTracker#addMovement(MotionEvent)
   */
  private void acquireVelocityTracker(final MotionEvent event) {
    if (null == mVelocityTracker) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(event);
  }

  /**
   * 释放VelocityTracker
   *
   * @see VelocityTracker#clear()
   * @see VelocityTracker#recycle()
   */
  private void releaseVelocityTracker() {
    if (null != mVelocityTracker) {
      mVelocityTracker.clear();
      mVelocityTracker.recycle();
      mVelocityTracker = null;
    }
  }


   /*  getScrollX()指的是由viewgroup调用View的scrollTo(int x, int y)或者scrollBy(int x, int y)产生的X轴的距离
//        换句话说,就是你手指每次滑动,引起的是viewgroup累计滑动的距离,右为正
//        指的是相当于控件的左上角的为原点的坐标值
        Log.e("qqq","getX():"+event.getX());
//        指的是相当于屏幕的左上角的为原点的坐标值
        Log.e("qqq","getRawX():"+event.getRawX());*/
}

布局文件

 <beautlful.time.com.beautlfultime.view.VerticalViewPager
      android:id="@+id/verticalViewPager"
      android:layout_width="match_parent"
      android:layout_height="150dp">
      <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
      android:background="@android:color/holo_orange_dark"
        android:text="聊天具体的信息哟" />

      <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent"
        android:text="置顶" />

      <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"
        android:text="删除" />
      <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent"
        android:text="美好" />

 

    </beautlful.time.com.beautlfultime.view.VerticalViewPager>


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


# Android竖向的ViewPager的实现  # Android  # ViewPager竖向布局  # Android ViewPager自定义轮播图并解决播放冲突  # Android自定义引导玩转ViewPager的方法详解  # Android自定义ViewPager实现纵向滑动翻页效果  # Android使用自定义PageTransformer实现个性的ViewPager动画切换效果  # android自定义ViewPager水平滑动弹性效果  # Android自定义ViewPager指示器  # Android自定义ViewPager实现个性化的图片切换效果  # Android自定义ViewPager实例  # Android自定义超级炫酷的ViewPage指示器  # 什么时候  # 的是  # 自定义  # 第一个  # 适用于  # 第二个  # 几个  # 都是  # 指的是  # 滑倒  # 左端  # 翻页  # 你想要  # 过了  # 多个  # 能有  # 拖动  # 速度快  # 告诉自己  # 什么都不 


相关文章: 制作网站的模板软件,网站怎么建设?  如何用免费手机建站系统零基础打造专业网站?  如何自己制作一个网站链接,如何制作一个企业网站,建设网站的基本步骤有哪些?  建站之星上传入口如何快速找到?  湖南网站制作公司,湖南上善若水科技有限公司做什么的?  如何用低价快速搭建高质量网站?  香港服务器租用费用高吗?如何避免常见误区?  深圳网站制作平台,深圳市做网站好的公司有哪些?  合肥制作网站的公司有哪些,合肥聚美网络科技有限公司介绍?  Python路径拼接规范_跨平台处理说明【指导】  香港服务器网站搭建教程-电商部署、配置优化与安全稳定指南  ,网页ppt怎么弄成自己的ppt?  c# F# 的 MailboxProcessor 和 C# 的 Actor 模型  ,有什么在线背英语单词效率比较高的网站?  建站168自助建站系统:快速模板定制与SEO优化指南  Swift中循环语句中的转移语句 break 和 continue  如何在万网自助建站中设置域名及备案?  如何在阿里云域名上完成建站全流程?  如何正确下载安装西数主机建站助手?  寿县云建站:智能SEO优化与多行业模板快速上线指南  成都网站制作报价公司,成都工业用气开户费用?  制作网站的软件下载免费,今日头条开宝箱老是需要下载怎么回事?  网站制作壁纸教程视频,电脑壁纸网站?  香港服务器网站卡顿?如何解决网络延迟与负载问题?  哈尔滨网站建设策划,哈尔滨电工证查询网站?  如何在阿里云ECS服务器部署织梦CMS网站?  建站之星后台管理系统如何操作?  网站按钮制作软件,如何实现网页中按钮的自动点击?  宝塔建站助手安装配置与建站模板使用全流程解析  网站专业制作公司,网站编辑是做什么的?好做吗?工作前景如何?  怎么制作一个起泡网,水泡粪全漏粪育肥舍冬季氨气超过25ppm,可以有哪些措施降低舍内氨气水平?  制作宣传网站的软件,小红书可以宣传网站吗?  公司网站制作费用多少,为公司建立一个网站需要哪些费用?  兔展官网 在线制作,怎样制作微信请帖?  佛山企业网站制作公司有哪些,沟通100网上服务官网?  如何选择香港主机高效搭建外贸独立站?  如何在Windows虚拟主机上快速搭建网站?  建站之星CMS建站配置指南:模板选择与SEO优化技巧  网站app免费制作软件,能免费看各大网站视频的手机app?  教程网站设计制作软件,怎么创建自己的一个网站?  如何选择高效响应式自助建站源码系统?  如何快速上传自定义模板至建站之星?  家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?  北京网站制作公司哪家好一点,北京租房网站有哪些?  网站制作外包价格怎么算,招聘网站上写的“外包”是什么意思?  建站主机是否等同于虚拟主机?  c++怎么编写动态链接库dll_c++ __declspec(dllexport)导出与调用【方法】  c# Task.ConfigureAwait(true) 在什么场景下是必须的  Android自定义控件实现温度旋转按钮效果  宝塔新建站点报错如何解决? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。