web-dev-qa-db-ja.com

Android-CoordinatorLayoutで使用するとフッターが画面からスクロールします

AppBarLayoutをスクロールすると画面外にスクロールするRecyclerViewがあります。 RecyclerViewの下には、フッターであるRelativeLayoutがあります。

フッターは上にスクロールした後にのみ表示されます。

layout_scrollFlags="scroll|enterAlways"

しかし、スクロールフラグはありません。バグですか、何か間違っていますか?常に見えるようにしたい

スクロールする前に

enter image description here

スクロール後

enter image description here

更新

これで google issue を開きました-「WorkingAsIntended」とマークされていましたが、フラグメント内のフッターの有効なソリューションが必要なため、これはまだ役に立ちません。

更新2

ここでアクティビティとフラグメントxmlを見つけることができます -

activity.xmlの34行目-app:layout_behavior="@string/appbar_scrolling_view_behavior"を含む行がコメントアウトされている場合、テキストendが最初から見えることに注意してください-それ以外の場合、上にスクロールした後にのみ表示されます

57
Noa Drach

Learn OpenGL ESのソリューションの簡易バージョンを使用します( https://stackoverflow.com/a/33396965/778951 )-Noaのソリューションを改善します(- https:// stackoverflow。 com/a/31140112/1317564 )。各タブのViewPagerコンテンツにフッターボタンがあるTabLayoutの上にあるシンプルなクイックリターンツールバーでうまく機能します。

画面の下部に配置したいView/ViewGroupで、FixScrollingFooterBehaviorをlayout_behaviorに設定するだけです。

レイアウト:

<?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"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/appbar"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

            <Android.support.v7.widget.Toolbar
                Android:id="@+id/toolbar"
                Android:layout_width="match_parent"
                Android:layout_height="?android:attr/actionBarSize"
                Android:minHeight="?android:attr/actionBarSize"
                app:title="Foo"
                app:layout_scrollFlags="scroll|enterAlways|snap"
                />

            <Android.support.design.widget.TabLayout
                Android:id="@+id/tabs"
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                app:tabMode="fixed"/>

    </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="com.spreeza.shop.ui.widgets.FixScrollingFooterBehavior"
        />

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

動作:

public class FixScrollingFooterBehavior extends AppBarLayout.ScrollingViewBehavior {

    private AppBarLayout appBarLayout;

    public FixScrollingFooterBehavior() {
        super();
    }

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

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

        if (appBarLayout == null) {
            appBarLayout = (AppBarLayout) dependency;
        }

        final boolean result = super.onDependentViewChanged(parent, child, dependency);
        final int bottomPadding = calculateBottomPadding(appBarLayout);
        final boolean paddingChanged = bottomPadding != child.getPaddingBottom();
        if (paddingChanged) {
            child.setPadding(
                child.getPaddingLeft(),
                child.getPaddingTop(),
                child.getPaddingRight(),
                bottomPadding);
            child.requestLayout();
        }
        return paddingChanged || result;
    }


    // Calculate the padding needed to keep the bottom of the view pager's content at the same location on the screen.
    private int calculateBottomPadding(AppBarLayout dependency) {
        final int totalScrollRange = dependency.getTotalScrollRange();
        return totalScrollRange + dependency.getTop();
    }
}
44
jhavatar

更新

以下の解決策は5.1では機能しません。5で機能するため、getTopの代わりにgetTranslationYを使用して計算します。

_layout.getTop()-->(int)layout.getTranslationY()
appbar.getTop()+toolbar.getHeight()-->(int)(appbar.getTranslationY()+toolbar.getHeight())
_

更新2新しいサポートライブラリ-22.2.1-5.1バージョンと前バージョンの間に違いはありません。getTopのみを使用し、この回答の以前の更新を無視する必要があります。

元のソリューション多くの方向を調べた後、ソリューションは実際にシンプルであることが判明しました。フラグメントにpaddingBottomを追加し、ページのスクロールに合わせて調整します。

ツールバーの変更をカバーするためにパディングが必要ですy位置-toolbarが消えて再表示されると、コーディネーターレイアウトはページ全体を上下に移動します。

これは、_AppBarLayout.ScrollingViewBehavior_を拡張し、これをactivityfragment要素の動作として設定することで実現できます。

コードの基本は次のとおりです-ツールバーのみのアクティビティで機能します-appbar.getTop() + toolbar.getHeight()に置き換えることができ、appbartabsが含まれます。

