android 左滑删除 一个简单的自定义控件
程序员文章站
2022-06-08 17:34:16
...
转自那里忘记了,并非原创
package com.guo.qlzx.nongji.service.costom;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.widget.Scroller;
/**
* Created by 李 on 2018/5/29.
*/
public class SwipeItemLayout extends ViewGroup {
enum Mode{
RESET, DRAG, FLING, CLICK
}
private Mode touchMode;
private View mainItemView;
private boolean mInLayout =false;
private int scrollOffset;
private int maxScrollOffset;
private ScrollRunnable scrollRunnable;
public SwipeItemLayout(Context context) {
this(context,null);
}
public SwipeItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
touchMode = Mode.RESET;
scrollOffset = 0;
scrollRunnable = new ScrollRunnable(context);
}
public int getScrollOffset(){
return scrollOffset;
}
public void open(){
if(scrollOffset!=-maxScrollOffset){
if(touchMode== Mode.FLING)
scrollRunnable.abort();
scrollRunnable.startScroll(scrollOffset,-maxScrollOffset);
}
}
public void close(){
if(scrollOffset!=0){
if(touchMode== Mode.FLING)
scrollRunnable.abort();
scrollRunnable.startScroll(scrollOffset,0);
}
}
void fling(int xVel){
scrollRunnable.startFling(scrollOffset,xVel);
}
void revise(){
if(scrollOffset<-maxScrollOffset/2)
open();
else
close();
}
private void ensureChildren(){
int childCount = getChildCount();
for (int i=0;i<childCount;i++){
View childView = getChildAt(i);
ViewGroup.LayoutParams tempLp = childView.getLayoutParams();
if(tempLp==null || !(tempLp instanceof LayoutParams))
throw new IllegalStateException("缺少layout参数");
LayoutParams lp = (LayoutParams) tempLp;
if(lp.itemType==0x01){
mainItemView = childView;
}
}
if(mainItemView==null)
throw new IllegalStateException("main item不能为空");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//确定children
ensureChildren();
//先测量main
LayoutParams lp = (LayoutParams) mainItemView.getLayoutParams();
measureChildWithMargins(
mainItemView,
widthMeasureSpec,getPaddingLeft()+getPaddingRight(),
heightMeasureSpec,getPaddingTop()+getPaddingBottom());
setMeasuredDimension(
mainItemView.getMeasuredWidth()+ getPaddingLeft()+getPaddingRight()+ lp.leftMargin+lp.rightMargin
,mainItemView.getMeasuredHeight()+getPaddingTop()+getPaddingBottom()+lp.topMargin+lp.bottomMargin);
//测试menu
int menuWidthSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
int menuHeightSpec = MeasureSpec.makeMeasureSpec(mainItemView.getMeasuredHeight(),MeasureSpec.EXACTLY);
for(int i=0;i<getChildCount();i++){
View menuView = getChildAt(i);
lp = (LayoutParams) menuView.getLayoutParams();
if(lp.itemType==0x01)
continue;
measureChildWithMargins(menuView,menuWidthSpec,0,menuHeightSpec,0);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mInLayout = true;
//确定children
ensureChildren();
int pl = getPaddingLeft();
int pt = getPaddingTop();
int pr = getPaddingRight();
int pb = getPaddingBottom();
LayoutParams lp;
//layout main
lp = (LayoutParams) mainItemView.getLayoutParams();
mainItemView.layout(
pl+lp.leftMargin,
pt+lp.topMargin,
getWidth()-pr-lp.rightMargin,
getHeight()-pb-lp.bottomMargin);
//layout menu
int totalLength = 0;
int menuLeft = mainItemView.getRight()+lp.rightMargin;
for(int i=0;i<getChildCount();i++){
View menuView = getChildAt(i);
lp = (LayoutParams) menuView.getLayoutParams();
if(lp.itemType==0x01)
continue;
int tempLeft = menuLeft+lp.leftMargin;
int tempTop = pt+lp.topMargin;
menuView.layout(
tempLeft,
tempTop,
tempLeft+menuView.getMeasuredWidth()+lp.rightMargin,
tempTop+menuView.getMeasuredHeight()+lp.bottomMargin);
menuLeft = menuView.getRight()+lp.rightMargin;
totalLength += lp.leftMargin+lp.rightMargin+menuView.getMeasuredWidth();
}
maxScrollOffset = totalLength;
scrollOffset = scrollOffset<-maxScrollOffset/2 ? -maxScrollOffset:0;
offsetChildrenLeftAndRight(scrollOffset);
mInLayout = false;
}
void offsetChildrenLeftAndRight(int delta){
for(int i=0;i<getChildCount();i++){
View childView = getChildAt(i);
ViewCompat.offsetLeftAndRight(childView,delta);
}
}
@Override
public void requestLayout() {
if (!mInLayout) {
super.requestLayout();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeCallbacks(scrollRunnable);
touchMode = Mode.RESET;
scrollOffset = 0;
}
//展开的情况下,拦截down event,避免触发点击main事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View pointView = findTopChildUnder(this,x,y);
if(pointView!=null && pointView==mainItemView && scrollOffset !=0)
return true;
break;
}
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_UP:{
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View pointView = findTopChildUnder(this,x,y);
if(pointView!=null && pointView==mainItemView && touchMode== Mode.CLICK && scrollOffset !=0)
return true;
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View pointView = findTopChildUnder(this,x,y);
if(pointView!=null && pointView==mainItemView && scrollOffset !=0)
return true;
break;
}
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_UP:{
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View pointView = findTopChildUnder(this,x,y);
if(pointView!=null && pointView==mainItemView && touchMode== Mode.CLICK && scrollOffset !=0) {
close();
return true;
}
}
}
return false;
}
void setTouchMode(Mode mode){
if(mode==touchMode)
return;
if(touchMode== Mode.FLING)
removeCallbacks(scrollRunnable);
touchMode = mode;
}
public Mode getTouchMode(){
return touchMode;
}
boolean trackMotionScroll(int deltaX){
if(deltaX==0)
return true;
boolean over = false;
int newLeft = scrollOffset+deltaX;
if((deltaX>0 && newLeft>0) || (deltaX<0 && newLeft<-maxScrollOffset)){
over = true;
newLeft = Math.min(newLeft,0);
newLeft = Math.max(newLeft,-maxScrollOffset);
}
offsetChildrenLeftAndRight(newLeft-scrollOffset);
scrollOffset = newLeft;
return over;
}
private static final Interpolator sInterpolator = new Interpolator() {
@Override
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
private class ScrollRunnable implements Runnable{
private Scroller scroller;
private boolean abort;
private int minVelocity;
ScrollRunnable(Context context){
scroller = new Scroller(context,sInterpolator);
abort = false;
ViewConfiguration configuration = ViewConfiguration.get(context);
minVelocity = configuration.getScaledMinimumFlingVelocity();
}
void startScroll(int startX,int endX){
if(startX!=endX){
Log.e("scroll - startX - endX",""+startX+" "+endX);
setTouchMode(Mode.FLING);
abort = false;
scroller.startScroll(startX,0,endX-startX,0, 400);
ViewCompat.postOnAnimation(SwipeItemLayout.this,this);
}
}
void startFling(int startX,int xVel){
Log.e("fling - startX",""+startX);
if(xVel> minVelocity && startX!=0) {
startScroll(startX, 0);
return;
}
if(xVel<-minVelocity && startX!=-maxScrollOffset) {
startScroll(startX, -maxScrollOffset);
return;
}
startScroll(startX,startX>-maxScrollOffset/2 ? 0:-maxScrollOffset);
}
void abort(){
if(!abort){
abort = true;
if(!scroller.isFinished()){
scroller.abortAnimation();
removeCallbacks(this);
}
}
}
@Override
public void run() {
Log.e("abort",Boolean.toString(abort));
if(!abort){
boolean more = scroller.computeScrollOffset();
int curX = scroller.getCurrX();
Log.e("curX",""+curX);
boolean atEdge = false;
if(curX!=scrollOffset)
atEdge = trackMotionScroll(curX-scrollOffset);
if(more && !atEdge) {
ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
return;
}else{
removeCallbacks(this);
if(!scroller.isFinished())
scroller.abortAnimation();
setTouchMode(Mode.RESET);
}
}
}
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams ? (LayoutParams) p : new LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams && super.checkLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends MarginLayoutParams{
public int itemType = -1;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.SwipeItemLayout_Layout);
itemType = a.getInt(R.styleable.SwipeItemLayout_Layout_layout_itemType,-1);
a.recycle();
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(LayoutParams source) {
super(source);
itemType = source.itemType;
}
public LayoutParams(int width, int height) {
super(width, height);
}
}
static View findTopChildUnder(ViewGroup parent,int x, int y) {
final int childCount = parent.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View child = parent.getChildAt(i);
if (x >= child.getLeft() && x < child.getRight()
&& y >= child.getTop() && y < child.getBottom()) {
return child;
}
}
return null;
}
public static class OnSwipeItemTouchListener implements RecyclerView.OnItemTouchListener {
private SwipeItemLayout captureItem;
private float lastMotionX;
private float lastMotionY;
private VelocityTracker velocityTracker;
private int activePointerId;
private int touchSlop;
private int maximumVelocity;
private boolean parentHandled;
private boolean probingParentProcess;
private boolean ignoreActions = false;
public OnSwipeItemTouchListener(Context context){
ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
maximumVelocity = configuration.getScaledMaximumFlingVelocity();
activePointerId = -1;
parentHandled = false;
probingParentProcess = false;
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
if(probingParentProcess)
return false;
boolean intercept = false;
final int action = ev.getActionMasked();
if(action!=MotionEvent.ACTION_DOWN && ignoreActions)
return true;
if(action!=MotionEvent.ACTION_DOWN && (captureItem==null||parentHandled))
return false;
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(ev);
switch (action){
case MotionEvent.ACTION_DOWN:{
ignoreActions = false;
parentHandled = false;
activePointerId = ev.getPointerId(0);
final float x = ev.getX();
final float y = ev.getY();
lastMotionX = x;
lastMotionY = y;
boolean pointOther = false;
SwipeItemLayout pointItem = null;
//首先知道ev针对的是哪个item
View pointView = findTopChildUnder(rv,(int)x,(int)y);
if(pointView==null || !(pointView instanceof SwipeItemLayout)){
//可能是head view或bottom view
pointOther = true;
}else
pointItem = (SwipeItemLayout) pointView;
//此时的pointOther=true,意味着点击的view为空或者点击的不是item
//还没有把点击的是item但是不是capture item给过滤出来
if(!pointOther && (captureItem ==null || captureItem !=pointItem))
pointOther = true;
//点击的是capture item
if(!pointOther){
Mode mode = captureItem.getTouchMode();
//如果它在fling,就转为drag
//需要拦截,并且requestDisallowInterceptTouchEvent
boolean disallowIntercept = false;
if(mode== Mode.FLING){
captureItem.setTouchMode(Mode.DRAG);
disallowIntercept = true;
intercept = true;
}else {//如果是expand的,就不允许parent拦截
captureItem.setTouchMode(Mode.CLICK);
if(captureItem.getScrollOffset()!=0)
disallowIntercept = true;
}
if(disallowIntercept){
final ViewParent parent = rv.getParent();
if (parent!= null)
parent.requestDisallowInterceptTouchEvent(true);
}
}else{//capture item为null或者与point item不一样
//直接将其close掉
if(captureItem !=null &&
captureItem.getScrollOffset()!=0) {
captureItem.close();
ignoreActions = true;
return true;
}
captureItem = null;
if(pointItem!=null) {
captureItem = pointItem;
captureItem.setTouchMode(Mode.CLICK);
}
}
//如果parent处于fling状态,此时,parent就会转为drag。应该将后续move都交给parent处理
probingParentProcess = true;
parentHandled = rv.onInterceptTouchEvent(ev);
probingParentProcess = false;
if(parentHandled) {
intercept = false;
//在down时,就被认定为parent的drag,所以,直接交给parent处理即可
if(captureItem !=null && captureItem.getScrollOffset()!=0)
captureItem.close();
}
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int actionIndex = ev.getActionIndex();
activePointerId = ev.getPointerId(actionIndex);
lastMotionX = ev.getX(actionIndex);
lastMotionY = ev.getY(actionIndex);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int actionIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(actionIndex);
if (pointerId == activePointerId) {
final int newIndex = actionIndex == 0 ? 1 : 0;
activePointerId = ev.getPointerId(newIndex);
lastMotionX = ev.getX(newIndex);
lastMotionY = ev.getY(newIndex);
}
break;
}
//down时,已经将capture item定下来了。所以,后面可以安心考虑event处理
case MotionEvent.ACTION_MOVE: {
final int activePointerIndex = ev.findPointerIndex(activePointerId);
if (activePointerIndex == -1)
break;
final int x = (int) (ev.getX(activePointerIndex)+.5f);
final int y = (int) ((int) ev.getY(activePointerIndex)+.5f);
int deltaX = (int) (x - lastMotionX);
int deltaY = (int)(y- lastMotionY);
final int xDiff = Math.abs(deltaX);
final int yDiff = Math.abs(deltaY);
Mode mode = captureItem.getTouchMode();
if(mode== Mode.CLICK){
//如果capture item是open的,下拉有两种处理方式:
// 1、下拉后,直接close item
// 2、只要是open的,就拦截所有它的消息,这样如果点击open的,就只能滑动该capture item
if(xDiff> touchSlop && xDiff>yDiff){
captureItem.setTouchMode(Mode.DRAG);
final ViewParent parent = rv.getParent();
parent.requestDisallowInterceptTouchEvent(true);
deltaX = deltaX>0 ? deltaX-touchSlop:deltaX+touchSlop;
}else/* if(yDiff>touchSlop)*/{
probingParentProcess = true;
parentHandled = rv.onInterceptTouchEvent(ev);
probingParentProcess = false;
if(parentHandled && captureItem.getScrollOffset() != 0)
captureItem.close();
}
}
mode = captureItem.getTouchMode();
if(mode== Mode.DRAG){
intercept = true;
lastMotionX = x;
lastMotionY = y;
//对capture item进行拖拽
captureItem.trackMotionScroll(deltaX);
}
break;
}
case MotionEvent.ACTION_UP:
Mode mode = captureItem.getTouchMode();
if(mode== Mode.DRAG){
final VelocityTracker velocityTracker = this.velocityTracker;
velocityTracker.computeCurrentVelocity(1000, maximumVelocity);
int xVel = (int) velocityTracker.getXVelocity(activePointerId);
captureItem.fling(xVel);
intercept = true;
}
cancel();
break;
case MotionEvent.ACTION_CANCEL:
captureItem.revise();
cancel();
break;
}
return intercept;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
if(ignoreActions)
return;
final int action = ev.getActionMasked();
final int actionIndex = ev.getActionIndex();
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(ev);
switch (action){
case MotionEvent.ACTION_POINTER_DOWN:
activePointerId = ev.getPointerId(actionIndex);
lastMotionX = ev.getX(actionIndex);
lastMotionY = ev.getY(actionIndex);
break;
case MotionEvent.ACTION_POINTER_UP:
final int pointerId = ev.getPointerId(actionIndex);
if(pointerId== activePointerId){
final int newIndex = actionIndex == 0 ? 1 : 0;
activePointerId = ev.getPointerId(newIndex);
lastMotionX = ev.getX(newIndex);
lastMotionY = ev.getY(newIndex);
}
break;
//down时,已经将capture item定下来了。所以,后面可以安心考虑event处理
case MotionEvent.ACTION_MOVE: {
final int activePointerIndex = ev.findPointerIndex(activePointerId);
if (activePointerIndex == -1)
break;
final float x = ev.getX(activePointerIndex);
final float y = (int) ev.getY(activePointerIndex);
int deltaX = (int) (x - lastMotionX);
if(captureItem !=null && captureItem.getTouchMode()== Mode.DRAG){
lastMotionX = x;
lastMotionY = y;
//对capture item进行拖拽
captureItem.trackMotionScroll(deltaX);
}
break;
}
case MotionEvent.ACTION_UP:
if(captureItem !=null){
Mode mode = captureItem.getTouchMode();
if(mode== Mode.DRAG){
final VelocityTracker velocityTracker = this.velocityTracker;
velocityTracker.computeCurrentVelocity(1000, maximumVelocity);
int xVel = (int) velocityTracker.getXVelocity(activePointerId);
captureItem.fling(xVel);
}
}
cancel();
break;
case MotionEvent.ACTION_CANCEL:
if(captureItem !=null)
captureItem.revise();
cancel();
break;
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
void cancel(){
parentHandled = false;
activePointerId = -1;
if(velocityTracker !=null){
velocityTracker.recycle();
velocityTracker = null;
}
}
}
}
中间的其他资源文件 attrs_swipeitemlayout.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SwipeItemLayout.Layout">
<attr name="layout_itemType">
<flag name="main" value="0x01"/>
<flag name="menu" value="0x02"/>
</attr>
</declare-styleable>
</resources>
最后就是例子= = 单个条目的写法,母为平常显示的,子为滑出来的删除
母:
app:layout_itemType="main"
子:
app:layout_itemType="menu"
<?xml version="1.0" encoding="utf-8"?>
<自定义地址.SwipeItemLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/ll_content"
app:layout_itemType="main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="44dp"
android:textColor="@color/textcolor3"
android:textSize="14sp"
android:gravity="center_vertical"
android:lines="1"
tools:text="123"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray_e5"
android:layout_marginLeft="15dp"/>
</LinearLayout>
<Button
android:id="@+id/btn_delete"
app:layout_itemType="menu"
android:layout_width="60dp"
android:layout_height="44dp"
android:text="删除"
android:textSize="18sp"
android:textColor="@color/white"
android:background="@color/red_e7"/>
</自定义地址.SwipeItemLayout>
无论是recycle还是listview 都需要在加入一句话
recyclerView.addOnItemTouchListener(new SwipeItemLayout.OnSwipeItemTouchListener(this));
这样才会滑动有效,不然是无效的
自然如果已经使用这个,单个条目点击事件会失效,自需要在母布局里价格id变成点击这个id跳转即可代替。
以上转自地址忘记了= =,作者有看到可以删除
推荐阅读
-
Android使用CardView作为RecyclerView的Item并实现拖拽和左滑删除
-
iOS应用中UITableView左滑自定义选项及批量删除的实现
-
iOS应用中UITableView左滑自定义选项及批量删除的实现
-
Android使用CardView作为RecyclerView的Item并实现拖拽和左滑删除
-
android实现简单左滑删除控件
-
Android 自定义控件教程:绘制一个会动的能力分布图
-
iOS自定义UITableView实现不同系统下的左滑删除功能详解
-
Android自定义控件练手——简单的时钟
-
Android自定义view、动手实现一个简单的xmind思维导图结构
-
android studio中让一个控件按钮居于底部的几种方法,以及在RelativeLayout中的左中右