android listview 滑动动画效果,让你的ListView更炫酷,实现侧滑删除效果

又到了更新博客的时间了,今天给大家带来的是ListView侧滑出现删除等按钮的效果。相信大家在平时玩app的时候都接触过这种效果吧。比如说QQ聊天列表侧滑就会出现“置顶”、“标为已读”、“删除”等按钮。这篇博文将用ViewDragHelper这个神器来实现侧滑效果。友情链接一下之前写的博文使用ViewDragHelper来实现侧滑菜单的,点击此处跳转。如果你对ViewDragHelper不熟悉,你可以去看看鸿洋_的《Android ViewDragHelper完全解析 自定义ViewGroup神器》。

好了,话说的那么多,先来看看我们实现的效果图吧:

d738bfcac7c7?appinstall=0

侧滑ListView效果图.gif

可以看出来,我们实现的和QQ的效果相差无几。下面就是源码时间了。

先来看一下ListView的item的slip_item_layout.xml:

android:id="@+id/sll_main"

android:layout_width="match_parent"

android:layout_height="wrap_content" >

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:layout_gravity="center_vertical"

android:orientation="horizontal" >

android:id="@+id/tv_top"

android:layout_width="80dp"

android:layout_height="match_parent"

android:background="#66ff0000"

android:gravity="center"

android:text="置顶" />

android:id="@+id/tv_delete"

android:layout_width="80dp"

android:layout_height="match_parent"

android:background="#330000ff"

android:gravity="center"

android:text="删除" />

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_gravity="center_vertical"

android:background="#66ffffff"

android:orientation="horizontal" >

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:layout_margin="10dp"

android:src="@drawable/head_1" />

android:id="@+id/tv_name"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:layout_marginLeft="10dp"

android:gravity="center_vertical"

android:text="hello" />

我们可以看出,要先把侧滑出的按钮布局放在SwipeListLayout的第一层,而item的布局放在第二层。还有一点要注意的是,侧滑出的按钮如果有两个或两个以上,那么必须用ViewGroup作为父布局。要整体保持SwipeListLayout的直接子View为2个。

而activity的布局文件里就是一个ListView,这里就不再给出了。

下面我们直接来看看SwipeListLayout的内容:

public SwipeListLayout(Context context) {

this(context, null);

}

public SwipeListLayout(Context context, AttributeSet attrs) {

super(context, attrs);

mDragHelper = ViewDragHelper.create(this, callback);

}

// ViewDragHelper的回调

Callback callback = new Callback() {

@Override

public boolean tryCaptureView(View view, int arg1) {

return view == itemView;

}

@Override

public int clampViewPositionHorizontal(View child, int left, int dx) {

if (child == itemView) {

if (left > 0) {

return 0;

} else {

left = Math.max(left, -hiddenViewWidth);

return left;

}

}

return 0;

}

@Override

public int getViewHorizontalDragRange(View child) {

return hiddenViewWidth;

}

@Override

public void onViewPositionChanged(View changedView, int left, int top,

int dx, int dy) {

if (itemView == changedView) {

hiddenView.offsetLeftAndRight(dx);

}

// 有时候滑动很快的话 会出现隐藏按钮的linearlayout没有绘制的问题

// 为了确保绘制成功 调用 invalidate

invalidate();

}

public void onViewReleased(View releasedChild, float xvel, float yvel) {

// 向右滑xvel为正 向左滑xvel为负

if (releasedChild == itemView) {

if (xvel == 0

&& Math.abs(itemView.getLeft()) > hiddenViewWidth / 2.0f) {

open(smooth);

} else if (xvel < 0) {

open(smooth);

} else {

close(smooth);

}

}

}

};

