Android 自定义控件----学习笔记----侧滑菜单

仿QQ的侧滑菜单删除。

1.设置布局:两个TextView,一个为内容,一个为删除

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

</TextView>
<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:text="Delete"
    android:textSize="25sp"
    android:background="#88000000"
    android:textColor="#ff0000"
    android:gravity="center"
    android:layout_height="60dp"
    >

</TextView>
<?xml version="1.0" encoding="utf-8"?>
<com.example.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.example.slidemenu.SlideLayout>

2.设置布局中各个控件的位置

指定孩子:

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

在测量方法里面得到各个控件的高和宽 :

    @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);
    }

 

3.打开或关闭菜单(删除)

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.w("www","ACTION_DOWN");
                //1.按下记录坐标
                startX = event.getX();
                startY = event.getY();
                
                break;
            case MotionEvent.ACTION_MOVE:
                Log.w("www","ACTION_MOVE");
                //2.记录结束值
                float endX = event.getX();
                float endY = event.getY();

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

                int toScrollX = (int) (getScrollX()  - distanceX);

                //4.屏蔽非法值
                if(toScrollX < 0){
                    toScrollX = 0;
                }else if(toScrollX > menuWidth){
                    toScrollX = menuWidth;
                }


                scrollTo(toScrollX,0);

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

                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }

4.菜单回弹:

            case MotionEvent.ACTION_UP:
                Log.w("www","ACTION_UP");
                int totalScrollX = getScrollX();//偏移量
                if(totalScrollX < menuWidth / 2){
                    //关闭menu
                    closeMenu();
                }else {
                    //打开menu
                    openMenu();
                }

                break;

关于int totalScrollX = getScrollX();//偏移量----打印日志观察: 

03-03 10:24:11.889 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 0
03-03 10:24:11.889 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -12.612488
03-03 10:24:11.889 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 12
03-03 10:24:11.908 25832-25832/com.example.slidemenu W/www: ACTION_MOVE
03-03 10:24:11.908 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 12
03-03 10:24:11.908 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -9.052307
03-03 10:24:11.908 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 21
03-03 10:24:11.926 25832-25832/com.example.slidemenu W/www: ACTION_MOVE
03-03 10:24:11.926 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 21
03-03 10:24:11.926 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -8.729614
03-03 10:24:11.926 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 29

 

03-03 10:24:13.198 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 165
03-03 10:24:13.198 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -0.9217529
03-03 10:24:13.198 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 165
03-03 10:24:13.216 25832-25832/com.example.slidemenu W/www: ACTION_MOVE
03-03 10:24:13.216 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 165
03-03 10:24:13.216 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -0.56695557
03-03 10:24:13.216 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 165
03-03 10:24:13.269 25832-25832/com.example.slidemenu W/www: ACTION_UP
03-03 10:24:13.269 25832-25832/com.example.slidemenu W/www: ACTION_UP === 165 

int totalScrollX = getScrollX();

每一次的toScrollX的值会是下一次getScrollX()的值。

所以最后up手指离开的时候getScrollX()是偏移量。

    public void closeMenu() {
        //--> 0  目标为0, 使用以下公式 : int distanceX = 目标 - getScrollX();
        int distanceX = 0 - getScrollX();
        Log.w("www",""+getScrollX());
        Log.w("www",""+distanceX);
        scroller.startScroll(getScrollX(),getScrollY(),distanceX,0);
        Log.w("www",""+getScrollX());//当前
        Log.w("www",""+getScrollY());
        invalidate();//强制刷新
        if(onstateCahngeListener != null){
            onstateCahngeListener.onClose(this);
        }
    }

当小于1/2 的时候,执行closeMenu() ,负值,向右移动。

打印日志:

03-03 10:42:57.550 26991-26991/com.example.slidemenu W/www: 78

03-03 10:42:57.550 26991-26991/com.example.slidemenu W/www: -78

03-03 10:42:57.550 26991-26991/com.example.slidemenu W/www: 78

03-03 10:42:57.550 26991-26991/com.example.slidemenu W/www: 0

    public void openMenu() {
        //--> 0  使用以下公式 :int distanceX = 目标 - getScrollX();
        int distanceX = menuWidth - getScrollX();
        Log.w("www",""+getScrollX());
        Log.w("www",""+distanceX);
        scroller.startScroll(getScrollX(),getScrollY(),distanceX,0);
        Log.w("www",""+getScrollX());
        Log.w("www",""+getScrollY());
        invalidate();//强制刷新
        if(onstateCahngeListener != null){
            onstateCahngeListener.onOpen(this);
        }
    }

 当大于1/2 的时候,执行openMenu() ,正值,向左移动。

