Android自定义组合控件---教你如何自定义下拉刷新和左滑删除

绪论

最近项目里面用到了下拉刷新和左滑删除,网上找了找并没有可以用的,有比较好的左滑删除,但是并没有和下拉刷新上拉加载结合到一起,要不就是一些比较水的结合,并不能在项目里面使用,小编一着急自己组合了一个,做完了和QQ的对比了一下,并没有太大区别,今天分享给大家,其实并不难,但是不知道为什么网上没有比较好的Demo,当你的项目真的很急的时候,又没有比较好的Demo,那么“那条友谊的小船儿真是说翻就翻啊”,好了,下面先来具体看一下实现后的效果吧:

代码已经上传到Github上了,小伙伴们记得star和follow哦
https://github.com/Hankkin/SwipeRefreshDemo

这里写图片描述

还不错吧?比QQ多了个上拉加载,好了看看怎么实现的吧,小编在之前的游客评论中了解到,代码注释很重要,所以尽量把注释写的很清楚:

实现思路

由于时间有限,左滑菜单是在网上找的Demo

  1. 自定义下拉刷新头、尾
  2. 手势判断,根据滑动距离显示头部下拉布局
  3. 判断是否滑动到底部显示尾部上拉布局
  4. 创建左滑菜单,根据手势滑动事件弹出菜单

详细的看一下实现过程
1.首先我们先自定义下拉头布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="bottom" >
<RelativeLayout
android:id="@+id/xlistview_header_content"
android:layout_width="fill_parent"
android:background="#00000000"
android:layout_height="60dp" >
<LinearLayout
android:id="@+id/xlistview_header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:id="@+id/xlistview_header_hint_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000"
android:textSize="16sp"
android:text="@string/xlistview_header_hint_normal" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000"
android:text="@string/xlistview_header_last_time"
android:textSize="14sp" />
<TextView
android:id="@+id/xlistview_header_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/xlistview_header_arrow"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_alignLeft="@id/xlistview_header_text"
android:layout_centerVertical="true"
android:layout_marginLeft="-50dp"
android:src="@drawable/xlistview_arrow" />
<ProgressBar
android:id="@+id/xlistview_header_progressbar"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_alignLeft="@id/xlistview_header_text"
android:layout_centerVertical="true"
android:layout_marginLeft="-55dp"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>

2.接下来我们自定义HeaderView,代码很详细了,不多介绍了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.hankkin.library;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
public class RefreshListHeader extends LinearLayout {
//根布局
private LinearLayout mContainer;
//下拉箭头图片
private ImageView mArrowImageView;
//下拉进度条
private ProgressBar mProgressBar;
//下拉文本
private TextView mHintTextView;
//状态值 0-正常 1-准备刷新 2-正在刷新
private int mState = STATE_NORMAL;
//抬起动画
private Animation mRotateUpAnim;
//下拉动画
private Animation mRotateDownAnim;
//下拉动画时间
private final int ROTATE_ANIM_DURATION = 180;
public final static int STATE_NORMAL = 0;
public final static int STATE_READY = 1;
public final static int STATE_REFRESHING = 2;
public RefreshListHeader(Context context) {
super(context);
initView(context);
}
/**
* @param context
* @param attrs
*/
public RefreshListHeader(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
/**
* 初始化组件
* @param context
*/
private void initView(Context context) {
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, 0);
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_header, null);
addView(mContainer, lp);
setGravity(Gravity.BOTTOM);
mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow);
mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview);
mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar);
//设置抬起动画
mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
mRotateUpAnim.setFillAfter(true);
//设置下拉动画
mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
mRotateDownAnim.setFillAfter(true);
}
//设置状态
public void setState(int state) {
if (state == mState)
return;
//正在刷新时-显示进度条,隐藏下拉箭头
if (state == STATE_REFRESHING) {
mArrowImageView.clearAnimation();
mArrowImageView.setVisibility(View.INVISIBLE);
mProgressBar.setVisibility(View.VISIBLE);
} else {
//显示下拉箭头,隐藏进度条
mArrowImageView.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.INVISIBLE);
}
switch (state) {
case STATE_NORMAL:
if (mState == STATE_READY) {//准备刷新时开启动画
mArrowImageView.startAnimation(mRotateDownAnim);
}
if (mState == STATE_REFRESHING) {//正在刷新时开启动画
mArrowImageView.clearAnimation();
}
mHintTextView.setText(R.string.xlistview_header_hint_normal);
break;
case STATE_READY:
if (mState != STATE_READY) {
mArrowImageView.clearAnimation();
mArrowImageView.startAnimation(mRotateUpAnim);
mHintTextView.setText(R.string.xlistview_header_hint_ready);
}
break;
case STATE_REFRESHING:
mHintTextView.setText(R.string.xlistview_header_hint_loading);
break;
default:
}
mState = state;
}
/**
* 设置头部高度
* @param height
*/
public void setVisiableHeight(int height) {
if (height < 0)
height = 0;
LayoutParams lp = (LayoutParams) mContainer.getLayoutParams();
lp.height = height;
mContainer.setLayoutParams(lp);
}
public int getVisiableHeight() {
return mContainer.getHeight();
}
}