我们主要来看callback,首先在tryCaptureView(View view, int arg1)里设置了只有是itemView的时候才能被捕获,也就是说当你去滑动“删除”、“置顶”等按钮的时候,侧滑按钮是不会被关闭的,因为根本就没捕获。(当然你也可以设置都捕获,那样的话下面的逻辑要调整了),剩余的几个函数中的逻辑较为简单,在onView Released(View releasedChild, float xvel, float yvel)也是判断了当手指抬起时itemView所处的位置。如果向左滑或者停止滑动时按钮已经显示出1/2的宽度,则打开;其余情况下都将关闭按钮。

以下分别是close()和open()的方法:

/**

* 侧滑关闭

*

* @param smooth

* 为true则有平滑的过渡动画

*/

private void close(boolean smooth) {

preStatus = status;

status = Status.Close;

if (smooth) {

if (mDragHelper.smoothSlideViewTo(itemView, 0, 0)) {

if (listener != null) {

Log.i(TAG, "start close animation");

listener.onStartCloseAnimation();

}

ViewCompat.postInvalidateOnAnimation(this);

}

} else {

layout(status);

}

if (listener != null && preStatus == Status.Open) {

Log.i(TAG, "close");

listener.onStatusChanged(status);

}

}

/**

* 侧滑打开

*

* @param smooth

* 为true则有平滑的过渡动画

*/

private void open(boolean smooth) {

preStatus = status;

status = Status.Open;

if (smooth) {

if (mDragHelper.smoothSlideViewTo(itemView, -hiddenViewWidth, 0)) {

if (listener != null) {

Log.i(TAG, "start open animation");

listener.onStartOpenAnimation();

}

ViewCompat.postInvalidateOnAnimation(this);

}

} else {

layout(status);

}

if (listener != null && preStatus == Status.Close) {

Log.i(TAG, "open");

listener.onStatusChanged(status);

}

}

SwipeListLayout大致的代码就这些,相信对于熟悉ViewDragHelper的同学们来说应该是不成问题的。其实整体的逻辑和之前用ViewDragHelper来实现侧滑菜单大同小异。

顺便下面贴出SwipeListLayout的全部代码:

/**

* 侧滑Layout

*/

