android 卡片画廊效果及RecycleView、ViewPager、ScrollView之前的冲突解决

话不多说,看图

图片.gif

层级结构图

image.png

层级结构初衷

1、内容需要通过卡片的形式来展现,还有支持加载更多,所以最底部使用RecyclerView,最好是做成预加载形式,提前n页加载下一页,这样体验更好。
2、为了展示更多内容卡片内要支持垂直分页,这时候我使用了ViewPager,一是可以更好的管理分页内容,二是ViewPager的垂直分页容易实现,三是可以处理不同控件之前的滑动冲突
3、ViewPager第一页使用的可回弹的ScrollView,可以在下拉的时候做一些动画之类的操作,例如关注操作等。
4、ViewPager的第二页只是一个普通的ScrollView,具体使用可以根据实际情况来处理

关键点

1、RecycleView的分页效果基于PagerSnapHelper,RecyclerView在25.1.0版本中添加了一盒基于SnapHelper的子类PagerSnapHelper,可以使RecyclerView像ViewPager一样的效果,一次只能滑一页,而且居中显示。

/**
 * 防止卡片在第一页和最后一页因无法"居中"而一直循环调用onScrollStateChanged-->SnapHelper.snapToTargetExistingView-->onScrollStateChanged
 */
public class CardLinearSnapHelper extends PagerSnapHelper {

    public boolean mNoNeedToScroll = false;

    @Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
        if (mNoNeedToScroll) {
            return new int[]{0, 0};
        } else {
            return super.calculateDistanceToFinalSnap(layoutManager, targetView);
        }
    }

    @Nullable
    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
        return super.findSnapView(layoutManager);
    }
}

2、卡片的效果是在滑动的时候根据RecycleView的偏移量计算缩放因子进行缩放

/**
     * RecyclerView位移事件监听, view大小随位移事件变化
     */
    private void onScrolledChangedCallback() {
        int offset = mCurrentItemOffset - mCurrentItemPos * mOnePageWidth;
        float percent = (float) Math.max(Math.abs(offset) * 1.0 / mOnePageWidth, 0.0001);

//        Logger.d(String.format("offset=%s, percent=%s", offset, percent));
        View leftView = null;
        View currentView;
        View rightView = null;
        if (mCurrentItemPos > 0) {
            leftView = mRecyclerView.getLayoutManager().findViewByPosition(mCurrentItemPos - 1);
        }
        currentView = mRecyclerView.getLayoutManager().findViewByPosition(mCurrentItemPos);
        if (mCurrentItemPos < mRecyclerView.getAdapter().getItemCount() - 1) {
            rightView = mRecyclerView.getLayoutManager().findViewByPosition(mCurrentItemPos + 1);
        }

        if (leftView != null) {
            // y = (1 - mScale)x + mScale
            leftView.setScaleY((1 - mScale) * percent + mScale);
        }
        if (currentView != null) {
            // y = (mScale - 1)x + 1
            currentView.setScaleY((mScale - 1) * percent + 1);
        }
        if (rightView != null) {
            // y = (1 - mScale)x + mScale
            rightView.setScaleY((1 - mScale) * percent + mScale);
        }
    }

3、RecycleView的item内有一个垂直分页的VerticalViewPager,VerticalViewPager是在ViewPager上转换X,Y即可

private void init() {
        //设置viewpager的切换动画,这里设置才能真正实现垂直滑动的viewpager
        setPageTransformer(true, new VerticalPageTransformer());
        setOverScrollMode(OVER_SCROLL_NEVER);
    }

private MotionEvent swapXY(MotionEvent ev) {
        //获取宽高
        float width = getWidth();
        float height = getHeight();

        //将Y轴的移动距离转变成X轴的移动距离
        float newX = (ev.getY() / height) * width;
        //将X轴的移动距离转变成Y轴的移动距离
        float newY = (ev.getX() / width) * height;
        //重设event的位置
        ev.setLocation(newX, newY);

        return ev;
    }

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(swapXY(ev)) && !noScroll;
    }

4、解决ViewPager与RecycleView滑动的冲突,在ViewPager中屏蔽父视图的上下滑动事件

private float xDispatchLast;
    private float yDispatchLast;
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:{
                xDispatchLast = event.getX();
                yDispatchLast = event.getY();
            }
            break;
            case MotionEvent.ACTION_MOVE:{
                float curX = event.getX();
                float curY = event.getY();

                float xDiff = curX - xDispatchLast;
                float yDiff = curY - yDispatchLast;
                float xAbsDiff = Math.abs(xDiff);
                float yAbsDiff = Math.abs(yDiff);
                if (yAbsDiff > xAbsDiff) {//上下滑动时拦截父控件的事件
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

            }
            break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:{
                xDispatchLast = event.getX();
                yDispatchLast = event.getY();
            }
            break;
        }
        return super.dispatchTouchEvent(event) && !noScroll;
    }

5、解决ViewPager子视图ScrollView的冲突,在ViewPager中拦截事件

private float startY;
    private float endY;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:{
                startY = event.getY();
            }
            break;
            case MotionEvent.ACTION_MOVE:{
                float curY = event.getY();
                if (getCurrentItem() == 0) {
                    //当第一页的scrollView在顶端且向上拉时,拦截事件
                    if (topSubScrollView != null && topSubScrollView.getScrollY() == 0 && curY < startY) {//topScrollView向上拉
                        return true;
                    }

                }else if (getCurrentItem() == 1) {
                    //btmScrollView滑动到最顶部且还向下拉
                    if (btmSubScrollView != null && btmSubScrollView.getScrollY() == 0 && curY > startY) {
                        return true;
                    }
                }


            }
            break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:{
                endY = event.getY();
                if (getCurrentItem() == 0) {
                    //当第一页的scrollView在顶端且向上拉时,拦截事件
                    if (topSubScrollView != null && topSubScrollView.getScrollY() == 0 && endY < startY) {//topScrollView向上拉
                        return true;
                    }
                }else if (getCurrentItem() == 1) {
                    //btmScrollView滑动到最顶部且还向下拉
                    if (btmSubScrollView != null && btmSubScrollView.getScrollY() == 0 && endY > startY) {
                        return true;
                    }
                }
            }
            break;
        }
        boolean intercepted = super.onInterceptTouchEvent(swapXY(event)) && !noScroll;
        swapXY(event); // return touch coordinates to original reference frame for any child views
        return intercepted;
    }

6、解决ViewPager与BounceScrollView的冲突,在下拉过程中有时会被ViewPager拦截

// 指定此视图可以垂直滚动
    @Override
    public boolean canScrollVertically(int direction) {
        return true;
    }

以上就是此项目中的所有关键点。

下载地址

ScrollViewDemo 欢迎Star

参考文章

RecycleViewCardGallary

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

推荐阅读更多精彩内容