彷QQ滑动删除效果—侧滑删除(置顶、标记、未读标记功能)

实现效果图

1:功能介绍

QQ条目删除,主要实现的主条目和删除条目的 摆放,使用的类是ViewDragHelper

2、布局页面

这里写图片描述

 1.main的xml文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context="itheima.com.qqitemdelete.MainActivity">
    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="#ccc"
        android:dividerHeight="2dp"
        />
</RelativeLayout>

这里写图片描述

 2.delete的删除条目——layout_delete
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="60dp"
    android:orientation="horizontal" >
    <TextView android:layout_width="70dp"
        android:layout_height="match_parent"
        android:textSize="18sp"
        android:textColor="#ffffff"
        android:gravity="center"
        android:background="#D4D4D7"
        android:text="置顶"/>
    <TextView android:layout_width="120dp"
        android:layout_height="match_parent"
        android:textSize="18sp"
        android:textColor="#ffffff"
        android:gravity="center"
        android:background="#FF9901"
        android:text="标记未读"/>

    <TextView android:layout_width="70dp"
        android:layout_height="match_parent"
        android:textSize="18sp"
        android:textColor="#ffffff"
        android:id="@+id/tv_delete"
        android:gravity="center"
        android:background="#FF3A30"
        android:text="删除"/>
</LinearLayout>

这里写图片描述

   3.主条目的布局文件——layout_content
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:paddingLeft="15dp"
    android:background="#fff"
    android:gravity="center_vertical"
    android:orientation="horizontal" >

    <ImageView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@mipmap/head_1"/>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    <TextView 
android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#99000000"
        android:id="@+id/tv_name"
        android:layout_marginLeft="10dp"
        android:textSize="20sp"
        android:text="名称"/>
    </LinearLayout>
</LinearLayout>
4.adapter的item的布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <itheima.com.qqitemdelete.SwipeLayout
        android:id="@+id/swipeLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <!--content的布局-->
        <include layout="@layout/layout_content"/>
        <!--delete的布局-->
        <include layout="@layout/layout_delete"/>
    </itheima.com.qqitemdelete.SwipeLayout>

3、先写一个类SwipeLayout,继承自FrameLayout

public class SwipeLayout extends FrameLayout{
        private ViewDragHelper viewDragHelper;
        private View content;
        private View delete;
        private long mDownTime;
        private boolean isOpen = true;
        //实现三个构造函数
        public SwipeLayout(Context context) {
            this(context, null);
        }
        public SwipeLayout(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
        public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            viewDragHelper = ViewDragHelper.create(this, callback);
        }
        //在主页xml页面读取界面时候获取子View
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            content = getChildAt(0);
            delete = getChildAt(1);
        }

4、在onLayout方法中对contentView和deleteView进行摆放

  @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
           //摆放内容控件
            content.layout(0,0,content.getMeasuredWidth(),content.getMeasuredHeight());
            //获取内容控件的宽
            int L= content.getRight();
            delete.layout(L,0,L+delete.getMeasuredWidth(),delete.getMeasuredHeight());
        }
        //把触摸事件传给ViewDragHelper处理,经常两种方法
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            boolean b = viewDragHelper.shouldInterceptTouchEvent(ev);
            return b;
        }
        6.1、解决第一个bug的思路是:在onTouchEvent方法判断当前手指移动的方向到底是偏向于水平还是偏向于垂直,如果是偏向于水平那么就认为用户是希望滑动item,那么则请求父View不要去拦截事件
        float downX,downY;
         @Override
             public boolean onTouchEvent(MotionEvent event) {
                 switch (event.getAction()) {
                     case MotionEvent.ACTION_DOWN:
                         //获取按下的坐标
                         downX = event.getX();
                         downY = event.getY();
                         //获取按下的时间
                         mDownTime = System.currentTimeMillis();
                         break;
                     case MotionEvent.ACTION_MOVE:
                         float moveX = event.getX();
                         float moveY = event.getY();
                         //计算移动距离
                         float dx = moveX-downX;
                         float dy = moveY-downY;
                         //判断到底偏向哪个方向
                         if(Math.abs(dx)>Math.abs(dy)){
                             //说明是偏向水平方向,那么就认为用户想滑动条目,此时应该让listview不要拦截
                             requestDisallowInterceptTouchEvent(true);
                         }
                         break;
                         6.2、由于我们重写onTouchEvent处理了事件,导致ListView的条目点击无效了,此时最有效最简单的做法是自己去判断触摸事件实现点击行为,思路是:记录按下的坐标和时间,在抬起的时候计算整个按下抬起的时间和距离,如果时间小于400毫秒,并且距离小于touchSlop,则认为是点击事件,事实上系统也是这样实现点击事件的:
                     case MotionEvent.ACTION_UP:
                         //1.计算按下抬起的时间
                         long duration = System.currentTimeMillis()-mDownTime;
                         //2.计算按下抬起的距离
                         float deltaX = event.getX() - downX;
                         float deltaY = event.getY() - downY;
                         //deltaX的平方+deltaY的平方和进行开放,获得点与点的直线距离
                         float distance = (float) Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
                          //3. 配置,如果duration小于500,并且distance小于8px
                         ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
                         if (duration < getLongPressTimeout() && distance < viewConfiguration.getScaledTouchSlop()) {//8dp
                             //作用就是执行OnClickListener的onClick方法
                               if(isOpen){
                                 openDeleteMenu();
                                 isOpen=false;
                             }else{
                                 closeDeleteMenu();
                                 isOpen=true;
                             }
                         }
                         break;
                 }
                viewDragHelper.processTouchEvent(event);
                 return true;
             }

