|
|
51CTO旗下网站
|
|
移动端

撸起袖子自己写一个Android通用刷新控件

项目中我们经常有上拉、下拉刷新的需求,几乎所有的listView、RecyclerView都会伴随着上拉、下拉刷新的需求,如果我们使用一些开源控件,换了控件我们就要更新,现在我们自己撸起袖子写一个通用的刷新控件

作者:佚名来源:安卓巴士|2018-07-02 12:49

项目中我们经常有上拉、下拉刷新的需求,几乎所有的listView、RecyclerView都会伴随着上拉、下拉刷新的需求,如果我们使用一些开源控件,换了控件我们就要更新,现在我们自己撸起袖子写一个通用的刷新控件

撸起袖子自己写一个Android通用的刷新控件

思路:

  • 写一个继承RelativeLayout的RefreshLayout
  • 添加头尾控件作为刷新控件
  • 通过事件分发来进行刷新操作
  • 通过动画来控制控件移动

目的:让他的所有子控件都可以使用,哪怕是一个TextView

  1. public class RefreshLayout extends RelativeLayout { 
  2.  
  3.     /** 
  4.      * 滑动控件时拉去的速度比例 
  5.      */ 
  6.     private final int V_REFRESH = 2; 
  7.     /** 
  8.      * 是否是刷新过程 
  9.      * true 是 
  10.      * false 不是 
  11.      * 为false的时候才可以进行刷新 
  12.      */ 
  13.     private boolean mIsRefreshDuring; 
  14.     /** 
  15.      * 可以进下拉刷新 
  16.      */ 
  17.     private boolean mCanDownPull; 
  18.     /** 
  19.      * 可以进行上拉刷新 
  20.      */ 
  21.     private boolean mCanUpPull; 
  22.     /** 
  23.      * 判断触摸后是否是初次移动 
  24.      */ 
  25.     private boolean mIsFirstMove; 
  26.     /** 
  27.      * y轴呢平移的距离 
  28.      */ 
  29.     private int mDistanceY; 
  30.     /** 
  31.      * 刷新接口对象 
  32.      */ 
  33.     private OnRefresh mOnRefresh; 
  34.     /** 
  35.      * 用于控制事件拦截的变量 
  36.      */ 
  37.     private boolean mCanIntercept; 
  38.     private int mTouchSlop; 
  39.     private int mDistance; 
  40.     private LayoutParams mHeaderParams; 
  41.     private View mHeaderView; 
  42.     private View mFootView; 
  43.     private int mHeaderMaxHeight; 
  44.     private int mStartY; 
  45.     private LayoutParams mFootParams; 
  46.     private int mFootMaxHeight; 
  47.     private PullCallBack mCallBack; 
  48.     private View mChildView; 
  49.     private ObjectAnimator mAnimator; 
  50.  
  51.     public RefreshLayout(Context context) { 
  52.         super(context); 
  53.         initData(); 
  54.     } 
  55.  
  56.     public RefreshLayout(Context context, AttributeSet attrs) { 
  57.         super(context, attrs); 
  58.         initData(); 
  59.     } 
  60.  
  61.     public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { 
  62.         super(context, attrs, defStyleAttr); 
  63.         initData(); 
  64.     } 
  65.  
  66.     /** 
  67.      * 必须让头尾控件实现的接口 
  68.      */ 
  69.     public interface HeadAndFootCallBack { 
  70.         //设置属性 
  71.         void setAttribute(); 
  72.  
  73.         //开始刷新 
  74.         void startPull(); 
  75.  
  76.         //停止刷新 
  77.         void stopPull(); 
  78.     } 
  79.  
  80.     /** 
  81.      * 必须让被拖动的控件子类实现 
  82.      */ 
  83.     public interface PullCallBack { 
  84.         boolean canDownPull(); 
  85.  
  86.         boolean canUpPull(); 
  87.     } 
  88.  
  89.     private void initData() { 
  90.         //不调用该方法不能进行绘制 
  91.         setWillNotDraw(false); 
  92.     } 
  93.  
  94.     /** 
  95.      * 下拉刷新完成后必须使用该方法 
  96.      */ 
  97.     public void downPullFinish() { 
  98.         mAnimator.setFloatValues(mChildView.getTranslationY(), 0); 
  99.         mAnimator.start(); 
  100.         ((HeadAndFootCallBack) mHeaderView).stopPull(); 
  101.     } 
  102.  
  103.     /** 
  104.      * 上拉完成后必须调用该方法 
  105.      */ 
  106.     public void upPullFinish() { 
  107.         mAnimator.setFloatValues(mChildView.getTranslationY(), 0); 
  108.         mAnimator.start(); 
  109.         ((HeadAndFootCallBack) mFootView).stopPull(); 
  110.     } 
  111.  
  112.     /** 
  113.      * 自动下拉刷新 
  114.      */ 
  115.     public void autoDownPullForHead() { 
  116.         postDelayed(new Runnable() { 
  117.             @Override 
  118.             public void run() { 
  119.                 mCanDownPull = true
  120.                 mCanUpPull = false
  121.                 mAnimator.setFloatValues(10, mHeaderMaxHeight); 
  122.                 mAnimator.start(); 
  123.                 ((HeadAndFootCallBack) mHeaderView).startPull(); 
  124.                 mOnRefresh.onDownPullRefresh(); 
  125.             } 
  126.         }, 500); 
  127.     } 
  128.  
  129.     /** 
  130.      * 自动下拉刷新 
  131.      */ 
  132.     public void autoUpPullForHead() { 
  133.         postDelayed(new Runnable() { 
  134.             @Override 
  135.             public void run() { 
  136.                 mCanDownPull = false
  137.                 mCanUpPull = true
  138.                 mAnimator.setFloatValues(0, mFootMaxHeight); 
  139.                 mAnimator.start(); 
  140.                 ((HeadAndFootCallBack) mFootView).startPull(); 
  141.                 mOnRefresh.onUpPullRefresh(); 
  142.             } 
  143.         }, 500); 
  144.     } 
  145.  
  146.     @Override 
  147.     public boolean onInterceptTouchEvent(MotionEvent ev) { 
  148.         return mCanIntercept; 
  149.     } 
  150.  
  151.     @Override 
  152.     public boolean onTouchEvent(MotionEvent event) { 
  153.         return true
  154.     } 
  155.  
  156.     @Override 
  157.     public boolean dispatchTouchEvent(MotionEvent event) { 
  158.         Log.e("shen""mIsRefreshDuring=" + mIsRefreshDuring); 
  159.         if (mIsRefreshDuring)/*如果正在进行刷新将不会获取MotionEvent*/ { 
  160.             return super.dispatchTouchEvent(event); 
  161.         } 
  162.         switch (event.getAction()) { 
  163.             case MotionEvent.ACTION_DOWN: 
  164.                 mStartY = (int) event.getY(); 
  165.                 initPull(); 
  166.                 break; 
  167.             case MotionEvent.ACTION_MOVE: 
  168.                 if (event.getPointerCount() == 1) { 
  169.                     int moveY = (int) event.getY(); 
  170.                     mDistanceY = (moveY - mStartY) / V_REFRESH; 
  171.                     if (!mIsFirstMove && mDistanceY != 0 && mDistanceY < mTouchSlop) { 
  172.                         mCanDownPull = mDistanceY > 0; 
  173.                         mCanUpPull = !mCanDownPull; 
  174.                         mIsFirstMove = true
  175.                     } 
  176.                     if (mCanDownPull && mCallBack.canDownPull()) { 
  177.                         upDataForDownPull();//下拉刷新 
  178.                         mChildView.setEnabled(false); 
  179.                         mCanIntercept = true
  180.                     } 
  181.                     if (mCanUpPull && mCallBack.canUpPull()) { 
  182.                         upDataForUpPull();//上拉加载 
  183.                         mChildView.setEnabled(false); 
  184.                         mCanIntercept = true
  185.                     } 
  186.                     mStartY = moveY; 
  187.                 } 
  188.                 break; 
  189.             case MotionEvent.ACTION_UP: 
  190.                 mIsRefreshDuring = true
  191.                 mIsFirstMove = false
  192.                 if (mHeaderParams.height >= mHeaderMaxHeight)/*可以下拉刷新*/ { 
  193.                     ((HeadAndFootCallBack) mHeaderView).startPull(); 
  194.                     mOnRefresh.onDownPullRefresh(); 
  195.                 } else if (mFootParams.height >= mFootMaxHeight)/*可以上拉刷新*/ { 
  196.                     ((HeadAndFootCallBack) mFootView).startPull(); 
  197.                     mOnRefresh.onUpPullRefresh(); 
  198.                 } else if (mHeaderParams.height > 0 && mHeaderParams.height < mHeaderMaxHeight)/*不能进行下拉刷新,收回*/ { 
  199.                     releaseForDownFinished(); 
  200.                 } else if (mFootParams.height > 0 && mFootParams.height < mFootMaxHeight)/*不能进行下拉刷新,收回*/ { 
  201.                     releaseForUpFinished(); 
  202.                 } else { 
  203.                     mIsRefreshDuring = false
  204.                     mCanIntercept = false
  205.                 } 
  206.                 break; 
  207.         } 
  208.         super.dispatchTouchEvent(event); 
  209.         return true
  210.     } 
  211.  
  212.     /** 
  213.      * 每次进行触摸都需要进行初始化 
  214.      */ 
  215.     private void initPull() { 
  216.         mCanDownPull = false
  217.         mCanUpPull = false
  218.     } 
  219.  
  220.     /** 
  221.      * 不需要进行上拉刷新 
  222.      */ 
  223.     private void releaseForUpFinished() { 
  224.         mAnimator.setFloatValues(mChildView.getTranslationY(), 0); 
  225.         mAnimator.start(); 
  226.     } 
  227.  
  228.     /** 
  229.      * 不需要进行下拉刷新 
  230.      */ 
  231.     private void releaseForDownFinished() { 
  232.         mAnimator.setFloatValues(mChildView.getTranslationY(), 0); 
  233.         mAnimator.start(); 
  234.     } 
  235.  
  236.     /** 
  237.      * 上拉时处理手势 
  238.      */ 
  239.     private void upDataForUpPull() { 
  240.         if (mDistanceY != 0) { 
  241.             mFootParams.height -= mDistanceY; 
  242.             if (mFootParams.height <= 0) { 
  243.                 mFootParams.height = 0; 
  244.             } 
  245.             if (mFootParams.height >= mFootMaxHeight) { 
  246.                 mFootParams.height = mFootMaxHeight; 
  247.             } 
  248.             mChildView.setTranslationY(-mFootParams.height); 
  249.             mFootView.requestLayout(); 
  250.         } 
  251.     } 
  252.  
  253.     /** 
  254.      * 下拉时处理手势 
  255.      */ 
  256.     private void upDataForDownPull() { 
  257.         if (mDistanceY != 0) { 
  258.             mHeaderParams.height += mDistanceY; 
  259.             if (mHeaderParams.height >= mHeaderMaxHeight) { //最大 
  260.                 mHeaderParams.height = mHeaderMaxHeight; 
  261.             } 
  262.             if (mHeaderParams.height <= 0) { //最小 
  263.                 mHeaderParams.height = 0; 
  264.             } 
  265.             mChildView.setTranslationY(mHeaderParams.height); 
  266.             mHeaderView.requestLayout(); 
  267.         } 
  268.     } 
  269.  
  270.     @Override 
  271.     protected void onAttachedToWindow() { 
  272.         super.onAttachedToWindow(); 
  273.     } 
  274.  
  275.     @Override 
  276.     protected void onFinishInflate() { 
  277.         super.onFinishInflate(); 
  278.         //加载头 
  279.         mHeaderView = getChildAt(0); 
  280.         if (!(mHeaderView instanceof HeadAndFootCallBack)) { 
  281.             new IllegalStateException("HeaderView必须实现HeadAndFootCallBack接口"); 
  282.         } 
  283.         ((HeadAndFootCallBack) mHeaderView).setAttribute(); 
  284.         mHeaderParams = (LayoutParams) mHeaderView.getLayoutParams(); 
  285.         mHeaderParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); 
  286.  
  287.         //加载尾 
  288.         mFootView = getChildAt(2); 
  289.         if (!(mFootView instanceof HeadAndFootCallBack)) { 
  290.             new IllegalStateException("FootView必须实现HeadAndFootCallBack接口"); 
  291.         } 
  292.         ((HeadAndFootCallBack) mFootView).setAttribute(); 
  293.         mFootParams = (LayoutParams) mFootView.getLayoutParams(); 
  294.         mFootParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); 
  295.  
  296.         mChildView = getChildAt(1); 
  297.         if (!(mChildView instanceof HeadAndFootCallBack)) { 
  298.             new IllegalStateException("ChildView必须实现PullCallBack接口"); 
  299.         } 
  300.         mCallBack = (PullCallBack) getChildAt(1); 
  301.  
  302.         //设置动画 
  303.         mAnimator = ObjectAnimator.ofFloat(mChildView, "translationY", 0); 
  304.         mAnimator.setInterpolator(new DecelerateInterpolator()); 
  305.         mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
  306.             @Override 
  307.             public void onAnimationUpdate(ValueAnimator animation) { 
  308.                 int translationY = (int) mChildView.getTranslationY(); 
  309.                 if (mCanUpPull) { //从移动到的位置往下滑 
  310.                     mFootParams.height = Math.abs(translationY); 
  311.                     mFootView.requestLayout(); 
  312.                 } else if (mCanDownPull) { 
  313.                     mHeaderParams.height = Math.abs(translationY); 
  314.                     mHeaderView.requestLayout(); 
  315.                 } 
  316.                 Log.e("shen""translationY=" + translationY); 
  317.                 Log.e("shen""mHeaderParams.height=" + mHeaderParams.height); 
  318.                 if (translationY == 0) { 
  319.                     mChildView.setEnabled(true); 
  320.                     mDistanceY = 0; //重置 
  321.                     mIsRefreshDuring = false; //重置 
  322.                     mCanIntercept = false
  323.                 } else { 
  324.                     mIsRefreshDuring = true
  325.                 } 
  326.             } 
  327.         }); 
  328.     } 
  329.  
  330.     @Override 
  331.     protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
  332.         super.onSizeChanged(w, h, oldw, oldh); 
  333.         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 
  334.         mDistance = mTouchSlop * 5; 
  335.         //设置下拉头初始属性 
  336.         mHeaderMaxHeight = mHeaderParams.height; 
  337.         mHeaderParams.height = 0; 
  338.         mHeaderView.requestLayout(); 
  339.         //设置上拉尾初始属性 
  340.         mFootMaxHeight = mFootParams.height; 
  341.         mFootParams.height = 0; 
  342.         mFootView.requestLayout(); 
  343.     } 
  344.  
  345.     /** 
  346.      * 下拉/上拉事件监听 
  347.      */ 
  348.     public interface OnRefresh { 
  349.         /** 
  350.          * 下拉刷新 
  351.          */ 
  352.         void onDownPullRefresh(); 
  353.  
  354.         /** 
  355.          * 上拉加载 
  356.          */ 
  357.         void onUpPullRefresh(); 
  358.     } 
  359.  
  360.     public void setOnRefresh(OnRefresh onRefresh) { 
  361.         mOnRefresh = onRefresh; 
  362.     } 
  363.  

