前言:这个月真是过得太快了,本来说要看四章的内容,这下才看了两章,擦……严重没完成预算啊……撞豆腐死了算了
相关文章:
1、《 ListView滑动删除实现之一——merge标签与LayoutInflater.inflate()》
2、《ListView滑动删除实现之二——scrollTo、scrollBy详解》
3、《 ListView滑动删除实现之三——创建可滑动删除的ListView》
4、《ListView滑动删除实现之四——Scroller类与listview缓慢滑动》
上篇我们已经完成了利用scrollTo实现listview的滑动删除,但我们最后也提到了一个问题:当手指抬起时,scrollTo直接将ITEM滑动到了指定位置,中间没有缓冲过程,用户体验不好。我们想实现一个能缓慢滑动的listview:如下图所示:
从示图中明显可以看到,这里并不像上一章一样,一下子还原到初始化状态或者一下子到完全展开的状态了。而是有一个过程,它会慢慢点,一点点地还原或展开。这就是Scroller类所起的作用。下面我们先看看Scroller类的使用方法,然后再在前一篇的基础上加上Scroller。
public Scroller (Context context) public Scroller (Context context, Interpolator interpolator)如果我们用第一个构造函数,那么Scroller就会给我们传入一个默认的插值器;一般我们会选择第二个构造函数,传入一个我们想要的插值器。插值器的概念是从Animation来的,以前我曾把所有插值器的动画特效都列举了一遍,大家有兴趣可以参考: 《Animation动画详解(二)——Interpolator插值器》
public void startScroll(int startX, int startY, int dx, int dy, int duration) public void startScroll(int startX, int startY, int dx, int dy)我们先看第一个构造函数:
private Scroller mScroller; mScroller = new Scroller(context,new LinearInterpolator(context,null));步骤二:startScroll
mScroller.startScroll(0,0,200,0,500); invalidate();步骤三: public void computeScroll()中处理
@Override public void computeScroll() { if (mScroller.computeScrollOffset()){ itemRoot.scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); } invalidate(); }在这里注意一个地方,我们在computeScroll()中最终调用了itemRoot的scrollTo(),这里又涉及两个函数:
mScroller.getCurrX() mScroller.getCurrY()这两个是什么意思?看表面意思是获取当前scroller所在的X轴坐标和Y轴坐标。难道它的X轴坐标和Y轴坐标还都不一样吗?
mScroller.startScroll(0,0,200,0,500);这个代码就是说用500毫秒的时间从(0,0)的位置,沿X轴反方向移动200,Y轴不动;
//步骤二: mScroller.startScroll(0,0,200,0,500); invalidate(); ``` ``` java @Override public void computeScroll() { //步骤三: if (mScroller.computeScrollOffset()){ itemRoot.scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); invalidate(); } }这里还要再说一个点:我们如何判断什么时候停止重绘呢。scroller给我们提供了一个函数:
Scroller.computeScrollOffset()当scroller还在移动时,就返回TRUE,如果scroller已经移动结束就返回FALSE;所以,我们可以在直接itemRoot.scrollTo(mScroller.getCurrX(),mScroller.getCurrY());前利用mScroller.computeScrollOffset()来判断当前scroller是不是已经结束了。
/** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is * animating a scroll using a {@link android.widget.Scroller Scroller} * object. */ public void computeScroll() { }明显这里是一个空函数,所以,这就交由我们自己来实现,Scroller中的移动部分了,这也就是为什么我们会在computeScroll() 中调用 scrollTo来实现视图的移动了。
最后我们总结一下:Scroller在调用startScroll()之后,会自己根据移动距离和时间来计算每毫秒的移动目的坐标,用户可以通过scroller.getCurrX()和scroller.getCurrY()来获取。当VIEW在重绘时,会调用View的computeScroll()函数来处理与scroller有关的重绘操作。而由于View类并没有对computeScroll()做任何的实现(只是一个空函数),所以有关scroller的移动操作,就只能靠我们自己完成了。(重写computeScroll函数,调用)
public class MyListView extends ListView { private Context mContext; private Scroller mScroller; public MyListView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mScroller = new Scroller(context, new LinearInterpolator(context, null)); } …… }我们这里初始化mScroller时,使用线性插值器;大家也可以用其它插值器,每一个插值器的运动效果都是不一样的。
public boolean onTouchEvent(MotionEvent event) { int maxLength = dipToPx(mContext, MAX_WIDTH); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { ………… case MotionEvent.ACTION_UP: { int scrollX = itemRoot.getScrollX(); int newScrollX = scrollX + mlastX - x; if (scrollX > maxLength / 2) { newScrollX = maxLength; } else { newScrollX = 0; } mScroller.startScroll(scrollX,0,newScrollX - scrollX,0); invalidate(); } break; } mlastX = x; return super.onTouchEvent(event); }我们把核心代码摘出来:
int scrollX = itemRoot.getScrollX(); int newScrollX = scrollX + mlastX - x; if (scrollX > maxLength / 2) { newScrollX = maxLength; } else { newScrollX = 0; } mScroller.startScroll(scrollX,0,newScrollX - scrollX,0); invalidate();首先跟上篇一样,根据当前手指的位置(如果超过黄色块1/2就将newScrollX设置到最大值;如果小于黄色块1/2,那就还原到初始化状态),然后是调用
mScroller.startScroll(scrollX,0,newScrollX - scrollX,0);首先是移动距离,大家一定要注意的是移动距离不一定是正数,它只是表示在当前方向上的移动数,X轴为正:视角向右移动,即其中的子控件向左移动。为负:视角向左移动,即其中的子控件所右移动;Y轴同理。所以这里的第三个参数dx:就等于目的坐标减去原坐标。这就是第三个参数为什么是newScrollX - scrollX的原因。
public void computeScroll() { if (mScroller.computeScrollOffset()){ itemRoot.scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); invalidate(); } }可以看到,首先是利用mScroller.computeScrollOffset()来判断当前Scroller的状态——是否已经计算移动位置结束,如果结束返回FALSE,如果还在计算就返回TRUE。
1、首先,当另一个ITEM滑出来的时候,上一个ITEM缩回去。
2、但最后大家会发现问题:当我连续点击一个ITEM之后,上一个ITEM就会停止滑动。这个效果不是我们想要的,我们先完成第一个效果,后面再解决这个问题。
由于我们要让上一个ITEM滑动,所以我们要在MyListView中另外定义一个变量来保存上次滑动的VIEW
private LinearLayout mPreScrollView;然后额外添加一个SCROLLER,用来计算mPreScrollView的滚动坐标:
private Scroller mPreScroller; public MyListView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mScroller = new Scroller(context, new LinearInterpolator(context, null)); mPreScroller = new Scroller(context, new LinearInterpolator(context, null)); }然后是对mPreScrollView赋值和滚动了。我们在下按一个ITEM的时候,就要把前一个ITEM还原。当我们手指上抬的时候,那这个ITEM就已经滚动结束了,那么它就是下一次的mPreScrollView。
public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { if (mPreScrollView != null){ int preScrollX = mPreScrollView.getScrollX(); mPreScroller.startScroll(preScrollX,0,0-preScrollX,0); } } break; case MotionEvent.ACTION_UP: { ………… mPreScrollView = itemRoot; ………… invalidate(); } break; } mlastX = x; return super.onTouchEvent(event); }最后是在computeScroll()中移动mPreScrollView
public void computeScroll() { ………… if (mPreScroller.computeScrollOffset()){ mPreScrollView.scrollTo(mPreScroller.getCurrX(),mPreScroller.getCurrY()); } invalidate(); }到这里就结束了,mPreScrollView在下一次滑动ITEM时就会自己收缩了,但这里有个问题,也就是效果图中的第二点问题。当我们对一个ITEM点击两下的时候,它的上一个ITEM就会停止滑动。导致不能完全收缩回去。这是为什么呢?主要是因为我们在MotionEvent.ACTION_UP中对mPreScrollView = itemRoot;进行的赋值。如果我们抬起的太快,而上一个ITEM还没有滑动结束,那么mPreScrollView还没结束就已经被赋值给最新的ITEM了,这时候的computeScroll(),调用的mPreScrollView.scrollTo(mPreScroller.getCurrX(),mPreScroller.getCurrY());就已经是最新的ITEM了,所以上一个ITEM就会停止滑动。
public class MyLinearLayout extends LinearLayout { private int mlastX = 0; private final int MAX_WIDTH = 200; private Context mContext; private Scroller mScroller; public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mScroller = new Scroller(context, new LinearInterpolator(context, null)); } public void disPatchTouchEvent(MotionEvent event){ int maxLength = dipToPx(mContext, MAX_WIDTH); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { } break; case MotionEvent.ACTION_MOVE: { int scrollX = this.getScrollX(); int newScrollX = scrollX + mlastX - x; if (newScrollX < 0) { newScrollX = 0; } else if (newScrollX > maxLength) { newScrollX = maxLength; } this.scrollTo(newScrollX, 0); } break; case MotionEvent.ACTION_UP: { int scrollX = this.getScrollX(); int newScrollX = scrollX + mlastX - x; if (scrollX > maxLength / 2) { newScrollX = maxLength; } else { newScrollX = 0; } mScroller.startScroll(scrollX, 0, newScrollX - scrollX, 0); invalidate(); } break; } mlastX = x; } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { this.scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); } invalidate(); } private int dipToPx(Context context, int dip) { return (int) (dip * context.getResources().getDisplayMetrics().density + 0.5f); } }这里就是集成了所有对ITEM跟随手指移动的操作。
<com.harvic.com.blog3_4_mylinearlayout.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/lin_root" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" android:minHeight="120dp"> <TextView android:id="@+id/title" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#0000ff" android:gravity="center" android:textSize="25dp" /> <TextView android:id="@+id/del" android:layout_width="200dp" android:layout_height="fill_parent" android:background="#ffff00" android:text="删除" android:textSize="25dp" android:textColor="#ffffff" android:gravity="center" /> </com.harvic.com.blog3_4_mylinearlayout.MyLinearLayout>
public class MyListView extends ListView { private MyLinearLayout mCurView; public MyListView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { //我们想知道当前点击了哪一行 int position = pointToPosition(x, y); if (position != INVALID_POSITION) { DataHolder data = (DataHolder) getItemAtPosition(position); mCurView = data.rootView; } } break; default: break; } if (mCurView != null){ mCurView.disPatchTouchEvent(event); } return super.onTouchEvent(event); } }首先根据位置找到所有ITEM的VIEW,然后把事件传给对应的MyLinearLayout自己处理。
下面的内容可能比较绕,大家可能加上我的讲解,大家可以还要自己理解一下了
首先,我们有几个点先列举一下:
1、我们需要在一个ITEM滑出来的时候,通知到上一个ITEM,让它收缩
2、每一个ITEM都应该具有监听功能,当上一个ITEM滑出来时,它就能对应的收缩回去
首先,我们要明白的一点是,我们要监听!!!监听当前ITEM是否全部拉了出来,所以我们在MyLinearLayout中需要加一个回调函数来做监听:
public class MyLinearLayout extends LinearLayout { private OnScrollListener mScrollListener; public static interface OnScrollListener { public void OnScroll(MyLinearLayout view); } public void disPatchTouchEvent(MotionEvent event) { switch (event.getAction()) { ………… case MotionEvent.ACTION_UP: { int scrollX = this.getScrollX(); int newScrollX = scrollX + mlastX - x; if (scrollX > maxLength / 2) { newScrollX = maxLength; //当完全展开时,通知出去 mScrollListener.OnScroll(this); } else { newScrollX = 0; } mScroller.startScroll(scrollX, 0, newScrollX - scrollX, 0); invalidate(); } break; } mlastX = x; } ………… }上面的代码,总共做了两件事:
private OnScrollListener mScrollListener; public static interface OnScrollListener{ public void OnScroll(MyLinearLayout view); }由于我们后面要保存当前的VIEW来做自动收缩,所以我们要把当前伸展出来的ITEM的VIEW传出去,供对方保存。当下一个ITEM出来的时候,让这个VIEW缩回去
当用户手指抬起来,而且ITEM完全展开的时候,调用onScroll()通知出去
case MotionEvent.ACTION_UP: { int scrollX = this.getScrollX(); int newScrollX = scrollX + mlastX - x; if (scrollX > maxLength / 2) { //当完全展开时,通知出去 newScrollX = maxLength; mScrollListener.OnScroll(this); } else { newScrollX = 0; } mScroller.startScroll(scrollX, 0, newScrollX - scrollX, 0); invalidate(); } break;然后,还有两个额外的函数:
//设置监听器 public void setOnScrollListener(OnScrollListener scrollListener) { mScrollListener = scrollListener; } //缓慢将ITEM滚动到指定位置 public void smoothScrollTo(int destX, int destY) { int scrollX = getScrollX(); int delta = destX - scrollX; mScroller.startScroll(scrollX, 0, delta, 0); invalidate(); }
public View getView(final int position, View convertView, ViewGroup parent) { ………… DataHolder item = mDataList.get(position); holder.title.setText(item.title); item.rootView = (MyLinearLayout) convertView.findViewById(R.id.lin_root); item.rootView.scrollTo(0, 0); item.rootView.setOnScrollListener(mScrollListener); ………… return convertView; }而Adapter中是没有办法做监听的,所以它的监听函数也只有从MainActivity传过来:
public class MergeListAdapter extends BaseAdapter { private View.OnClickListener mDelClickListener; private MyLinearLayout.OnScrollListener mScrollListener; public MergeListAdapter(Context context, List<DataHolder> dataList, View.OnClickListener delClickListener, MyLinearLayout.OnScrollListener listener) { ………… mDelClickListener = delClickListener; mScrollListener = listener; } ………… }
public class MainActivity extends Activity implements View.OnClickListener,MyLinearLayout.OnScrollListener{ private MyLinearLayout mLastScrollView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ………… adapter = new MergeListAdapter(this,items,this,this); listView.setAdapter(adapter); } @Override public void OnScroll(MyLinearLayout view) { if (mLastScrollView != null){ mLastScrollView.smoothScrollTo(0,0); } mLastScrollView = view; } }首先初始化adapter,当一个ITEM完全展开时,我们的OnScroll()函数就会得到调用。用一个变量:MyLinearLayout mLastScrollView来保存上一次完全展开的ITEM;当一个ITEM完全展开时,就先调用mLastScrollView.smoothScrollTo(0,0);将上一个ITEM收缩回去,然后再将这个VIEW赋值给mLastScrollView;
源码在文章底部给出
自定义listview的参数必须是layout_width:match_parent/fill_parent,layout_height:match_parent/fill_parent
即下面的代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.harvic.com.blog3_4_mylinearlayout.MyListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
如果给Mylistview使用wrap_content,将导致scrollTo无效,具体什么原因我也不知道,所以如果listView底部还有什么界面的话,建议大家使用listview::addFooterView()来添加底部布局的方式来做。一定要避免使用wrap_content,不然就真的会滑动无效,大家可以尝试一下,反正我是找不到原因了……
好了,到这里有关滑动删除的所有东东都讲完了,这篇文章容量有点大,大家可能要根据源码仔细理解一下了。逻辑确实有点太绕,大家多看看源码应该问题不大。
最后,最终优化部分的代码是我们这个系列建议使用的代码结构,即ITEM的根布局使用自定义的MyLinearLayout,由它自己来负责自己的滚动。对应的源码名称为:《ScrollViewUltimate》
源码内容:
1、《ScrollView_Scroller》:第二部分:《实现缓慢滑动的ListView》对应源码
2、《ScrollViewUltimate》:第三部分:《优化》的最终代码,也是本系统的最终可用代码
如果本文有帮到你,记得加关注哦
源码下载地址:http://download.csdn.net/detail/harvic880925/8642751
请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/45317951 谢谢