activity.xml

_<Android.support.design.widget.CoordinatorLayout
Android:id="@+id/main"
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.support.design.widget.AppBarLayout
    Android:id="@+id/appbar"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:elevation="3dp"
    app:elevation="3dp">
    <Android.support.v7.widget.Toolbar
        Android:id="@+id/toolbar"
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        app:layout_scrollFlags="scroll|enterAlways"
        />
</Android.support.design.widget.AppBarLayout>
<fragment
    Android:id="@+id/fragment"
    Android:name="com.example.noa.footer2.MainActivityFragment"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    app:layout_behavior="com.example.noa.footer2.MyBehavior"
    tools:layout="@layout/fragment"/>
</Android.support.design.widget.CoordinatorLayout>
_

fragment.xml

_<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
            xmlns:tools="http://schemas.Android.com/tools"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:paddingBottom="48dp"
            Android:background="@Android:color/holo_green_dark"
            tools:context=".MainActivityFragment">
<Android.support.v7.widget.RecyclerView
    Android:id="@+id/list"
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:background="@null"/>
<View
    Android:layout_width="match_parent"
    Android:layout_height="100dp"
    Android:layout_alignParentBottom="true"
    Android:background="@Android:color/holo_red_light"/>
</RelativeLayout>
_

MainActivityFragment#onActivityCreated

_    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        CoordinatorLayout.LayoutParams lp = (LayoutParams) getView().getLayoutParams();
        MyBehavior behavior = (MyBehavior) lp.getBehavior();
        behavior.setLayout(getView());
    }
_

MyBehavior

_public class MyBehavior extends AppBarLayout.ScrollingViewBehavior {

    private View layout;

    public MyBehavior() {
    }

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

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        boolean result = super.onDependentViewChanged(parent, child, dependency);
        if (layout != null) {
            layout.setPadding(layout.getPaddingLeft(), layout.getPaddingTop(), layout
                .getPaddingRight(), layout.getTop());
        }
        return result;
    }

    public void setLayout(View layout) {
        this.layout = layout;
    }
}
_
19
Noa Drach

私はノアのソリューションから始めました( https://stackoverflow.com/a/31140112/1317564 )、それは指のドラッグで機能しましたが、私は投げ飛ばしで問題に直面していました。メソッド呼び出しをトレースし、さまざまなアイデアを試してみた後、ここで解決策を見つけました。

// Workaround for https://code.google.com/p/Android/issues/detail?id=177195
// Based off of solution originally found here: https://stackoverflow.com/a/31140112/1317564
@SuppressWarnings("unused")
public class CustomScrollingViewBehavior extends AppBarLayout.ScrollingViewBehavior {
    private AppBarLayout appBarLayout;
    private boolean onAnimationRunnablePosted = false;

    @SuppressWarnings("unused")
    public CustomScrollingViewBehavior() {

    }

    @SuppressWarnings("unused")
    public CustomScrollingViewBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        if (appBarLayout != null) {
            // We need to check from when a scroll is started, as we may not have had the chance to update the layout at
            // the start of a scroll or fling event.
            startAnimationRunnable(child, appBarLayout);
        }
        return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, final View child, int parentWidthMeasureSpec, int widthUsed,
                                  int parentHeightMeasureSpec, int heightUsed) {
        if (appBarLayout != null) {
            final int bottomPadding = calculateBottomPadding(appBarLayout);
            if (bottomPadding != child.getPaddingBottom()) {
                // We need to update the padding in onMeasureChild as otherwise we won't have the correct padding in
                // place when the view is flung, and the changes done in onDependentViewChanged will only take effect on
                // the next animation frame, which means it will be out of sync with the new scroll offset. This is only
                // needed when the view is flung -- when dragged with a finger, things work fine with just
                // implementing onDependentViewChanged().
                child.setPadding(child.getPaddingLeft(), child.getPaddingTop(), child.getPaddingRight(), bottomPadding);
            }
        }

        return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, final View child, final View dependency) {
        if (appBarLayout == null)
            appBarLayout = (AppBarLayout) dependency;

        final boolean result = super.onDependentViewChanged(parent, child, dependency);
        final int bottomPadding = calculateBottomPadding(appBarLayout);
        final boolean paddingChanged = bottomPadding != child.getPaddingBottom();
        if (paddingChanged) {
            // If we've changed the padding, then update the child and make sure a layout is requested.
            child.setPadding(child.getPaddingLeft(),
                    child.getPaddingTop(),
                    child.getPaddingRight(),
                    bottomPadding);
            child.requestLayout();
        }

        // Even if we didn't change the padding, if onDependentViewChanged was called then that means that the app bar
        // layout was changed or was flung. In that case, we want to check for these changes over the next few animation
        // frames so that we can ensure that we capture all the changes and update the view pager padding to match.
        startAnimationRunnable(child, dependency);
        return paddingChanged || result;
    }

    // Calculate the padding needed to keep the bottom of the view pager's content at the same location on the screen.
    private int calculateBottomPadding(AppBarLayout dependency) {
        final int totalScrollRange = dependency.getTotalScrollRange();
        return totalScrollRange + dependency.getTop();
    }

    private void startAnimationRunnable(final View child, final View dependency) {
        if (onAnimationRunnablePosted)
            return;

        final int onPostChildTop = child.getTop();
        final int onPostDependencyTop = dependency.getTop();
        onAnimationRunnablePosted = true;
        // Start looking for changes at the beginning of each animation frame. If there are any changes, we have to
        // ensure that layout is run again so that we can update the padding to take the changes into account.
        child.postOnAnimation(new Runnable() {
            private static final int MAX_COUNT_OF_FRAMES_WITH_NO_CHANGES = 5;
            private int previousChildTop = onPostChildTop;
            private int previousDependencyTop = onPostDependencyTop;
            private int countOfFramesWithNoChanges;

            @Override
            public void run() {
                // Make sure we request a layout at the beginning of each animation frame, until we notice a few
                // frames where nothing changed.
                final int currentChildTop = child.getTop();
                final int currentDependencyTop = dependency.getTop();
                boolean hasChanged = false;

                if (currentChildTop != previousChildTop) {
                    previousChildTop = currentChildTop;
                    hasChanged = true;
                    countOfFramesWithNoChanges = 0;
                }
                if (currentDependencyTop != previousDependencyTop) {
                    previousDependencyTop = currentDependencyTop;
                    hasChanged = true;
                    countOfFramesWithNoChanges = 0;
                }
                if (!hasChanged) {
                    countOfFramesWithNoChanges++;
                }
                if (countOfFramesWithNoChanges <= MAX_COUNT_OF_FRAMES_WITH_NO_CHANGES) {
                    // We can still look for changes on subsequent frames.
                    child.requestLayout();
                    child.postOnAnimation(this);
                } else {
                    // We've encountered enough frames with no changes. Do a final layout request, and don't repost.
                    child.requestLayout();
                    onAnimationRunnablePosted = false;
                }
            }
        });
    }
}

私はすべてのアニメーションフレームのレイアウトを再確認するのが好きではありません。アプリバーのレイアウトをプログラムで拡大/縮小すると問題が発生するため、このソリューションは完璧ではありませんが、今のところより良いソリューションは見つかりませんでした。新しいデバイスではパフォーマンスは良好で、古いデバイスでは許容できます。他の誰かがそうするならば、ソースとして私の答えを受け取って、再掲してください。

18
Learn OpenGL ES
package pl.mkaras.utils;

import Android.content.Context;
import Android.support.design.widget.AppBarLayout;
import Android.support.design.widget.CoordinatorLayout;
import Android.support.v4.view.ViewCompat;
import Android.support.v7.widget.Toolbar;
import Android.util.AttributeSet;
import Android.view.View;
import Android.view.ViewGroup;
import Java.util.List;

public class ScrollViewBehaviorFix extends AppBarLayout.ScrollingViewBehavior {

    public ScrollViewBehaviorFix() {
        super();
    }

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

