Chinaunix首页 | 论坛 | 博客
  • 博客访问: 438786
  • 博文数量: 205
  • 博客积分: 5630
  • 博客等级: 大校
  • 技术积分: 1945
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-06 20:28
文章分类

全部博文(205)

文章存档

2016年(1)

2015年(6)

2014年(9)

2013年(10)

2012年(53)

2011年(25)

2010年(87)

2009年(14)

分类: Android平台

2015-03-26 11:28:05

拉动ListView,Item之间的间距会变大,释放后恢复原样;

点击(此处)折叠或打开

  1. package cn.tangdada.tangbang.widget;

  2. import android.annotation.TargetApi;
  3. import android.content.Context;
  4. import android.content.res.TypedArray;
  5. import android.graphics.drawable.BitmapDrawable;
  6. import android.os.Build;
  7. import android.util.AttributeSet;
  8. import android.view.MotionEvent;
  9. import android.view.View;
  10. import android.view.ViewConfiguration;
  11. import android.view.animation.AccelerateInterpolator;
  12. import android.widget.AbsListView;
  13. import android.widget.ListView;
  14. import cn.tangdada.tangbang.R;

  15. import com.nineoldandroids.view.ViewPropertyAnimator;

  16. /**
  17.  * 当滑动到顶部和底部时,实现Item的分离效果。
  18.  *
  19.  * @author pythoner
  20.  *
  21.  */
  22. @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  23. public class PullSeparateListView extends ListView
  24. {
  25.     /**
  26.      * 最大滑动距离
  27.      */
  28.     private static final float MAX_DELTAY = 100;

  29.     /**
  30.      * 分离后恢复的动画时长
  31.      */
  32.     private static final long SEPARATE_RECOVER_DURATION = 200;

  33.     /**
  34.      * 摩擦系数
  35.      */
  36.     private static final float FACTOR = 0.25f;

  37.     /**
  38.      * 按下x的缩放比例
  39.      */
  40.     private static final float SCALEX = 1.0f;

  41.     /**
  42.      * 按下y的缩放比例
  43.      */
  44.     private static final float SCALEY = 1.0f;

  45.     /**
  46.      * 展开全部
  47.      */
  48.     private boolean separateAll;

  49.     /**
  50.      * 到达边界时,滑动的起始位置
  51.      */
  52.     private float startY;

  53.     /**
  54.      * 按下时的View
  55.      */
  56.     private View downView;

  57.     private int touchSlop;

  58.     private boolean separate = false;

  59.     private boolean showDownAnim;

  60.     /**
  61.      * 原始按下位置(在所有Item中的位置)
  62.      */
  63.     private int originDownPosition;

  64.     /**
  65.      * 按下的位置(在屏幕中的位置)
  66.      */
  67.     private int downPosition;

  68.     /**
  69.      * 上次滑动的位置,用于判断方向
  70.      */
  71.     private float preY;

  72.     private float deltaY;

  73.     private boolean reachTop, reachBottom, move;

  74.     private OnScrollListener mScrollListener;

  75.     public PullSeparateListView(Context context, AttributeSet attrs)
  76.     {
  77.         super(context, attrs);
  78.         TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.PullSeparateListView);
  79.         separateAll = t.getBoolean(R.styleable.PullSeparateListView_separate_all, false);
  80.         showDownAnim = t.getBoolean(R.styleable.PullSeparateListView_showDownAnim, true);
  81.         t.recycle();
  82.         init();
  83.     }

  84.     public PullSeparateListView(Context context, AttributeSet attrs, int defStyle)
  85.     {
  86.         super(context, attrs, defStyle);
  87.         init();
  88.     }

  89.     public PullSeparateListView(Context context)
  90.     {
  91.         super(context);
  92.         init();
  93.     }

  94.     @SuppressWarnings("deprecation")
  95.     private void init()
  96.     {
  97.         // 不知道怎么让divider和selector和Item一起移动,所以去除,需要自己加分割线
  98.         this.setDivider(null);
  99.         this.setSelector(new BitmapDrawable());

  100.         touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
  101.         super.setOnScrollListener(listener);
  102.     }

  103.     /**
  104.      * 是否全部分离
  105.      *
  106.      * @param separateAll 如果为true,那么全部都会分离。否则的话,如果是顶部下拉,只有点击位置之前的Item会分离
    如果是底部上拉,则只有点击位置之后的item会分离。默认为false
  107.      */
  108.     public void setSeparateAll(boolean separateAll)
  109.     {
  110.         this.separateAll = separateAll;
  111.     }

  112.     public boolean isSeparateAll()
  113.     {
  114.         return separateAll;
  115.     }

  116.     /**
  117.      * 设置是否显示按下的Item的动画效果
  118.      *
  119.      * @param showDownAnim 默认为true
  120.      */
  121.     public void setShowDownAnim(boolean showDownAnim)
  122.     {
  123.         this.showDownAnim = showDownAnim;
  124.     }

  125.     public boolean isShowDownAnim()
  126.     {
  127.         return showDownAnim;
  128.     }

  129.     public void setOnScrollListener(OnScrollListener l)
  130.     {
  131.         mScrollListener = l;
  132.     }

  133.     // 核心代码
  134.     @Override
  135.     public boolean dispatchTouchEvent(MotionEvent ev)
  136.     {
  137.         float currentY = ev.getY();
  138.         switch (ev.getAction())
  139.         {
  140.             case MotionEvent.ACTION_DOWN:
  141.                 float downX = ev.getX();
  142.                 float downY = ev.getY();
  143.                 // 记录按下位置,当isSeparateAll()返回false时,会用到
  144.                 originDownPosition = pointToPosition((int) downX, (int) downY);
  145.                 downPosition = originDownPosition - getFirstVisiblePosition();
  146.                 if (showDownAnim)
  147.                 {
  148.                     performDownAnim(downPosition);
  149.                 }
  150.                 break;
  151.             case MotionEvent.ACTION_MOVE:
  152.                 // 记录到达顶部或底部时手指的位置
  153.                 if (!separate)
  154.                 {
  155.                     startY = currentY;
  156.                 }
  157.                 deltaY = currentY - startY;

  158.                 // 到达顶部
  159.                 if (reachTop)
  160.                 {
  161.                     if (!separateFromTop(currentY))
  162.                     {
  163.                         return super.dispatchTouchEvent(ev);
  164.                     }
  165.                     return false;
  166.                 }
  167.                 // 到达底部
  168.                 if (reachBottom)
  169.                 {
  170.                     if (!separateFromBottom(currentY))
  171.                     {
  172.                         return super.dispatchTouchEvent(ev);
  173.                     }
  174.                     return false;
  175.                 }
  176.                 preY = currentY;
  177.                 break;
  178.             case MotionEvent.ACTION_CANCEL:
  179.             case MotionEvent.ACTION_UP:
  180.                 preY = 0;
  181.                 recoverDownView();
  182.                 if (separate)
  183.                 {
  184.                     separate = false;
  185.                     recoverSeparate();
  186.                     // 移动,不响应点击事件
  187.                     if (move)
  188.                     {
  189.                         move = false;
  190.                         return false;
  191.                     }
  192.                 }
  193.                 break;
  194.         }
  195.         return super.dispatchTouchEvent(ev);
  196.     }

  197.     private boolean separateFromTop(float currentY)
  198.     {
  199.         // 不能放在外部,否则在顶部滑动没有Fling效果
  200.         if (deltaY > touchSlop)
  201.         {
  202.             move = true;
  203.         }
  204.         separate = true;
  205.         // 超过滑动允许的最大距离,则将起始位置向下移
  206.         if (deltaY > MAX_DELTAY)
  207.         {
  208.             startY = currentY - MAX_DELTAY;
  209.             // 超过最大距离时,出现overScroll效果//有问题
  210.             // return super.dispatchTouchEvent(ev);
  211.         }
  212.         else if (deltaY < 0)
  213.         { // 为负值时(说明反方向超过了起始位置startY)归0
  214.             deltaY = 0;
  215.             separate = false;
  216.         }

  217.         if (deltaY <= MAX_DELTAY)
  218.         {
  219.             for (int index = 0; index < getChildCount(); index++)
  220.             {
  221.                 View child = getChildAt(index);
  222.                 int multiple = index;
  223.                 if (!separateAll)
  224.                 {
  225.                     if (index > downPosition)
  226.                     {
  227.                         multiple = Math.max(1, downPosition);
  228.                     }
  229.                 }
  230.                 float distance = multiple * deltaY * FACTOR;
  231.                 child.setTranslationY(distance);
  232.             }
  233.             // 向分离方向的反方向滑动,但位置还未复原时
  234.             if (deltaY != 0 && currentY - preY < 0)
  235.             {
  236.                 return true;
  237.             }
  238.             // deltaY=0,说明位置已经复原,然后交给父类处理
  239.         }
  240.         if (deltaY == 0)
  241.         {
  242.             return false;
  243.         }
  244.         return true;
  245.     }

  246.     private boolean separateFromBottom(float currentY)
  247.     {
  248.         if (Math.abs(deltaY) > touchSlop)
  249.         {
  250.             move = true;
  251.         }
  252.         separate = true;
  253.         // 超过滑动允许的最大距离,则将起始位置向上移
  254.         if (Math.abs(deltaY) > MAX_DELTAY)
  255.         {
  256.             startY = currentY + MAX_DELTAY;
  257.             // 超过最大距离时,出现overScroll效果
  258.             // return super.dispatchTouchEvent(ev);
  259.         }
  260.         else if (deltaY > 0)
  261.         { // 为正值时(说明反方向移动超过起始位置startY),归0
  262.             deltaY = 0;
  263.             separate = false;
  264.         }
  265.         if (Math.abs(deltaY) <= MAX_DELTAY)
  266.         {
  267.             int visibleCount = getChildCount();
  268.             for (int inedex = 0; inedex < visibleCount; inedex++)
  269.             {
  270.                 View child = getChildAt(inedex);
  271.                 int multiple = visibleCount - inedex - 1;
  272.                 if (!separateAll)
  273.                 {
  274.                     if (inedex < downPosition)
  275.                     {
  276.                         multiple = Math.max(1, visibleCount - downPosition - 1);
  277.                     }
  278.                 }
  279.                 float distance = multiple * deltaY * FACTOR;
  280.                 child.setTranslationY(distance);
  281.             }
  282.             // 向分离方向的反方向滑动,但位置还未复原时
  283.             if (deltaY != 0 && currentY - preY > 0)
  284.             {
  285.                 return true;
  286.             }
  287.             // deltaY=0,说明位置已经复原,然后交给父类处理
  288.             if (deltaY == 0)
  289.             {
  290.                 return false;
  291.             }
  292.         }
  293.         return true;
  294.     }

  295.     /**
  296.      * 恢复
  297.      */
  298.     private void recoverSeparate()
  299.     {
  300.         for (int i = 0; i < getChildCount(); i++)
  301.         {
  302.             View child = getChildAt(i);
  303.             ViewPropertyAnimator.animate(child).translationY(0).setDuration(SEPARATE_RECOVER_DURATION).setInterpolator(new AccelerateInterpolator());
  304.         }
  305.     }

  306.     /**
  307.      * 按下的动画
  308.      *
  309.      * @param downPosition 在屏幕中的位置
  310.      */
  311.     private void performDownAnim(int downPosition)
  312.     {
  313.         downView = getChildAt(downPosition);
  314.         if (downView != null)
  315.         {
  316.             ViewPropertyAnimator.animate(downView).scaleX(SCALEX).scaleY(SCALEY).setDuration(50).setInterpolator(new AccelerateInterpolator());
  317.         }
  318.     }

  319.     /**
  320.      * 恢复点击的View
  321.      */
  322.     private void recoverDownView()
  323.     {
  324.         if (showDownAnim && downView != null)
  325.         {
  326.             ViewPropertyAnimator.animate(downView).scaleX(1f).scaleY(1f).setDuration(separate ? SEPARATE_RECOVER_DURATION : 100)
  327.                     .setInterpolator(new AccelerateInterpolator());
  328.         }
  329.     }

  330.     private OnScrollListener listener = new OnScrollListener()
  331.     {
  332.         @Override
  333.         public void onScrollStateChanged(AbsListView view, int scrollState)
  334.         {
  335.             if (mScrollListener != null)
  336.             {
  337.                 mScrollListener.onScrollStateChanged(view, scrollState);
  338.             }
  339.         }

  340.         @Override
  341.         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
  342.         {
  343.             if (mScrollListener != null)
  344.             {
  345.                 mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
  346.             }

  347.             // 是否到达顶部
  348.             if (firstVisibleItem == 0)
  349.             {
  350.                 View firstView = getChildAt(firstVisibleItem);
  351.                 if (firstView != null && (firstView.getTop() + getPaddingTop()) >= 0)
  352.                 {
  353.                     downPosition = originDownPosition;
  354.                     reachTop = true;
  355.                 }
  356.                 else
  357.                 {
  358.                     reachTop = false;
  359.                 }
  360.             }
  361.             else
  362.             {
  363.                 reachTop = false;
  364.             }
  365.             // 是否到达底部
  366.             if (firstVisibleItem + visibleItemCount == getCount())
  367.             {
  368.                 View lastView = getChildAt(visibleItemCount - 1);
  369.                 if (lastView != null && (lastView.getBottom() + getPaddingBottom()) <= getHeight() && getCount() > getChildCount())
  370.                 {
  371.                     downPosition = originDownPosition - firstVisibleItem;
  372.                     reachBottom = true;
  373.                 }
  374.                 else
  375.                 {
  376.                     reachBottom = false;
  377.                 }
  378.             }
  379.             else
  380.             {
  381.                 reachBottom = false;
  382.             }
  383.         }
  384.     };

  385.     /**
  386.      * 是否到达顶部
  387.      *
  388.      * @return
  389.      */
  390.     @Deprecated
  391.     protected boolean isReachTopBound()
  392.     {
  393.         int firstVisPos = getFirstVisiblePosition();
  394.         if (firstVisPos == 0)
  395.         {
  396.             View firstView = getChildAt(firstVisPos);
  397.             if (firstView != null && firstView.getTop() >= 0)
  398.             {
  399.                 return true;
  400.             }
  401.             else
  402.             {
  403.                 return false;
  404.             }
  405.         }
  406.         return false;
  407.     }

  408.     /**
  409.      * 是否到达底部
  410.      *
  411.      * @return
  412.      */
  413.     @Deprecated
  414.     protected boolean isReachBottomBound()
  415.     {
  416.         int lastVisPos = getLastVisiblePosition();
  417.         if (lastVisPos == getCount() - 1)
  418.         {
  419.             View lastView = getChildAt(getChildCount() - 1);
  420.             if (lastView != null && lastView.getBottom() <= getHeight() && getCount() > getChildCount())
  421.             {
  422.                 return true;
  423.             }
  424.             else
  425.             {
  426.                 return false;
  427.             }
  428.         }
  429.         return false;
  430.     }

  431. }

自定义属性

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3.     <declare-styleable name="PullSeparateListView">
  4.         <attr name="separate_all" format="boolean"/>
  5.         <attr name="showDownAnim" format="boolean"/>
  6.     </declare-styleable>
  7. </resources>

http://gundumw100.iteye.com/blog/2178560

阅读(1111) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~