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. 再结合“动画”和“速率” 提升用户体验即可


posted @ 2017-02-20 20:57  行走的思想  阅读(28)  评论(0编辑  收藏  举报  来源