3.HeaderView定义结束后,我们需要自定义一个既支持下拉刷新又支持左滑删除的ListView,看看我是怎么做的:(左滑菜单是引用网上的Demo,代码写的也比较易懂,这里不详细给大家介绍了)
然后我们在他的基础上添加下拉上拉事件:(重点看一下onTouchEvent事件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLastY == -1) { //获取上次y轴坐标
mLastY = ev.getRawY();
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: //手势按下事件、获取坐标、设置上次下拉时间
firstTouchY = ev.getRawY();
mLastY = ev.getRawY();
setRefreshTime(RefreshTime.getRefreshTime(getContext()));
int oldPos = mTouchPosition;
mDownX = ev.getX();
mDownY = ev.getY();
mTouchState = TOUCH_STATE_NONE;
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,此方法是因为getChildAt()传入index值导致listview不可见的item会报空指针
// 防止listview不可见的item获取到的为空,使用下面方法
View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
if (mTouchView != null && mTouchView.isOpen()) {//如果滑动的item不为空并且已经开启,则关闭该菜单
mTouchView.smoothCloseMenu();
mTouchView = null;
return super.onTouchEvent(ev);
}
if (mTouchView != null) {//否则打开左滑菜单
mTouchView.onSwipe(ev);
}
if (view instanceof SwipeMenuLayout) {
mTouchView = (SwipeMenuLayout) view;
}
break;
case MotionEvent.ACTION_MOVE://手势滑动事件
final float deltaY = ev.getRawY() - mLastY;
float dy = Math.abs((ev.getY() - mDownY));
float dx = Math.abs((ev.getX() - mDownX));
mLastY = ev.getRawY();
//判断左滑菜单是否未激活、或者x轴偏移平方小于y轴偏移平方3倍的时候
if ((mTouchView == null || !mTouchView.isActive()) && Math.pow(dx, 2) / Math.pow(dy, 2) <= 3) {
//判断第一个可见位置并且头部布局可见高度大于0时或者y轴偏移量>0
if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {
// 重新更新头部高度
updateHeaderHeight(deltaY / OFFSET_RADIO);
invokeOnScrolling();
}
}
if (mTouchState == TOUCH_STATE_X) {//如果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) { //如果y轴偏移量>指定y轴偏移量,设置y轴偏移状态
mTouchState = TOUCH_STATE_Y;
} else if (dx > MAX_X) {//如果x轴偏移量>指定x轴偏移量,设置x轴偏移状态,开始弹出左滑菜单
mTouchState = TOUCH_STATE_X;
if (mOnSwipeListener != null) {
mOnSwipeListener.onSwipeStart(mTouchPosition);
}
}
}
break;
case MotionEvent.ACTION_UP://手势抬起事件
mLastY = -1; // reset
if (getFirstVisiblePosition() == 0) {
// 设置下拉刷新状态值,开启下拉刷新状态
if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
mPullRefreshing = true;
mHeaderView.setState(RefreshListHeader.STATE_REFRESHING);
if (onRefreshListener != null) {
tag=REFRESH;
onRefreshListener.onRefresh();
}
}
resetHeaderHeight();
}
lastTouchY = ev.getRawY();//获取上次y轴偏移量
if (canLoadMore()) {//判断是否满足上拉
loadData();
}
if (mTouchState == TOUCH_STATE_X) {//如果为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);
}

