수색…


소개

하단 시트는 화면의 하단 가장자리에서 위로 밀어 올리는 시트입니다.

비고

하단 시트 는 화면 하단에서 위로 밀어서 더 많은 내용을 표시합니다.
그들은 v23.2.0 버전의 안드로이드 지원 라이브러리에 추가되었습니다.

Google지도와 같은 BottomSheetBehavior

2.1.x

이 예제는 지원 라이브러리 23.4.0. +에 따라 다릅니다.

BottomSheetBehavior의 특징은 다음과 같습니다.

  1. 하단 시트 이동에 반응하는 애니메이션이있는 두 개의 도구 모음.
  2. "모달 도구 모음"(위로 올릴 때 나타나는 모달 도구 모음) 근처에있을 때 숨 깁니다.
  3. 어떤 종류의 시차 효과와 함께 바닥 시트 뒤에 배경 이미지.
  4. 툴바의 제목 (TextView)은 하단 시트가 도달 할 때 나타납니다.
  5. 알림 상태 표시 줄은 배경을 투명하거나 풀 컬러로 바꿀 수 있습니다.
  6. "앵커"상태의 사용자 정의 바닥 시트 동작입니다.

이제 하나씩 확인해 보겠습니다.

도구 모음
Google지도에서 해당보기를 열면 검색 할 수있는 곳에서 도구 모음을 볼 수 있습니다.이 도구 모음은 내가 더 일반적으로하고 싶었 기 때문에 Google지도와 정확히 똑같지는 않습니다. 어쨌든 ToolBarAppBarLayout 안에 있고 AppBarLayout 끌기 시작할 때 숨겨져 있으며 BottomSheet가 COLLAPSED 상태에 도달하면 다시 나타납니다.
그것을 달성하기 위해서는 다음을해야합니다.

  • Behavior 생성하고 그것을 AppBarLayout.ScrollingViewBehavior 에서 확장하십시오.
  • layoutDependsOnonDependentViewChanged 메소드를 오버라이드 (override)합니다. 그것을하면 바닥 시트 운동을 듣게됩니다.
  • 애니메이션으로 AppBarLayout / ToolBar를 숨기거나 숨기기위한 몇 가지 메소드를 생성하십시오.

이것은 첫 번째 툴바 또는 ActionBar에 대한 방법입니다.

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    return dependency instanceof NestedScrollView;
}

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                                      View dependency) {

    if (mChild == null) {
        initValues(child, dependency);
        return false;
    }

    float dVerticalScroll = dependency.getY() - mPreviousY;
    mPreviousY = dependency.getY();

    //going up
    if (dVerticalScroll <= 0 && !hidden) {
        dismissAppBar(child);
        return true;
    }

    return false;
}

private void initValues(final View child, View dependency) {

    mChild = child;
    mInitialY = child.getY();

    BottomSheetBehaviorGoogleMapsLike bottomSheetBehavior = BottomSheetBehaviorGoogleMapsLike.from(dependency);
    bottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehaviorGoogleMapsLike.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, @BottomSheetBehaviorGoogleMapsLike.State int newState) {
            if (newState == BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED ||
                    newState == BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN)
                showAppBar(child);
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {

        }
    });
}

private void dismissAppBar(View child){
    hidden = true;
    AppBarLayout appBarLayout = (AppBarLayout)child;
    mToolbarAnimation = appBarLayout.animate().setDuration(mContext.getResources().getInteger(android.R.integer.config_shortAnimTime));
    mToolbarAnimation.y(-(mChild.getHeight()+25)).start();
}

private void showAppBar(View child) {
    hidden = false;
    AppBarLayout appBarLayout = (AppBarLayout)child;
    mToolbarAnimation = appBarLayout.animate().setDuration(mContext.getResources().getInteger(android.R.integer.config_mediumAnimTime));
    mToolbarAnimation.y(mInitialY).start();
}

필요한 경우 여기에 전체 파일이 있습니다.

두 번째 툴바 또는 "모달"툴바 :
같은 방법을 오버라이드해야하지만이 경우에는 더 많은 행동을 처리해야합니다.

  • 애니메이션으로 툴바 표시 ​​/ 숨기기
  • 상태 막대 색상 / 배경 변경
  • 도구 모음에서 BottomSheet 제목 표시 / 숨기기
  • 바닥 시트를 닫거나 접힌 상태로 보냅니다.

이 코드는 약간 광범위하므로 링크 를 허용 합니다.

FAB

이것은 사용자 정의 동작이지만 FloatingActionButton.Behavior 에서 확장됩니다. onDependentViewChanged 에서 "offSet"에 도달하거나 숨길 위치를 가리켜 야합니다. 내 경우에는 내가 두 번째 도구 모음에 가까운 때를 숨기려면, 그래서 나는 그때처럼 툴바의 위치를 사용하여 도구 모음을 포함하는 AppBarLayout을 찾고 FAB 부모 (A CoordinatorLayout) 파고 OffSet :

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {

    if (offset == 0)
        setOffsetValue(parent);

    if (dependency.getY() <=0)
        return false;

    if (child.getY() <= (offset + child.getHeight()) && child.getVisibility() == View.VISIBLE)
        child.hide();
    else if (child.getY() > offset && child.getVisibility() != View.VISIBLE)
        child.show();

    return false;
}

완전한 사용자 지정 FAB 동작 링크

시차 효과가있는 BottomSheet 뒤에있는 이미지 :
다른 것들과 마찬가지로, 이것은 커스텀 비헤이비어이며, BottomSheet에 앵커 된 이미지를 유지하고 디폴트 시차 효과처럼 이미지 붕괴를 피하는 작은 알고리즘이이 "복잡"한 것입니다 :

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                                      View dependency) {

    if (mYmultiplier == 0) {
        initValues(child, dependency);
        return true;
    }

    float dVerticalScroll = dependency.getY() - mPreviousY;
    mPreviousY = dependency.getY();

    //going up
    if (dVerticalScroll <= 0 && child.getY() <= 0) {
        child.setY(0);
        return true;
    }

    //going down
    if (dVerticalScroll >= 0 && dependency.getY() <= mImageHeight)
        return false;

    child.setY( (int)(child.getY() + (dVerticalScroll * mYmultiplier) ) );

    return true;
}


시차 효과가있는 배경 이미지의 전체 파일

이제 마지막으로 : Custom BottomSheet Behavior
3 단계를 수행하려면 먼저 기본 BottomSheetBehavior에 STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN 5 가지 상태가 있고 Google지도 비헤이비어에 대해 접힌 상태와 펼쳐진 상태 사이에 중간 상태를 추가해야한다고 STATE_ANCHOR_POINT 합니다 ( STATE_ANCHOR_POINT .
나는 성공없이 기본 bottomSheetBehavior를 확장하려고 시도했기 때문에 모든 코드를 붙여 넣고 필요한 부분을 수정했다.
제가 이야기하는 것을 성취하려면 다음 단계를 따르십시오.

  1. Java 클래스를 만들고 CoordinatorLayout.Behavior<V> 에서 확장합니다 CoordinatorLayout.Behavior<V>

  2. 기본 BottomSheetBehavior 파일의 붙여 넣기 코드를 새 파일로 복사하십시오.

  3. 다음 코드를 사용하여 clampViewPositionVertical 메서드를 수정합니다.

    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return constrain(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset);
    }
    int constrain(int amount, int low, int high) {
        return amount < low ? low : (amount > high ? high : amount);
    }
    
  4. 새 상태 추가

    공공 정적 final int STATE_ANCHOR_POINT = 엑스;

  5. 다음 메소드를 수정하십시오 : onLayoutChild , onStopNestedScroll , BottomSheetBehavior<V> from(V view)setState (선택 사항)



public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
    // First let the parent lay it out
    if (mState != STATE_DRAGGING && mState != STATE_SETTLING) {
        if (ViewCompat.getFitsSystemWindows(parent) &&
                !ViewCompat.getFitsSystemWindows(child)) {
            ViewCompat.setFitsSystemWindows(child, true);
        }
        parent.onLayoutChild(child, layoutDirection);
    }
    // Offset the bottom sheet
    mParentHeight = parent.getHeight();
    mMinOffset = Math.max(0, mParentHeight - child.getHeight());
    mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset);

    //if (mState == STATE_EXPANDED) {
    //    ViewCompat.offsetTopAndBottom(child, mMinOffset);
    //} else if (mHideable && mState == STATE_HIDDEN...
    if (mState == STATE_ANCHOR_POINT) {
        ViewCompat.offsetTopAndBottom(child, mAnchorPoint);
    } else if (mState == STATE_EXPANDED) {
        ViewCompat.offsetTopAndBottom(child, mMinOffset);
    } else if (mHideable && mState == STATE_HIDDEN) {
        ViewCompat.offsetTopAndBottom(child, mParentHeight);
    } else if (mState == STATE_COLLAPSED) {
        ViewCompat.offsetTopAndBottom(child, mMaxOffset);
    }
    if (mViewDragHelper == null) {
        mViewDragHelper = ViewDragHelper.create(parent, mDragCallback);
    }
    mViewRef = new WeakReference<>(child);
    mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
    return true;
}