5、利用ViewDragHelper实现让SwipeLayout的2个子View进行拖拽移动,主要是Callback的实现

ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
            //1.捕获子View,谁为true,下面的方法就执行谁
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                return true;
            }
            //2.鸡肋的方法,大于1就行
            @Override
            public int getViewHorizontalDragRange(View child) {
                return 1;
            }
            //修改水平方向滑动的距离
            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                //限制content
                if(child==content){
                    if(left>0){
                        left=0;
                    }else if(left<-delete.getMeasuredWidth()){
                        left=-delete.getMeasuredWidth();
                    }
                }else if(child==delete){
                    //限制delete
                    if(left>content.getMeasuredWidth()){
                        left = content.getMeasuredWidth();
                    }else if(left<(content.getMeasuredWidth()-delete.getMeasuredWidth())){
                        left = (content.getMeasuredWidth()-delete.getMeasuredWidth());
                    }
                }
                return left;
            }
            //经常执行一些伴随动画
            @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
                super.onViewPositionChanged(changedView, left, top, dx, dy);
                //如果移动的是content,那么让delete伴随移动
                if(changedView==content){
                    ViewCompat.offsetLeftAndRight(delete,dx);
                }else if(changedView==delete){
                //让content进行伴随移动
                    ViewCompat.offsetLeftAndRight(content,dx);
                }
                //回调接口的方法
                if(listener!=null){
                    if(content.getLeft()==0){
                        listener.onClose(SwipeLayout.this);
                    }else if(content.getLeft()==-delete.getMeasuredWidth()){
                        listener.onOpen(SwipeLayout.this);
                    }
                }
            }
            //手指离开执行的方法
            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
                if(content.getLeft()>-delete.getMeasuredWidth()/2){
                    //关闭
                    closeDeleteMenu();
                    isOpen=true;
                }else{
                    //打开
                    openDeleteMenu();
                    isOpen=false;
                }
            }
        };
        /**
         * 打开页面
         */
        public void openDeleteMenu() {
            viewDragHelper.smoothSlideViewTo(content,-delete.getMeasuredWidth(),0);
            ViewCompat.postInvalidateOnAnimation(this);
        }
        /**
         * 关闭页面
         */
        public void closeDeleteMenu() {
            //平滑至
            viewDragHelper.smoothSlideViewTo(content,0,0);
            //动画更新
            ViewCompat.postInvalidateOnAnimation(this);
        }
    //如果动画还没有结束,继续刷新  compute计算
        @Override
        public void computeScroll() {
            super.computeScroll();
            if(viewDragHelper.continueSettling(true)){
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
        //创建回调,把SwipeLayout返回Activity中
        private OnSwipeListener listener;
        public void setOnSwipeListener(OnSwipeListener listener){
            this.listener=listener;
        }
        public interface OnSwipeListener{
            void onOpen(SwipeLayout currentLayout);
            void onClose(SwipeLayout currentLayout);
        }
    }

6、然后将实现好的可滑动的SwipeLayout放入ListView的adapter的布局中,此时我们遇到2个bug:

    1、当我们左右拖动item滑动时,再上下滑动会遇到事件被ListView捕获并处理,导致我们无法继续控制item的滑动;
    2、我们可以同时滑动出多个item,而需求是只能允许一个item是打开的;
public class MainActivity extends AppCompatActivity {
    @BindView(R.id.list_view)
    ListView listView;
    boolean isOpen=true;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        //设置适配器
        listView.setAdapter(new MyAdapter());
        //监听listView的滚动,实现功能:当条目滑动的时候,菜单关闭
        listView.setOnScrollListener(new AbsListView.OnScrollListener(){
            @Override
            public void onScrollStateChanged(AbsListView absListView, int i) {
                if(openedLayout!=null){
                    openedLayout.closeDeleteMenu();
                }
            }
            @Override
            public void onScroll(AbsListView absListView, int i, int i1, int i2) {
            }
        });
    }
     //用来记录打开的SwipeLayout
     SwipeLayout openedLayout;
     class MyAdapter extends BaseAdapter implements SwipeLayout.OnSwipeListener {
         @Override
         public int getCount() {
             return Constant.NAMES.length;
         }
         @Override
         public Object getItem(int i) {
             return null;
         }
         @Override
         public long getItemId(int i) {
             return 0;
         }
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
             MyHolder myHolder = null;
             //简单的复用缓存
             if (convertView == null) {
                 convertView = View.inflate(parent.getContext(), R.layout.adapter_list_item, null);
                 myHolder = new MyHolder(convertView);
                 convertView.setTag(myHolder);
             } else {
                 myHolder = (MyHolder) convertView.getTag();
             }
             //绑定数据
             myHolder.tvName.setText(Constant.NAMES[position]);
             //设置监听器
             myHolder.swipeLayout.setOnSwipeListener(this);
             return convertView;
         }
         @Override
         public void onOpen(SwipeLayout currentLayout) {
             //判断SwipeLayout不为空并且不等于当前条目,就关闭其他条目
             if (openedLayout!= null && openedLayout!=currentLayout) {
                 openedLayout.closeDeleteMenu();
             }
             openedLayout=currentLayout;
         }
         @Override
         public void onClose(SwipeLayout currentLayout) {
             //如果传来的条目是自己,就把SwipeLayout置为空
             if (openedLayout == currentLayout) {
                 openedLayout = null;
             }
         }
     }
        static class MyHolder {
            @BindView(R.id.tv_name)
            TextView tvName;
            @BindView(R.id.tv_delete)
            TextView tvDelete;
            @BindView(R.id.swipeLayout)
            SwipeLayout swipeLayout;
            MyHolder(View view) {
            ButterKnife.bind(this, view);
            }
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值