给他添加三个控件,头尾就是刷新头、尾,第二个就是正常显示的控件。必须让头尾实现HeadAndFootCallBack接口,来设置属性,通知开始刷新、结束刷新

难点: 现在来说下开发时遇到的难点

  • 由于判断在dispatchTouchEvent中,导致如果该控件以及子控件都不消费该事件的话,就会造成事件不会发送到它,因为如果不消费DOWN事件的话,之后所有的事件都不会在进行接收。解决方式,让该控件onTouchEvent方法消返回true,当子控件不进行事件消费的话,就会返回由该控件消费,不会造成因DOWN事件不消费而无法接收到事件,导致dispatchTouchEvent也不消费事件
  • 动画,动画就是我的伤痛,最近在学习估值器

这个控件自认为写的不错,通过他可以帮我们学习事件分发、动画、接口回调,也是有一定的学习意义

【编辑推荐】

  1. iOS 12这几个功能借鉴了Android系统?
  2. 谷歌神秘的新系统 Fuchsia 会是 Android 的终点吗?
  3. 一篇文章搞懂Android 自定义Viewgroup的难点
  4. Google 为 Android P 引入新的生物识别身份验证 API
  5. Android屏幕适配很麻烦吗?不!太简单了。。。
【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

'ASP.NET'程序设计教程

《ASP.NET程序设计教程》是在总结多年ASP.NET教学和应用项目开发经验基础上编写完成的,编写过程中充分吸取了其他畅销实用教程的成功经验。...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