打印日志:

03-03 11:47:36.588 26991-26991/com.example.slidemenu W/www: 158

03-03 11:47:36.588 26991-26991/com.example.slidemenu W/www: 54

03-03 11:47:36.588 26991-26991/com.example.slidemenu W/www: 158

03-03 11:47:36.588 26991-26991/com.example.slidemenu W/www: 0

 

5.解决item滑动后不能自动打开或关闭

问题:Menu被listview拦截。

解决:SlideLayout反拦截listview。孩子请求反拦截,如果水平方向的滑动大于8dp,就反拦截,响应侧滑菜单。

                if(DX > DY && DX> 8){
                    //水平方向滑动
                    //响应侧滑
                    //反拦截 事件给孩子 SlideLayout
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

 

6.内容视图设置点击事件时不能滑动listview

问题:Content把事件消费掉了,SlideLayout不起作用。

解决:SlideLayout拦截ContentView。设置一个boolean 值,只要水平方向有滑动,就返回true。 true:拦截孩子的事件,但会执行当前控件的onTouchEvent()方法,menu可以实现侧滑。false:不拦截孩子的事件,事件继续传递。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        super.onInterceptTouchEvent(event);
        boolean intercpet = false;//默认传给孩子
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.w("www", "ACTION_DOWN");
                //1.按下记录坐标
                downX = startX = event.getX();
                downY = startY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.w("www", "ACTION_MOVE");
                //2.记录结束值
                float endX = event.getX();

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

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

                //在X轴和Y轴滑动的距离
                float DX = Math.abs(endX - downX);
                if (DX > 8) {
                    intercpet = true;

                }

                break;
            case MotionEvent.ACTION_UP:
                Log.w("www", "ACTION_UP");
                break;
        }
        return intercpet;
    }

作为中间层,事件被上层拦截了,中间反拦截;下层把事件消费掉了,中间拦截事件。

 

7.设置删除功能

问题:删除后下一个打开了。

解决:

            viewHolder.item_menu.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    SlideLayout slideLayout = (SlideLayout) v.getParent();
                    slideLayout.closeMenu();
                    bean.remove(beans);
                    myadapter.notifyDataSetChanged();

                }
            });

 

8.限制只能打开一个item。

解决:使用接口

    /**
     * 监听slidelayout状态的改变
     */
    public interface OnstateCahngeListener{
        void onClose(SlideLayout layout);
        void onDown(SlideLayout layout);
        void onOpen(SlideLayout layout);
    }

    public OnstateCahngeListener onstateCahngeListener;

    /**
     * 设置slidelayout状态的监听
     * @param onstateCahngeListener
     */
    public void setOnstateCahngeListener(OnstateCahngeListener onstateCahngeListener){
        this.onstateCahngeListener = onstateCahngeListener;
    }

