您的位置:首页 > 其它

ListView的item水平滑动(类QQ的左滑显示删除按钮)

2015-03-19 18:51 316 查看
QQ的一个聊天界面的listview每一行向左滑动的时候,会出现删除的按钮,特别炫酷,这个效果可以有,今天跟大家分享下。

先上demo的效果图





界面很丑,因为主要是介绍功能,界面什么的,搞那么复杂,下demo的时候还浪费资源,哈哈哈。

用到的几个类(4个)

SwipeItemLayout,SwipeListView,SwipeAdapter,FragmentTestActivity.

SwipeItemLayout就是listView的一个item,这个类集成了FrameLayout。SwipeListView是重写的一个ListView,其实主要在她的OnTouch事件的处理上。SwipeAdapter是一个adapter,这个不用解释了,FragmenTestActivity这个就是怎么用的了。

好,一个一个来

首先我们看一个item怎么写,先上代码,代码里面基本上有逐行的解释。

public class SwipeItemLayout extends FrameLayout {
//这个是内容的item,也就是不左滑的时候的布局
private View contentView = null;
//这个是左滑之后显示的那个部分,即多出的部分
private View menuView = null;
//这个是动画的速度控制器,其实没用到
private Interpolator closeInterpolator = null;
private Interpolator openInterpolator = null;
//控制控件滑动的,会平滑滑动,一个开一个关
private ScrollerCompat mOpenScroller;
private ScrollerCompat mCloseScroller;
//左滑之后,contentView左边距离屏幕左边的距离,基线,用于滑回
private int mBaseX;
//手指点击的初始位置
private int mDownX;
//当前item的状态,open和close两种
private int state = STATE_CLOSE;

private static final int STATE_CLOSE = 0;
private static final int STATE_OPEN = 1;
//构造函数
public SwipeItemLayout(View contentView,View menuView,Interpolator closeInterpolator, Interpolator openInterpolator){
super(contentView.getContext());
this.contentView = contentView;
this.menuView = menuView;
this.closeInterpolator = closeInterpolator;
this.openInterpolator = openInterpolator;

init();
}

private void init(){
//设置一个item的宽和高,其实就是设置宽充满而已
setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
//初始化mColoseScroller和mOpenScroller
if (closeInterpolator != null) {
mCloseScroller = ScrollerCompat.create(getContext(),
closeInterpolator);
} else {
mCloseScroller = ScrollerCompat.create(getContext());
}
if (openInterpolator != null) {
mOpenScroller = ScrollerCompat.create(getContext(),
openInterpolator);
} else {
mOpenScroller = ScrollerCompat.create(getContext());
}
//这也是设置宽和高
LayoutParams contentParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
contentView.setLayoutParams(contentParams);

menuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
//将这两个布局都add到这个view中
addView(contentView);
addView(menuView);
}
//这个类就是当用户在界面上滑动的时候,通过ListView的onTouch方法,将MotionEvent的动作传到这里来,通过这个函数执行操作。
public boolean onSwipe(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录当前手指点击的x的坐标
mDownX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
//当手指移动的时候,获取这个差值
int dis = (int) (mDownX - event.getX());
//这个地方,当状态是open的时候,为啥要执行 += 这个操作,我们在下面就会找到答案的
if (state == STATE_OPEN) {
dis += menuView.getWidth();
}
//这个函数在下面说
swipe(dis);
break;
case MotionEvent.ACTION_UP:
//这里其实是一个判断,当用户滑了menuView的一半的时候,自动滑出来,否则滑进去。
if ((mDownX - event.getX()) > (menuView.getWidth() / 2)) {
// 平滑的滑出
smoothOpenMenu();
} else {
// 平滑的滑进
smoothCloseMenu();
return false;
}
break;
}
//这个地方一定要return true,才能保证这个动作不会继续往下传递
return true;
}
// 判断是否滑出的状态
public boolean isOpen() {
return state == STATE_OPEN;
}
//这个方法就是滑动dis的距离,还记得那个 += 吗,如果dis > menuView.getWidth()的 话,dis = menuView.getWidth().这样,当滑到最大限度的时候,就不会滑动了
private void swipe(int dis) {
if (dis > menuView.getWidth()) {
dis = menuView.getWidth();
}
if (dis < 0) {
dis = 0;
}
// layout的四个参数分别是(l,t,r,b),这样实现contentView的移动,这个应该没问题的吧?
contentView.layout(-dis, contentView.getTop(),
contentView.getWidth() - dis, getMeasuredHeight());
// 这个跟上面方法一样
menuView.layout(contentView.getWidth() - dis, menuView.getTop(),
contentView.getWidth() + menuView.getWidth() - dis,
menuView.getBottom());
}
//这个方法是系统的方法,就是执行一个刷新而已
@Override
public void computeScroll() {
if (state == STATE_OPEN) {
if (mOpenScroller.computeScrollOffset()) {
swipe(mOpenScroller.getCurrX());
postInvalidate();
}
} else {
if (mCloseScroller.computeScrollOffset()) {
swipe(mBaseX - mCloseScroller.getCurrX());
postInvalidate();
}
}
}
// 额,这个不用解释了
public void smoothCloseMenu() {
state = STATE_CLOSE;
mBaseX = -contentView.getLeft();
mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);
postInvalidate();
}
// 额 这个也不用解释了
public void smoothOpenMenu() {
state = STATE_OPEN;
mOpenScroller.startScroll(-contentView.getLeft(), 0,
menuView.getWidth(), 0, 350);
postInvalidate();
}
// 这个也懒得解释了
public void closeMenu() {
if (mCloseScroller.computeScrollOffset()) {
mCloseScroller.abortAnimation();
}
if (state == STATE_OPEN) {
state = STATE_CLOSE;
swipe(0);
}
}
// 各位码大大最棒了
public void openMenu() {
if (state == STATE_CLOSE) {
state = STATE_OPEN;
swipe(menuView.getWidth());
}
}

