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

效果图如下:


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

4.底部文字点击事件处理


dmo7_1.gif

相关代码如下

在 OnRecyclerItemClickListener中添加方法
public abstract void onOtherClick(MotionEvent e);

private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener{
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
            if (childViewUnder != null) {
                RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
                onItemClick(childViewHolder);
            } else {
          //判断点击的不是图片 走这个方法
                onOtherClick(e);
            }
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
            if (childViewUnder != null) {
                RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
                onLongClick(childViewHolder);
            }
        }
    }
//将textview固定高度好计算
    <dimen name="bottom_textview_height">60dp</dimen>
 <TextView
     style="@style/demo6_text_style"
     android:text="所在位置" />
//原有 textview  style更改
  <style name="demo6_text_style">
        <item name="android:gravity">center</item>
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">@dimen/bottom_textview_height</item>
        <item name="android:textColor">@color/black33</item>
        <item name="android:textSize">@dimen/dimen_18</item>
    </style>
//初始化高度属性
       //textview 高度
        bottomItemHeight = getResources().getDimensionPixelSize(R.dimen.bottom_textview_height);
        //textview之间分割线 高度
        lineSpace = (int) getResources().getDimension(R.dimen.dimen_1);
        //左边距
        leftMargin = (int)getResources().getDimension(R.dimen.dimen_20);
        //同步到控件 星星的宽度  图片大小为30dp 左右各留10dp 方便用户点击
        starWidth =(int) getResources().getDimension(R.dimen.dimen_50);

//
   private void fixBottom() {
    之前方法省略
        //用于判断 在每次fix底部布局高度后判断 注意要减去顶部edittext的高度
        judgeClickMargin = marginTop-getResources().getDimensionPixelSize(R.dimen.edittext_height);

    }

//Activity中处理
      @Override
            public void onOtherClick(MotionEvent e) {
                if (e.getY()>judgeClickMargin) {
                    int between=(int)e.getY()-judgeClickMargin;//判读触摸点与 bottom布局分界处的距离

                    int oneItem=(bottomItemHeight+lineSpace);//一个textview+一个分割线的高度
                    LogUtil.e(e.getY()+"---"+judgeClickMargin+"==="+oneItem);
                    if (between>0 && between<=oneItem) {
                        //点击在第一个textview上 ---所在位置
                        ToastUtil.normal("所在位置");
                    } else if (between>oneItem && between<=2*oneItem) {

                        //点击在第二个textview上 ---谁可以看
                        ToastUtil.normal("谁可以看");
                    } else if (between>2*oneItem && between<=3*oneItem) {

                        //点击在第三个textview上 ---提醒谁看
                        ToastUtil.normal("提醒谁看");
                    } else if (between>3*oneItem && between<=4*oneItem && e.getX()>=leftMargin && e.getX()<=(starWidth+leftMargin)) {
                        //点击星星 同步到空间
                        ToastUtil.normal("同步到空间");
                    }

                }
            }

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

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

源码地址:Github传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,039评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,711评论 2 59
  • 内容 抽屉菜单 ListView WebView SwitchButton 按钮 点赞按钮 进度条 TabLayo...
    小狼W阅读 1,613评论 0 10
  • 原文链接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影阅读 32,929评论 6 472
  • 正好下午休息,打开被我冷落好久的简书,找到我收藏的作品开始临摹,以前几乎是完全参照,但是这次把花儿画出以后,下来的...
    起光阅读 203评论 0 1