Android精选高级UI

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

2018-11-29  本文已影响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传送门

上一篇下一篇

猜你喜欢

热点阅读