खोज…


परिचय

एक निचली शीट एक शीट है जो स्क्रीन के निचले किनारे से ऊपर स्लाइड करती है।

टिप्पणियों

नीचे की शीट अधिक सामग्री को प्रकट करने के लिए स्क्रीन के नीचे से स्लाइड करती है।
उन्हें v23.2.0 संस्करण में एंड्रॉइड सपोर्ट लाइब्रेरी में जोड़ा गया था।

बॉटमशीटबहेवियर गूगल मैप्स की तरह

2.1.x

यह उदाहरण समर्थन लाइब्रेरी 23.4.0 + पर निर्भर करता है।

BottomSheetBehavior की विशेषता है:

  1. एनिमेशन के साथ दो टूलबार जो नीचे शीट आंदोलनों का जवाब देते हैं।
  2. एक एफएबी जो "मोडल टूलबार" के पास छुपाता है (वह है जो तब दिखाई देता है जब आप ऊपर स्लाइड कर रहे होते हैं)।
  3. कुछ प्रकार के लंबन प्रभाव के साथ नीचे की शीट के पीछे एक पृष्ठभूमि छवि।
  4. टूलबार में एक शीर्षक (पाठ दृश्य) जो नीचे की शीट तक पहुंचने पर दिखाई देता है।
  5. नोटिफिकेशन सैटस बार इसकी पृष्ठभूमि को पारदर्शी या पूर्ण रंग में बदल सकता है।
  6. एक "लंगर" राज्य के साथ एक कस्टम तल शीट व्यवहार।

अब एक-एक करके उनकी जाँच करते हैं:

टूलबार
जब आप Google मानचित्र में उस दृश्य को खोलते हैं, तो आप एक टूलबार देख सकते हैं, जहाँ आप खोज कर सकते हैं, यह केवल वही है जो मैं Google मैप्स की तरह नहीं कर रहा हूँ, क्योंकि मैं इसे और अधिक सामान्य करना चाहता था। वैसे भी है कि ToolBar एक अंदर है AppBarLayout और इसे छिपा कर ली जब आप BottomSheet खींचना शुरू करें और इसे फिर से प्रकट होता है जब BottomSheet तक पहुँचने के COLLAPSED राज्य।
इसे प्राप्त करने के लिए आपको इसकी आवश्यकता है:

  • एक Behavior बनाएँ और AppBarLayout.ScrollingViewBehavior से इसका विस्तार करें
  • ओवरराइड layoutDependsOn और onDependentViewChanged तरीके। ऐसा करने से आप बॉटमशीट मूवमेंट के लिए सुनेंगे।
  • एनिमेशन के साथ AppBarLayout / ToolBar को छिपाने और अनसाइड करने के लिए कुछ तरीके बनाएं।

ऐसा मैंने पहले टूलबार या एक्शनबार के लिए किया था:

@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();
}

यहां आपको जरूरत होने पर पूरी फाइल दी गई है

दूसरा टूलबार या "मोडल" टूलबार:
आपको समान विधियों को ओवरराइड करना होगा, लेकिन इसमें आपको अधिक व्यवहारों का ध्यान रखना होगा:

  • एनिमेशन के साथ टूलबार दिखाएँ / छिपाएँ
  • स्थिति पट्टी रंग / पृष्ठभूमि बदलें
  • टूलबार में बॉटमशीट शीर्षक दिखाएं / छिपाएँ
  • बॉटमशीट बंद करें या उसे ढह गई स्थिति में भेजें

इस एक के लिए कोड थोड़ा व्यापक है, इसलिए मैं लिंक दूंगा

एफएबी

यह एक कस्टम व्यवहार भी है, लेकिन FloatingActionButton.Behavior से फैलता है। FloatingActionButton.BehavioronDependentViewChanged आपको यह देखना होगा कि यह "ऑफ़सेट" तक पहुँचेगा या उस बिंदु पर जहाँ आप इसे छुपाना चाहते हैं। मेरे मामले में मैं इसे तब छिपाना चाहता हूं जब यह दूसरे टूलबार के पास होता है, इसलिए मैं FAB माता-पिता (एक समन्वयकलेयआउट) में खुदाई करता हूं जो AppBarLayout की तलाश करता है जिसमें टूलबार शामिल है, फिर मैं OffSet जैसे टूलबार स्थिति का उपयोग करता 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 व्यवहार लिंक

लंबन प्रभाव के साथ बॉटमशीट के पीछे की छवि :
अन्य लोगों की तरह, यह एक कस्टम व्यवहार है, इसमें केवल "जटिल" चीज़ थोड़ी एल्गोरिथ्म है जो छवि को बॉटमशीट के लिए लंगर डाले रखती है और डिफ़ॉल्ट लंबन प्रभाव जैसी छवि के पतन से बचती है:

