使用官方支持库23.x. + bottomSheet(如google maps)向上滑动图像

时间:2016-05-19 22:57:41

标签: android google-maps parallax bottom-sheet

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

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

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

    like you can see in this gif
  3. 我已经使用官方的bottomSheet工作(甚至在标签和视图寻呼机内)。

    是什么让我变得疯狂是如何实现使用官方bottomSheet向上滑动时从BottomSheet产生的图像行为?

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

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

    <?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>
    

2 个答案:

答案 0 :(得分:71)

如果你想使用支持库23.4.0实现它。+我会告诉你我是如何得到它以及它是如何工作的。

注意:我为我的英语道歉,我试图给出一个编程答案(只是简短并且混合了有用的代码)但似乎不够好......

到目前为止,我可以看到活动/片段具有以下行为:

  1. 带有动画的2个工具栏,可响应底部的纸张移动。
  2. 当它靠近“模态工具栏”(当您向上滑动时出现的那个)时隐藏的FAB。
  3. 底部背后的背景图像,带有某种视差效果。
  4. 工具栏中的标题(TextView),当底页到达时显示。
  5. 通知satus栏可以将其背景变为透明或全彩。
  6. 具有“锚”状态的自定义底部纸张行为。

  7. 注意2:这个答案谈到6个不是1或2的问题,就像其他问题一样,你现在能看到差异吗?

    好的,现在让我们逐一检查一下:

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

    • 创建Behavior并从AppBarLayout.ScrollingViewBehavior
    • 扩展它
    • 覆盖layoutDependsOnonDependentViewChanged方法。这样做你将听取底部动作。
    • 创建一些隐藏和取消隐藏动画的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();
    }
    

    the complete file if you need it

    第二个工具栏或“模态”工具栏:
    你必须覆盖相同的方法,但在这一方面你必须注意更多的行为:

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

    这个代码有点广泛,所以我会the link

    FAB

    这也是一种自定义行为,但是从FloatingActionButton.Behavior延伸。在onDependentViewChanged中,您必须查看它何时到达“offSet”或指向要隐藏它的位置。在我的情况下,我想隐藏它靠近第二个工具栏,所以我挖掘FAB父(一个CoordiantorLayout)寻找包含ToolBar的AppBarLayout,然后我使用像OffSet这样的ToolBar位置:

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

    Complete Custom FAB Behavior link

    具有视差效果的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;
    }
    


    complete file for backdrop Image with parallax effect

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

    1. 创建一个Java类并从CoordinatorLayout.Behavior<V>
    2. 扩展它
    3. 将粘贴代码从默认BottomSheetBehavior文件复制到新文件。
    4. 使用以下代码修改方法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);
      }
      
    5. 添加新状态

      public static final int STATE_ANCHOR_POINT = X;

    6. 修改下列方法:onLayoutChildonStopNestedScrollBottomSheetBehavior<V> from(V view)setState(可选)



    7. 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您可以在其中查看所有自定义行为

      注意3:下次添加评论,以礼貌的方式询问答案的变化,或者问为什么这个答案比其他人在关闭相同主题之前回答的情况要好一些,或者标记为重复。

      以下是它的外观:
      [CustomBottomSheetBehavior]

答案 1 :(得分:1)

您可以使用协调器布局行为来实现此效果。您需要扩展一个CoordinatorLayout.Behaviour类,并在协调器布局中的一个视图上编写一个依赖项,使您的图像包含视图作为子项。简单地说,您需要将自定义书写行为附加到包含视图的图像。有关编写自定义行为的帮助,请点击链接 Writing custom behaviours