使用官方支持库 23.x.+ bottomSheet 像谷歌地图一样向上滑动图像 [英] Sliding up image with Official Support Library 23.x.+ bottomSheet like google maps

查看:23
本文介绍了使用官方支持库 23.x.+ bottomSheet 像谷歌地图一样向上滑动图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新
我想实现与谷歌地图相同的行为支持库 23.x.+ 而没有任何第三个库

注意:这不是一个重复的问题,因为:

NOTE: this is not a duplicated question because:

  1. 我想使用行为、支持库而不使用任何 3rd 方库(我在问题标题和上面的描述中添加了它)
  2. 我想要你在下一个 gif 中看到的所有行为,其他问题是要求一两个行为并无论如何使用来实现它.

  1. I want to use Behaviors, Support Library and without ANY 3rd party library (I added it in question title and above description)
  2. I wanted ALL behaviors that you see in the next gif, the other questions are asking for one or two behaviors and using anyway to achieve it.

我已经使用了 Official bottomSheet(甚至在选项卡和视图寻呼机中).

I have already the Official bottomSheet working (even inside a tab and view pager).

是什么让我发疯是如何实现使用官方bottomSheet向上滑动时从BottomSheet出现的图像行为?.

我曾尝试使用像 FAB 这样的锚但没有成功.
我读了一些关于使用滚动侦听器的内容,但 ppl 说它不像谷歌地图那样流畅和快速.

我的 XML(我认为它不会有帮助,但无论如何):

What is making me going crazy is how to achieve the image behavior that comes up from the BottomSheet when sliding up using the official bottomSheet?.

I have tried using anchor like FAB with no success.
I read something about using a scroll listener but ppl said it's not smooth and faster like google maps.

My XML (I don't think it's going to help but anyway):

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.MasterActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:layout_scrollFlags="scroll|enterAlways|snap">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="?android:attr/borderlessButtonStyle"
                android:text="Departure"
                android:layout_gravity="center"
                android:id="@+id/buttonToolBar"
                />


        </android.support.v7.widget.Toolbar>

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabBackground="@android:color/white"
            app:tabTextColor="@color/colorAccent"
            app:tabSelectedTextColor="@color/colorAccent"/>

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

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    
    
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/asdf"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:behavior_peekHeight="100dp"
        android:fitsSystemWindows="true"
            app:layout_behavior="android.support.design.widget.BottomSheetBehavior">

        <LinearLayout
            android:id="@+id/qwert"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:paddingBottom="16dp"
            android:background="@android:color/white"
            android:padding="15dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="BOOTOMSHEET TITLE"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Button1"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="text 2"
                android:layout_margin="10dp"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="text 3"
                android:layout_margin="10dp"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="text 4"
                android:layout_margin="10dp"/>


            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="320dp"
                android:background="@color/colorAccent">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="Your remaining content here"
                    android:textColor="@android:color/white" />

            </FrameLayout>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>


    <android.support.design.widget.FloatingActionButton
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_anchor="@id/asdf"
        app:layout_anchorGravity="top|right|end"
        android:src="@drawable/abc_ic_search_api_mtrl_alpha_copy"
        android:layout_margin="@dimen/fab_margin"
        android:clickable="true"/>

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

推荐答案

如果你想使用 Support Library 23.4.0.+ 来实现它,我会告诉你我是如何得到它以及它是如何工作的.

If you want to achieve it using Support Library 23.4.0.+ I will tell you how I got it and how its works.

据我所知,活动/片段具有以下行为:

As far I can see that activity/fragment has the followings behaviors:

  1. 2 个带有动画的工具栏,可响应底部工作表的移动.
  2. 靠近模态工具栏"时隐藏的 FAB;(向上滑动时出现的那个).
  3. 底片后面的背景图像,具有某种视差效果.
  4. 工具栏中的标题 (TextView),在底部工作表到达时出现.
  5. 通知状态栏可以将其背景变为透明或全色.
  6. 带有锚点"的自定义底部工作表行为状态.

note2:这个答案讨论了 6 件事,而不是像其他问题一样关于 1 或 2,你现在能看出区别吗?

note2: This answer talk about 6 things not about 1 or 2 like other question, can you see the difference now?

好的,现在让我们来看看:

Ok, now let's check one bye one:

工具栏
当您在谷歌地图中打开该视图时,您可以看到一个可以搜索的工具栏,这是唯一一个我没有像谷歌地图那样做的,因为我想让它更通用.无论如何,ToolBar 位于 AppBarLayout 内,当您开始拖动 BottomSheet 时它会隐藏起来,当 BottomSheet 到达 COLLAPSED 状态时它会再次出现.
要实现它,您需要:

