仿QQ相册RecyclerView滑动选中进阶

GitHub

Gradle

compile ‘com.march.slidingselect:slidingselect:0.0.1’

Usage

  • step1 :添加依赖
    依赖compile 'com.march.slidingselect:slidingselect:0.0.1'

  • step2 :xml中使用

    <com.march.slidingselect.SlidingSelectLayout
        android:id="@+id/scl"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </com.march.slidingselect.SlidingSelectLayout>
  • step3: java代码中配置,将pos和data与view进行绑定以便内部获取
private SlidingSelectLayout mScl;
mScl = getView(R.id.scl);

class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        mScl.markView(holder.itemView,position,demos.get(position));
    }
}
  • step4:监听回调,使用范型获得手指触摸到的位置和当前位置对应的数据进行更新adapter
mScl.setOnSlidingSelectListener(new SlidingSelectLayout.OnSlidingSelectListener<Demo>() {
            @Override
            public void onSlidingSelect(int pos, View parentView, Demo data) {
                demos.get(pos).isChanged = !demos.get(pos).isChanged;
                adapter.notifyItemChanged(pos);
            }
        });

推荐阅读

演示视频

前言

  • 显示相册在app中是一个比较常见的操作,大致的操作就是通过ContentProvider获取多媒体资源进行展示,我综合了一下QQ 的和微信的显示效果,实现了一下,仿微信QQ显示手机相册,在QQ的相册选择时是支持滑动选中的,即手指碰到哪个就选中哪张照片,正好公司的项目中用到了这个功能,在网上找了找没有很好的解决方案,所以通过自定义控件处理事件,这篇文章主要介绍这个功能的实现。

大体思路

  • 打算继承FramLayout实现,当然继承别的也可以,习惯继承FramLayout

  • 当手指竖向滑动时,RecyclerView处理事件,进行滑动。当手指横向滑动达到阈值自定义控件截断事件自己进行处理。

  • 根据手指的滑动获取x,y坐标,使用RecycelrViewfindViewUnder(float x,float y) 的方法,可以直接获取制定位置的View,再使用tag从view中拿到之前使用mScl.markView()方法绑定的pos和data数据

  • 使用该方法就不会因为动态计算距离而局限于RecyclerView的布局,九宫格模式下仍然可以很好的支持。

对外封闭

  • 遍历所有的childView获取RecyclerView,获得GridLayoutManager的列数,初始化一些值,旨在尽量简化使用方法,

  • 此方法想获得RecyclerView必须将RecyclerView作为该控件的直接childView,为了兼容特殊情况,开放一个API用来设置RecyclerView

private void setTargetRv(RecyclerView mTargetRv) {
        this.mTargetRv = mTargetRv;
}
  • 遍历子控件获取RecyclerView
    private void ensureTarget() {
        if (mTargetRv != null)
            return;
        for (int i = 0; i < getChildCount(); i++) {
            View childAt = getChildAt(i);
            if (childAt instanceof RecyclerView) {
                mTargetRv = (RecyclerView) childAt;
                return;
            }
        }
    }
  • 处理LayoutManager,初始化xTouchSlop,这个值是滑动多大距离触发水平滑动,根据GridLayoutManager的列数来动态设置,当一次水平滑动超过一个item宽度的0.25时触发。
   private void ensureLayoutManager() {
        if (mTargetRv == null || itemSpanCount != INVALID_PARAM)
            return;
        RecyclerView.LayoutManager lm = mTargetRv.getLayoutManager();
        if (lm == null)
            return;
        if (lm instanceof GridLayoutManager) {
            GridLayoutManager glm = (GridLayoutManager) lm;
            itemSpanCount = glm.getSpanCount();
        } else {
            itemSpanCount = 4;
        }
        int size = (int) (getResources().getDisplayMetrics().widthPixels / (itemSpanCount * 1.0f));
        xTouchSlop = yTouchSlop = size * TOUCH_SLOP_RATE;
    }

拦截事件

private boolean isReadyToIntercept() {
    return mTargetRv != null 
    && mTargetRv.getAdapter() != null 
    && itemSpanCount != INVALID_PARAM;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

    if (!isEnabled())
        return super.onInterceptTouchEvent(ev);

    ensureTarget();
    ensureLayoutManager();

    if (!isReadyToIntercept())
        return super.onInterceptTouchEvent(ev);

    int action = MotionEventCompat.getActionMasked(ev);
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            // init
            mInitialDownX = ev.getX();
            mInitialDownY = ev.getY();
            break;
        case MotionEvent.ACTION_UP:
            // stop
            isBeingSlide = false;
            break;
        case MotionEvent.ACTION_MOVE:
            // handle
            // 水平滑动超过阈值,垂直滑动没有超过阈值时拦截事件
            float xDiff = Math.abs(ev.getX() - mInitialDownX);
            float yDiff = Math.abs(ev.getY() - mInitialDownY);
            if (yDiff < xTouchSlop && xDiff > yTouchSlop) {
                isBeingSlide = true;
            }
            break;
    }
    return isBeingSlide;
}

触摸事件

  • 重点是up事件时重新初始化一些值

  • move事件时处理位置的移动

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = MotionEventCompat.getActionMasked(ev);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_UP:
                // re init
                isBeingSlide = false;
                preViewPos = INVALID_PARAM;
                break;
            case MotionEvent.ACTION_MOVE:
                // 使用监听发布事件
                publishSlidingCheck(ev);
                break;
        }
        return isBeingSlide;
    }

处理Move事件

  • 从Tag中获取pos
private int getPos(View parentView) {
        int pos = INVALID_PARAM;
        Object tag = parentView.getTag(tagPosKey);
        if (tag != null)
            pos = (int) tag;
        return pos;
    }
  • 从tag中获取data
private Object getData(View parentView) {
        return parentView.getTag(tagDataKey);
    }
  • 使用监听向外发布事件,将获取的pos和data通过监听发布
private void publishSlidingCheck(MotionEvent event) {
        float x = generateX(event.getX());
        float y = generateY(event.getY());
        View childViewUnder = mTargetRv.findChildViewUnder(x, y);
        // fast stop
        if (onSlidingSelectListener == null || childViewUnder == null)
            return;
        int pos = getPos(childViewUnder);
        Object data = getData(childViewUnder);
        // fast stop
        if (pos == INVALID_PARAM || preViewPos == pos || data == null)
            return;

        try {
            onSlidingSelectListener.onSlidingSelect(pos, childViewUnder, data);
            preViewPos = pos;
        } catch (ClassCastException e) {
            Log.e("SlidingSelect", "ClassCastException:填写的范型有误,无法转换");
        }
    }
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值