Android自定义控件6----继承ViewGroup自定义侧滑删除菜单

效果图:


思路

1先自定义一个滑动布局

写一个类MySlideLayout继承RelativeLayout

重写onFinishInflate(布局加载完成后调用),在其中拿到子控件的对象

重写onMeasure,在其中拿到子控件的宽和本控件的高

重写onLayout,设置删除按钮的位置

重写onTouchEvent在其中实现控件的滑动

2把滑动布局作为ListView的条目,展示

出现bug1

listView上下滑动

滑动布局(MySlideLayout)套在ListView中,当左右滑动的,一部分,再

上下滑动时,会出现左右滑动停止

原因是ListView滑动事件默认把子控件(MySlideLayout)的事件给拦截了,

只要子控件在左右滑动时反拦截,让ListView把事件传递给子控件(MySlideLayout)

就可以了。

主要代码:在onTouchEvent的MotionEvent.ACTION_MOVE中

 if(DX>8) {

//                  反拦截
//                  请求 驳回 拦截 触摸事件
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

出现Bug2

点击MySlideLayout的子控件

当滑动布局(MySlideLayout)中的子控件TextView设置点击事件后,滑动布局(MySlideLayout)

就无法左右滑动

原因是滑动布局(MySlideLayout)把事件传给了子控件,所以不能左右滑动了,可以判断左右

滑动时不把事件传给子控件(拦截),当点击时,把事件传给子控件。

主要代码:在onInterceptTouchEvent中的MotionEvent.ACTION_MOVE中

  if(DX>8) {
//                  水平滑动,执行onTouchEvent方法,否则执行的子类的事件
                    intercept=true;
                }

出现Bug3

点击别的条目,关闭已打开的条目

用接口回调调适配器中处理

主要 setOnStateChangeListenter 方法

完美,下面贴出所有代码:


MySlideLayout中
package com.zhh.myapplication;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.Scroller;

import com.orhanobut.logger.Logger;

/**
 * Created by 16838 on 2018/6/26.
 */
public class MySlideLayout extends RelativeLayout{
//  内容TextView
    View contentView;
//  删除TextView
    View delView;

//  内容的宽
    int contentViewWidth;
//  删除按钮的宽
    int delViewWidth;
//  布局的高
    int layoutHeight;

    private float startX;
    private float startY;
    private float downX;//只赋值一次
    private float downY;
    //  滑动对象
    private Scroller scroller;
    /**
     * 构造方法
     * @param context
     * @param attrs
     */
    public MySlideLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        scroller = new Scroller(context);
    }

    /**
     * 布局加载完成后调用
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        contentView=getChildAt(0);
        delView=getChildAt(1);
    }

    /**
     * 测量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        contentViewWidth = contentView.getMeasuredWidth();
        delViewWidth = delView.getMeasuredWidth();
        layoutHeight = getMeasuredHeight();
    }

    /**
     * 设置子字控件的位置
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        delView.layout(contentViewWidth,0,contentViewWidth+delViewWidth,layoutHeight);
    }

    /**
     * 判断事件是否传给子控件
     * true:拦截孩子的事件,但会执行当前控件的onTouchEvent()方法
     * false:不拦截孩子的事件,事件继续传递
     * @param event
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //1.按下记录坐标
                downX = startX = event.getX();
                if(onStateChangeListenter!=null) {
                    onStateChangeListenter.onDown(this);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("111","SlideLayout-onTouchEvent-ACTION_MOVE");
                //2.记录结束值
                float endX = event.getX();
                //在X轴和Y轴滑动的距离
                float DX = Math.abs(endX-downX);
                if(DX>8) {
//                  水平滑动,执行onTouchEvent方法,否则执行的子类的事件
                    intercept=true;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return intercept;
    }

    /**
     * 触摸事件中实现滑动
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //1.按下记录坐标
//              event.getX()指的是当前控件(MySlideLayout)左上方为原点坐标,X轴上的坐标,向右越大
//              坐标和按下的位置有关系(比如这个控件宽度铺满屏幕,在左边按下开始坐标startX很小,在中间按下坐标startX就较大)
                downX = startX = event.getX();
                downY = startY = event.getY();

                break;
            case MotionEvent.ACTION_MOVE:
                //2.记录结束值
                float endX = event.getX();
                float endY = event.getY();
                Logger.t("111").d("startX>"+startX+">>>"+"endX>"+endX);
                //3.计算偏移量
//              distanceX = endX - startX 这个是公式
                float distanceX = endX - startX;

//              getScrollX()就是当前视图(MySlideLayout)相对于自己左上角为原点坐标,的偏移量
//              child.getScrollX()当前视图(MySlideLayout)子控件相对于自己左上角为原点坐标,的偏移量
//              当滑动的是当前的控件(MySlideLayout)时,getScrollX()有值,(本例滑动的是自己),child.getScrollX()是0;
//              当滑动控件是子控件时child.getScrollX()有值,getScrollX()是0
//              简单的说是滑动谁,谁的getScrollX()有值
//              从左滑到右是负值,从右到左是正值

                Logger.t("222").d(">>>contentView.getScrollX()"+contentView.getScrollX());
                int dX = (int) (getScrollX() - distanceX);
                if (dX < 0) {
                    dX = 0;
                } else if (dX > delViewWidth) {
                    dX = delViewWidth;
                }
//              scrollTo中的dX,从左滑到右是负值,从右到左是正值
//              scrollTo中的dX,中的x就是(getScrollX() - distanceX),这是公式
//             (distanceX = endX - startX;)
//              代表的意思就是:将父视图左上角定为(0,0),在移动这个屏幕的左上角到父视图的点(x,y)处,
//              (注意:此处的x,y是根据父视图的坐标系来定的)
                scrollTo(dX, getScrollY());
                startX = event.getX();
                startY = event.getY();
                //在X轴和Y轴滑动的距离
                float DX = Math.abs(endX-downX);
                float DY = Math.abs(endY-downY);
//              水平滑动的时候,让listView把事件传递过来,MySlideLayout处理,竖直滑动listView自己处理,不用传递过来
//              简单的说水平滑动,禁止竖直滑动,竖直滑动,禁止水平滑动
                if(DX>8) {
//                  反拦截
//                  请求 驳回 拦截 触摸事件
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.e("111", "SlideLayout-onTouchEvent-ACTION_UP");
                int totalScrollX = getScrollX();//偏移量
                if(totalScrollX < delViewWidth/2){
                    //关闭Menu
                    closeMenu();
                }else{
                    //打开Menu
                    openMenu();
                }
                break;
        }

        return true;
    }
    /**
     * 打开menu
     */
    public void openMenu() {
        //--->menuWidth
        int distanceX = delViewWidth - getScrollX();
//      scroller.startScroll中的坐标
//      中第一个参数startX,参照父视图为原点坐标的坐标系,滑屏时经常使用getScrollX()(代表品目左边缘)
//      中第三个参数dx,表示正值向左滑,负值向右滑。
//      公式:目标 - getScrollX();
//      这里要移动的目标是控件的宽,所以就是int distanceX = delViewWidth - getScrollX();
        scroller.startScroll(getScrollX(), getScrollY(), distanceX, getScrollY());
        invalidate();//强制刷新
        if(onStateChangeListenter!=null) {
            onStateChangeListenter.onOpen(this);
        }
    }

    /**
     * 关闭menu
     */
    public void closeMenu() {
        //--->0
        int distanceX = 0 - getScrollX();
        scroller.startScroll(getScrollX(), getScrollY(), distanceX, getScrollY());
        invalidate();//强制刷新
        if(onStateChangeListenter!=null) {
            onStateChangeListenter.onClose(this);
        }
    }
    /**
     * scroller对象滑动平滑的方法
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),scroller.getCurrY());
            invalidate();
        }
    }

    /**
     * 设置SlideLayout状态的监听
     * @param onStateChangeListenter
     *
     */
    private  OnStateChangeListenter onStateChangeListenter;
    public void setOnStateChangeListenter(OnStateChangeListenter onStateChangeListenter) {
        this.onStateChangeListenter = onStateChangeListenter;
    }

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


}