ToolBars
When you open that view in google maps u can see a toolbar where you can search, it's the only one that I'm not doing equals like google maps because I wanted to do it more generic. Anyway, that ToolBar is inside an AppBarLayout and it got hidden when you start dragging the BottomSheet and it appears again when the BottomSheet reaches the COLLAPSED state.
To achieve it you need:

  • 创建一个 Behavior 并从 AppBarLayout.ScrollingViewBehavior
  • 扩展它
  • 覆盖 layoutDependsOnonDependentViewChanged 方法.这样做你会听到bottomSheet的运动.
  • 创建一些方法来使用动画隐藏和取消隐藏 AppBarLayout/ToolBar.

  • create a Behavior and extend it from AppBarLayout.ScrollingViewBehavior
  • override layoutDependsOn and onDependentViewChanged methods. Doing it you will listen for bottomSheet movements.
  • create some methods to hide and unhide the AppBarLayout/ToolBar with animations.

这就是我为第一个工具栏或 ActionBar 所做的:

This is how I did it for the first toolbar or 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();
}

如果你需要完整的文件

第二个工具栏或模态"工具栏:
您必须覆盖一些方法,但在此方法中,您必须处理更多行为:

The second Toolbar or "Modal" toolbar:
You have to override some methods but in this one, you have to take care of more behaviors:

  • 显示/隐藏带有动画的工具栏
  • 更改状态栏颜色/背景
  • 在工具栏中显示/隐藏BottomSheet标题
  • 关闭bottomSheet或将其发送到折叠状态

这个代码有点多,所以我会让 链接

The code for this one is a little extensive so I will let the link

FAB

这也是一个自定义行为,但从 FloatingActionButton.Behavior 扩展而来.在 onDependentViewChanged 中,您必须查看它何时到达offSet"或指向您想要隐藏它的位置.在我的情况下,我想在它靠近第二个工具栏时隐藏它,所以我深入 FAB 父级(一个 CoordiantorLayout)寻找包含 ToolBar 的 AppBarLayout,然后我使用 ToolBar 位置,如 OffSet:

The FAB

This is a Custom Behavior too but extends from FloatingActionButton.Behavior. In onDependentViewChanged you have to look when it reaches the "offSet" or point in where you want to hide it. In my case I want to hide it when it's near to the second toolbar, so I dig into FAB parent (a CoordiantorLayout) looking for the AppBarLayout that contains the ToolBar, then I use the ToolBar position like 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并避免图像像默认视差效果那样折叠的小算法:

The Image behind the BottomSheet with parallax effect:
Like the others its a custom behavior, the only "complicated" thing in this one is the little algorithm that keeps the Image anchored to the BottomSheet and avoids the image collapse like the default parallax effect:

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


[具有视差效果的背景图像的完整文件][4]

现在结束:自定义BottomSheet行为
要首先完成 3 个步骤,您需要了解默认的 BottomSheetBehavior 有 5 个状态:STATE_DRAGGING、STATE_SETTLING、STATE_EXPANDED、STATE_COLLAPSED、STATE_HIDDEN,对于 Google 地图行为,您需要在折叠和折叠之间添加一个中间状态扩展:STATE_ANCHOR_POINT.
我尝试扩展默认的 bottomSheetBehavior 没有成功,所以我只是复制粘贴所有代码并修改了我需要的内容.
要实现我所说的内容,请执行以下步骤:


[complete file for backdrop Image with parallax effect][4]

Now for the end: The Custom BottomSheet Behavior
To achieve the 3 steps first you need to understand that default BottomSheetBehavior has 5 states: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN, and for the Google Maps behavior you need to add a middle state between collapsed and expanded: STATE_ANCHOR_POINT.
I tried extends the default bottomSheetBehavior with no success, so I just copy-paste all code and modified what I need.
To achieve what I'm talking about following the next steps:

  1. 创建一个Java类并从CoordinatorLayout.Behavior

将粘贴代码从默认的 BottomSheetBehavior 文件复制到您的新文件中.

Copy paste code from the default BottomSheetBehavior file to your new one.

使用以下代码修改方法 clampViewPositionVertical:

Modify the method clampViewPositionVertical with the following code:

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

  • 添加新状态

  • Add a new state

    public static final int STATE_ANCHOR_POINT = X;

    public static final int STATE_ANCHOR_POINT = X;

    修改接下来的方法:onLayoutChildonStopNestedScrollBottomSheetBehaviorfrom(V view)setState (可选)

    Modify the next methods: onLayoutChild, onStopNestedScroll, BottomSheetBehavior<V> from(V view) and setState (optional)



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



    指向洞项目的链接,您可以在其中查看所有自定义行为

    link to the hole project in where you can see all Custom Behaviors

    注意3:下次添加评论以礼貌的方式要求更改答案或询问为什么此答案的某些内容与我关于同一主题的其他答案相同,然后再关闭它或标记为重复.

    note3: next time add a comment asking in a polite way for a change of the answer or ask why this answer has SOME equals stuff than others answer of mine about the same topic BEFORE close it or mark like duplicated.

    这是它的样子
    []

    这篇关于使用官方支持库 23.x.+ bottomSheet 像谷歌地图一样向上滑动图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