在   case MotionEvent.ACTION_DOWN: , openMenu() , closeMenu()中都写入以下代码

                if(onstateCahngeListener != null){
                    onstateCahngeListener.onDown(this);
                }
          

 如果item是打开的,就赋值当前的;如果按下,并且item不等于空,也不等于当前的,就关闭。关闭时把slideLayout 赋值为null。

    SlideLayout slideLayout;
    class MyOnstateCahngeListener implements SlideLayout.OnstateCahngeListener{

        @Override
        public void onClose(SlideLayout layout) {
            if(slideLayout == layout){
                slideLayout = null;
            }

        }

        @Override
        public void onDown(SlideLayout layout) {
            if(slideLayout != null && slideLayout != layout){
                slideLayout.closeMenu();
            }

        }

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

源码:

SlideLayout

public class SlideLayout extends FrameLayout {
    private View contentView;
    private View menuView;
    private int contentWidth;
    private int menuWidth;
    private int viewHeight;

    private Scroller scroller;


    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);
    }

    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.w("www","ACTION_DOWN");
                //1.按下记录坐标
                downX = startX = event.getX();
                downY = startY = event.getY();

                break;
            case MotionEvent.ACTION_MOVE:
                Log.w("www","ACTION_MOVE");
                //2.记录结束值
                float endX = event.getX();
                float endY = event.getY();

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

                int toScrollX = (int) (getScrollX()  - distanceX);
                Log.w("www","ACTION_MOVE === "+getScrollX());
                Log.w("www","ACTION_MOVE === "+getScrollX());
                Log.w("www","ACTION_MOVE === "+toScrollX);


                //4.屏蔽非法值
                if(toScrollX < 0){
                    toScrollX = 0;
                }else if(toScrollX > menuWidth){
                    toScrollX = menuWidth;
                }


                scrollTo(toScrollX,0);

                //5.重新赋值
                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){
                    //水平方向滑动
                    //响应侧滑
                    //反拦截 事件给孩子 SlideLayout
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

                break;
            case MotionEvent.ACTION_UP:
                Log.w("www","ACTION_UP");
                int totalScrollX = getScrollX();//偏移量
                Log.w("www","ACTION_UP === "+totalScrollX);
                if(totalScrollX < menuWidth / 2){
                    //关闭menu
                    closeMenu();
                }else {
                    //打开menu
                    openMenu();
                }

                break;
        }
        return true;
    }

    public void closeMenu() {
        //--> 0  目标为0, 使用以下公式 : int distanceX = 目标 - getScrollX();
        int distanceX = 0 - getScrollX();
        Log.w("www",""+getScrollX());
        Log.w("www",""+distanceX);
        scroller.startScroll(getScrollX(),getScrollY(),distanceX,0);
        Log.w("www",""+getScrollX());//当前
        Log.w("www",""+getScrollY());
        invalidate();//强制刷新
        if(onstateCahngeListener != null){
            onstateCahngeListener.onClose(this);
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),scroller.getCurrY());

        }
    }

    public void openMenu() {
        //--> 0  使用以下公式 :int distanceX = 目标 - getScrollX();
        int distanceX = menuWidth - getScrollX();
        Log.w("www",""+getScrollX());
        Log.w("www",""+distanceX);
        scroller.startScroll(getScrollX(),getScrollY(),distanceX,0);
        Log.w("www",""+getScrollX());
        Log.w("www",""+getScrollY());
        invalidate();//强制刷新
        if(onstateCahngeListener != null){
            onstateCahngeListener.onOpen(this);
        }
    }


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

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

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

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

                break;
            case MotionEvent.ACTION_UP:
                Log.w("www", "ACTION_UP");
                break;
        }
        return intercpet;
    }

    /**
     * 监听slidelayout状态的改变
     */
    public interface OnstateCahngeListener{
        void onClose(SlideLayout layout);
        void onDown(SlideLayout layout);
        void onOpen(SlideLayout layout);
    }

    public OnstateCahngeListener onstateCahngeListener;

    /**
     * 设置slidelayout状态的监听
     * @param onstateCahngeListener
     */
    public void setOnstateCahngeListener(OnstateCahngeListener onstateCahngeListener){
        this.onstateCahngeListener = onstateCahngeListener;
    }

}

MainAvtivity

public class MainActivity extends AppCompatActivity {
    private ListView listview;
    private ArrayList<Bean> bean;
    private MyAdapter myadapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listview  = (ListView)findViewById(R.id.listview);

        //准备数据
        //设置适配器
        bean = new ArrayList<>();
        for(int i = 0;i<100; i++){
            bean.add(new Bean("Content"+i));
        }
        myadapter = new MyAdapter();
        listview.setAdapter(myadapter);
    }

    class MyAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return bean.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 Bean beans = bean.get(position);
            viewHolder.item_content.setText(beans.getName());

            viewHolder.item_content.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Bean bean1 = bean.get(position);
                    Toast.makeText(MainActivity.this,"Content",Toast.LENGTH_SHORT).show();
                }
            });

            viewHolder.item_menu.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    SlideLayout slideLayout = (SlideLayout) v.getParent();
                    slideLayout.closeMenu();
                    bean.remove(beans);
                    myadapter.notifyDataSetChanged();

                }
            });

            SlideLayout slideLayout = (SlideLayout) convertView;
            slideLayout.setOnstateCahngeListener(new MyOnstateCahngeListener());

            return convertView;
        }
    }

    SlideLayout slideLayout;
    class MyOnstateCahngeListener implements SlideLayout.OnstateCahngeListener{

        @Override
        public void onClose(SlideLayout layout) {
            if(slideLayout == layout){
                slideLayout = null;
            }

        }

        @Override
        public void onDown(SlideLayout layout) {
            if(slideLayout != null && slideLayout != layout){
                slideLayout.closeMenu();
            }

        }

        @Override
        public void onOpen(SlideLayout layout) {
            slideLayout = layout;
        }
    }
    static class ViewHolder{
        TextView item_content;
        TextView item_menu;
    }
}

Bean

public class Bean {
    public String getName() {
        return name;
    }

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

    String name;
    Bean(String name){
        this.name = name;
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值