当然这部分也是滑动冲突解决的主要部分,看一下完整的代码,应该能够看的懂了,小编几乎每一句都加上了注释,如果有看不明白的地方可以留言,小编会及时回复的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
package com.hankkin.library;
import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import android.widget.TextView;
import java.util.Date;
public class RefreshSwipeMenuListView extends ListView implements OnScrollListener {
private static final int TOUCH_STATE_NONE = 0;
private static final int TOUCH_STATE_X = 1; //x轴触摸状态值
private static final int TOUCH_STATE_Y = 2; //y轴触摸状态值
public static final int BOTH=2;//上拉和下拉
public static final int HEADER=0;//下拉
public static final int FOOTER=1;//上拉
public static String tag;//ListView的动作
public static final String REFRESH="refresh";
public static final String LOAD="load";
private int MAX_Y = 5; //Y轴最大偏移量
private int MAX_X = 3; //X轴最大偏移量
private float mDownX; //触摸x
private float mDownY; //触摸y
private int mTouchState; //触摸状态
private int mTouchPosition; //触摸位置
private SwipeMenuLayout mTouchView; //滑动弹出布局
private OnSwipeListener mOnSwipeListener; //弹出监听器
private float firstTouchY; //第一次触摸y坐标
private float lastTouchY; //最后一次触摸y坐标
//创建左滑菜单接口
private SwipeMenuCreator mMenuCreator;
//菜单点击事件
private OnMenuItemClickListener mOnMenuItemClickListener;
//关闭菜单动画修饰Interpolator
private Interpolator mCloseInterpolator;
//开启菜单动画修饰Interpolator
private Interpolator mOpenInterpolator;
private float mLastY = -1;
private Scroller mScroller;
private OnScrollListener mScrollListener; // 滑动监听
// 下拉上拉监听器
private OnRefreshListener onRefreshListener;
//下拉头
private RefreshListHeader mHeaderView;
//头部视图内容,用来计算头部高度,不下拉时隐藏
private RelativeLayout mHeaderViewContent;
//下拉时间文本控件
private TextView mHeaderTimeView;
private int mHeaderViewHeight; // 头部高度
private boolean mEnablePullRefresh = true;//能否下拉刷新
private boolean mPullRefreshing = false; // 是否正在刷新
//上拉尾部视图
private LinearLayout mFooterView;
private boolean mEnablePullLoad;//是否可以上拉加载
private boolean mPullLoading; //是否正在上拉
private boolean mIsFooterReady = false;
private int mTotalItemCount;
private int mScrollBack;
private final static int SCROLLBACK_HEADER = 0;
private final static int SCROLLBACK_FOOTER = 1;
private final static int SCROLL_DURATION = 400;
private final static int PULL_LOAD_MORE_DELTA = 50;
private final static float OFFSET_RADIO = 1.8f;
private boolean isFooterVisible=false;
public RefreshSwipeMenuListView(Context context) {
super(context);
init(context);
}
public RefreshSwipeMenuListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public RefreshSwipeMenuListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 初始化组件
* @param context
*/
private void init(Context context) {
mScroller = new Scroller(context, new DecelerateInterpolator());
super.setOnScrollListener(this);
// 初始化头部视图
mHeaderView = new RefreshListHeader(context);
mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.xlistview_header_content);
mHeaderTimeView = (TextView) mHeaderView.findViewById(R.id.xlistview_header_time);
addHeaderView(mHeaderView);
// 初始化尾部视图
mFooterView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_footer, null, false);
// 初始化头部高度
mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mHeaderViewHeight = mHeaderViewContent.getHeight();
//向 ViewTreeObserver 注册方法,以获取控件尺寸
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
MAX_X = dp2px(MAX_X);
MAX_Y = dp2px(MAX_Y);
mTouchState = TOUCH_STATE_NONE;
}
/**
* 添加适配器
* @param adapter
*/
@Override
public void setAdapter(ListAdapter adapter) {
if (mIsFooterReady == false) { //添加尾部隐藏
mIsFooterReady = true;
addFooterView(mFooterView);
mFooterView.setVisibility(GONE);
}
super.setAdapter(new SwipeMenuAdapter(getContext(), adapter) {
@Override
public void createMenu(SwipeMenu menu) {//创建左滑菜单
if (mMenuCreator != null) {
mMenuCreator.create(menu);
}
}
@Override
public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) {
if (mOnMenuItemClickListener != null) {//左滑菜单点击事件
mOnMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index);
}
if (mTouchView != null) {
mTouchView.smoothCloseMenu();
}
}
});
}
public void setCloseInterpolator(Interpolator interpolator) {
mCloseInterpolator = interpolator;
}
public void setOpenInterpolator(Interpolator interpolator) {
mOpenInterpolator = interpolator;
}
public Interpolator getOpenInterpolator() {
return mOpenInterpolator;
}
public Interpolator getCloseInterpolator() {
return mCloseInterpolator;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLastY == -1) { //获取上次y轴坐标
mLastY = ev.getRawY();
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: //手势按下事件、获取坐标、设置上次下拉时间
firstTouchY = ev.getRawY();
mLastY = ev.getRawY();
setRefreshTime(RefreshTime.getRefreshTime(getContext()));
int oldPos = mTouchPosition;
mDownX = ev.getX();
mDownY = ev.getY();
mTouchState = TOUCH_STATE_NONE;
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,此方法是因为getChildAt()传入index值导致listview不可见的item会报空指针
// 防止listview不可见的item获取到的为空,使用下面方法
View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
if (mTouchView != null && mTouchView.isOpen()) {//如果滑动的item不为空并且已经开启,则关闭该菜单
mTouchView.smoothCloseMenu();
mTouchView = null;
return super.onTouchEvent(ev);
}
if (mTouchView != null) {//否则打开左滑菜单
mTouchView.onSwipe(ev);
}
if (view instanceof SwipeMenuLayout) {
mTouchView = (SwipeMenuLayout) view;
}
break;
case MotionEvent.ACTION_MOVE://手势滑动事件
final float deltaY = ev.getRawY() - mLastY;
float dy = Math.abs((ev.getY() - mDownY));
float dx = Math.abs((ev.getX() - mDownX));
mLastY = ev.getRawY();
//判断左滑菜单是否未激活、或者x轴偏移平方小于y轴偏移平方3倍的时候
if ((mTouchView == null || !mTouchView.isActive()) && Math.pow(dx, 2) / Math.pow(dy, 2) <= 3) {
//判断第一个可见位置并且头部布局可见高度大于0时或者y轴偏移量>0
if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {
// 重新更新头部高度
updateHeaderHeight(deltaY / OFFSET_RADIO);
invokeOnScrolling();
}
}
if (mTouchState == TOUCH_STATE_X) {//如果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) { //如果y轴偏移量>指定y轴偏移量,设置y轴偏移状态
mTouchState = TOUCH_STATE_Y;
} else if (dx > MAX_X) {//如果x轴偏移量>指定x轴偏移量,设置x轴偏移状态,开始弹出左滑菜单
mTouchState = TOUCH_STATE_X;
if (mOnSwipeListener != null) {
mOnSwipeListener.onSwipeStart(mTouchPosition);
}
}
}
break;
case MotionEvent.ACTION_UP://手势抬起事件
mLastY = -1; // reset
if (getFirstVisiblePosition() == 0) {
// 设置下拉刷新状态值,开启下拉刷新状态
if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
mPullRefreshing = true;
mHeaderView.setState(RefreshListHeader.STATE_REFRESHING);
if (onRefreshListener != null) {
tag=REFRESH;
onRefreshListener.onRefresh();
}
}
resetHeaderHeight();
}
lastTouchY = ev.getRawY();//获取上次y轴偏移量
if (canLoadMore()) {//判断是否满足上拉
loadData();
}
if (mTouchState == TOUCH_STATE_X) {//如果为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);
}
class ResetHeaderHeightTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Void result) {
mPullRefreshing = false;
mHeaderView.setState(RefreshListHeader.STATE_NORMAL);
resetHeaderHeight();
}
}
public void smoothOpenMenu(int position) {
if (position >= getFirstVisiblePosition() && position <= getLastVisiblePosition()) {
View view = getChildAt(position - getFirstVisiblePosition());
if (view instanceof SwipeMenuLayout) {
mTouchPosition = position;
if (mTouchView != null && mTouchView.isOpen()) {
mTouchView.smoothCloseMenu();
}
mTouchView = (SwipeMenuLayout) view;
mTouchView.smoothOpenMenu();
}
}
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
getContext().getResources().getDisplayMetrics());
}
public void setMenuCreator(SwipeMenuCreator menuCreator) {
this.mMenuCreator = menuCreator;
}
public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
this.mOnMenuItemClickListener = onMenuItemClickListener;
}
public void setOnSwipeListener(OnSwipeListener onSwipeListener) {
this.mOnSwipeListener = onSwipeListener;
}
public static interface OnMenuItemClickListener {
void onMenuItemClick(int position, SwipeMenu menu, int index);
}
public static interface OnSwipeListener {
void onSwipeStart(int position);
void onSwipeEnd(int position);
}
/**
* 设置刷新可用
* @param enable
*/
private void setPullRefreshEnable(boolean enable) {
mEnablePullRefresh = enable;
if (!mEnablePullRefresh) { // disable, hide the content
mHeaderViewContent.setVisibility(View.INVISIBLE);
} else {
mHeaderViewContent.setVisibility(View.VISIBLE);
}
}
/**
* enable or disable pull up load more feature.
* 设置加载可用
* @param enable
*/
private void setPullLoadEnable(boolean enable) {
mEnablePullLoad = enable;
if (!mEnablePullLoad) {
mFooterView.setVisibility(GONE);
mFooterView.setOnClickListener(null);
} else {
mPullLoading = false;
mFooterView.setVisibility(VISIBLE);
// both "pull up" and "click" will invoke load more.
mFooterView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startLoadMore();
}
});
}
}
/**
* stop refresh, reset header view.
* 停止刷新,重置头部控件
*/
private void stopRefresh() {
if (mPullRefreshing == true) {
mPullRefreshing = false;
resetHeaderHeight();
}
}
/**
* stop load more, reset footer view.
* 停止加载更多,重置尾部控件
*/
private void stopLoadMore() {
if (mPullLoading == true) {
mPullLoading = false;
mFooterView.setVisibility(GONE);
}
}
/**
* set last refresh time
*
* @param time
*/
public void setRefreshTime(String time) {
mHeaderTimeView.setText(time);
}
private void invokeOnScrolling() {
if (mScrollListener instanceof OnXScrollListener) {
OnXScrollListener l = (OnXScrollListener) mScrollListener;
l.onXScrolling(this);
}
}
/**
* 更新头部高度,设置状态值
* @param delta
*/
private void updateHeaderHeight(float delta) {
mHeaderView.setVisiableHeight((int) delta + mHeaderView.getVisiableHeight());
if (mEnablePullRefresh && !mPullRefreshing) {
if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
mHeaderView.setState(RefreshListHeader.STATE_READY);
} else {
mHeaderView.setState(RefreshListHeader.STATE_NORMAL);
}
}
setSelection(0); // scroll to top each time
}
/**
* 重置头部视图高度
*/
private void resetHeaderHeight() {
int height = mHeaderView.getVisiableHeight();
if (height == 0) // 不可见
return;
// 如果正在刷新并且头部高度没有完全显示不做操作
if (mPullRefreshing && height <= mHeaderViewHeight) {
return;
}
int finalHeight = 0; // 默认
//如果正在刷新并且滑动高度大于头部高度
if (mPullRefreshing && height > mHeaderViewHeight) {
finalHeight = mHeaderViewHeight;
}
mScrollBack = SCROLLBACK_HEADER;
mScroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION);
// 触发computescroll
invalidate();
}
/**
* 开启上啦
*/
private void startLoadMore() {
mPullLoading = true;
mFooterView.setVisibility(VISIBLE);
if (onRefreshListener != null) {
tag=LOAD;
onRefreshListener.onLoadMore();
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
if (mScrollBack == SCROLLBACK_HEADER) {
mHeaderView.setVisiableHeight(mScroller.getCurrY());
}
postInvalidate();
invokeOnScrolling();
}
super.computeScroll();
}
@Override
public void setOnScrollListener(OnScrollListener l) {
mScrollListener = l;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(view, scrollState);
}
}
/**
* 滑动监听
* @param view
* @param firstVisibleItem
* @param visibleItemCount
* @param totalItemCount
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mTotalItemCount = totalItemCount;
if (mScrollListener != null) {
mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if(firstVisibleItem+visibleItemCount>=totalItemCount){
isFooterVisible=true;
}else{
isFooterVisible=false;
}
}
public void setOnRefreshListener(OnRefreshListener l) {
onRefreshListener = l;
}
/**
* you can listen ListView.OnScrollListener or this one. it will invoke
* onXScrolling when header/footer scroll back.
*/
public interface OnXScrollListener extends OnScrollListener {
public void onXScrolling(View view);
}
/**
* implements this interface to get refresh/load more event.
*/
public interface OnRefreshListener {
public void onRefresh();
public void onLoadMore();
}
/**
* 上拉加载和下拉刷新请求完毕
*/
public void complete(){
stopLoadMore();
stopRefresh();
if(REFRESH.equals(tag)){
RefreshTime.setRefreshTime(getContext(),new Date());
}
}
/**
* 设置ListView的模式,上拉和下拉
* @param mode
*/
public void setListViewMode(int mode){
if(mode==BOTH){
setPullRefreshEnable(true);
setPullLoadEnable(true);
}else if(mode==FOOTER){
setPullLoadEnable(true);
}else if(mode==HEADER){
setPullRefreshEnable(true);
}
}
/**
* 判断是否可以上蜡加载
* @return
*/
private boolean canLoadMore() {
return isBottom() && !mPullLoading && isPullingUp();
}
/**
* 判断是否到达底部
* @return
*/
private boolean isBottom() {
if (getCount() > 0) {
if (getLastVisiblePosition() == getAdapter().getCount() - 1 &&
getChildAt(getChildCount() - 1).getBottom() <= getHeight()) {
return true;
}
}
return false;
}
private boolean isPullingUp() {
return (firstTouchY - lastTouchY) >= 200;
}
private void loadData() {
if (onRefreshListener != null) {
setLoading(true);
}
}
/**
* 设置是否上拉
* @param loading
*/
public void setLoading(boolean loading) {
if (this == null) return;
mPullLoading = loading;
if (loading) {
mFooterView.setVisibility(VISIBLE);
setSelection(getAdapter().getCount() - 1);
onRefreshListener.onLoadMore();
} else {
mFooterView.setVisibility(GONE);
firstTouchY = 0;
lastTouchY = 0;
}
}
}

定义完了之后我们是这样用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
rsmLv.setAdapter(adapter);
rsmLv.setListViewMode(RefreshSwipeMenuListView.HEADER);
rsmLv.setOnRefreshListener(this);
SwipeMenuCreator creator = new SwipeMenuCreator() {
@Override
public void create(SwipeMenu menu) {
// 创建滑动选项
SwipeMenuItem rejectItem = new SwipeMenuItem(
getApplicationContext());
// 设置选项背景
rejectItem.setBackground(new ColorDrawable(getResources().getColor(R.color.top)));
// 设置选项宽度
rejectItem.setWidth(dp2px(80,getApplicationContext()));
// 设置选项标题
rejectItem.setTitle("置顶");
// 设置选项标题
rejectItem.setTitleSize(16);
// 设置选项标题颜色
rejectItem.setTitleColor(Color.WHITE);
// 添加选项
menu.addMenuItem(rejectItem);
// 创建删除选项
SwipeMenuItem argeeItem = new SwipeMenuItem(getApplicationContext());
argeeItem.setBackground(new ColorDrawable(getResources().getColor(R.color.del)));
argeeItem.setWidth(dp2px(80, getApplicationContext()));
argeeItem.setTitle("删除");
argeeItem.setTitleSize(16);
argeeItem.setTitleColor(Color.WHITE);
menu.addMenuItem(argeeItem);
}
};
rsmLv.setMenuCreator(creator);
rsmLv.setOnMenuItemClickListener(new RefreshSwipeMenuListView.OnMenuItemClickListener() {
@Override
public void onMenuItemClick(int position, SwipeMenu menu, int index) {
switch (index) {
case 0: //第一个选项
Toast.makeText(MainActivity.this,"您点击的是置顶",Toast.LENGTH_SHORT).show();
break;
case 1: //第二个选项
del(position,rsmLv.getChildAt(position+1-rsmLv.getFirstVisiblePosition()));
break;
}
}
});

为了和QQ有着一样的体验效果,小编加了一个删除item的动画
看起来就不会太生硬了,用户体验也比较好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 删除item动画
* @param index
* @param v
*/
private void del(final int index, View v){
final Animation animation = (Animation) AnimationUtils.loadAnimation(v.getContext(), R.anim.list_anim);
animation.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {}
public void onAnimationRepeat(Animation animation) {}
public void onAnimationEnd(Animation animation) {
data.remove(index);
adapter.notifyDataSetChanged();
animation.cancel();
}
});
v.startAnimation(animation);
}

好了到这里了,我们的组合控件-上拉下拉+左滑删除的组合ListView就定义好了,分享出来小伙伴们以后遇到可以直接拿来用,”到时候小船儿也不会再翻了”,不合理的地方希望大家提出来,共同交流,进步。

代码已经上传到Github上了,小伙伴们记得star和follow哦

https://github.com/Hankkin/SwipeRefreshDemo