美文网首页Android精选高级UI
(七)仿微信发布朋友圈拖拽删除

(七)仿微信发布朋友圈拖拽删除

作者: 达浪儿 | 来源:发表于2018-11-29 18:06 被阅读78次

    效果图如下:


    demo7.gif

    实现过程:

    1.先从布局入手。要实现recyclerview全屏的拖拽,布局思路一定要正确。布局关系如下:


    布局关系图.png

    布局有一点需要注意:
    recyclerview高度必须match_parent,这样才能全屏拖拽,但是为什么又要位于 editText之下呢。
    这样是为了防止editText焦点被夺取,无法输入。位于editText之下后recyclerview又怎么滑出自己的布局呢,这时需要一个属性设置clipChildren=false.允许子View超出父View。

    2.拖拽功能实现。 利用ItemTouchHelper

    (1)自定义一个类集成并实现ItemTouchHelper.Callback(功能核心,代码里有注释)

    /**
     * created by dalang at 2018/11/26
     * 微信拖拽排序删除
     */
    public class WXTouchHelper extends ItemTouchHelper.Callback {
    
        private int dragFlags;
        private int swipeFlags;
        private BGARecyclerViewAdapter adapter;
        private List<String> imagesList;//图片的顺序与拖拽顺序保持一致
        private boolean up;//手指抬起标记位
        private NestedScrollView scrollView;
    
        public WXTouchHelper(BGARecyclerViewAdapter adapter, List<String> imagesList, NestedScrollView scrollView) {
            this.adapter = adapter;
            this.imagesList = imagesList;
            this.scrollView=scrollView;
        }
    
        /**
         * 设置item是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向
         *
         * @param recyclerView
         * @param viewHolder
         * @return
         */
        @Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            //判断 recyclerView的布局管理器数据
            if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {//设置能拖拽的方向
    
                dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
    
                swipeFlags = 0;//0则不响应事件
            }
            return makeMovementFlags(dragFlags, swipeFlags);
        }
    
        /**
         * 当用户从item原来的位置拖动可以拖动的item到新位置的过程中调用
         *
         * @param recyclerView
         * @param viewHolder
         * @param target
         * @return
         */
        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder 
        viewHolder, RecyclerView.ViewHolder target) {
            int fromPosition = viewHolder.getAdapterPosition();//得到item原来的position
            int toPosition = target.getAdapterPosition();//得到目标position
            //因为没有将 +号的图片 加入imageList,所以不用imageList.size-1 此处限制不能移动到recyclerView最后一位
            if (toPosition == imagesList.size()  || imagesList.size()  == fromPosition) {
                return false;
            }
            if (fromPosition < toPosition) {
                for (int i = fromPosition; i < toPosition; i++) {
    
                    Collections.swap(imagesList, i, i + 1);
                }
            } else {
                for (int i = fromPosition; i > toPosition; i--) {
    
                    Collections.swap(imagesList, i, i - 1);
                }
            }
            adapter.notifyItemMoved(fromPosition, toPosition);
            return true;
        }
    
        /**
         * 设置是否支持长按拖拽
         * 此处必须返回false
         * 需要在recyclerView长按事件里限制,否则最后+号长按后扔可拖拽
         * @return
         */
        @Override
        public boolean isLongPressDragEnabled() {
            return false;
        }
    
        /**
         * @param viewHolder
         * @param direction
         */
        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    
        }
    
        /**
         * 当用户与item的交互结束并且item也完成了动画时调用
         *
         * @param recyclerView
         * @param viewHolder
         */
        @Override
        public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            super.clearView(recyclerView, viewHolder);
            adapter.notifyDataSetChanged();
            initData();
            if (dragListener != null) {
                dragListener.clearView();
            }
        }
    
        /**
         * 重置
         */
        private void initData() {
            if (dragListener != null) {
                dragListener.deleteState(false);
                dragListener.dragState(false);
            }
            up = false;
        }
    
        /**
         * 自定义拖动与滑动交互
         *
         * @param c
         * @param recyclerView
         * @param viewHolder
         * @param dX
         * @param dY
         * @param actionState
         * @param isCurrentlyActive
         */
        @Override
        public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
        float dX, float dY, int actionState, boolean isCurrentlyActive) {
            if (null == dragListener) {
                return;
            }
           //recyclerview上面的editText的高度为100
            int editTextHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_100);
          //删除按钮高度
            int buttonHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_50);
            /**
             * item间隔10dp,因为item的xml布局中有个10dp的空白,
             * 拖拽时是拖拽整个itemView所有导致底部10dp空白先接触删除按钮所以在判断阈值时应该考虑此10dp,
             * 如果是采用addItemDecoration添加分割线就不用考虑这10dp,但是拖拽时会出现分割线遮挡的情况,具体效果可以自己实验一下
             */
            int spaceHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_10);//
            /**
             * scrollView.getHeight()-editTextHeight 为recyclerview的高度
             * 此处不用onChildDraw里的参数recyclerView.getHeight来计算,因为当添加图片至超出屏幕高度
             * 即scrollView可以滑动后获取的recyclerview不准确,亲测。
             */
            if (dY>=(scrollView.getHeight()-editTextHeight)
                    - viewHolder.itemView.getBottom()//item底部距离recyclerView顶部高度
                    -buttonHeight
                    +scrollView.getScrollY()
                    +spaceHeight) {//拖到删除处
                dragListener.deleteState(true);
                if (up) {//在删除处放手,则删除item
                    //先设置不可见,如果不设置的话,会看到viewHolder返回到原位置时才消失
                  //,因为remove会在viewHolder动画执行完成后才将viewHolder删除
                    viewHolder.itemView.setVisibility(View.INVISIBLE);
                    imagesList.remove(viewHolder.getAdapterPosition());
                    dragListener.deleteOk();
                    adapter.notifyItemRemoved(viewHolder.getAdapterPosition());
                    initData();
                    return;
                }
            } else {//没有到删除处
              //如果viewHolder不可见,则表示用户放手,重置删除区域状态
                if (View.INVISIBLE == viewHolder.itemView.getVisibility()) {
                    dragListener.dragState(false);
                }
                dragListener.deleteState(false);
            }
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
        }
    
        /**
         * 当长按选中item的时候(拖拽开始的时候)调用
         *
         * @param viewHolder
         * @param actionState
         */
        @Override
        public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
            if (ItemTouchHelper.ACTION_STATE_DRAG == actionState && dragListener != null) {
                dragListener.dragState(true);
            }
            super.onSelectedChanged(viewHolder, actionState);
        }
    
        /**
         * 设置手指离开后ViewHolder的动画时间,在用户手指离开后调用
         *
         * @param recyclerView
         * @param animationType
         * @param animateDx
         * @param animateDy
         * @return
         */
        @Override
        public long getAnimationDuration(RecyclerView recyclerView, int animationType, 
        float animateDx, float animateDy) {
            //手指放开
            up = true;
            return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
        }
    
        public interface DragListener {
            /**
             * 用户是否将 item拖动到删除处,根据状态改变颜色
             *
             * @param delete
             */
            void deleteState(boolean delete);
    
            /**
             * 是否于拖拽状态
             *
             * @param start
             */
            void dragState(boolean start);
    
            /**
             * 当用户与item的交互结束并且item也完成了动画时调用
             */
            void clearView();
    
    
            /**
             * 当删除完成后调用
             */
            void deleteOk();
        }
    
        private DragListener dragListener;
    
        public void setDragListener(DragListener dragListener) {
            this.dragListener = dragListener;
        }
    

    (2)activity调用及处理

    拖拽事件开启

          //绑定recyclerview
            WXTouchHelper myCallBack = new WXTouchHelper(photoPublishAdapter, imgSelected,scrollView);
            itemTouchHelper = new ItemTouchHelper(myCallBack);
            itemTouchHelper.attachToRecyclerView(recyclerPhoto);
    
    
            recyclerPhoto.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerPhoto) {
                @Override
                public void onItemClick(RecyclerView.ViewHolder viewHolder) {
                    if (viewHolder.getAdapterPosition() == imgSelected.size()) {
                        DialogUtil.uploadMultiplePhoto(mActivity, getTakePhoto(), limit);
                    } else {
                    if (imgSelected.size() != 0) {
                        Intent intent = new Intent(mActivity, BigPhotoActivity.class);
                        intent.putStringArrayListExtra("imgUrls", (ArrayList<String>) imgSelected);
                        intent.putExtra("position", viewHolder.getAdapterPosition());
                        mSwipeBackHelper.forward(intent);
                    }
                    }
                }
    
            //长按事件中开启拖拽 需要判断position不是+号图片
                @Override
                public void onLongClick(RecyclerView.ViewHolder viewHolder) {
                    if (viewHolder.getAdapterPosition() != imgSelected.size()) {
                        BGAKeyboardUtil.closeKeyboard(mActivity);
                        itemTouchHelper.startDrag(viewHolder);
                    }
                }
            });
    

    删除按钮显示

      myCallBack.setDragListener(new WXTouchHelper.DragListener() {
                @Override
                public void deleteState(boolean delete) {
                    if (delete) {
                        tvDelete.setAlpha(0.8f);
                        tvDelete.setText("松手即可删除");
                    } else {
                        tvDelete.setAlpha(0.5f);
                        tvDelete.setText("拖到此处删除");
                    }
                }
    
                @Override
                public void dragState(boolean start) {
                    if (start) {
                        tvDelete.setVisibility(View.VISIBLE);
                    } else {
                        tvDelete.setVisibility(View.GONE);
                    }
                }
    
                @Override
                public void clearView() {
                  //删除图片后需要重新计算recyclerview下面布局的margin
                    fixBottom();
    
                }
    
                @Override
                public void deleteOk() {
                    //删除后重新计算图片选择数量
                    limit = 9 - imgSelected.size();
    
                }
            });
    

    底部布局处理

       /**
         * 处理recyclerView下面的布局
         */
        private void fixBottom() {
    
            int row = photoPublishAdapter.getItemCount() / 3;
            row = (0 == photoPublishAdapter.getItemCount() % 3) ? row : row + 1;//少于3为1行
            row = (4 == row) ? 3 : row;//最多为三行
    
            int width = DisplayUtil.getScreenWidth(mActivity);
            int itemWidth = (int) (width - getResources().getDimension(R.dimen.dimen_60)) / 3;//item宽高
            int itemSpace=(int) getResources().getDimension(R.dimen.dimen_10);//item间隔
            int marginTop = (getResources().getDimensionPixelSize(R.dimen.recycle_margin_top)
                    + itemWidth * row
                    +itemSpace*(row-1)
                    + getResources().getDimensionPixelSize(R.dimen.bottom_margin_top));
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) llBottom.getLayoutParams();
            params.setMargins(0, marginTop, 0, 0);
            llBottom.setLayoutParams(params);
    
        }
    

    3.scrollView包裹editText处理其滚动冲突

    /**
     * created by dalang at 2018/11/28
     */
    @SuppressLint("AppCompatCustomView")
    public class EditTextWithScrollView extends EditText {
    
        //滑动距离的最大边界
        private int mOffsetHeight;
        //是否到顶或者到底的标志
        private boolean mBottomFlag = false;
        private boolean mCanVerticalScroll;
    
        public EditTextWithScrollView(Context context) {
            super(context);
            init();
        }
    
        public EditTextWithScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public EditTextWithScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            mCanVerticalScroll = canVerticalScroll();
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN)
                //如果是新的按下事件,则对mBottomFlag重新初始化
                mBottomFlag = false;
            //如果已经不要这次事件,则传出取消的信号,这里的作用不大
            if (mBottomFlag)
                event.setAction(MotionEvent.ACTION_CANCEL);
    
            return super.dispatchTouchEvent(event);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            boolean result = super.onTouchEvent(event);
            if (mCanVerticalScroll) {
                //如果是需要拦截,则再拦截,这个方法会在onScrollChanged方法之后再调用一次
                if (!mBottomFlag)
                    getParent().requestDisallowInterceptTouchEvent(true);
            } else {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            return result;
        }
    
        @Override
        protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
            super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
            if (vert == mOffsetHeight || vert == 0) {
                //这里触发父布局或祖父布局的滑动事件
                getParent().requestDisallowInterceptTouchEvent(false);
                mBottomFlag = true;
            }
        }
    
        /**
         * EditText竖直方向是否可以滚动
         *
         * @return true:可以滚动   false:不可以滚动
         */
        private boolean canVerticalScroll() {
            //滚动的距离
            int scrollY = getScrollY();
            //控件内容的总高度
            int scrollRange = getLayout().getHeight();
            //控件实际显示的高度
            int scrollExtent = getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
            //控件内容总高度与实际显示高度的差值
            mOffsetHeight = scrollRange - scrollExtent+5;
    
            if (mOffsetHeight == 0) {
    
                return false;
            }
    
            return (scrollY > 0) || (scrollY < mOffsetHeight - 1);
        }
    }
    

    其他文章链接地址:
    (一)高斯模糊实现毛玻璃效果丶共享元素动画 丶地址选择器
    (二)仿京东顶部伸缩渐变丶自定义viewpager指示器丶viewpager3D回廊丶recyclerview瀑布流
    (三)RxJava2常用操作符merge、flatmap、zip--结合MVP架构讲解
    (四)仿支付宝首页顶部伸缩滑动/中间层下拉刷新
    (五)TabLayout+ViewPager悬浮吸顶及刷新数量动画显示
    (六)仿QQ首页drawer/侧滑删除/浮动imgaeView/角标拖拽

    将持续更新.. 不喜勿喷,仅个人分享,希望能帮助到你

    源码地址:Github传送门

    相关文章

      网友评论

        本文标题:(七)仿微信发布朋友圈拖拽删除

        本文链接:https://www.haomeiwen.com/subject/larfcqtx.html