美文网首页
与ViewPager完美结合的顺滑引导条系列之二

与ViewPager完美结合的顺滑引导条系列之二

作者: s1991721 | 来源:发表于2018-01-30 22:22 被阅读0次

    引言


    上一篇的最后提出了

    • 索引滑动时游标不动
    • 一次完整的滑动必须是索引或游标,不能共存
    • 频繁不规则地滑动ViewPager或引导条时,会引起NullPointerException,因为RecyclerView找不到内部的View(所以才重写了dispatchTouchEvent)

    这三个需要修改的缺陷。

    针对第一点:索引滑动是因为RecyclerView会滑动,游标是添加在RelativeLayout上的,所以游标在响应手势的情况下是不会滑动的
    针对第二点:由于一次完整的滑动结束后才会重置状态,导致滑动过程中不能切换滑动对象
    针对第三点:由于RecyclerView的回收复用机制,导致无法获得想要项的具体View

    从这三个问题出发,这次我改用HorizontalScrollView和View来进行组合,封装控件GuideSlideView。

    这里立即就爆出一个可优化的点,控件没有复用,先把他当作缺陷列出,后续改进。

    正文


    GuideSlideView的布局如下

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <HorizontalScrollView
                android:id="@+id/horizontalscrollview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scrollbars="none">
    
                <LinearLayout
                    android:id="@+id/linearlayout"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal" />
            </HorizontalScrollView>
    
            <View
                android:id="@+id/lineView"
                android:layout_width="10dp"
                android:layout_height="3dp"
                android:layout_alignParentBottom="true"
                android:layout_marginBottom="7dp"
                android:background="#444444" />
        </RelativeLayout>
    </merge>
    

    索引项中的View全部inflate出来后添加到LinearLayout,通过HorizontalScrollView的scrollBy进行与ViewPager的联动。

    重点代码分析

            horizontalScrollView.setOnTouchListener(new OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (whichOne != 0) {
                        switch (event.getAction()) {
                            case MotionEvent.ACTION_DOWN:
                                lastX = event.getX();
                                break;
                            case MotionEvent.ACTION_MOVE:
                                setScrollViewPager(event.getX() - lastX);
                                lastX = event.getX();
                                break;
                            case MotionEvent.ACTION_UP:
                            case MotionEvent.ACTION_CANCEL:
                                lastX = 0;
                                break;
                        }
    
                        return true;
                    }
    
                    return false;
                }
            });
       
        public ViewPager.OnPageChangeListener getOnPageChangeListener() {
            return new ViewPager.SimpleOnPageChangeListener() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    Log.i("onPageScrolled", "position-->" + position + "     positionOffset-->" + positionOffset + "     positionOffsetPixels-->" + positionOffsetPixels);
                    setScroll(position, positionOffset);
                }
            };
        }
    
        private void setScroll(int position, float offset) {
    
            Log.i("setScroll", "position-->" + position + "     offset-->" + offset);
    
            if (!whetherOrNotToMove(position, offset)) {
                return;
            }
    
            if (!positiveDirection) {//根据方向设置偏移
                offset = offset - 1;
            }
    
            int x = calculateMoveLength(position, offset);
    
            move(x);
        }
    
        private boolean whetherOrNotToMove(int position, float offset) {
            if (position > currentPosition) {//第一次正向滑动触发
                positiveDirection = true;
                directionPrepare(position);
                return false;
            }
            if (position < currentPosition) {//第一次反向滑动触发
                positiveDirection = false;
                directionPrepare(position);
                return false;
            }
            if (position == 0 && offset == 0) {//滑到第一个 初始状态
                positiveDirection = true;
                directionPrepare(position);
                return false;
            }
    
            lineView.setVisibility(VISIBLE);
            ViewHolder viewHolder;
            if (positiveDirection) {
                viewHolder = new ViewHolder(linearLayout.getChildAt(currentPosition));
            } else {
                viewHolder = new ViewHolder(linearLayout.getChildAt(currentPosition));
                viewHolder.lineView.setVisibility(INVISIBLE);
                viewHolder = new ViewHolder(linearLayout.getChildAt(currentPosition + 1));
            }
            viewHolder.lineView.setVisibility(INVISIBLE);
    
            if (offset == 0) {
                viewHolder = new ViewHolder(linearLayout.getChildAt(currentPosition));
                viewHolder.lineView.setVisibility(VISIBLE);
                lineView.setVisibility(INVISIBLE);
            }
            return true;
        }
    
        private void directionPrepare(int position) {
            resetState(position);
            if (whichOne != 1) {
                if (positiveDirection) {
                    layoutLineView(position);
                } else {
                    layoutLineView(position + 1);
                }
            }
    
            ViewHolder viewHolder;
            if (positiveDirection) {
                viewHolder = new ViewHolder(linearLayout.getChildAt(currentPosition));
            } else {
                viewHolder = new ViewHolder(linearLayout.getChildAt(currentPosition + 1));
            }
            viewHolder.lineView.setVisibility(VISIBLE);
            lineView.setVisibility(INVISIBLE);
        }
    
        private void resetState(int position) {//重置状态
            currentPosition = position;
            whichOne = 0;
            lastx = 0;
        }
    
        private void layoutLineView(int currentPosition) {//将游标布局到指定位置
            View item = linearLayout.getChildAt(currentPosition);
            lineViewMarginLeft = item.getLeft() - horizontalScrollView.getScrollX() + (item.getWidth() - lineView.getWidth()) / 2;
    
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) lineView.getLayoutParams();
            params.leftMargin = lineViewMarginLeft;
            lineView.setLayoutParams(params);
    
        }
    
        private int calculateMoveLength(int position, float offset) {
            int temp = position;
            if (temp >= linearLayout.getChildCount()) {
                temp = linearLayout.getChildCount() - 1;
            }
            View currentView = linearLayout.getChildAt(temp);//当前所指view
    
            int lastPosition = position + 1;
            if (lastPosition >= linearLayout.getChildCount()) {
                lastPosition = linearLayout.getChildCount() - 1;
            }
            View nextView = linearLayout.getChildAt(lastPosition);//将要滑动到的view
    
            Log.i("viewnull", temp + "   currentView-->" + currentView + "----------" + lastPosition + "     nextView-->" + nextView);
    
            int width = (currentView.getWidth() + nextView.getWidth()) / 2;//计算两view之间的总的滑动距离
            return Math.round(width * offset);//此次滑动的距离
        }
    
        private void move(int x) {
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) lineView.getLayoutParams();
            if (whichOne == 0) {//判断   一次完整的滑动   是否结束      一次滑动结束后 whichOne=0
                if (positiveDirection) {
                    if (params.leftMargin % screenWidth > screenWidth / 2 && horizontalScrollView.canScrollHorizontally(1)) {//游标在右半边且recyclerView可以右滑 则滑动recyclerView
                        whichOne = whichOne | 1;
                    } else {
                        whichOne = whichOne | 2;
                    }
                } else {
                    if (params.leftMargin % screenWidth < screenWidth / 2 && horizontalScrollView.canScrollHorizontally(-1)) {//游标在左半边且recyclerView可以左滑 则滑动recyclerView
                        whichOne = whichOne | 1;
                    } else {
                        whichOne = whichOne | 2;
                    }
                }
            }
    
            if (whichOne == 1) {
                horizontalScrollView.scrollBy(x - lastx, 0);//因为scrollBy的滑动是累加的,所以必须减去上次滑动的距离,实际移动的距离为差值  exp:第一次滑动1 第二次滑动2,第二次滑动3,scrollBy会滑动6,但实际我只想滑动3
                lastx = x;
            } else {
                params.leftMargin = lineViewMarginLeft + x;
                lineView.setLayoutParams(params);
            }
        }
    
        //GuideSlideView 滑动联动ViewPager
        private void setScrollViewPager(float length) {
            viewPager.scrollBy(-Math.round(length), 0);
        }
    

    同样的重点在onPageScrolled回调中获取位置和偏移量,然后调用setScroll,有点不同的是每个ViewHolder都有自己的游标,滑动结束后,滑动到具体位置时需要控制ViewHolder和满屏跑游标的显示状态。

    还有一点不同的就是对HorizontalScrollView设置了OnTouchListener,这是为了满足多触点的需求,当ViewPager滑到一半,另一个触点拖动引导条时,ViewPager会随之滑动。

    看过之前一篇文章的话,这里针对滑动的逻辑并没有太大出入,只是用HorizontalScrollView替换了RecyclerView

    效果

    效果图

    截图可能不太清楚,当快速滑动时,光标会有闪烁现象

    缺陷

    • 控件没有复用
    • 快速滑动时,光标会闪

    为了解决上述问题见与ViewPager完美结合的顺滑引导条系列之三

    相关文章

      网友评论

          本文标题:与ViewPager完美结合的顺滑引导条系列之二

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