public View getContentView() {
return contentView;
}

public View getMenuView() {
return menuView;
}
//这个方法 其实就是获取menuView的宽和高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
menuView.measure(MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY));
}
//这个方法就把两个控件的相对布局表现出来了
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
contentView.layout(0, 0, getMeasuredWidth(),
contentView.getMeasuredHeight());
menuView.layout(getMeasuredWidth(), 0,
getMeasuredWidth() + menuView.getMeasuredWidth(),
contentView.getMeasuredHeight());
}
}


接下来就是另一个重要的类了,那就是ListView到底怎样,之后我还是把源码放上去,绝对不要积分,有需要的可以自己下了看看。这里看下核心的代码(主要原因是加班时间快要到了,再不回去的话就出不去了,哈哈哈)

这是变量,额,写代码的时候没写注释,我怕你们看不懂额,就在这里写算了

//表示没有触摸的时候
private static final int TOUCH_STATE_NONE = 0;
// 水平滑动的时候哦
private static final int TOUCH_STATE_X = 1;
// 垂直滑动的时候
private static final int TOUCH_STATE_Y = 2;
//这是设置的两个方向的阀值
private int MAX_Y = 5;
private int MAX_X = 3;
// 记录初始时候的坐标
private float mDownX;
//状态标志符
private int mTouchState;
// 触摸的位置
private int mTouchPosition;
private SwipeItemLayout mTouchView;
//private OnSwipeListener mOnSwipeListener;

private Interpolator mCloseInterpolator;
private Interpolator mOpenInterpolator;


说完了变量之后,核心的就两个方法

@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
return super.onTouchEvent(ev);
int action = MotionEventCompat.getActionMasked(ev);
action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
int oldPos = mTouchPosition;
mDownX = ev.getX();
mDownY = ev.getY();
mTouchState = TOUCH_STATE_NONE;
//这个方法就是获取当前的x,y坐标对应的是listView中的哪个position,是系统方法。
mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());

