ListView拉伸回弹后续更新

2020-06-19  本文已影响0人  我的阿福

接上一篇https://www.jianshu.com/p/a3fed8b73625
给ListView加上顶端和底端的弹性拉动效果。

package com.h.anthony.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.Scroller;

/**
* Created by hf on 2020-06-17.
* <p>
* {@link Scroller}或者{@link android.view.ViewGroup.MarginLayoutParams}实现ListView的上下弹性拉伸
* <p>
* <p>
* 其实用@{@link View#setTranslationY(float)} }也可以实现拉伸,但每次拉伸的时候边界总是有闪烁的情况,
* 未找到原因,这里就不实现这种方式了。
*/
public class TelescopingListView extends ListView implements AbsListView.OnScrollListener {

   private static final String TAG = "WechatListView";


   /**
    * 标记是否滑到了最顶端
    */
   private boolean mTopArrived = true;

   /**
    * 标记是否滑到了最底端
    */
   private boolean mBottomArrived = false;

   /**
    * 处理相关回弹效果的工具类,包含两种实现方式
    */
   private TelescopTool mTelescopTool;

   /**
    * 滑动的最小距离,小于这个距离不认为是在滑动,跟具体的设备有关
    */
   private static int MINSCROLLDISTANCE;

   public TelescopingListView(Context context, AttributeSet attrs) {
       super(context, attrs);
       MINSCROLLDISTANCE = ViewConfiguration.get(getContext()).getScaledTouchSlop();
       setOverScrollMode(View.OVER_SCROLL_NEVER);
       setOnScrollListener(this);
//        mTelescopTool = new ScrollerTelescopTool(this);
       mTelescopTool = new MarginTelescopTool(this);
   }


   @Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {
       super.onLayout(changed, l, t, r, b);
       mTelescopTool.listViewLayoutChange();
   }


   @Override
   public boolean onTouchEvent(MotionEvent ev) {
       boolean ret = mTelescopTool.dealTouchEvent(mTopArrived, mBottomArrived, ev) ? true : super.onTouchEvent(ev);
       Log.e(TAG, "onTouchEvent: " + ev.getAction() + ";" + ret);
       return ret;
   }


   @Override
   public void computeScroll() {
       mTelescopTool.computeScroll();
   }


   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {
   }

   /**
    * @param view
    * @param firstVisibleItem
    * @param visibleItemCount
    * @param totalItemCount
    */
   @Override
   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
       View firstChildView = getChildAt(0);
       if (firstChildView != null) {
           int top = firstChildView.getTop();
           if (top == 0 && firstVisibleItem == 0) {//滑到最顶端
               mTopArrived = true;
               Log.d(TAG, "滑到了最顶端");
           } else {
               mTopArrived = false;
           }
       } else {
           mTopArrived = false;
       }


