Android 实现item可左右滑动移除的GridView
前言
见如下效果图,实现了如下功能:
1. 上下滑动可翻页(GridView自带的功能)
2. 左右滑动Item,可以进行移除,且有动画效果,当移除完成后,onAnimationEnd方法会被回调
3. 点击黑色区域(子View,此处为TextView)时响应点击事件
4. 点击红色区域(父View,此处为FrameLayout)时响应点击事件
开发期间遇到的问题:
1.事件没有传递到子view,即TextView不能响应点击事件
2.事件已传递到子view(TextView),但是父View(TaskView)不能响应点击事件
3.左右滑动item时,TaskView高概率突然接受到"ACTION_CANCEL"事件,导致item的滑动动作不受控制
开发期间遇到的问题解决方法:
第1和第2个问题是onInterceptTouchEvent和onTouchEvent的返回值导致的,见如下TaskView类
第3个问题:左右滑动item时突然接受到"ACTION_CANCEL"事件,是由于GridView的事件干扰导致的,解决方法:在“ACTION_MOVE”事件中加入"getParent().requestDisallowInterceptTouchEvent(true)"(不能在“ACTION_DOWN"事件中调用该方法,否则会影响GridView的上下滑动翻页事件),在“ACTION_UP”和"ACTION_CANCEL"时需要requestDisallowInterceptTouchEvent(false)。
原文地址:http://blog.csdn.net/yelangjueqi/article/details/56021609
效果图
源码
一 grid_recents.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<GridView
android:id="@+id/grid_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="8dp"
android:columnWidth="120dp"
android:horizontalSpacing="20dp"
android:numColumns="2"
android:scrollbars="none"
android:stretchMode="columnWidth"
android:verticalSpacing="20dp" />
</FrameLayout>
二 simple_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.android.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:background="#ff0000" >
<TextView
android:id="@+id/text"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:background="#000000"
android:gravity="center"
android:textColor="#00ff00" />
</com.example.android.TaskView>
三 TaskView.java
package com.example.android;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
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.ViewParent;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.Toast;
public class TaskView extends FrameLayout {
private String TAG = "TaskView";
private ValueAnimator mValueAnimator;
private int mPosition = -1;
private int DIRECTION_LEFT_TO_RIGHT = 0;
private int DIRECTION_RIGHT_TO_LEFT = 1;
private int mSlideDirection = -1;
private int mTaskViewDownLeft;
private int mTaskViewLeft;
private int mTaskViewTop;
private int mTaskViewRight;
private int mTaskViewBottom;
private int mDownX;
private float mLastX;
private final ViewConfiguration mConfiguration;
private float mTouchSlop;
private ViewParent mViewParent;
private int offsetDistanceThreshold;
private final float BASEVELOCOTY = 100;
private VelocityTracker mVelocityTracker;
private float mVelocityX;
private boolean eventConsumed = false;
public TaskView(Context context) {
this(context, null);
}
public TaskView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public TaskView(final Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfiguration = ViewConfiguration.get(context);
mTouchSlop = mConfiguration.getScaledTouchSlop();
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "TaskView clicked", 1000).show();
}
});
}
public void updatePositionInAdapter(int position) {
mPosition = position;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean consumed = handleTouchEvent(event);
Log.d(TAG, "consumed =" + consumed);
return consumed ? true : super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean consumed = handleTouchEvent(event);
Log.d(TAG, "consumed =" + consumed);
return consumed ? true : super.onTouchEvent(event);
}
private boolean handleTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
mDownX = (int) event.getX();
mLastX = (int) event.getRawX();
mTaskViewDownLeft = getLeft();
mTaskViewLeft = mTaskViewDownLeft;
mTaskViewTop = getTop();
mTaskViewRight = getRight();
mTaskViewBottom = getBottom();
offsetDistanceThreshold = getWidth() / 3;
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
mVelocityTracker.addMovement(event);
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
// TODO
break;
}
case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000);
mVelocityX = mVelocityTracker.getXVelocity();
}
mViewParent = getParent();
if (mViewParent != null) {
// Avoid this view occurence unexpected "ACTION_CANCEL" event
mViewParent.requestDisallowInterceptTouchEvent(true);
}
int x = (int) event.getX();
int dx = (int) Math.abs(event.getRawX() - mLastX);
int offsetX = x - mDownX;
if (Math.abs(offsetX) > mTouchSlop && (mPosition % 2) == 0 && offsetX < 0) {
Log.d(TAG, "slide to left");
mSlideDirection = DIRECTION_RIGHT_TO_LEFT;
mTaskViewLeft = getLeft() - dx;
mTaskViewRight = getRight() - dx;
} else if (Math.abs(offsetX) > mTouchSlop && (mPosition % 2) != 0 && offsetX > 0) {
Log.d(TAG, "slide to right");
mSlideDirection = DIRECTION_LEFT_TO_RIGHT;
mTaskViewLeft = getLeft() + dx;
mTaskViewRight = getRight() + dx;
}
this.layout(mTaskViewLeft, mTaskViewTop, mTaskViewRight, mTaskViewBottom);
mLastX = (int) event.getRawX();
break;
}
case MotionEvent.ACTION_POINTER_UP: {
// TODO
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
Log.d(TAG, (action == 1) ? "ACTION_UP" : "ACTION_CANCEL");
eventConsumed = upOrcancel();
reset();
if (mViewParent != null) {
mViewParent.requestDisallowInterceptTouchEvent(false);
}
break;
}
}
return eventConsumed;
}
private boolean upOrcancel() {
boolean consumed = false;
int offsetDistance = Math.abs(getLeft() - mTaskViewDownLeft);
boolean overstep = (offsetDistance > offsetDistanceThreshold);
int viewCurrentLeft = getLeft(); // current left position
int viewEndLeft = viewCurrentLeft; // default value
boolean fling = (Math.abs(mVelocityX) > BASEVELOCOTY);
// left column
if ((mPosition % 2) == 0 && mSlideDirection == DIRECTION_RIGHT_TO_LEFT) {
// exceed "offsetDistanceThreshold" value or fling gesture
boolean shouldDismissChildView = overstep || fling;
viewEndLeft = shouldDismissChildView ? (mTaskViewDownLeft - getWidth()) : mTaskViewDownLeft;
finishSlide(viewCurrentLeft, viewEndLeft, shouldDismissChildView);
consumed = true;
// right column
} else if ((mPosition % 2) != 0 && mSlideDirection == DIRECTION_LEFT_TO_RIGHT) {
// exceed "offsetDistanceThreshold" value or fling gesture
boolean shouldDismissChildView = overstep || fling;
viewEndLeft = shouldDismissChildView ? (mTaskViewDownLeft + getWidth()) : mTaskViewDownLeft;
finishSlide(viewCurrentLeft, viewEndLeft, shouldDismissChildView);
consumed = true;
}
return consumed;
}
private void finishSlide(int startPosition, int endPosition, final boolean shouldDismissChildView) {
mValueAnimator = ValueAnimator.ofFloat(startPosition, endPosition);
mValueAnimator.setDuration(220);
mValueAnimator.setInterpolator(new AccelerateInterpolator());
mValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (Float) animation.getAnimatedValue();
int left = (int) currentValue;
int right = (int) (left + getWidth());
layout(left, mTaskViewTop, right, mTaskViewBottom);
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (shouldDismissChildView) {
Log.d(TAG, "--->onAnimationEnd");
// TODO Notify other object to update by callback
}
}
});
mValueAnimator.start();
}
private void reset() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mSlideDirection = -1;
}
}
四 SwipeGridViewActivity.java
package com.example.android;
import java.util.ArrayList;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.TextView;
import android.widget.Toast;
public class SwipeGridViewActivity extends Activity {
private String TAG = "SwipeGridViewActivity";
private GridView mGridView;
private ArrayList<String> mAllList = new ArrayList<String>();
private GridTaskAdapter mGridTaskAdapter;
private LayoutInflater mInflater;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.grid_recents);
mInflater = LayoutInflater.from(SwipeGridViewActivity.this);
loadData();
mGridView = (GridView) findViewById(R.id.grid_view);
mGridView.setAdapter(new GridTaskAdapter());
}
private class GridTaskAdapter extends BaseAdapter {
@Override
public int getCount() {
return mAllList.size();
}
@Override
public Object getItem(int position) {
return mAllList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TaskView taskView = (TaskView) mInflater.inflate(R.layout.simple_list_item, null);
convertView = taskView;
TextView textView = (TextView) taskView.findViewById(R.id.text);
taskView.updatePositionInAdapter(position);
textView.setText((String) getItem(position));
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "zzz setOnClickListener");
Toast.makeText(SwipeGridViewActivity.this, "child textView clicked", 1000).show();
}
});
return convertView;
}
}
public Object[] loadData() {
mAllList.clear();
mAllList.add("aa");
mAllList.add("ddfa");
mAllList.add("qw");
mAllList.add("sd");
mAllList.add("fd");
mAllList.add("cf");
mAllList.add("re");
mAllList.add("aa");
mAllList.add("ddfa");
mAllList.add("qw");
mAllList.add("sd");
mAllList.add("fd");
mAllList.add("cf");
mAllList.add("re");
mAllList.add("aa");
mAllList.add("ddfa");
mAllList.add("qw");
mAllList.add("sd");
mAllList.add("fd");
mAllList.add("cf");
mAllList.add("re");
mAllList.add("aa");
mAllList.add("ddfa");
mAllList.add("qw");
mAllList.add("sd");
mAllList.add("fd");
mAllList.add("cf");
mAllList.add("re");
return mAllList.toArray();
}
}
View滑动实现:
1.原始“相对位置”(getLeft()等方法)
2.计算偏移量(通过滑动事件“坐标差值”计算)
3.“原始相对位置” 结合 “偏移量” 得出 “新位置”,然后根据“新位置”刷新界面即可实现滑动
4. 再结合“动画”和“速率” 提升用户体验即可