Android自定义实现侧滑菜单功能

侧滑菜单如同QQ往左侧滑动,可以删除对应的消息记录,如下图所示:
Android自定义实现侧滑菜单功能

1 布局实现

item_content.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="#44000000"
    android:gravity="center"
    android:text="Content"
    android:textColor="#000000"
    android:textSize="25sp">
</TextView>

item_menu.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="60dp"
    android:background="#22000000"
    android:gravity="center"
    android:text="Delete"
    android:textColor="#ff0000"
    android:textSize="25sp">
</TextView>

item_main.xml

<com.wang.slidemenu.SlideLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <include android:id="@+id/item_content" layout="@layout/item_content"/>
    <include android:id="@+id/item_menu" layout="@layout/item_menu"/>

</com.wang.slidemenu.SlideLayout>

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wang.slidemenu.MainActivity">
    <ListView
        android:id="@+id/lv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

2 代码实现

MainActivity.java

public class MainActivity extends AppCompatActivity {

    //实例化
    private ListView lv_main;

    private ArrayList<MyBean> myBeens; //准备数据

    private MyAdapter myAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //实例化
        lv_main = (ListView) findViewById(R.id.lv_main);

        //设置适配器
        //准备数据
        myBeens = new ArrayList<>();
        for (int i = 0;i<100;i++){
            myBeens.add(new MyBean("Content" + i));
        }

        //设置适配器
        myAdapter = new MyAdapter();
        lv_main.setAdapter(myAdapter);
    }

    class MyAdapter extends BaseAdapter{
        @Override
        public int getCount() {
            return myBeens.size();
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if(convertView == null){
                convertView = View.inflate(MainActivity.this,R.layout.item_main,null);
                viewHolder = new ViewHolder();
                viewHolder.item_content = (TextView) convertView.findViewById(R.id.item_content);
                viewHolder.item_menu = (TextView) convertView.findViewById(R.id.item_menu);
                convertView.setTag(viewHolder);
            }else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            //根据位置得到内容
            final MyBean myBean = myBeens.get(position);
            viewHolder.item_content.setText(myBean.getName());

            //点击item_content事件
            viewHolder.item_content.setOnClickListener(new View.OnClickListener() { //监听写在里面
                @Override
                public void onClick(View v) {
                    MyBean myBean1 = myBeens.get(position);
                    Toast.makeText(MainActivity.this, myBean1.getName(), Toast.LENGTH_SHORT).show();
                }
            });

            //点击item_menu删除事件
            viewHolder.item_menu.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    SlideLayout slideLayout = (SlideLayout) v.getParent();
                    slideLayout.closeMeun();
                    myBeens.remove(myBean);
//                    myAdapter.notifyDataSetChanged();//刷新
                    notifyDataSetChanged(); //刷新,功能同上面的代码
                }
            });

            SlideLayout slideLayout = (SlideLayout) convertView;
            slideLayout.setOnStateChangeListenter(new MyOnStateChangeListenter());  //监听写在外面

            return convertView;
        }
    }

    private SlideLayout slideLayout;

    class MyOnStateChangeListenter implements SlideLayout.OnStateChangeListenter {
        @Override
        public void onClose(SlideLayout layout) {
            if (slideLayout == layout){
                slideLayout = null;
            }
        }

        @Override
        public void onDown(SlideLayout layout) {
            if (slideLayout != null && slideLayout != null){
                slideLayout.closeMeun();  //优先把打开的关闭
            }
        }

        @Override
        public void onOpen(SlideLayout layout) {
            slideLayout = layout;
        }
    }

    static class ViewHolder{
        TextView item_content;
        TextView item_menu;
    }
}

MyBean.java

public class MyBean {
    private String name;

    public MyBean(String name) {  //生成构造器
        this.name = name;
    }

    //Getter和Setter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

SlideLayout.java

/**
 * 作用:测滑菜单item
 */

public class SlideLayout extends FrameLayout {

    private static final String TAG = SlideLayout.class.getSimpleName();  //得到类名SlideLayout
    private View contentView;
    private View menuView;

    /**
     * 滚动器
     */
    private Scroller scroller;

    /**
     * Content的宽
     */
    private int contentWidth;
    private int menuWidth;
    private int viewHeight; //他们的高都是相同的


    public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        scroller = new Scroller(context);
    }