if (mTouchPosition == oldPos && mTouchView != null
&& mTouchView.isOpen()) {
mTouchState = TOUCH_STATE_X;
mTouchView.onSwipe(ev);
return true;
}
//这个方法获取当前的item的View,也是系统的方法
View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

if (mTouchView != null && mTouchView.isOpen()) {
mTouchView.smoothCloseMenu();
mTouchView = null;
return super.onTouchEvent(ev);
}
if (view instanceof SwipeItemLayout) {
mTouchView = (SwipeItemLayout) view;
}
if (mTouchView != null) {
mTouchView.onSwipe(ev);
}
break;
case MotionEvent.ACTION_MOVE:
float dy = Math.abs((ev.getY() - mDownY));
float dx = Math.abs((ev.getX() - mDownX));
if (mTouchState == TOUCH_STATE_X) {
if (mTouchView != null) {
mTouchView.onSwipe(ev);
}
getSelector().setState(new int[] { 0 });
ev.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(ev);
return true;
} else if (mTouchState == TOUCH_STATE_NONE) {
if (Math.abs(dy) > MAX_Y) {
mTouchState = TOUCH_STATE_Y;
} else if (dx > MAX_X) {
mTouchState = TOUCH_STATE_X;
//                  if (mOnSwipeListener != null) {
//                      mOnSwipeListener.onSwipeStart(mTouchPosition);
//                  }
}
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_X) {
if (mTouchView != null) {
mTouchView.onSwipe(ev);
if (!mTouchView.isOpen()) {
mTouchPosition = -1;
mTouchView = null;
}
}
//              if (mOnSwipeListener != null) {
//                  mOnSwipeListener.onSwipeEnd(mTouchPosition);
//              }
ev.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(ev);
return true;
}
break;
}
return super.onTouchEvent(ev);
}

public void smoothOpenMenu(int position) {
if (position >= getFirstVisiblePosition()
&& position <= getLastVisiblePosition()) {
View view = getChildAt(position - getFirstVisiblePosition());
if (view instanceof SwipeItemLayout) {
mTouchPosition = position;
if (mTouchView != null && mTouchView.isOpen()) {
mTouchView.smoothCloseMenu();
}
mTouchView = (SwipeItemLayout) view;
mTouchView.smoothOpenMenu();
}
}
}


唉,实在是很麻烦,其实代码不难,就是很繁琐,各种控制,各种判断,大家把这段代码中的核心的几个方法搞明白了,也就没问题了。我就不多说了哈。。。

好了,剩下的两个类,一个是adapter类,一个是用法,我就只说adapter中的一个getView方法了哈,用法的话跟一般的ListView的用法一样,对了,说adapter不就是再说用法么,哈哈哈。

@Override
public View getView(int position, View contentView, ViewGroup arg2) {
ViewHolder holder = null;
if(contentView==null){
holder = new ViewHolder();
View view01 = LayoutInflater.from(mContext).inflate(R.layout.test01, null);
View view02 = LayoutInflater.from(mContext).inflate(R.layout.test2, null);

//这个地方就用到了我们自己写的那个类了,后面两个参数我上面已经说了,没用到,用到的话也可以,自己改下代码就好了。其他的没什么区别吧
contentView = new SwipeItemLayout(view01, view02, null, null);
contentView.setTag(holder);
}else{
holder = (ViewHolder) contentView.getTag();
}
//这个地方如果你的menu里面有button什么的,就可以在这个地方注册监听,或者,你直接将view02(上面声明的)自定义也行,在自定义的类中实现onClickListener 方法
//      holder.btn.setOnClickListener(new OnClickListener() {
//
//          @Override
//          public void onClick(View arg0) {
//              // TODO Auto-generated method stub
//              Toast.makeText(mContext, "click", Toast.LENGTH_LONG).show();
//          }
//      });
return contentView;
}


是不是看完整个过程有点点晕,其实很简单的,你下了源码,自己去看就好了,真的简单,各位大神不要见怪.

demo下载链接(免费)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: