안드로이드 ui 학습 시리즈 - 사용자 정의 Behavior(3) - 보기 페이지

15227 단어
이번에는 좀 어려운 예를 들겠습니다. 이 예는 데이터 계산과 관련이 있습니다. 이번 예를 잘 배우면 사용자 정의 Behavior는 일반적인 것을 배울 수 있습니다.
이번의 예는 다음과 같다.
내가 이룬 효과:
ezgif.com-video-to-gif.gif
어떻게 스크롤 수치를 소비하고 미끄럼 방향을 판단합니까
이번 예를 배우기 전에 중요한 것이 하나 있는데 그것이 바로 미끄럼의 방향을 어떻게 판단하는가이다. 여기서 우리는 이 두 가지 방법을 중점적으로 살펴보자.
 /**
     *           
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param dxConsumed target      x     
     * @param dyConsumed target      y     
     * @param dxUnconsumed x          
     * @param dyUnconsumed y          
     */
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

/**
     *            
     *  nested scroll child             ,       nested scroll child
     *       onNestedPreScroll。         consumed,             
     *      。       100px,child   90px    ,     consumed[1]    90,
     *   coordinatorLayout          10px   。
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param dx             
     * @param dy             
     * @param consumed
     */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }


여러분이 onNested PreScroll과 onNested Scroll에 대해 얼마나 많은 인상을 가지고 있는지 모르겠습니다. 이 두 가지 방법은 핵심 방법입니다. 이 두 가지 방법 중에서 우리는 스크롤 이벤트를 소비할 수 있습니다.
두 가지 방법 중 중요한 매개변수는 다음과 같습니다.
  • dx/dy on Nested PreScroll의 매개 변수입니다. 이 방법은 스크롤 컨트롤러가 스크롤 데이터를 소비하기 전에 스크롤 컨트롤러가 스크롤 이벤트보다 먼저 스크롤 이벤트에 참여하는지 물어보는 것입니다.dx/dy는 x/y축의 굴림량을 나타낸다
  • consumed도 onNested PreScroll의 매개 변수이다. 이것은 플러그인 스크롤에 참여하는 컨트롤러가 스크롤 컨트롤러보다 먼저 소비하는 스크롤 수치가 얼마나 되는지 나타낸다. [0]는 x축을 나타내고 [1]은 y축을 나타낸다.예를 들어 만약에 여기 dx=100,consumed[0]=80이 있다면 플러그인 컨트롤러가 이미 80개의 픽셀을 소비했음을 나타낸다. 스크롤 컨트롤러, 예를 들어 목록은 이자가 생기는 20개의 픽셀만 소비할 수 있다. 즉, 20개의 픽셀만 스크롤할 수 있다.이 점은 매우 중요하니 이따가 쓸 것이다.
  • dxConsumed/dyConsumed on Nested Scroll의 매개 변수입니다. 이 방법은 스크롤 컨트롤입니다. 예를 들어 목록이 스크롤 수치를 소비한 후에 플러그인 컨트롤이 같은 스크롤 수치를 소비해야 하는지 물어보는 것입니다. 스크롤 컨트롤이 스크롤 수치를 소비한 후에 같은 스크롤 수치는 플러그인 컨트롤에 전달됩니다. 이전 방법과 같이 바로 사라집니다.이것도 이해하기 쉽다. 바로 스크롤 컨트롤을 따라 스크롤하는view를 디자인한 것이다.dxConsumed/dyConsumed는 스크롤 컨트롤이 x/y 축에서 실제로 얼마나 소비되었는지 나타냅니다.
  • dxUnconsumed/dyUnconsumed도 onNestedScroll의 매개 변수로 스크롤 컨트롤이 x/y축에 소비되지 않은 수치가 얼마나 되는지 위의 데이터 값과 상반된다는 뜻이다.하나의 스크롤 이벤트가 내려오면 스크롤 컨트롤, 예를 들어 머리를 스크롤하면 소비 스크롤 수치를 계속 스크롤할 수 없다. 그러면 이 매개 변수가 생겨서 얼마나 많은 스크롤 수치가 없느냐를 나타낸다.이 두 개의 매개 변수를 사용하면 우리는 스크롤 컨트롤의 스크롤 상태를 판단할 수 있다
  • 여기 보시면:
    if (dyConsumed > 0 && dyUnconsumed == 0) {
        System.out.println("   。。。");
    }
    if (dyConsumed == 0 && dyUnconsumed > 0) {
        System.out.println("        。。。");
    }
    if (dyConsumed < 0 && dyUnconsumed == 0) {
        System.out.println("   。。。");
    }
    if (dyConsumed == 0 && dyUnconsumed < 0) {
        System.out.println("    ,    。。。");
    }
    

    이렇게 하면 우리는 스크롤 방향과 스크롤 컨트롤러가 끝까지 굴러갔는지 판단할 수 있다.컨트롤러가 머리를 굴릴 때 손가락이 계속 미끄러질 수 있다. 이때 스크롤 이벤트는 여전히 발생하고 있다. 단지 더 이상 컨트롤러에 의해 소비되지 않을 뿐이다.
    사고의 방향을 분석하다.
    위의 방법 사용, 매개 변수의 의미를 보면 우리는 이번 예를 완성할 수 있다.
    논리에 따라 두 개의 사고 단락으로 나뉜다.
  • 슬라이딩 시 헤드가 가려지고 헤드가 투명하지 않습니다. -> 반투명
  • 하락 시 헤드가 노출되며 헤드는 반투명->불투명
  • 하지만 그 전에 Coordinator Layout은 Frame Layout 프레임 레이아웃으로 누가 누구의 부분에 있는지 설정할 수 없다는 것을 알아야 한다. 다행히 구글은 Behavior에 레이아웃과 관련된 생명주기가 있다고 생각했다. onLayout Child는 이 방법에서 목록을 다시 레이아웃해서 그가 헤드 아래에 있게 할 수 있다.여기에 목록에 헤드하이트 높이의translationY를 추가하면 됩니다.
    @Override
        public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
    
            mViewHead = parent.findViewById(R.id.kaiyan_appbar);
    
            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            if (layoutParams != null && layoutParams.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) {
                child.layout(0, 0, layoutParams.width, layoutParams.height);
                child.setTranslationY(getAppBarHeight());
            }
    
            return super.onLayoutChild(parent, child, layoutDirection);
        }
    

    슬라이딩
    슬라이딩을 할 때 우리는 목록을 스크롤하지 않고 위로 이동하여 헤드를 가려야 하기 때문에 스크롤 컨트롤을 하기 전에 스크롤 데이터를 소비해야 하기 때문에 onNested PreScroll이라는 방법에서 조작을 선택했다. 코드는 다음과 같다.
      @Override
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
    
            //          ,           ,           ,           ,         ,         
            float translationY = child.getTranslationY();
            float offsetaAlphe = 0;
    
            if (dy > 0 && translationY > 0) {
    
                //        
                float offestY = translationY - dy;
                Log.d("AAA", "translationY:" + translationY + " / dy:" + dy);
    
                if (offestY < 0) {
                    //     Y                   ,          
                    Log.d("AAA", "AAA");
                    offestY = 0;
                    consumed[1] = (int) translationY;
                } else {
                    Log.d("AAA", "BBB:");
                    offestY = translationY - dy;
                    consumed[1] = dy;
                }
                Log.d("AAA", "translationY:" + offestY);
                child.setTranslationY(offestY);
    
                //           
                if (mViewHead != null) {
                    offsetaAlphe = 0.5f + 0.5f * (translationY / getAppBarHeight());
                    mViewHead.setAlpha(offsetaAlphe);
                }
    
            }
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        }
    

    아이디어:
  • dy > 0의 경우 설명 목록을 위로 스크롤
  • 이때 우리는 목록의 Y축에 있는 편이량floattranslationY=child를 가져옵니다.getTranslationY()
  • translationY = 헤드하이트는 시작할 때 헤드의 높이와 같다. 이것은 우리가 온 Layout Child에서 목록의 위치를 조정한 것이다. 헤드의 아래
  • 손가락을 위로 움직일 때translationY의 변화는 헤드하이트 -> 0의 변화 과정이다.translationY = 0일 때head가 모두 가려졌다는 것을 의미한다
  • translationY는headHeight->0에서 우리는 항상translationY=translationY-dy,그리고consumed[1]=dy(스크롤 데이터 차단),이렇게 하면 목록을 위로 옮기고 스크롤 데이터를 먼저 소비하고consumed[1]=dy 생명 스크롤이 소비되면 스크롤 컨트롤이 받아들일 수 있는 스크롤 데이터 = 0,스크롤하지 않습니다.
  • translationY=0이면 스크롤 수치를 차단하지 않고 목록을 이동하지 않고 목록이 스스로 스크롤하면 됩니다
  • 주의해야 할 것은 인터넷의 다른 예에 모두 이 문제가 있다. 위로 이동하는 목록의 마지막 부분에서 남은 편이량이 손가락으로 굴러가는 데이터보다 작을 수 있기 때문에 우리는 이번 굴러가는 것을 무시하고 translationY는 약간의 수치가 있을 수 없다. =0은 안 된다. 목록의 꼭대기에 작은 헤드가 드러나게 한다.여기 저희가 처리해야 돼요.
  • 코드에 경계치 처리가 있으니 주의하세요. 제 경험상 쓸 수 있고 쓸 수 없는 것은 상관하지 말고 직접 쓰면 엉뚱한 미끄럼을 줄이고 버그를 줄이며 우리의 정력을 절약할 수 있습니다.

  • 하강시
    미끄러질 때 우리는 목록을 스크롤하지 않고 아래로 이동하고 헤드를 노출해야 한다. 이것은 위의 어느 함수에 쓸 수 없다. 모두가 생각하기에 목록이 머리를 스크롤할 수 있고 더 이상 위로 스크롤할 수 없기 때문에 헤드를 드러내야 한다. 그래서 onNested Scroll이라는 방법에서 조작한다. 코드는 다음과 같다.
    @Override
        public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    
            int appBarHeight = getAppBarHeight();
            float offestY = 0;
            float translationY = child.getTranslationY();
            float offsetaAlphe = 0;
    
            if (dyUnconsumed < 0 && translationY < appBarHeight) {
    
                //        
                offestY = translationY + Math.abs(dyUnconsumed);
                if (offestY < 0) {
                    offestY = 0;
                }
                if (offestY >= appBarHeight) {
                    offestY = appBarHeight;
                }
                child.setTranslationY(offestY);
    
                //           
                if (mViewHead != null) {
                    if (translationY > appBarHeight) {
                        translationY = appBarHeight;
                    }
                    offsetaAlphe = 0.5f + 0.5f * (translationY / getAppBarHeight());
                    mViewHead.setAlpha(offsetaAlphe);
                }
            }
        }
    

    사고방식은 위의 것과 많이 다르지 않아서 너무 상세하게 말하지 않는다.
  • dyUnconsumed<0, 무엇을 설명하는가. 쌀이 손가락을 미끄러지게 하고 리스트가 끝까지 미끄러진다고 하면 우리는 헤드를 드러내야 한다.
  • 투명도 값 계산에 주의하세요. 투명도가 1보다 크면 안 돼요
  • 같은 이치로 경계치 처리에 주의해야 한다.

  • 모든 코드:
    
    
    
        
    
        
    
            
    
        
    
    
    
    public class KaiYanAppBarBehavior extends CoordinatorLayout.Behavior {
    
        private Context mContext;
        private View mViewHead;
        private int appbarHeight = 0;
    
        public KaiYanAppBarBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
            mContext = context;
        }
    
        @Override
        public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
    
            mViewHead = parent.findViewById(R.id.kaiyan_appbar);
    
            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            if (layoutParams != null && layoutParams.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) {
                child.layout(0, 0, layoutParams.width, layoutParams.height);
                child.setTranslationY(getAppBarHeight());
            }
    
            return super.onLayoutChild(parent, child, layoutDirection);
        }
    
        @Override
        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
            return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
        }
    
        @Override
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
    
            //          ,           ,           ,           ,         ,         
            float translationY = child.getTranslationY();
            float offsetaAlphe = 0;
    
            if (dy > 0 && translationY > 0) {
    
                //        
                float offestY = translationY - dy;
                Log.d("AAA", "translationY:" + translationY + " / dy:" + dy);
    
                if (offestY < 0) {
                    //     Y                   ,          
                    Log.d("AAA", "AAA");
                    offestY = 0;
                    consumed[1] = (int) translationY;
                } else {
                    Log.d("AAA", "BBB:");
                    offestY = translationY - dy;
                    consumed[1] = dy;
                }
                Log.d("AAA", "translationY:" + offestY);
                child.setTranslationY(offestY);
    
                //           
                if (mViewHead != null) {
                    offsetaAlphe = 0.5f + 0.5f * (translationY / getAppBarHeight());
                    mViewHead.setAlpha(offsetaAlphe);
                }
    
            }
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        }
    
        @Override
        public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    
            int appBarHeight = getAppBarHeight();
            float offestY = 0;
            float translationY = child.getTranslationY();
            float offsetaAlphe = 0;
    
            if (dyUnconsumed < 0 && translationY < appBarHeight) {
    
                //        
                offestY = translationY + Math.abs(dyUnconsumed);
                if (offestY < 0) {
                    offestY = 0;
                }
                if (offestY >= appBarHeight) {
                    offestY = appBarHeight;
                }
                child.setTranslationY(offestY);
    
                //           
                if (mViewHead != null) {
                    if (translationY > appBarHeight) {
                        translationY = appBarHeight;
                    }
                    offsetaAlphe = 0.5f + 0.5f * (translationY / getAppBarHeight());
                    mViewHead.setAlpha(offsetaAlphe);
                }
            }
        }
    
        @Override
        public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
            Log.d("AAA", "onNestedPreFling:" + velocityY);
            return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
        }
    
        private int getAppBarHeight() {
    
            if (appbarHeight == 0) {
                if (mViewHead == null) {
                    return appbarHeight;
                }
                appbarHeight = mViewHead.getMeasuredHeight();
                if (appbarHeight == 0) {
                    mViewHead.measure(0, 0);
                    appbarHeight = mViewHead.getMeasuredHeight();
                }
            }
            return appbarHeight;
    //        return mContext.getResources().getDimensionPixelOffset(R.dimen.app_bar_height);
        }
    }
    

    마지막
    여기서 우리는 몇 가지를 알아야 한다. 매우 중요하다.
  • 우리가 설정한 Behavior?Nested Scroll View, 그가 스크롤을 만들 수 있는데 왜 Behavior를 추가할 수 있습니까?androidui 학습 시리즈 - 사용자 정의 Behavior(1) - 관련 원리를 기억하고 있는 그 그림은 Nested ScrollView가 Nested Scrolling Child 인터페이스에서 스크롤 이벤트를 보내는 것을 실현했고 Nested Scrolling Parent 인터페이스에서 소비 스크롤 이벤트를 차단하는 것도 실현했다.하하, 약간 빙빙 돌지만 구글의nestedscroll은 스크롤을 끼워 넣는다. 핵심은 스크롤을 차단하는 것이다. 우선 스크롤 컨트롤러는 스크롤 데이터를 소비하고 다른 사람의 길을 가면 다른 사람이 갈 길이 없는 사고방식을 취한다. 그렇지 않으면 덧붙이는 효과를 실현할 수 있다.여러분이 굴러가려고 하면 틀림없이 한 사람이 소비를 해야 합니다. 여기서 우리가 결정하는 사람은 누구인지, 우선적으로 소비를 해야 하는지, 아니면 따라야 하는지입니다.
  • 여기에 플러그인 스크롤에 참여하는 것은 2개의view만 있기 때문에 글씨를 거칠게 쓴다. 비록 Behavior로 썼지만 결합이 풀리는 것 같지만 헤드에 대해서는 결합이 풀리지 않는다.사고방식은 전통적인 감청 컨트롤의 스크롤 이벤트와 유사하다. 스크롤 정지 대상에서 데이터를 조작하고 다른view를 제어하는데 본질은 Behavior와 충돌한다.Behavior의 사고방식은 결합이다. 관련된 모든view는 자신의 스크롤 논리를 Behavior에 추상화하여 코드를 변경하지 않고 동적으로view를 대체할 수 있다.이 예가 이런 고도에 이르지 못한 것은 주로 모두가 순서에 따라 점진적인 과정을 배우도록 하기 위해서이다. 모든view가 자신의 Behavior와 같은 결합 효과를 어떻게 실현하는지에 대해서는 다음 예를 보아야 한다.다음 편에서는 Behavior의 이 두 가지 방법을 배웁니다:layoutDependsOn/onDependentViewChanged
  • demo 주소:github
    참조 자료:
  • Material Design의 Behavior 사용 및 사용자 정의 Behavior
  • CoordinatorLayout을 사용하여 복잡한 연결 효과
  • 좋은 웹페이지 즐겨찾기