Android 중첩 슬라이딩 메커니즘을 통해 상단 레이아웃 상단

8412 단어
Google이 LOLLIPOP(SDK21)에 추가한 플러그인 슬라이딩 공식 솔루션
1. 문제의 전형적인 장면
일반적으로 정보 흐름(예를 들어 지역사회, 정보, 뉴스) 페이지나 상품 상세 페이지의 상호작용 디자인이다.그림 참조:
코드로 분해되는 것은 일반적인 세 가지 컨트롤이다. 하나는 머리 레이아웃이고 아마도banner.내비게이션 컨트롤다음 내용의 목록 컨트롤입니다.헤드 레이아웃과 네비게이션 레이아웃이 내용 레이아웃에서 일정한 거리(일반적으로 헤드 레이아웃의 높이에 네비게이션 컨트롤의 높이)를 미끄러뜨린 후에 네비게이션 컨트롤을 꼭대기에 놓고 내용 목록을 계속 미끄러져야 한다.
2. 안드로이드 이벤트 배달 메커니즘 처리 문제의 문제점
전통적인 안드로이드 이벤트 분배는 하위 컨트롤러가 이벤트를 소비했기 때문에 부모 컨트롤러는 이 사건을 더 이상 처리할 수 없습니다.즉, 내부의 미끄럼 컨트롤러가 미끄럼 조작을 소비하면 외부의 미끄럼 컨트롤러가 이 미끄럼 동작을 얻지 못하고 처리할 수 없다는 것이다.우리의 이전 상황에서 슬라이딩 내용 목록 컨트롤러는 헤드 레이아웃과 네비게이션 레이아웃에 응답을 요구하는 것은 그들의 공동 아버지 레이아웃에 응답을 요구하는 것이다. 전통적인 사건으로 나누어 처리하는 것은 매우 어려운 것이다.
3. 안드로이드 플러그인 슬라이딩 메커니즘의 기초 개념
스크롤 중의 두 인터페이스는 위에서 언급한 바와 같다.Nested Scrolling Parent와 Nested Scrolling Child 인터페이스의 방법은 다음과 같다. Nested Scrolling Child
  • startNestedScroll: 시작 방법, 주요 역할은 슬라이딩 거리 정보를 수신하는 외부 컨트롤을 찾는 것이다.
  • dispatchNested PreScroll: 내부 컨트롤러가 미끄럼을 처리하기 전에 미끄럼 정보를 외부 컨트롤러에 나누어 줍니다.
  • dispatchNestedScroll: 내부 컨트롤에서 미끄럼을 처리한 후 남은 미끄럼 거리 정보를 외부 컨트롤에 나누어 줍니다.
  • stopNestedScroll: 끝 방법, 주요 역할은 플러그 슬라이딩과 관련된 상태를 비우는 것이다
  • setNested Scrolling Enabled와 isNested Scrolling Enabled: 컨트롤러가 플러그인 슬라이딩을 지원하는지 판단하는 데 사용되는 get & set 방법
  • dispatchNestedPreFling과 dispatchNestedFling: Scroll의 대응 방법과 유사
  • NestedScrollingParent
  • onStartNestedScroll: startNestedScroll에 대응하여 내부 컨트롤러는 외부 컨트롤러를 호출하는 방법으로 외부 컨트롤러가 미끄럼 정보를 받는지 확인합니다.
  • onNestedScrollAccepted: 외부 컨트롤러가 슬라이딩 정보를 수신한 것을 확인한 후에 이 방법은 리셋되어 외부 컨트롤러가 끼워 넣은 슬라이딩에 대해 전기 작업을 할 수 있다.
  • onNested PreScroll: 관건적인 방법으로 내부 컨트롤러가 미끄럼 전의 미끄럼 거리 정보를 처리하고 여기서 외부 컨트롤러는 미끄럼 조작에 우선적으로 응답하며 일부 또는 전체 미끄럼 거리를 소모할 수 있다.
  • onNestedScroll: 관건적인 방법으로 내부 컨트롤러가 미끄러진 후의 미끄럼 거리 정보를 처리하고 이 밖의 컨트롤러는 남은 미끄럼 거리를 처리할지 여부를 선택할 수 있다.
  • onStopNestedScroll:stopNestedScroll에 대응하여 마무리 작업을 합니다.
  • onNestedPreFling과 onNestedFling: 상략
  • 4. 플러그 슬라이딩 관건 소스 분석
    하위 뷰가 스크롤 이벤트를 받아들인 후 플러그인 스크롤을 시작합니다. 부모 뷰가 먼저 스크롤을 할지 묻습니다. 부모 뷰가 자신의 스크롤 요구를 처리한 후에 하위 뷰로 돌아가서 자신의 스크롤 요구를 처리합니다. 만약에 부모 뷰가 스크롤 거리를 소모한다면 하위 뷰는 남은 스크롤 거리만 가져와서 처리할 수 있습니다.하위 뷰는 자신의 스크롤 요구 사항을 처리한 후 부모 뷰로 돌아가 나머지 스크롤 거리를 처리합니다.관성fling의 유사.
    public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    setNestedScrollingParentForType(type, p);
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }
    

    다음은 RecyclerView의 onTouch Event의 Motion Event.ACTION_MOVE에서 디스패치 Nested PreScroll과 scroll By 인터넷이 호출됐어요.
    case MotionEvent.ACTION_MOVE: {
       
        if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
            dx -= mScrollConsumed[0];
            dy -= mScrollConsumed[1];
            vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
            // Updated the nested offsets
            mNestedOffsets[0] += mScrollOffset[0];
            mNestedOffsets[1] += mScrollOffset[1];
        }
        
        if (mScrollState == SCROLL_STATE_DRAGGING) {
            mLastTouchX = x - mScrollOffset[0];
            mLastTouchY = y - mScrollOffset[1];
    
            if (scrollByInternal(
                    canScrollHorizontally ? dx : 0,
                    canScrollVertically ? dy : 0,
                    vtev)) {
                getParent().requestDisallowInterceptTouchEvent(true);
            }
           
        }
    } break;
    

    dispatchNested PreScroll에서 부모 View의 onNested PreScroll을 조정하고dy와consumed로 전송합니다.소비 계수에 쓰다.
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
                @Nullable int[] offsetInWindow, @NestedScrollType int type) {
        if (isNestedScrollingEnabled()) {
            final ViewParent parent = getNestedScrollingParentForType(type);
            if (parent == null) {
                return false;
            }
    
            if (dx != 0 || dy != 0) {
                ⋯⋯
                consumed[0] = 0;
                consumed[1] = 0;
                ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
    
                ⋯⋯
                return consumed[0] != 0 || consumed[1] != 0;
            } else if (offsetInWindow != null) {
                offsetInWindow[0] = 0;
                offsetInWindow[1] = 0;
            }
        }
        return false;
    }
    

    최종적으로 부모 뷰의 onNested PreScroll () 방법이 호출되었습니다.다음 순서에 따라 네스트된 스크롤을 수행하는 방법을 분석할 수 있습니다.
    (자)startNestedScroll→(부)onStartNestedScroll→(부)onNestedScrollAccepted→(자)dispatchNestedPreScroll→(부)onNestedPreScroll→(자)dispatchNestedScroll→(부)onNestedScroll→(자)dispatchNestedPreFling→(부)onNestedPreFling→(부)dispatchNestedPreFling→(자)onestedNestedScrollScroll(부))
    5. 플러그인 슬라이딩 전형적인 사례 실천
    관건적인 방법은 두 가지로 효과를 완성할 수 있다. 단지 경직되고 경직되어 있기 때문에 더욱 좋은 사용자 체험을 위해 제스처 속도의 미끄럼 예판을 넣어야 한다.
    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            mHeaderView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            mMaxScrollHeight = mHeaderView.getMeasuredHeight() - mHeaderRetainHeight;
            //       :     match_parent
            if (mBodyView.getLayoutParams().height < getMeasuredHeight() - mHeaderRetainHeight) {
                mBodyView.getLayoutParams().height = getMeasuredHeight() - mHeaderRetainHeight;
            }
            setMeasuredDimension(getMeasuredWidth(), mBodyView.getLayoutParams().height + mHeaderView.getMeasuredHeight());
        }
    

    onMeasure()에서 머리 레이아웃과 천장 레이아웃 높이를 계산하여 전체 컨트롤의 측정을 완성하고 머리 레이아웃에서 천장 레이아웃의 최대 미끄럼 거리 값을 제거하는 것을 기록합니다.
    @Override
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
            boolean hiddenTop = dy > 0 && getScrollY() < mMaxScrollHeight;
            boolean showTop = dy < 0 && getScrollY() > 0 && !ViewCompat.canScrollVertically(target, -1);
            if (hiddenTop || showTop) {
                scrollBy(0, dy);
                consumed[1] = dy;
            }
        }
    

    그리고 이 방법을 다시 쓰면 대응하는 슬라이딩 플러그인을 실현할 수 있다. 즉, 네비게이션 슬롯 컨트롤러를 꼭대기에 놓는 것이다. 사실은 네비게이션 슬롯의 높이를 미리 알고 슬라이딩과 슬라이딩 거리가 최대 슬라이딩 거리보다 크며 슬라이딩이 가능하고 내용 컨트롤러가 슬라이딩이 불가능할 때 모든 슬라이딩 거리를 아버지 컨트롤러에게 건네주는 것이다. 즉, Nested Scroll Parent 인터페이스의 자신을 실현하는 것이다.
    상당한 코드는 나의 github 실례를 참고할 수 있다: Sticky Nested Scroll Layout
    참고: Android Nested Scrolling 메커니즘 완전 해석
    플러그인 스크롤 디자인과 원본 분석

    좋은 웹페이지 즐겨찾기