目录
案例效果
Scroller基本使用
其实Scroller的使用非常简单总共就两步,这里为了方便为下面的滑动删除案例做准备我们就先实现一个类似的效果,这里我是通过继承LinearLayout实现的,代码如下:
public class ScrollUserLayout extends LinearLayout {
private Scroller mScroller;
public ScrollUserLayout(Context context) {
this(context,null);
}
public ScrollUserLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ScrollUserLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScroller = new Scroller(getContext());
}
/**
* 开始滑动
* @param distance 滑动的距离(注意这里不是坐标)
*/
public void startScroll(int distance){
mScroller.startScroll(0,0,distance,0);
}
/**
* 恢复初态
*/
public void reset(){
mScroller.startScroll(getScrollX(),0,-getScrollX(),0);
}
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
}
invalidate();
}
}
如上面代码所示,整个逻辑非常简单,接下来我就解释下使用Scroller的方法:
1.startScroll
mScroller.startScroll(0,0,distance,0);
这里需要传四个参数:
第一个参数:为X轴方向的开始执行滚动的位置
第二个参数:为Y轴方向的开始执行滚动的位置
第三个参数:为X轴方向滑动的距离(注意不是位置坐标)
第四个参数:为Y轴方向滑动的距离(注意不是位置坐标)
2.computeScroll
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
}
invalidate();
}
这个方法不是Scroller的方法而是View的方法,这个方法是计算滚动时的一个回调,其中Scroller的computeScrollOffset方法是用来判断是否还在计算滚动,如果还在计算,那么就需要调用View的scrollTo方法将当前控件滚动到Scroller计算的位置,然后在最后需要调用View的invalidate()方法来刷新控件。
而这个简单的控件的使用也是比较简单的,布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/bt_open"
android:text="展开"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/bt_close"
android:text="收回"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<com.example.scrolldeletelayout.ScrollUserLayout
android:id="@+id/sul"
android:layout_width="match_parent"
android:layout_height="100dp">
<View
android:background="#ff00ff"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<View
android:id="@+id/v_right"
android:background="#ff0000"
android:layout_width="100dp"
android:layout_height="match_parent"/>
</com.example.scrolldeletelayout.ScrollUserLayout>
</LinearLayout>
Activity中的代码如下:
public class TestActivity extends AppCompatActivity {
private View vRight;
private Button btOpen;
private Button btClose;
private ScrollUserLayout sul;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.testactivity);
vRight = (View) findViewById(R.id.v_right);
btOpen = (Button) findViewById(R.id.bt_open);
btClose = (Button) findViewById(R.id.bt_close);
sul = (ScrollUserLayout) findViewById(R.id.sul);
btOpen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sul.startScroll(vRight.getMeasuredWidth());
}
});
btClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sul.reset();
}
});
}
}
运行的效果如下:
实现列表滑动删除
接下来我们就以文章开头效果图展示的滑动删除案例来熟悉Scroller的使用方法,这里效果的核心其实就是上面所说的知识,而需要添加的逻辑就是对onTouchEvent事件的处理,和在onLayout方法中获取滑动的最大偏移,如下所示:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
firstTouchXTemp = event.getX();
firstTouchX = event.getX();
firstTouchY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
//计算出横向和纵向滑动的距离,用来判断是想滑动列表还是想拉出右侧删除按钮
float xLen = Math.abs(event.getX() - firstTouchX);
float yLen = Math.abs(event.getY() - firstTouchY);
if(xLen > yLen){
//当确定了是横向滑动时就请求父控件不拦截事件,让触摸事件给本控件
getParent().requestDisallowInterceptTouchEvent(true);
float moveX = firstTouchXTemp - event.getX();
firstTouchXTemp = event.getX();
//计算控件之前的X轴滚动的距离和当前滑动的距离之和
float totalX = getScrollX() + moveX;
if(totalX > mMaxXOffset){
scrollTo(mMaxXOffset,0);
}else if(totalX < 0) {
scrollTo(0,0);
}else {
scrollBy((int) moveX,0);
}
}
break;
case MotionEvent.ACTION_UP:
//抬起手指的时候自动展开或收起
int scrollX = getScrollX();
if(scrollX > mMaxXOffset / 2){
//当滑动的距离大于右边布局的一半时就展开
mScroller.startScroll(getScrollX(),0,mMaxXOffset - getScrollX(),0);
}else {
//当滑动的距离小于右边布局的一半时就缩回
mScroller.startScroll(getScrollX(),0,-getScrollX(),0);
}
break;
}
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(getChildCount() > 1){
//找出删除按钮
deleteView = getChildAt(1);
setMaxXOffset(deleteView.getMeasuredWidth());
}
}
这里的逻辑其实也是比较简单的,大家可以通过看下面的图来理解
实现了以上逻辑之后,基本上就完成了我们需要的效果了
但是我们我们发现,会同时有很多的条目可以展开显示删除按钮,因此我们需要优化下,同一时间只能一个条目可以滑出删除按钮,也就是说我们需要在当前条目滑动出删除按钮的时候将上一个已经滑出删除按钮的条目恢复原状(即如文章开头展示的效果那样),因此我们需要在控件滑出删除按钮的时候加一个回调事件,用来通知上一个条目恢复原状,代码如下:
/**
* 当右边隐藏的控件显示出来的时候的回调
*/
public interface OnRightShowListener{
void onRightShow(ScrollDeleteLayout scrollDeleteLayout);
}
private OnRightShowListener onRightShowListener;
public OnRightShowListener getOnRightShowListener() {
return onRightShowListener;
}
public void setOnRightShowListener(OnRightShowListener onRightShowListener) {
this.onRightShowListener = onRightShowListener;
}
在onTouchEvent的MotionEvent.ACTION_UP中加入回调事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_UP:
//抬起手指的时候自动展开或收起
int scrollX = getScrollX();
if(scrollX > mMaxXOffset / 2){
//当滑动的距离大于右边布局的一半时就展开
mScroller.startScroll(getScrollX(),0,mMaxXOffset - getScrollX(),0);
//加入滑出删除按钮的回调事件
if(onRightShowListener != null){
onRightShowListener.onRightShow(this);
}
}else {
//当滑动的距离小于右边布局的一半时就缩回
mScroller.startScroll(getScrollX(),0,-getScrollX(),0);
}
break;
}
return true;
}
然后给适配器中的ScrollDeleteLayout设置OnRightShowListener事件回调,然后在回调方法中做如下处理(其中preSdl为全局变量用来存储当前滑开的ScrollDeleteLayout):
scrollDeleteLayout.setOnRightShowListener(new ScrollDeleteLayout.OnRightShowListener() {
@Override
public void onRightShow(ScrollDeleteLayout sdl) {
//展开当前的时候,复原上一个展开的,如果是同一个的话就不复原了
if(preSdl != null && preSdl != sdl){
preSdl.reset(true);
}
preSdl = sdl;
}
});
加上回调之后的效果如下:
案例源码
其中更多的细节可以看下案例源码:https://gitee.com/itfitness/scroll-delete-layout