    public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
                                  int heightUsed) {
        if (child.getLayoutParams().height == -1) {
            List<View> dependencies = parent.getDependencies(child);
            if (dependencies.isEmpty()) {
                return false;
            }

            final AppBarLayout appBar = findFirstAppBarLayout(dependencies);
            if (appBar != null && ViewCompat.isLaidOut(appBar)) {
                int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
                if (availableHeight == 0) {
                    availableHeight = parent.getHeight();
                }

                final int height = availableHeight - appBar.getMeasuredHeight();
                int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);

                parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
                int childContentHeight = getContentHeight(child);

                if (childContentHeight <= height) {
                    updateToolbar(parent, appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed, false);

                    heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
                    parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);

                    return true;
                } else {
                    updateToolbar(parent, appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed, true);

                    return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
                }
            }
        }

        return false;
    }

    private static int getContentHeight(View view) {
        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;

            int contentHeight = 0;
            for (int index = 0; index < viewGroup.getChildCount(); ++index) {
                View child = viewGroup.getChildAt(index);
                contentHeight += child.getMeasuredHeight();
            }
            return contentHeight;
        } else {
            return view.getMeasuredHeight();
        }
    }

    private static AppBarLayout findFirstAppBarLayout(List<View> views) {
        int i = 0;

        for (int z = views.size(); i < z; ++i) {
            View view = views.get(i);
            if (view instanceof AppBarLayout) {
                return (AppBarLayout) view;
            }
        }

        throw new IllegalArgumentException("Missing AppBarLayout in CoordinatorLayout dependencies");
    }

    private void updateToolbar(CoordinatorLayout parent, AppBarLayout appBar, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
                               int heightUsed, boolean toggle) {
        toggleToolbarScroll(appBar, toggle);

        appBar.forceLayout();
        parent.onMeasureChild(appBar, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
    }

    private void toggleToolbarScroll(AppBarLayout appBar, boolean toggle) {
        for (int index = 0; index < appBar.getChildCount(); ++index) {
            View child = appBar.getChildAt(index);

            if (child instanceof Toolbar) {
                Toolbar toolbar = (Toolbar) child;
                AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
                int scrollFlags = params.getScrollFlags();

                if (toggle) {
                    scrollFlags |= AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
                } else {
                    scrollFlags &= ~AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
                }

                params.setScrollFlags(scrollFlags);
            }
        }
    }
}

この動作は基本的に、従属ビュー(SCROLLAppBarLayout)のコンテンツのスクロールがビューの高さより小さい場合、RecyclerViewからスクロールフラグNestedScrollViewを削除します。スクロールが不要な場合。また、通常はAppBarLayout.ScrollingViewBehaviorによって実行されるオフセットスクロールビューもオーバーライドします。フッターを追加する場合に有効です。ボタン、スクロールビューまたはViewPagerで、コンテンツの長さが各ページで異なる場合があります。

1
TrueCurry

私はAndroid:layout_gravity="end|bottom"CoordinatorLayoutの下部に必要なXMLのレイアウト

そして、コードで設定します:

 mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @SuppressLint("NewApi")
        @SuppressWarnings("deprecation")
        @Override
        public void onGlobalLayout() {
            if (mFooterView != null) {
                final int height = mFooterView.getHeight();
                mRecyclerView.setPadding(0, 0, 0, height);
                mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        }
    });

注:フッターView/ViewGroupは、適切に機能するためにz軸(XMLのRecyclerViewの下にリストされている)で高くする必要があることに注意してください。

0
AllDayAmazing

固定ヘッダーとフッターを作成すると、問題を解決できると思います。私はコメントでこれを書いたでしょうが、50人の担当者はいません。あなたはそれを行う方法を理解することができます ここ

0
Anthony

Android CoordinatorLayout下部レイアウトの動作の例

activity_bottom.xml

<Android.support.design.widget.CoordinatorLayout 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">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/app_bar"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            Android:background="?attr/colorPrimaryDark"
            app:layout_scrollFlags="scroll|enterAlways"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
    </Android.support.design.widget.AppBarLayout>

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/list"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:background="#C0C0C0"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <com.example.Android.coordinatedeffort.widget.FooterBarLayout
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        Android:layout_gravity="bottom">

        <TextView
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            Android:background="#007432"
            Android:gravity="center"
            Android:text="Footer View"
            Android:textColor="@Android:color/white"
            Android:textSize="25sp" />
    </com.example.Android.coordinatedeffort.widget.FooterBarLayout>

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

FooterBarLayout.Java

FooterBarBehavior.Java

0

次のようなlinearlayoutで要素を囲みます。

<Android.support.design.widget.CoordinatorLayout >

  <LinearLayout
        Android:orientation="vertical"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent">

    <Android.support.design.widget.AppBarLayout>
      <Android.support.v7.widget.Toolbar />
    </Android.support.design.widget.AppBarLayout>
    <include layout="@layout/content_main" />

    </LinearLayout>

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