public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
    if (child.getTop() == mMinOffset) {
        setStateInternal(STATE_EXPANDED);
        return;
    }
    if (target != mNestedScrollingChildRef.get() || !mNestedScrolled) {
        return;
    }
    int top;
    int targetState;
    if (mLastNestedScrollDy > 0) {
        //top = mMinOffset;
        //targetState = STATE_EXPANDED;
        int currentTop = child.getTop();
        if (currentTop > mAnchorPoint) {
            top = mAnchorPoint;
            targetState = STATE_ANCHOR_POINT;
        }
        else {
            top = mMinOffset;
            targetState = STATE_EXPANDED;
        }
    } else if (mHideable && shouldHide(child, getYVelocity())) {
        top = mParentHeight;
        targetState = STATE_HIDDEN;
    } else if (mLastNestedScrollDy == 0) {
        int currentTop = child.getTop();
        if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) {
            top = mMinOffset;
            targetState = STATE_EXPANDED;
        } else {
            top = mMaxOffset;
            targetState = STATE_COLLAPSED;
        }
    } else {
        //top = mMaxOffset;
        //targetState = STATE_COLLAPSED;
        int currentTop = child.getTop();
        if (currentTop > mAnchorPoint) {
            top = mMaxOffset;
            targetState = STATE_COLLAPSED;
        }
        else {
            top = mAnchorPoint;
            targetState = STATE_ANCHOR_POINT;
        }
    }
    if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
        setStateInternal(STATE_SETTLING);
        ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState));
    } else {
        setStateInternal(targetState);
    }
    mNestedScrolled = false;
}

public final void setState(@State int state) {
    if (state == mState) {
        return;
    }
    if (mViewRef == null) {
        // The view is not laid out yet; modify mState and let onLayoutChild handle it later
        /**
         * New behavior (added: state == STATE_ANCHOR_POINT ||)
         */
        if (state == STATE_COLLAPSED || state == STATE_EXPANDED ||
                state == STATE_ANCHOR_POINT ||
                (mHideable && state == STATE_HIDDEN)) {
            mState = state;
        }
        return;
    }
    V child = mViewRef.get();
    if (child == null) {
        return;
    }
    int top;
    if (state == STATE_COLLAPSED) {
        top = mMaxOffset;
    } else if (state == STATE_ANCHOR_POINT) {
        top = mAnchorPoint;
    } else if (state == STATE_EXPANDED) {
        top = mMinOffset;
    } else if (mHideable && state == STATE_HIDDEN) {
        top = mParentHeight;
    } else {
        throw new IllegalArgumentException("Illegal state argument: " + state);
    }
    setStateInternal(STATE_SETTLING);
    if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
        ViewCompat.postOnAnimation(child, new SettleRunnable(child, state));
    }
}


public static <V extends View> BottomSheetBehaviorGoogleMapsLike<V> from(V view) {
    ViewGroup.LayoutParams params = view.getLayoutParams();
    if (!(params instanceof CoordinatorLayout.LayoutParams)) {
        throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
    }
    CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params)
            .getBehavior();
    if (!(behavior instanceof BottomSheetBehaviorGoogleMapsLike)) {
        throw new IllegalArgumentException(
                "The view is not associated with BottomSheetBehaviorGoogleMapsLike");
    }
    return (BottomSheetBehaviorGoogleMapsLike<V>) behavior;
}



모든 사용자 정의 비헤이비어를 볼 수 있는 전체 프로젝트에 대한 링크

그리고 그것은 다음과 같이 보입니다.
[ CustomBottomSheetBehavior ]

빠른 설치

종속성에 따라 앱의 build.gradle 파일에 다음 종속성이 추가되었는지 확인하십시오.

compile 'com.android.support:design:25.3.1'

그런 다음 아래 옵션을 사용하여 하단 시트를 사용할 수 있습니다.

