FAB 结合 NestedScrollView

FAB in combination with NestedScrollView

我有一个 "details" 片段,其中有很多文本视图、相对布局等。它们被包裹在 NestedScrollView 中:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:attr/windowBackground"
    android:orientation="vertical"
    android:paddingBottom="10dp">

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/movie_details_scroll"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        // Here are the textviews, relativelayouts etc...
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/edit_movies_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/ic_edit_white_24dp"
    />
</android.support.design.widget.CoordinatorLayout>

现在我想在屏幕底部添加一个 FAB(不在 nestedscrollview 的底部),当我滚动 nestedscrollview 时它也会向下滚动。但是在我的代码中,FAB 始终位于嵌套滚动视图的底部。所以当我一直向下滚动时,FAB 出现了。我希望 FAB 在右下角始终可见...

编辑

我忘了说我使用淡入淡出的操作栏 (https://github.com/ManuelPeinado/FadingActionBar) 但有点编辑。

相关代码:

m_FadingActionBarHelper.createView(getContext()); // this will create the view with header content etc.

创建视图:

public final View createView(LayoutInflater inflater) {
  //
  // Prepare everything

  mInflater = inflater;
  if (mContentView == null) {
    mContentView = inflater.inflate(mContentLayoutResId, null); // this will load my view which i already posted.
  }
  if (mHeaderView == null) {
    mHeaderView = inflater.inflate(mHeaderLayoutResId, null, false);
  }

  // See if we are in a ListView, WebView or ScrollView scenario

  ListView listView = (ListView) mContentView.findViewById(android.R.id.list);
  View root;
  if (listView != null) {
    root = createListView(listView);
  } else if (mContentView instanceof CDMObservableWebViewWithHeader){
    root = createWebView();
  } else {
    root = createScrollView(); // this will be called in my example
  }

  if (mHeaderOverlayView == null && mHeaderOverlayLayoutResId != 0) {
    mHeaderOverlayView = inflater.inflate(mHeaderOverlayLayoutResId, mMarginView, false);
  }
  if (mHeaderOverlayView != null) {
    mMarginView.addView(mHeaderOverlayView);
  }

  // Use measured height here as an estimate of the header height, later on after the layout is complete
  // we'll use the actual height
  int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
  int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
  mHeaderView.measure(widthMeasureSpec, heightMeasureSpec);
  updateHeaderHeight(mHeaderView.getMeasuredHeight());

  root.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
      int headerHeight = mHeaderContainer.getHeight();
      if ((!mFirstGlobalLayoutPerformed || (headerHeight != mLastHeaderHeight)) && headerHeight != 0) {
        updateHeaderHeight(headerHeight);
        mFirstGlobalLayoutPerformed = true;
      }
    }
  });
  return root;
}

createScrollView:

private View createScrollView() {
  ViewGroup scrollViewContainer = (ViewGroup) mInflater.inflate(R.layout.fab__scrollview_container, null);

  CDMObservableScrollView scrollView = (CDMObservableScrollView) scrollViewContainer.findViewById(R.id.fab__scroll_view);
  scrollView.setOnScrollChangedCallback(mOnScrollChangedListener);

  ViewGroup contentContainer = (ViewGroup) scrollViewContainer.findViewById(R.id.fab__container);
  LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
          LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  mContentView.setLayoutParams(layoutParams);
  contentContainer.addView(mContentView);
  mHeaderContainer = (FrameLayout) scrollViewContainer.findViewById(R.id.fab__header_container);
  initializeGradient(mHeaderContainer);
  mHeaderContainer.addView(mHeaderView, 0);
  mMarginView = (FrameLayout) contentContainer.findViewById(R.id.fab__content_top_margin);

  return scrollViewContainer;
}

将加载的xml:

<denis.de.meperdia.fadingactionbar.CDMRootLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/fab__header_container"/>

    <denis.de.meperdia.fadingactionbar.CDMObservableScrollView
        android:id="@+id/fab__scroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/fab__container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <FrameLayout
                android:id="@+id/fab__content_top_margin"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"/>
        </LinearLayout>
    </denis.de.meperdia.fadingactionbar.CDMObservableScrollView>

</denis.de.meperdia.fadingactionbar.CDMRootLayout>

class CDMRootLayout:

public class CDMRootLayout extends CoordinatorLayout {

  private View mHeaderContainer;
  private View mListViewBackground;
  private boolean mInitialized = false;

  public CDMRootLayout(Context context) {
    super(context);
  }

  public CDMRootLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public CDMRootLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    //at first find headerViewContainer and listViewBackground
    if(mHeaderContainer == null)
      mHeaderContainer = findViewById(R.id.fab__header_container);
    if(mListViewBackground == null)
      mListViewBackground = findViewById(R.id.fab__listview_background);

    //if there's no headerViewContainer then fallback to standard FrameLayout
    if(mHeaderContainer == null) {
      super.onLayout(changed, left, top, right, bottom);
      return;
    }

    if(!mInitialized) {
      super.onLayout(changed, left, top, right, bottom);
      //if mListViewBackground not exists or mListViewBackground exists
      //and its top is at headercontainer height then view is initialized
      if(mListViewBackground == null || mListViewBackground.getTop() == mHeaderContainer.getHeight())
        mInitialized = true;
      return;
    }