       View lastChildView = getChildAt(getChildCount() - 1);
       if (lastChildView != null) {
           int bottom = lastChildView.getBottom();
           if (bottom == getHeight() && (firstVisibleItem + visibleItemCount == totalItemCount)) {//滑到最低端
               mBottomArrived = true;
               Log.d(TAG, "滑到了最低端");
           } else {
               mBottomArrived = false;
           }
       } else {
           mBottomArrived = false;
       }
   }


   /**
    * 抽象类
    */
   private static abstract class TelescopTool {

       /**
        * 阻尼参数(0,1]越小拉动越费力,其实就是把实际拉伸的距离*这个参数。
        */
       private final static float DAMPING = 0.5f;

       /**
        * 记录当次触摸事件序列总共滑动的距离,如果小于这个距离,在手指抬起的时候会做点击处理,具体见ActionUp里面的处理
        */
       protected float mmDeltaYSum;//postive;

       protected TelescopingListView telescopingListView;
       /**
        * 记录上一次手指距离屏幕边缘的Y坐标
        */
       protected float mmLastRawY;

       public TelescopTool(TelescopingListView telescopingListView) {
           this.telescopingListView = telescopingListView;
       }

       abstract boolean dealTouchEvent(boolean topArrived, boolean bottomArrived, MotionEvent ev);

       /**
        * 给ScrollerTelescopTool用的,配合Scroller使用
        */
       void computeScroll() {

       }

       /**
        * 把实际的距离*阻尼参数,增加阻尼效果
        *
        * @param delTaY
        * @return
        */
       protected float adjustDelta(float delTaY) {
           return delTaY * DAMPING;
       }


       /**
        * 当listview onLayout时候,这时候可以重新获取listView的布局参数
        */
       public void listViewLayoutChange() {

       }
   }

   /**
    * 用Scroller实现
    */
   private static class ScrollerTelescopTool extends TelescopTool {
       private Scroller mmScroller;


       public ScrollerTelescopTool(TelescopingListView telescopingListView) {
           super(telescopingListView);
           mmScroller = new Scroller(telescopingListView.getContext());
       }

       @Override
       boolean dealTouchEvent(boolean topArrived, boolean bottomArrived, MotionEvent ev) {

           if (mmScroller != null) {
               mmScroller.forceFinished(true);
           }
           float nowRawY = ev.getRawY();
           switch (ev.getAction()) {
               case MotionEvent.ACTION_DOWN:
                   mmDeltaYSum = 0;
                   mmLastRawY = nowRawY;
                   break;
               case MotionEvent.ACTION_MOVE:
                   float orignalDeltaY = nowRawY - mmLastRawY;
                   mmDeltaYSum += Math.abs(orignalDeltaY);
                   float delTaY = adjustDelta(orignalDeltaY);
                   mmLastRawY = nowRawY;
                   float nowScrollY = telescopingListView.getScrollY();
                   if (topArrived) {
                       if (delTaY > 0) {
                           telescopingListView.scrollBy(0, (int) -delTaY);
                           return true;
                       } else if (delTaY < 0) {
                           if (nowScrollY < 0) {
                               telescopingListView.scrollBy(0, (int) -delTaY);
                               return true;
                           }
                       } else {
                           return true;
                       }
                   } else if (bottomArrived) {
                       if (delTaY < 0) {
                           telescopingListView.scrollBy(0, (int) -delTaY);
                           return true;
                       } else if (delTaY > 0) {
                           if (nowScrollY > 0) {
                               telescopingListView.scrollBy(0, (int) -delTaY);
                               return true;
                           }
                       } else {
                           return true;
                       }
                   }
                   break;
               //如果是顶端或者底端,抬起手指时如果在这次触摸过程中滑动了一段距离,
               // 那么就将这次事件手动设置为MotionEvent.ACTION_CANCEL,
               // 否则将会触发OnitemClick事件
               case MotionEvent.ACTION_UP:
                   smoothScrollTo(0);
                   if (topArrived || bottomArrived) {
                       if (mmDeltaYSum >= MINSCROLLDISTANCE) {
                           ev.setAction(MotionEvent.ACTION_CANCEL);
                       }
                   }
                   break;
           }
           return false;
       }


       @Override
       void computeScroll() {
           if (mmScroller.computeScrollOffset()) {
               telescopingListView.scrollTo(mmScroller.getCurrX(), mmScroller.getCurrY());
               telescopingListView.invalidate();
           }
       }

       /**
        * 工具方法
        * 平滑的滑动到一个指定的坐标
        *
        * @param distnationY y方向要滑动到的目的坐标
        */
       private void smoothScrollTo(int distnationY) {
           mmScroller.startScroll(telescopingListView.getScrollX(), telescopingListView.getScrollY(), telescopingListView.getScrollX(), distnationY - telescopingListView.getScrollY(), 200);
           telescopingListView.invalidate();
       }
   }


   /**
    * 用MarginLayoutParams实现
    */
   private static class MarginTelescopTool extends TelescopTool {

       private MarginLayoutParams mmMarginParams;

       public MarginTelescopTool(TelescopingListView telescopingListView) {
           super(telescopingListView);
       }

       @Override
       public void listViewLayoutChange() {
           mmMarginParams = (MarginLayoutParams) telescopingListView.getLayoutParams();
       }

       @Override
       boolean dealTouchEvent(boolean topArrived, boolean bottomArrived, MotionEvent ev) {
           float nowRawY = ev.getRawY();
           switch (ev.getAction()) {
               case MotionEvent.ACTION_DOWN:
                   mmLastRawY = nowRawY;
                   mmDeltaYSum = 0;
                   break;
               case MotionEvent.ACTION_MOVE:
                   float orignalDeltaY = nowRawY - mmLastRawY;
                   mmDeltaYSum += Math.abs(orignalDeltaY);
                   float delTaY = adjustDelta(orignalDeltaY);
                   mmLastRawY = nowRawY;
                   if (topArrived) {
                       float nowTopMargin = mmMarginParams.topMargin;
                       if (delTaY > 0) {
                           mmMarginParams.topMargin += delTaY;
                           listRequestLayout();
                           return true;
                       } else if (delTaY < 0) {
                           if (nowTopMargin > 0) {
                               mmMarginParams.topMargin += delTaY;
                               listRequestLayout();
                               return true;
                           }
                       } else {
                           return true;
                       }
                   } else if (bottomArrived) {
                       if (delTaY < 0) {
                           mmMarginParams.topMargin += delTaY;
                           mmMarginParams.bottomMargin -= delTaY;
                           listRequestLayout();
                           return true;
                       } else if (delTaY > 0) {
                           if (mmMarginParams.bottomMargin > 0) {
                               mmMarginParams.bottomMargin -= delTaY;
                               if (mmMarginParams.topMargin < 0) {
                                   mmMarginParams.topMargin += delTaY;
                                   mmMarginParams.topMargin = mmMarginParams.topMargin <= 0 ? mmMarginParams.topMargin : 0;
                               }
                               listRequestLayout();
                               return true;
                           }
                       } else {
                           return true;
                       }
                   }
                   break;
               case MotionEvent.ACTION_UP:
                   mmMarginParams.topMargin = 0;
                   mmMarginParams.bottomMargin = 0;
                   listRequestLayout();
                   if (topArrived || bottomArrived) {
                       if (mmDeltaYSum >= MINSCROLLDISTANCE) {
                           ev.setAction(MotionEvent.ACTION_CANCEL);
                       }
                   }
                   break;
           }
           return false;
       }

       private void listRequestLayout() {
           telescopingListView.setLayoutParams(mmMarginParams);
       }
   }

}

上一篇 下一篇

猜你喜欢

热点阅读