영구적 인 바닥 시트

BottomSheetBehavior 를 자식에게 연결하는 지속 바닥 시트CoordinatorLayout 의 하위 뷰를 얻을 수 있습니다.

<android.support.design.widget.CoordinatorLayout >

    <!-- .....   -->

    <LinearLayout
       android:id="@+id/bottom_sheet"
       android:elevation="4dp"
       android:minHeight="120dp"
       app:behavior_peekHeight="120dp"
       ...
       app:layout_behavior="android.support.design.widget.BottomSheetBehavior">

           <!-- .....   -->

       </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

그런 다음 코드에서 다음을 사용하여 참조를 만들 수 있습니다.

 // The View with the BottomSheetBehavior  
 View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);  
 BottomSheetBehavior mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);  

setState () 메서드를 사용하여 BottomSheetBehavior의 상태를 설정할 수 있습니다.

mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);

다음 상태 중 하나를 사용할 수 있습니다.

  • STATE_COLLAPSED :이 접힌 상태가 기본값이며 하단의 레이아웃 일부만 표시됩니다. 높이는 app:behavior_peekHeight 속성 (기본값은 0)으로 제어 할 수 있습니다 app:behavior_peekHeight

  • STATE_EXPANDED : 전체 바닥 시트 중 하나가 표시되는 바닥 판의 완전 팽창 상태 (높이가 함유 미만인 경우 CoordinatorLayout ) 또는 전체 CoordinatorLayout 가득

  • STATE_HIDDEN : 기본적으로 사용 중지되며 app:behavior_hideable 속성을 사용하도록 설정하면 사용자가 하단 시트를 아래로 스 와이프하여 하단 시트를 완전히 숨길 수 있습니다.

상태 변경 콜백을 받으려면 BottomSheetCallback 추가하면 BottomSheetCallback .

mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetCallback() {  
    @Override  
    public void onStateChanged(@NonNull View bottomSheet, int newState) {  
      // React to state change  
    }  
      @Override  
      public void onSlide(@NonNull View bottomSheet, float slideOffset) {  
       // React to dragging events  
   }  
 });  

BottomSheetDialogFragment가있는 모달 바닥 시트

BottomSheetDialogFragment 사용하여 모달 바닥 시트 를 구현할 수 있습니다.

BottomSheetDialogFragment 는 모달 하단 시트입니다.
이것은 DialogFragment 한 버전으로 부동 대화 상자 대신 BottomSheetDialog 를 사용하여 아래쪽 시트를 보여줍니다.

조각을 정의하면됩니다.

public class MyBottomSheetDialogFragment extends BottomSheetDialogFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.my_fragment_bottom_sheet, container);
    }
}

그런 다음이 코드를 사용하여 조각을 표시합니다.

MyBottomSheetDialogFragment mySheetDialog = new MyBottomSheetDialogFragment();
FragmentManager fm = getSupportFragmentManager();
mySheetDialog.show(fm, "modalSheetDialog");

이 Fragment는 BottomSheetDialog 를 생성합니다.

BottomSheetDialog가있는 모달 바닥 시트

BottomSheetDialog 는 하단 시트로 스타일이 지정된 대화 상자입니다.

그냥 사용 :

//Create a new BottomSheetDialog
BottomSheetDialog dialog = new BottomSheetDialog(context);
//Inflate the layout R.layout.my_dialog_layout
dialog.setContentView(R.layout.my_dialog_layout);
//Show the dialog
dialog.show();

이 경우 BottomSheet 비헤이비어를 첨부 할 필요가 없습니다.

BottomSheet DialogFragment를 기본적으로 펼치기 모드로 엽니 다.

BottomSheet DialogFragment는 기본적으로 STATE_COLLAPSED 에서 열립니다. 다음 코드 템플릿의 도움으로 STATE_EXPANDED 를 열어 전체 장치 화면을 열도록 강요 할 수 있습니다.

@NonNull @Override public 대화 상자 onCreateDialog (Bundle savedInstanceState) {

    BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

    dialog.setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            BottomSheetDialog d = (BottomSheetDialog) dialog;

            FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
            BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    });

    // Do something with your dialog like setContentView() or whatever
    return dialog;
}

다이얼로그 애니메이션은 약간 눈에 띄기는하지만 DialogFragment를 전체 화면으로 아주 잘 열어 보는 작업입니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow