與ViewPager完美結合的順滑引導條系列之二

引言


上一篇的最後提出了

  • 索引滑動時遊標不動
  • 一次完整的滑動必須是索引或遊標,不能共存
  • 頻繁不規則地滑動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完美結合的順滑引導條系列之三

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章