public class SwipeListLayout extends FrameLayout {

private View hiddenView;

private View itemView;

private int hiddenViewWidth;

private ViewDragHelper mDragHelper;

private int hiddenViewHeight;

private int itemWidth;

private int itemHeight;

private OnSwipeStatusListener listener;

private Status status = Status.Close;

private boolean smooth = true;

public static final String TAG = "SlipListLayout";

// 状态

public enum Status {

Open, Close

}

/**

* 设置侧滑状态

*

* @param status

* 状态 Open or Close

* @param smooth

* 若为true则有过渡动画,否则没有

*/

public void setStatus(Status status, boolean smooth) {

this.status = status;

if (status == Status.Open) {

open(smooth);

} else {

close(smooth);

}

}

public void setOnSwipeStatusListener(OnSwipeStatusListener listener) {

this.listener = listener;

}

/**

* 是否设置过渡动画

*

* @param smooth

*/

public void setSmooth(boolean smooth) {

this.smooth = smooth;

}

public interface OnSwipeStatusListener {

/**

* 当状态改变时回调

*

* @param status

*/

void onStatusChanged(Status status);

/**

* 开始执行Open动画

*/

void onStartCloseAnimation();

/**

* 开始执行Close动画

*/

void onStartOpenAnimation();

}

public SwipeListLayout(Context context) {

this(context, null);

}

public SwipeListLayout(Context context, AttributeSet attrs) {

super(context, attrs);

mDragHelper = ViewDragHelper.create(this, callback);

}

// ViewDragHelper的回调

Callback callback = new Callback() {

@Override

public boolean tryCaptureView(View view, int arg1) {

return view == itemView;

}

@Override

public int clampViewPositionHorizontal(View child, int left, int dx) {

if (child == itemView) {

if (left > 0) {

return 0;

} else {

left = Math.max(left, -hiddenViewWidth);

return left;

}

}

return 0;

}

@Override

public int getViewHorizontalDragRange(View child) {

return hiddenViewWidth;

}

@Override

public void onViewPositionChanged(View changedView, int left, int top,

int dx, int dy) {

if (itemView == changedView) {

hiddenView.offsetLeftAndRight(dx);

}

// 有时候滑动很快的话 会出现隐藏按钮的linearlayout没有绘制的问题

// 为了确保绘制成功 调用 invalidate

invalidate();

}

public void onViewReleased(View releasedChild, float xvel, float yvel) {

// 向右滑xvel为正 向左滑xvel为负

if (releasedChild == itemView) {

if (xvel == 0

&& Math.abs(itemView.getLeft()) > hiddenViewWidth / 2.0f) {

open(smooth);

} else if (xvel < 0) {

open(smooth);

} else {

close(smooth);

}

}

}

};

private Status preStatus = Status.Close;

/**

* 侧滑关闭

*

* @param smooth

* 为true则有平滑的过渡动画

*/

private void close(boolean smooth) {

preStatus = status;

status = Status.Close;

if (smooth) {

if (mDragHelper.smoothSlideViewTo(itemView, 0, 0)) {

if (listener != null) {

Log.i(TAG, "start close animation");

listener.onStartCloseAnimation();

}

ViewCompat.postInvalidateOnAnimation(this);

}

} else {

layout(status);

}

if (listener != null && preStatus == Status.Open) {

Log.i(TAG, "close");

listener.onStatusChanged(status);

}

}

/**

*

* @param status

*/

private void layout(Status status) {

if (status == Status.Close) {

hiddenView.layout(itemWidth, 0, itemWidth + hiddenViewWidth,

itemHeight);

itemView.layout(0, 0, itemWidth, itemHeight);

} else {

hiddenView.layout(itemWidth - hiddenViewWidth, 0, itemWidth,

itemHeight);

itemView.layout(-hiddenViewWidth, 0, itemWidth - hiddenViewWidth,

itemHeight);

}

}

/**

* 侧滑打开

*

* @param smooth

* 为true则有平滑的过渡动画

*/

private void open(boolean smooth) {

preStatus = status;

status = Status.Open;

if (smooth) {

if (mDragHelper.smoothSlideViewTo(itemView, -hiddenViewWidth, 0)) {

if (listener != null) {

Log.i(TAG, "start open animation");

listener.onStartOpenAnimation();

}

ViewCompat.postInvalidateOnAnimation(this);

}

} else {

layout(status);

}

if (listener != null && preStatus == Status.Close) {

Log.i(TAG, "open");

listener.onStatusChanged(status);

}

}

@Override

public void computeScroll() {

super.computeScroll();

// 开始执行动画

if (mDragHelper.continueSettling(true)) {

ViewCompat.postInvalidateOnAnimation(this);

}

}

// 让ViewDragHelper来处理触摸事件

public boolean onInterceptTouchEvent(MotionEvent ev) {

final int action = ev.getAction();

if (action == MotionEvent.ACTION_CANCEL) {

mDragHelper.cancel();

return false;

}

return mDragHelper.shouldInterceptTouchEvent(ev);

}

// 让ViewDragHelper来处理触摸事件

public boolean onTouchEvent(MotionEvent event) {

mDragHelper.processTouchEvent(event);

return true;

};

@Override

protected void onFinishInflate() {

super.onFinishInflate();

hiddenView = getChildAt(0); // 得到隐藏按钮的linearlayout

itemView = getChildAt(1); // 得到最上层的linearlayout

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

// 测量子View的长和宽

itemWidth = itemView.getMeasuredWidth();

itemHeight = itemView.getMeasuredHeight();

hiddenViewWidth = hiddenView.getMeasuredWidth();

hiddenViewHeight = hiddenView.getMeasuredHeight();

}

@Override

protected void onLayout(boolean changed, int left, int top, int right,

int bottom) {

layout(Status.Close);

}

}

最后,提供SwipeListLayout的源码下载:

GitHub:

~have fun!~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值