一个简单地list侧滑菜单,自己实现不是梦

来源:互联网 发布:用友软件怎么建账 编辑:程序博客网 时间:2024/06/05 04:47

1,SwipeItemLayout这个是滑动的条目。

package com.aitsuki.swipe;import android.annotation.SuppressLint;import android.content.Context;import android.graphics.Rect;import android.support.v4.view.GravityCompat;import android.support.v4.view.ViewCompat;import android.support.v4.widget.ViewDragHelper;import android.util.AttributeSet;import android.util.Log;import android.view.Gravity;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.widget.FrameLayout;import java.util.ArrayList;import java.util.LinkedHashMap;import java.util.List;/** * 1. 最多同时设置两个菜单 * 2. 菜单必须设置layoutGravity属性. start left end right */public class SwipeItemLayout extends FrameLayout {    public static final String TAG = "SwipeItemLayout";    private ViewDragHelper mDragHelper;    private int mTouchSlop;    private int mVelocity;    private float mDownX;    private float mDownY;    private boolean mIsDragged;    private boolean mSwipeEnable = true;    /**     * 通过判断手势进行赋值 {@link #checkCanDragged(MotionEvent)}     */    private View mCurrentMenu;    /**     * 某些情况下,不能通过mIsOpen判断当前菜单是否开启或是关闭。     * 因为在调用 {@link #open()} 或者 {@link #close()} 的时候,mIsOpen的值已经被改变,但是     * 此时ContentView还没有到达应该的位置。亦或者ContentView已经到拖拽达指定位置,但是此时并没有     * 松开手指,mIsOpen并不会重新赋值。     */    private boolean mIsOpen;    /**     * Menu的集合,以{@link android.view.Gravity#LEFT}和{@link android.view.Gravity#LEFT}作为key,     * 菜单View作为value保存。     */    private LinkedHashMap<Integer, View> mMenus = new LinkedHashMap<>();    private List<SwipeListener> mListeners;    public SwipeItemLayout(Context context) {        this(context, null);    }    public SwipeItemLayout(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public SwipeItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();        mVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();        mDragHelper = ViewDragHelper.create(this, new DragCallBack());    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        updateMenu();    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            // 关闭菜单过程中禁止接收down事件            if (isCloseAnimating()) {                return false;            }            // 菜单打开的时候,按下Content关闭菜单            if (mIsOpen && isTouchContent(((int) ev.getX()), ((int) ev.getY()))) {                close();                return false;            }        }        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        if (!mSwipeEnable) {            return false;        }        int action = ev.getAction();        switch (action) {            case MotionEvent.ACTION_DOWN:                mIsDragged = false;                mDownX = ev.getX();                mDownY = ev.getY();                break;            case MotionEvent.ACTION_MOVE:                checkCanDragged(ev);                break;            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                if (mIsDragged) {                    mDragHelper.processTouchEvent(ev);                    mIsDragged = false;                }                break;            default:                if (mIsDragged) {                    mDragHelper.processTouchEvent(ev);                }                break;        }        return mIsDragged || super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        if (!mSwipeEnable) {            return super.onTouchEvent(ev);        }        int action = ev.getAction();        switch (action) {            case MotionEvent.ACTION_DOWN:                mIsDragged = false;                mDownX = ev.getX();                mDownY = ev.getY();                break;            case MotionEvent.ACTION_MOVE:                boolean beforeCheckDrag = mIsDragged;                checkCanDragged(ev);                if (mIsDragged) {                    mDragHelper.processTouchEvent(ev);                }                // 开始拖动后,发送一个cancel事件用来取消点击效果                if (!beforeCheckDrag && mIsDragged) {                    MotionEvent obtain = MotionEvent.obtain(ev);                    obtain.setAction(MotionEvent.ACTION_CANCEL);                    super.onTouchEvent(obtain);                }                break;            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                if (mIsDragged || mIsOpen) {                    mDragHelper.processTouchEvent(ev);                    // 拖拽后手指抬起,或者已经开启菜单,不应该响应到点击事件                    ev.setAction(MotionEvent.ACTION_CANCEL);                    mIsDragged = false;                }                break;            default:                if (mIsDragged) {                    mDragHelper.processTouchEvent(ev);                }                break;        }        return mIsDragged || super.onTouchEvent(ev);    }    /**     * 判断是否可以拖拽View     */    @SuppressLint("RtlHardcoded")    private void checkCanDragged(MotionEvent ev) {        if (mIsDragged) {            return;        }        float dx = ev.getX() - mDownX;        float dy = ev.getY() - mDownY;        boolean isRightDrag = dx > mTouchSlop && dx > Math.abs(dy);        boolean isLeftDrag = dx < -mTouchSlop && Math.abs(dx) > Math.abs(dy);        if (mIsOpen) {            // 开启状态下,点击在content上就捕获事件,点击在菜单上则判断touchSlop            int downX = (int) mDownX;            int downY = (int) mDownY;            if (isTouchContent(downX, downY)) {                mIsDragged = true;            } else if (isTouchMenu(downX, downY)) {                mIsDragged = (isLeftMenu() && isLeftDrag) || (isRightMenu() && isRightDrag);            }        } else {            // 关闭状态,获取当前即将要开启的菜单。            if (isRightDrag) {                mCurrentMenu = mMenus.get(Gravity.LEFT);                mIsDragged = mCurrentMenu != null;            } else if (isLeftDrag) {                mCurrentMenu = mMenus.get(Gravity.RIGHT);                mIsDragged = mCurrentMenu != null;            }        }        if (mIsDragged) {            // 开始拖动后,分发down事件给DragHelper,并且发送一个cancel取消点击事件            MotionEvent obtain = MotionEvent.obtain(ev);            obtain.setAction(MotionEvent.ACTION_DOWN);            mDragHelper.processTouchEvent(obtain);            if (getParent() != null) {                // 解决和父控件的滑动冲突。                getParent().requestDisallowInterceptTouchEvent(true);            }        }    }    // 最后一个是内容,倒数第1第2个设置了layout_gravity = right or left的是菜单,其余的忽略    @SuppressLint("RtlHardcoded")    @Override    public void addView(View child, int index, ViewGroup.LayoutParams params) {        super.addView(child, index, params);        LayoutParams lp = (LayoutParams) child.getLayoutParams();        int gravity = GravityCompat.getAbsoluteGravity(lp.gravity, ViewCompat.getLayoutDirection(child));        switch (gravity) {            case Gravity.RIGHT:                mMenus.put(Gravity.RIGHT, child);                break;            case Gravity.LEFT:                mMenus.put(Gravity.LEFT, child);                break;            default:                break;        }    }    public void setSwipeEnable(boolean enable) {        mSwipeEnable = enable;    }    /**     * 获取ContentView,最上层显示的View即为ContentView     */    public View getContentView() {        return getChildAt(getChildCount() - 1);    }    /**     * 判断down是否点击在Content上     */    public boolean isTouchContent(int x, int y) {        View contentView = getContentView();        if (contentView == null) {            return false;        }        Rect rect = new Rect();        contentView.getHitRect(rect);        return rect.contains(x, y);    }    @SuppressLint("RtlHardcoded")    private boolean isLeftMenu() {        return mCurrentMenu != null && mCurrentMenu == mMenus.get(Gravity.LEFT);    }    @SuppressLint("RtlHardcoded")    private boolean isRightMenu() {        return mCurrentMenu != null && mCurrentMenu == mMenus.get(Gravity.RIGHT);    }    public boolean isTouchMenu(int x, int y) {        if (mCurrentMenu == null) {            return false;        }        Rect rect = new Rect();        mCurrentMenu.getHitRect(rect);        return rect.contains(x, y);    }    /**     * 关闭菜单     */    public void close() {        if (mCurrentMenu == null) {            mIsOpen = false;            return;        }        mDragHelper.smoothSlideViewTo(getContentView(), getPaddingLeft(), getPaddingTop());        mIsOpen = false;        if (mListeners != null) {            int listenerCount = mListeners.size();            for (int i = listenerCount - 1; i >= 0; i--) {                mListeners.get(i).onSwipeClose(this);            }        }        invalidate();    }    /**     * 开启菜单     */    public void open() {        if (mCurrentMenu == null) {            mIsOpen = false;            return;        }        if (isLeftMenu()) {            mDragHelper.smoothSlideViewTo(getContentView(), mCurrentMenu.getWidth(), getPaddingTop());        } else if (isRightMenu()) {            mDragHelper.smoothSlideViewTo(getContentView(), -mCurrentMenu.getWidth(), getPaddingTop());        }        mIsOpen = true;        if (mListeners != null) {            int listenerCount = mListeners.size();            for (int i = listenerCount - 1; i >= 0; i--) {                mListeners.get(i).onSwipeOpen(this);            }        }        invalidate();    }    /**     * 菜单是否开始拖动     */    public boolean isOpen() {        return mIsOpen;    }    /**     * 是否正在做开启动画     */    private boolean isOpenAnimating() {        if (mCurrentMenu != null) {            int contentLeft = getContentView().getLeft();            int menuWidth = mCurrentMenu.getWidth();            if (mIsOpen && ((isLeftMenu() && contentLeft < menuWidth)                    || (isRightMenu() && -contentLeft < menuWidth))) {                return true;            }        }        return false;    }    /**     * 是否正在做关闭动画     */    private boolean isCloseAnimating() {        if (mCurrentMenu != null) {            int contentLeft = getContentView().getLeft();            if (!mIsOpen && ((isLeftMenu() && contentLeft > 0) || (isRightMenu() && contentLeft < 0))) {                return true;            }        }        return false;    }    /**     * 当菜单被ContentView遮住的时候,要设置菜单为Invisible,防止已隐藏的菜单接收到点击事件。     */    private void updateMenu() {        // FIXME: 2017/4/1 在某些手机上,设置setVisibility会导致dragHelper失效,先用Clickable属性代替        View contentView = getContentView();        if (contentView != null) {            int contentLeft = contentView.getLeft();            if (contentLeft == 0) {                for (View view : mMenus.values()) {                    if (view.isClickable()) {                        view.setClickable(false);                    }                }            } else {                if (mCurrentMenu != null && !mCurrentMenu.isClickable()) {                    mCurrentMenu.setClickable(true);                }            }        }    }    /**     * 添加一个监听器用于监听SwipeItemLayout的开启和关闭     *     * @param listener SwipeListener     */    public void addSwipeListener(SwipeListener listener) {        if (listener == null) {            return;        }        if (mListeners == null) {            mListeners = new ArrayList<>();        }        mListeners.add(listener);    }    /**     * 移除监听器     */    public void removeSwipeListener(SwipeListener listener) {        if (listener == null) {            return;        }        if (mListeners == null) {            return;        }        mListeners.remove(listener);    }    @Override    public void computeScroll() {        super.computeScroll();        if (mDragHelper.continueSettling(true)) {            ViewCompat.postInvalidateOnAnimation(this);        }    }    private class DragCallBack extends ViewDragHelper.Callback {        @Override        public boolean tryCaptureView(View child, int pointerId) {            // menu和content都可以抓取,因为在menu的宽度为MatchParent的时候,是无法点击到content的            return child == getContentView() || mMenus.containsValue(child);        }        @Override        public int clampViewPositionHorizontal(View child, int left, int dx) {            // 如果child是内容, 那么可以左划或右划,开启或关闭菜单            if (child == getContentView()) {                if (isRightMenu()) {                    return left > 0 ? 0 : left < -mCurrentMenu.getWidth() ?                            -mCurrentMenu.getWidth() : left;                } else if (isLeftMenu()) {                    return left > mCurrentMenu.getWidth() ? mCurrentMenu.getWidth() : left < 0 ?                            0 : left;                }            }            // 如果抓取到的child是菜单,那么不移动child,而是移动contentView            else if (isRightMenu()) {                View contentView = getContentView();                int newLeft = contentView.getLeft() + dx;                if (newLeft > 0) {                    newLeft = 0;                } else if (newLeft < -child.getWidth()) {                    newLeft = -child.getWidth();                }                contentView.layout(newLeft, contentView.getTop(), newLeft + contentView.getWidth(),                        contentView.getBottom());                return child.getLeft();            } else if (isLeftMenu()) {                View contentView = getContentView();                int newLeft = contentView.getLeft() + dx;                if (newLeft < 0) {                    newLeft = 0;                } else if (newLeft > child.getWidth()) {                    newLeft = child.getWidth();                }                contentView.layout(newLeft, contentView.getTop(), newLeft + contentView.getWidth(),                        contentView.getBottom());                return child.getLeft();            }            return 0;        }        @Override        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {            super.onViewPositionChanged(changedView, left, top, dx, dy);            updateMenu();        }        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            Log.e(TAG, "onViewReleased: " + xvel + " ,releasedChild = " + releasedChild);            if (isLeftMenu()) {                if (xvel > mVelocity) {                    open();                } else if (xvel < -mVelocity) {                    close();                } else {                    if (getContentView().getLeft() > mCurrentMenu.getWidth() / 3 * 2) {                        open();                    } else {                        close();                    }                }            } else if (isRightMenu()) {                if (xvel < -mVelocity) {                    open();                } else if (xvel > mVelocity) {                    close();                } else {                    if (getContentView().getLeft() < -mCurrentMenu.getWidth() / 3 * 2) {                        open();                    } else {                        close();                    }                }            }        }    }    public interface SwipeListener {        void onSwipeOpen(SwipeItemLayout view);        void onSwipeClose(SwipeItemLayout view);    }}
2,SwipeMenuRecyclerView

package com.aitsuki.swipe;import android.content.Context;import android.graphics.Rect;import android.support.annotation.Nullable;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;/** * 仿IOS message列表,QQ好友列表的交互体验 * 当有菜单打开的时候,只要不是点击在菜单上,关闭该菜单。 * 只能同时打开一个菜单,防止多点触控打开菜单 */public class SwipeMenuRecyclerView extends RecyclerView {    public SwipeMenuRecyclerView(Context context) {        super(context);    }    public SwipeMenuRecyclerView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public SwipeMenuRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        int action = ev.getActionMasked();        // 手指按下的时候,如果有开启的菜单,只要手指不是落在该Item上,则关闭菜单, 并且不分发事件。        if (action == MotionEvent.ACTION_DOWN) {            int x = (int) ev.getX();            int y = (int) ev.getY();            View openItem = findOpenItem();            if (openItem != null && openItem != getTouchItem(x, y)) {                SwipeItemLayout swipeItemLayout = findSwipeItemLayout(openItem);                if (swipeItemLayout != null) {                    swipeItemLayout.close();                    return false;                }            }        } else if (action == MotionEvent.ACTION_POINTER_DOWN) {            // FIXME: 2017/3/22 不知道怎么解决多点触控导致可以同时打开多个菜单的bug,先暂时禁止多点触控            return false;        }        return super.dispatchTouchEvent(ev);    }    /**     * 获取按下位置的Item     */    @Nullable    private View getTouchItem(int x, int y) {        Rect frame = new Rect();        for (int i = 0; i < getChildCount(); i++) {            View child = getChildAt(i);            if (child.getVisibility() == VISIBLE) {                child.getHitRect(frame);                if (frame.contains(x, y)) {                    return child;                }            }        }        return null;    }    /**     * 找到当前屏幕中开启的的Item     */    @Nullable    private View findOpenItem() {        int childCount = getChildCount();        for (int i = 0; i < childCount; i++) {            SwipeItemLayout swipeItemLayout = findSwipeItemLayout(getChildAt(i));            if (swipeItemLayout != null && swipeItemLayout.isOpen()) {                return getChildAt(i);            }        }        return null;    }    /**     * 获取该View     */    @Nullable    private SwipeItemLayout findSwipeItemLayout(View view) {        if (view instanceof SwipeItemLayout) {            return (SwipeItemLayout) view;        } else if (view instanceof ViewGroup) {            ViewGroup group = (ViewGroup) view;            int count = group.getChildCount();            for (int i = 0; i < count; i++) {                SwipeItemLayout swipeLayout = findSwipeItemLayout(group.getChildAt(i));                if (swipeLayout != null) {                    return swipeLayout;                }            }        }        return null;    }}

3,上边两个就已经实现了,接下来是使用,跟平时的recycleview类似的用法。

package com.aitsuki.swipedemo;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.TextView;import android.widget.Toast;import com.aitsuki.swipe.SwipeItemLayout;import com.aitsuki.swipedemo.entity.Data;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);        recyclerView.setLayoutManager(linearLayoutManager);        DemoAdapter adapter = new DemoAdapter(getData(), mItemTouchListener);        recyclerView.setAdapter(adapter);    }    private List<Data> getData() {        List<Data> mData = new ArrayList<>();        for (int i = 0; i < 50; i++) {            Data data = new Data("item","left","right");            mData.add(data);        }        return mData;    }    ItemTouchListener mItemTouchListener = new ItemTouchListener() {        @Override        public void onItemClick(String str) {            Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();        }        @Override        public void onLeftMenuClick(String str) {            Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();        }        @Override        public void onRightMenuClick(String str) {            Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();        }    };    interface ItemTouchListener {        void onItemClick(String str);        void onLeftMenuClick(String str);        void onRightMenuClick(String str);    }    private static class DemoAdapter extends RecyclerView.Adapter<SimpleViewHolder> {        private ItemTouchListener mItemTouchListener;        private List<Data> mData;        DemoAdapter(List<Data> data, ItemTouchListener itemTouchListener) {            this.mData = data;            this.mItemTouchListener = itemTouchListener;        }        @Override        public int getItemCount() {            return mData.size();        }        @Override        public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {            View rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_left_and_right_menu, parent, false);            return new SimpleViewHolder(rootView);        }        @Override        public void onBindViewHolder(final SimpleViewHolder holder, int position) {            holder.mContent.setText(mData.get(position).content.concat(" " + position));            if (mItemTouchListener != null) {                holder.itemView.setOnClickListener(v -> mItemTouchListener.onItemClick(holder.mContent.getText().toString()));                if (holder.mLeftMenu != null) {                    holder.mLeftMenu.setOnClickListener(v -> {                        mItemTouchListener.onLeftMenuClick("left " + holder.getAdapterPosition());                        holder.mSwipeItemLayout.close();                    });                }                if (holder.mRightMenu != null) {                    holder.mRightMenu.setOnClickListener(v -> {                        mItemTouchListener.onRightMenuClick("right " + holder.getAdapterPosition());                        holder.mSwipeItemLayout.close();                    });                }            }        }    }    private static class SimpleViewHolder extends RecyclerView.ViewHolder {        private final View mLeftMenu;        private final View mRightMenu;        private final TextView mContent;        private final SwipeItemLayout mSwipeItemLayout;        SimpleViewHolder(View itemView) {            super(itemView);            mSwipeItemLayout = (SwipeItemLayout) itemView.findViewById(R.id.swipe_layout);            mContent = (TextView) itemView.findViewById(R.id.tv_content);            mLeftMenu = itemView.findViewById(R.id.left_menu);            mRightMenu = itemView.findViewById(R.id.right_menu);        }    }}