    //get last header and listViewBackground position
    int headerTopPrevious = mHeaderContainer.getTop();
    int listViewBackgroundTopPrevious = mListViewBackground != null ? mListViewBackground.getTop() : 0;

    //relayout
    super.onLayout(changed, left, top, right, bottom);

    //revert header top position
    int headerTopCurrent = mHeaderContainer.getTop();
    if(headerTopCurrent != headerTopPrevious) {
      mHeaderContainer.offsetTopAndBottom(headerTopPrevious - headerTopCurrent);
    }
    //revert listViewBackground top position
    int listViewBackgroundTopCurrent = mListViewBackground != null ? mListViewBackground.getTop() : 0;
    if(listViewBackgroundTopCurrent != listViewBackgroundTopPrevious) {
      mListViewBackground.offsetTopAndBottom(listViewBackgroundTopPrevious - listViewBackgroundTopCurrent);
    }
  }
}

以及 class CDMObservableScrollView:

public class CDMObservableScrollView extends ScrollView implements CDMObservableScrollable {
    // Edge-effects don't mix well with the translucent action bar in Android 2.X
    private boolean mDisableEdgeEffects = true;

    private CDMOnScrollChangedCallback mOnScrollChangedListener;

    public CDMObservableScrollView(Context context) {
      super(context);
    }

    public CDMObservableScrollView(Context context, AttributeSet attrs) {
      super(context, attrs);
    }

    public CDMObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
      super.onScrollChanged(l, t, oldl, oldt);
      if (mOnScrollChangedListener != null) {
        mOnScrollChangedListener.onScroll(l, t);
      }
    }

    @Override
    protected float getTopFadingEdgeStrength() {
      // 
      if (mDisableEdgeEffects && Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        return 0.0f;
      }
      return super.getTopFadingEdgeStrength();
    }

    @Override
    protected float getBottomFadingEdgeStrength() {
      // 
      if (mDisableEdgeEffects && Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        return 0.0f;
      }
      return super.getBottomFadingEdgeStrength();
    }

    @Override
    public void setOnScrollChangedCallback(CDMOnScrollChangedCallback callback) {
      mOnScrollChangedListener = callback;
    }
}

编辑 2

我现在可以划问题了:

如果我使用这些行,FAB 将按照我的意愿工作:

    <denis.de.meperdia.fadingactionbar.CDMObservableScrollView
        android:id="@+id/fab__scroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

但是我的淡入淡出操作栏的同步被破坏了...

抱歉代码太多,但如果没有这个,理解起来真的很复杂。

替换android:layout_gravity="bottom|end"

用这个android:layout_gravity="bottom|right"

在 NestedScrollView 中添加下行。

app:layout_behavior="@string/appbar_scrolling_view_behavior"

试试这个例子:

activity_main.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="com.internet.Main2Activity">

    <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" />

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

    <include
        layout="@layout/content_main2"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@android:drawable/ic_dialog_email" />

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

content_main2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.internet.Main2Activity"
    tools:showIn="@layout/activity_main2">

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:text="asdds" />
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>

</LinearLayout>

导入 app 命名空间,用 RelativeLayout 包围 NestedScrollView,然后将 RelativeLayout 设置为 anchor for FAB

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"     //Import app namespace
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:attr/windowBackground"
    android:orientation="vertical"
    android:paddingBottom="10dp">

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/mainview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        // Here are the textviews, relativelayouts etc...
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/edit_movies_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/ic_edit_white_24dp"
        app:layout_anchor="@id/mainview"           //attributes of app namespace
        app:layout_anchorGravity="bottom|end"
    />
</android.support.design.widget.CoordinatorLayout>

最后我通过将 FloatingActionButton 从内容布局移到外部解决了这个问题。

我的容器布局如下所示:

<?xml version="1.0" encoding="utf-8"?>
<denis.de.meperdia.fadingactionbar.CDMRootLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/fab__header_container"/>

    <denis.de.meperdia.fadingactionbar.CDMObservableScrollView
        android:id="@+id/fab__scroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/fab__container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <FrameLayout
                android:id="@+id/fab__content_top_margin"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"/>
        </LinearLayout>
    </denis.de.meperdia.fadingactionbar.CDMObservableScrollView>

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/fab__floating_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.design.widget.CoordinatorLayout>

</denis.de.meperdia.fadingactionbar.CDMRootLayout>

我为 FloatingActionButton 添加了一个容器,我通过从另一个文件加载它来动态填充它。 FloatingActionButton 的移动问题现已解决。还有一个小问题,但我为此开了一个新问题。

编辑

更改了我的解决方案。我有一个问题,如果我想显示一个小吃店,FloatingActionButton 没有正确滚动。我现在以编程方式将 FloatingActionButton 添加到根视图。现在它可以正常工作了。

您可以使用坐标布局锚点 属性 轻松完成此操作,将您的 fab 附加到您的任何视图节点

CoordinatorLayout 并使用 layout_anchor 和 layout_anchorGravity 属性。

      <ListView
          android:id="@+id/lvToDoList"
          android:layout_width="match_parent"
          android:layout_height="match_parent"></ListView>
<android.support.design.widget.FloatingActionButton
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="bottom|right"
          android:layout_margin="16dp"
          android:src="@drawable/ic_done"
          app:layout_anchor="@id/lvToDoList"
          app:layout_anchorGravity="bottom|right|end" />

这将使 FAB 能够在底部匹配到列表视图。你可以对任何其他 layout/View.

做同样的事情