MainActivity中
package com.zhh.myapplication;

import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {
    private ListView lvMain;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lvMain = findViewById(R.id.lvMain);
//      拿到数据
        List<String> list = getData();
        LvAdapter lvAdapter = new LvAdapter(list, MainActivity.this);
        lvMain.setAdapter(lvAdapter);
    }

    private List<String> getData() {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            list.add("内容数据" + i);
        }

        return list;
    }


}
LvAdapter中
package com.zhh.myapplication;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

/**
 * Created by 16838 on 2018/6/26.
 */
public class LvAdapter extends BaseAdapter {
    List<String> list;
    Context context;

    public LvAdapter(List<String> list, Context context) {
        this.list = list;
        this.context = context;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = LayoutInflater.from(context).inflate(R.layout.item_main, null);
            viewHolder.tvContent = convertView.findViewById(R.id.tvContent);
            viewHolder.tvDel = convertView.findViewById(R.id.tvDel);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.tvContent.setText(list.get(position));
//      内容控件的点击事件
        viewHolder.tvContent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context, "内容点击事件", Toast.LENGTH_SHORT).show();
            }
        });
//      删除控件的点击事件
        viewHolder.tvDel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context, "删除按钮", Toast.LENGTH_LONG).show();
                //                  先关闭再删除
                MySlideLayout slideLayout = (MySlideLayout) v.getParent();
                slideLayout.closeMenu();
                list.remove(position);
                notifyDataSetChanged();
            }
        });
        MySlideLayout mySlideLayout= (com.zhh.myapplication.MySlideLayout) convertView;
        mySlideLayout.setOnStateChangeListenter(new MyOnStateChangeListenter());
        return convertView;
    }

    /**
     * 把MySlideLayout中的事件回调出来
     */
    private MySlideLayout slideLayout;
    class MyOnStateChangeListenter implements MySlideLayout.OnStateChangeListenter {

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

        @Override
        public void onDown(MySlideLayout layout) {
//          判断点击的不是同一个,关掉上一个
            if (slideLayout != null && slideLayout != layout) {
                slideLayout.closeMenu();
            }
        }

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


    class ViewHolder {
        TextView tvContent;
        TextView tvDel;
    }


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

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

activity_main.xml中

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >

    <ListView
        android:id="@+id/lvMain"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

item_main.xml中

<?xml version="1.0" encoding="utf-8"?>
<com.zhh.myapplication.MySlideLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp">
    <TextView
        android:id="@+id/tvContent"
        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
         android:id="@+id/tvDel"
         android:layout_width="wrap_content"
         android:layout_height="60dp"
         android:background="#22000000"
         android:gravity="center"
         android:text="Delete"
         android:textColor="#ff0000"
         android:textSize="25sp" />



</com.zhh.myapplication.MySlideLayout>

源码下载:

TestLayout----slidetest3

https://download.csdn.net/download/zhaihaohao1/10500804

Myself ---- cehua1

http://download.csdn.net/download/zhaihaohao1/10111351

参考视频:

http://www.gulixueyuan.com/course/124/learn#lesson/1928

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值