一些其他的东西。

<LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.aitsuki.swipedemo.MainActivity">    <com.aitsuki.swipe.SwipeMenuRecyclerView        android:id="@+id/recyclerView"        android:layout_width="match_parent"        android:layout_height="match_parent">    </com.aitsuki.swipe.SwipeMenuRecyclerView></LinearLayout>

<com.aitsuki.swipe.SwipeItemLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/swipe_layout"    android:layout_width="match_parent"    android:layout_height="@dimen/swipe_item_height">    <TextView        android:id="@+id/left_menu"        android:layout_width="@dimen/swipe_item_menu_width"        android:layout_height="match_parent"        android:layout_gravity="left"        android:background="@color/red500"        android:gravity="center"        android:text="left"        android:textColor="@color/white"/>    <TextView        android:id="@+id/right_menu"        android:layout_width="@dimen/swipe_item_menu_width"        android:layout_height="match_parent"        android:layout_gravity="right"        android:background="@color/blue500"        android:gravity="center"        android:text="right"        android:textColor="@color/white"/>    <include layout="@layout/swipe_content"/></com.aitsuki.swipe.SwipeItemLayout>

<FrameLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="@dimen/swipe_item_height"    android:background="?android:colorBackground"    android:foreground="?listChoiceBackgroundIndicator">    <TextView        android:id="@+id/tv_content"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:gravity="center"        android:textColor="@color/primaryText"        tools:text="Content"/></FrameLayout>

public class Data {    public String content;    public String left;    public String right;    public Data(String content, String left, String right) {        this.content = content;        this.left = left;        this.right = right;    }}

结束。。。



原创粉丝点击