    /**
     * 当布局文件加载完成的时候回调这个方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        contentView = getChildAt(0);
        menuView = getChildAt(1);
    }

    /**
     * 在测量方法里,得到各个控件的高和宽
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        contentWidth = contentView.getMeasuredWidth();
        menuWidth = menuView.getMeasuredWidth();

        viewHeight = getMeasuredHeight();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        menuView.layout(contentWidth,0,contentWidth+menuWidth,viewHeight);  //默认把Delete隐藏了
    }

    private float startX;
    private float startY;
    private float downX;  //只赋值一次
    private float downY;

    //触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG,"SlideLayout-onTouchEvent-ACTION_DOWN");
                //1.按下记录坐标
                downX = startX = event.getX();
                downY = startY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG,"SlideLayout-onTouchEvent-ACTION_MOVE");
                //2.记录结束值
                float endX = event.getX();
                float endY = event.getY();

                //3.计算偏移量
                float distanceX = endX - startX;

                int toScrollX = (int) (getScrollX() - distanceX);
                //屏蔽非法值
                if (toScrollX < 0){
                    toScrollX = 0;
                }else if (toScrollX > menuWidth){
                    toScrollX = menuWidth;
                }
                scrollTo(toScrollX,getScrollY());

                //重新赋值
                startX = event.getX();
                startY = event.getY();

                //在X轴和Y轴滑动的距离
                float DX = Math.abs(endX - downX);
                float DY = Math.abs(endY - downY);
                if (DX > DY && DX > 8){  //8是像素
                    //水平方向滑动
                    //响应测滑
                    //反拦截---事件给SlideLayout
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG,"SlideLayout-onTouchEvent-ACTION_UP");

                /**
                 * 回弹
                 */
                int totalScrollX = getScrollX(); //偏移量
                if (totalScrollX < menuWidth/2){
                    //关闭 Menu
                    closeMeun();
                }else {
                    //打开 Menu
                    openMenu();
                }

                break;
        }

        return true;
    }

    /**
     * true:拦截孩子的事件,但会执行当前控件的onTouchEvent()方法
     * false:不拦截孩子的事件,事件继续传递
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false; //默认是传给孩子
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                //1.按下记录坐标
                downX = startX = ev.getX();
                Log.e(TAG,"SlideLayout-onTouchEvent-ACTION_DOWN");
                if (onStateChangeListenter != null){
                    onStateChangeListenter.onDown(this);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG,"SlideLayout-onTouchEvent-ACTION_MOVE");
                //2.记录结束值
                float endX = ev.getX();
                float endY = ev.getY();

                //3.计算偏移量
                float distanceX = endX - startX;

                //重新赋值
                startX = ev.getX();

                //在X轴和Y轴滑动的距离
                float DX = Math.abs(endX - downX);
                if (DX > 8){  //8是像素
                   intercept = true;  //拦截
                }

                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        return intercept;
    }

    //打开menu
    public void openMenu() {
        // ----->menuWidth(目标
        int distanceX = menuWidth - getScrollX();
        scroller.startScroll(getScrollX(),getScrollY(),distanceX,getScrollY());
        invalidate(); //强制刷新  会执行 onDraw()方法和computeScroll()方法
        if (onStateChangeListenter != null){
            onStateChangeListenter.onOpen(this);
        }
    }

    //关闭menu
    public void closeMeun() {
        // ----->0(目标)代表隐藏Delete状态
        int distanceX = 0 - getScrollX();
        scroller.startScroll(getScrollX(),getScrollY(),distanceX,getScrollY());
        invalidate(); //强制刷新  会执行 onDraw()方法和computeScroll()方法
        if (onStateChangeListenter != null){
            onStateChangeListenter.onClose(this);
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),scroller.getCurrY());
            invalidate(); //强制刷新
        }
    }


    /**
     * 1.监听SlideLayout状态的改变(接口)
     */
    public interface OnStateChangeListenter{
        void onClose(SlideLayout layout);
        void onDown(SlideLayout layout);
        void onOpen(SlideLayout layout);
    }
    //2.外界实例化出来,来方便接受
    private OnStateChangeListenter onStateChangeListenter;
    /**
     * 设置SlideLayout状态的监听
     * @param onStateChangeListenter
     */
    public void setOnStateChangeListenter(OnStateChangeListenter onStateChangeListenter) {  //3.Setter方法
        this.onStateChangeListenter = onStateChangeListenter;
    }
}