@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;
}


लंबन प्रभाव के साथ पृष्ठभूमि छवि के लिए पूरी फ़ाइल

अब अंत के लिए: कस्टम बॉटमशीट व्यवहार
3 चरणों को प्राप्त करने के लिए, पहले आपको यह समझने की आवश्यकता है कि डिफ़ॉल्ट बॉटमशीटबेहाइवर में 5 स्टेट्स हैं: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN और Google मैप्स व्यवहार के लिए, जो कि STATE_ANCHOR_POINT और विस्तारित के बीच एक मध्य स्थिति जोड़ने की जरूरत है।
मैंने बिना किसी सफलता के डिफ़ॉल्ट बॉटमशीट को बढ़ाने की कोशिश की, इसलिए मैंने सिर्फ सभी कोड को कॉपी किया और जो मुझे चाहिए उसे संशोधित किया।
अगले चरणों का पालन करने के लिए मैं जो बात कर रहा हूँ, उसे प्राप्त करने के लिए:

  1. एक जावा क्लास बनाएं और इसे 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. एक नई स्थिति जोड़ें

    सार्वजनिक स्थैतिक अंतिम int STATE_ANCHOR_POINT = X;

  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 ]

तत्काल प्रबंध

सुनिश्चित करें कि निम्न निर्भरता आपके ऐप की बिल्ड.ग्रेड फ़ाइल में निर्भरता के तहत जोड़ी गई है:

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

फिर आप इन विकल्पों का उपयोग करके नीचे की शीट का उपयोग कर सकते हैं:

  • CoordinatorLayout साथ इस्तेमाल किया जाने वाला BottomSheetBehavior
  • BottomSheetDialog जो कि एक शीट शीट व्यवहार के साथ एक संवाद है
  • BottomSheetDialogFragment जो का एक विस्तार है DialogFragment , कि एक बनाता है BottomSheetDialog के बजाय एक मानक संवाद।

लगातार नीचे की चादरें

आप एक बच्चे को एक CoordinatorLayout दृश्य के लिए एक BottomSheetBehavior को संलग्न करके एक निरंतर तल शीट प्राप्त कर सकते हैं:

<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);  

आप सेटस्टैट () पद्धति का उपयोग करके अपने बॉटमशीटबेहेयर की स्थिति सेट कर सकते हैं:

mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);

आप इनमें से किसी एक राज्य का उपयोग कर सकते हैं:

  • STATE_COLLAPSED : यह ध्वस्त स्थिति डिफ़ॉल्ट है और नीचे के साथ लेआउट का एक हिस्सा दिखाता है। ऊंचाई को app:behavior_peekHeight साथ नियंत्रित किया जा सकता है app:behavior_peekHeight क्षमता (डिफ़ॉल्ट के लिए चूक)

  • STATE_EXPANDED : नीचे की शीट की पूरी तरह से विस्तारित स्थिति, जहां या तो पूरी नीचे की शीट दिखाई देती है (यदि इसकी ऊंचाई STATE_EXPANDED CoordinatorLayout से कम है) या संपूर्ण CoordinatorLayout भरा हुआ है

  • STATE_HIDDEN : डिफ़ॉल्ट रूप से अक्षम (और app:behavior_hideable साथ सक्षम: STATE_HIDDEN विशेषता), यह सक्षम करने से उपयोगकर्ता नीचे की शीट पर पूरी तरह से नीचे की शीट को छिपाने के लिए नीचे स्वाइप कर सकते हैं

यदि आप राज्य परिवर्तन के कॉलबैक प्राप्त करना चाहते हैं, तो आप एक 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 एक मोडल बॉटम शीट है।
यह 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");

यह फ़्रैगमेंट एक 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();

इस मामले में आपको एक बॉटमशीट व्यवहार संलग्न करने की आवश्यकता नहीं है।

डिफ़ॉल्ट रूप से विस्तृत मोड में निचला बॉटम डायलॉगफ़्रेग्मेंट खोलें।

बॉटमशीट डायलॉगफ्रैगमेंट डिफ़ॉल्ट रूप से STATE_COLLAPSED में STATE_COLLAPSED है। जिसे STATE_EXPANDED को खोलने के लिए मजबूर किया जा सकता है और निम्नलिखित कोड टेम्पलेट की मदद से पूरी डिवाइस स्क्रीन को ले जा सकता है।

@NonNull @Override सार्वजनिक संवाद onCreateDialog (बंडल बचाया

    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;
}

हालांकि डायलॉग एनीमेशन थोड़ा ध्यान देने योग्य है लेकिन डायलॉगफ्रेम को पूरी स्क्रीन में खोलने का काम बहुत अच्छी तरह से करता है।



Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow