背景:在android开发中,列表是经常会使用到的一个主要控件,列表中可以展示大量的数据,像订单、商品、通讯录、浏览记录或者关注列表等等。可能产品一开始需求只做简单的数据展示,但后期随着功能越来越多,越来越完善,产品可能说在列表里面增加一些交互能力。比如说订单列表里面,一开始只是展示订单数据,后面需要加上删除订单的功能,以前Android中这种功能要的很多的可能就是长按操作这种的,因为程序猿只需要很少的代码就能实现。但是ios的习惯操作是左滑删除,为了保持统一的操作习惯,两端保持一致,最终产品会让Android程序猿去实现一种和ios一模一样的功能。如果你的代码已经维护了很久,代码量比较大,不愿意去大改,那么今天这个控件就能轻松的助你完成左滑删除的功能。
先上效果图:
设计思路:最好以最小的代码侵入来实现左滑删除的功能,在不破坏原来逻辑的基础上,只需稍加改造便可具备左滑删除的能力。
首先分析下左滑删除的基础原理:
原理分析:
1. 正常状态下,我们看到的是完整的内容部分,右侧菜单部分因为超出屏幕所以不在视线范围内。
2. 手指滑动过程中,容器的内容跟随手指移动,从而拉出在屏幕外面的菜单区域。
3. 当手指松开的时候,我们先假定一种逻辑,如果菜单区域显示超过一半,那就全部显示;如果少于一半那就滑出隐藏。
滑动原理分析完了之后,我们大概就有了实现思路了:
首先我们的控件里面需要两块区域,因为以前可能已经实现了列表item的显示,如果能不做任何改动,直接把以前的item包含到我们的内容区域里面来,那么我们内容区域就轻松搞定了。
菜单区域,需要什么能力,就把相关的View也传递给我容器,然后容器放到相应位置。
谈笑间,简单两步我们的左滑删除容器已经完成一个简单的雏形了!
接下来就是代码实现:
步骤一:内容和菜单分别加入容器
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/** * 设置内容区域 * @param contentView */ public void addContentView(View contentView) { this.mContentView = contentView; this.mContentView.setTag("contentView"); View cv = findViewWithTag("contentView"); if (cv != null) { this.removeView(cv); } LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ); this.addView(this.mContentView, layoutParams); } /** * 设置右边菜单区域 */ public void addMenuView(View menuView) { this.mMenuView = menuView; this.mMenuView.setTag("menuView"); View mv = findViewWithTag("menuView"); if (mv != null) { this.removeView(mv); } LayoutParams layoutParams = new LayoutParams(mRightCanSlide, ViewGroup.LayoutParams.MATCH_PARENT); this.addView(this.mMenuView, layoutParams); }
步骤二:左滑处理
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/** * 拦截触摸事件 * * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int actionMasked = ev.getActionMasked(); Log.e(TAG, "onInterceptTouchEvent: actionMasked = " + actionMasked); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mInitX = ev.getRawX() + getScrollX(); mInitY = ev.getRawY(); clearAnim(); if (mViewPager != null) { mViewPager.requestDisallowInterceptTouchEvent(true); } if (mCardView != null) { mCardView.requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_MOVE: if (mInitX - ev.getRawX() < 0) { // 让父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = false; } // 阻止ViewPager拦截事件 if (mViewPager != null) { mViewPager.requestDisallowInterceptTouchEvent(true); } return false; } // y轴方向上达到滑动最小距离, x 轴未达到 if (Math.abs(ev.getRawY() - mInitY) >= mTouchSlop && Math.abs(ev.getRawY() - mInitY) > Math.abs(mInitX - ev.getRawX() - getScrollX())) { // 让父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = false; } return false; } // x轴方向达到了最小滑动距离,y轴未达到 if (Math.abs(mInitX - ev.getRawX() - getScrollX()) >= mTouchSlop && Math.abs(ev.getRawY() - mInitY) <= Math.abs(mInitX - ev.getRawX() - getScrollX())) { // 阻止父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(true); isReCompute = false; } return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mRecyclerView != null) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = true; } break; default: break; } return super.onInterceptTouchEvent(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/** * 处理触摸事件 * 需要注意何时处理左滑,何时不处理 * * @param ev * @return */ @Override public boolean onTouchEvent(MotionEvent ev) { int actionMasked = ev.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mInitX = ev.getRawX() + getScrollX(); mInitY = ev.getRawY(); clearAnim(); if (mViewPager != null) { mViewPager.requestDisallowInterceptTouchEvent(true); } if (mCardView != null) { mCardView.requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_MOVE: if (mInitX - ev.getRawX() < 0) { // 让父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = false; } // 阻止ViewPager拦截事件 if (mViewPager != null) { mViewPager.requestDisallowInterceptTouchEvent(true); isReCompute = false; } } // y轴方向上达到滑动最小距离, x 轴未达到 if (Math.abs(ev.getRawY() - mInitY) >= mTouchSlop && Math.abs(ev.getRawY() - mInitY) > Math.abs(mInitX - ev.getRawX() - getScrollX())) { // 让父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = false; } } // x轴方向达到了最小滑动距离,y轴未达到 if (Math.abs(mInitX - ev.getRawX() - getScrollX()) >= mTouchSlop && Math.abs(ev.getRawY() - mInitY) <= Math.abs(mInitX - ev.getRawX() - getScrollX())) { // 阻止父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(true); isReCompute = false; } } /** 如果手指移动距离超过最小距离 */ float translationX = mInitX - ev.getRawX(); // 如果滑动距离已经大于右边可伸缩的距离后, 应该重新设置initx if (translationX > mRightCanSlide) { mInitX = ev.getRawX() + mRightCanSlide; } // 如果互动距离小于0,那么重新设置初始位置initx if (translationX < 0) { mInitX = ev.getRawX(); } translationX = translationX > mRightCanSlide ? mRightCanSlide : translationX; translationX = translationX < 0 ? 0 : translationX; // 向左滑动 if (translationX <= mRightCanSlide && translationX >= 0) { scrollTo((int) translationX, 0); return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mRecyclerView != null) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = true; } upAnim(); return true; default: break; } return true; }
以上两个方法主要处理了左滑移动功能以及滑动冲突问题,如果用的是RecyclerView那么为了防止垂直方向的同向冲突,那么需要将外层的RecyclerView传入左滑容器,在这个容器中会处理滑动冲突。
到这就已经实现了左滑功能,并且解决掉了垂直方向上的滑动冲突,然后我们还要实现一个功能是:如果有一个item向左滑动并显示出右边的菜单区域,当手指再次按下或者列表滑动的时候,需要将已经显示菜单区域的item收起,恢复原来的状态。为了提供这个能力,左滑容器里面提供一个菜单状态变化的监听:
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/** * 删除按钮状态变化监听 */ public interface OnDelViewStatusChangeLister { /** * 状态变化监听 * @param show 是否正在显示 */ void onStatusChange(boolean show); } /** * 重置 菜单展开/菜单收起 状态 */ public void resetDelStatus() { int scrollX = getScrollX(); if (scrollX == 0) { return; } clearAnim(); mValueAnimator = ValueAnimator.ofInt(scrollX, 0); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); scrollTo(value, 0); } }); mValueAnimator.setDuration(mAnimDuring); mValueAnimator.start(); }
菜单展开或者收起都会调用这个方法,方便第三方调用者处理状态。
再者还有就是加上动画,让滑动更加柔和:
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/** * 手指抬起执行动画 */ private void upAnim() { int scrollX = getScrollX(); if (scrollX == mRightCanSlide || scrollX == 0) { if (mStatusChangeLister != null) { mStatusChangeLister.onStatusChange(scrollX == mRightCanSlide); } return; } clearAnim(); // 如果显出一半松开手指,那么自动完全显示。否则完全隐藏 if (scrollX >= mRightCanSlide / 2) { mValueAnimator = ValueAnimator.ofInt(scrollX, mRightCanSlide); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); scrollTo(value, 0); } }); mValueAnimator.setDuration(mAnimDuring); mValueAnimator.start(); if (mStatusChangeLister != null) { mStatusChangeLister.onStatusChange(true); } } else { mValueAnimator = ValueAnimator.ofInt(scrollX, 0); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); scrollTo(value, 0); } }); mValueAnimator.setDuration(mAnimDuring); mValueAnimator.start(); if (mStatusChangeLister != null) { mStatusChangeLister.onStatusChange(false); } } }
#最后贴上左滑删除容器的完整代码:
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/** * @author luowang * @date 2020-08-19 17:31 * 左滑删除View */ public class LeftSlideView extends LinearLayout { /** * tag */ public static final String TAG = "LeftSlideView"; /** * 上下文 */ private Context mContext; /** * 最小触摸距离 */ private int mTouchSlop; /** * 右边可滑动距离 */ private int mRightCanSlide; /** * 按下x */ private float mInitX; /** * 按下y */ private float mInitY; /** * 属性动画 */ private ValueAnimator mValueAnimator; /** * 动画时长 */ private int mAnimDuring = 200; /** * 删除按钮的长度 */ private int mDelLength = 76; /** * ViewPager */ private ViewPager mViewPager; /** * RecyclerView */ private RecyclerView mRecyclerView; /** CardView */ private CardView mCardView; /** 是否重新计算 */ private boolean isReCompute = true; /** 状态监听 */ private OnDelViewStatusChangeLister mStatusChangeLister; /** * 内容区域View */ private View mContentView; /** * 菜单区域View */ private View mMenuView; public LeftSlideView(Context context) { this(context, null); } public LeftSlideView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public LeftSlideView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; init(); } /** * 初始化 */ private void init() { mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); mRightCanSlide = DPIUtil.dip2px(mContext, mDelLength); setBackgroundColor(Color.TRANSPARENT); // 水平布局 setOrientation(LinearLayout.HORIZONTAL); initView(); } /** * 设置内容区域 * @param contentView */ public void addContentView(View contentView) { this.mContentView = contentView; this.mContentView.setTag("contentView"); View cv = findViewWithTag("contentView"); if (cv != null) { this.removeView(cv); } LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ); this.addView(this.mContentView, layoutParams); } /** * 设置右边菜单区域 */ public void addMenuView(View menuView) { this.mMenuView = menuView; this.mMenuView.setTag("menuView"); View mv = findViewWithTag("menuView"); if (mv != null) { this.removeView(mv); } LayoutParams layoutParams = new LayoutParams(mRightCanSlide, ViewGroup.LayoutParams.MATCH_PARENT); this.addView(this.mMenuView, layoutParams); } /** * 设置Viewpager */ public void setViewPager(ViewPager viewPager) { mViewPager = viewPager; } /** * 设置RecyclerView */ public void setRecyclerView(RecyclerView recyclerView) { mRecyclerView = recyclerView; } /** 设置CardView */ public void setCardView(CardView cardView) { mCardView = cardView; } /** 设置状态监听 */ public void setStatusChangeLister(OnDelViewStatusChangeLister statusChangeLister) { mStatusChangeLister = statusChangeLister; } /** * 初始化View */ private void initView() { } /** * 拦截触摸事件 * * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int actionMasked = ev.getActionMasked(); Log.e(TAG, "onInterceptTouchEvent: actionMasked = " + actionMasked); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mInitX = ev.getRawX() + getScrollX(); mInitY = ev.getRawY(); clearAnim(); if (mViewPager != null) { mViewPager.requestDisallowInterceptTouchEvent(true); } if (mCardView != null) { mCardView.requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_MOVE: if (mInitX - ev.getRawX() < 0) { // 让父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = false; } // 阻止ViewPager拦截事件 if (mViewPager != null) { mViewPager.requestDisallowInterceptTouchEvent(true); } return false; } // y轴方向上达到滑动最小距离, x 轴未达到 if (Math.abs(ev.getRawY() - mInitY) >= mTouchSlop && Math.abs(ev.getRawY() - mInitY) > Math.abs(mInitX - ev.getRawX() - getScrollX())) { // 让父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = false; } return false; } // x轴方向达到了最小滑动距离,y轴未达到 if (Math.abs(mInitX - ev.getRawX() - getScrollX()) >= mTouchSlop && Math.abs(ev.getRawY() - mInitY) <= Math.abs(mInitX - ev.getRawX() - getScrollX())) { // 阻止父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(true); isReCompute = false; } return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mRecyclerView != null) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = true; } break; default: break; } return super.onInterceptTouchEvent(ev); } /** * 处理触摸事件 * 需要注意何时处理左滑,何时不处理 * * @param ev * @return */ @Override public boolean onTouchEvent(MotionEvent ev) { int actionMasked = ev.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mInitX = ev.getRawX() + getScrollX(); mInitY = ev.getRawY(); clearAnim(); if (mViewPager != null) { mViewPager.requestDisallowInterceptTouchEvent(true); } if (mCardView != null) { mCardView.requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_MOVE: if (mInitX - ev.getRawX() < 0) { // 让父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = false; } // 阻止ViewPager拦截事件 if (mViewPager != null) { mViewPager.requestDisallowInterceptTouchEvent(true); isReCompute = false; } } // y轴方向上达到滑动最小距离, x 轴未达到 if (Math.abs(ev.getRawY() - mInitY) >= mTouchSlop && Math.abs(ev.getRawY() - mInitY) > Math.abs(mInitX - ev.getRawX() - getScrollX())) { // 让父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = false; } } // x轴方向达到了最小滑动距离,y轴未达到 if (Math.abs(mInitX - ev.getRawX() - getScrollX()) >= mTouchSlop && Math.abs(ev.getRawY() - mInitY) <= Math.abs(mInitX - ev.getRawX() - getScrollX())) { // 阻止父级容器拦截 if (mRecyclerView != null && isReCompute) { mRecyclerView.requestDisallowInterceptTouchEvent(true); isReCompute = false; } } /** 如果手指移动距离超过最小距离 */ float translationX = mInitX - ev.getRawX(); // 如果滑动距离已经大于右边可伸缩的距离后, 应该重新设置initx if (translationX > mRightCanSlide) { mInitX = ev.getRawX() + mRightCanSlide; } // 如果互动距离小于0,那么重新设置初始位置initx if (translationX < 0) { mInitX = ev.getRawX(); } translationX = translationX > mRightCanSlide ? mRightCanSlide : translationX; translationX = translationX < 0 ? 0 : translationX; // 向左滑动 if (translationX <= mRightCanSlide && translationX >= 0) { scrollTo((int) translationX, 0); return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mRecyclerView != null) { mRecyclerView.requestDisallowInterceptTouchEvent(false); isReCompute = true; } upAnim(); return true; default: break; } return true; } /** * 清除动画 */ private void clearAnim() { if (mValueAnimator == null) { return; } mValueAnimator.end(); mValueAnimator.cancel(); mValueAnimator = null; } /** * 手指抬起执行动画 */ private void upAnim() { int scrollX = getScrollX(); if (scrollX == mRightCanSlide || scrollX == 0) { if (mStatusChangeLister != null) { mStatusChangeLister.onStatusChange(scrollX == mRightCanSlide); } return; } clearAnim(); // 如果显出一半松开手指,那么自动完全显示。否则完全隐藏 if (scrollX >= mRightCanSlide / 2) { mValueAnimator = ValueAnimator.ofInt(scrollX, mRightCanSlide); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); scrollTo(value, 0); } }); mValueAnimator.setDuration(mAnimDuring); mValueAnimator.start(); if (mStatusChangeLister != null) { mStatusChangeLister.onStatusChange(true); } } else { mValueAnimator = ValueAnimator.ofInt(scrollX, 0); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); scrollTo(value, 0); } }); mValueAnimator.setDuration(mAnimDuring); mValueAnimator.start(); if (mStatusChangeLister != null) { mStatusChangeLister.onStatusChange(false); } } } /** * 重置 */ public void resetDelStatus() { int scrollX = getScrollX(); if (scrollX == 0) { return; } clearAnim(); mValueAnimator = ValueAnimator.ofInt(scrollX, 0); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); scrollTo(value, 0); } }); mValueAnimator.setDuration(mAnimDuring); mValueAnimator.start(); } /** * 删除按钮状态变化监听 */ public interface OnDelViewStatusChangeLister { /** * 状态变化监听 * @param show 是否正在显示 */ void onStatusChange(boolean show); } }
完整DEMO直通车
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持靠谱客。
最后
以上就是微笑小熊猫最近收集整理的关于Android实现左滑删除控件的全部内容,更多相关Android实现左滑删除控件内容请搜索靠谱客的其他文章。
发表评论 取消回复