Android折叠式工具栏布局(CollapsingToolbarLayout)的折叠监听器

130
我正在使用 CollapsingToolBarLayout,同时与 AppBarLayoutCoordinatorLayout 一起使用,它们都能正常工作。我将我的 Toolbar 设为当我向上滚动时固定的,我想知道是否有一种方法可以在 CollapsingToolBarLayout 折叠时更改标题文本。

总之,我希望在滚动展开时拥有两个不同的标题

提前感谢大家。

12个回答

175

我分享完整的实现,基于@Frodio Beggins和@Nifhel的代码:

public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {

    public enum State {
        EXPANDED,
        COLLAPSED,
        IDLE
    }

    private State mCurrentState = State.IDLE;

    @Override
    public final void onOffsetChanged(AppBarLayout appBarLayout, int i) {
        if (i == 0) {
            if (mCurrentState != State.EXPANDED) {
                onStateChanged(appBarLayout, State.EXPANDED);
            }
            mCurrentState = State.EXPANDED;
        } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
            if (mCurrentState != State.COLLAPSED) {
                onStateChanged(appBarLayout, State.COLLAPSED);
            }
            mCurrentState = State.COLLAPSED;
        } else {
            if (mCurrentState != State.IDLE) {
                onStateChanged(appBarLayout, State.IDLE);
            }
            mCurrentState = State.IDLE;
        }
    }

    public abstract void onStateChanged(AppBarLayout appBarLayout, State state);
}

然后您就可以使用它:

appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() {
    @Override
    public void onStateChanged(AppBarLayout appBarLayout, State state) {
        Log.d("STATE", state.name());
    }
});

22
没错。但请注意,使用Proguard会将该枚举翻译为整数值。 - rciovati
1
我不知道那个。太好了! - tim687
2
枚举也是确保类型安全的一种非常好的方式。你不能有State.IMPLODED,因为它不存在(编译器会抱怨),但是使用整数常量,你可以使用编译器不知道是否正确的值。它们也很适合作为单例,但那是另一个故事了。 - droppin_science
对于 Android 中的枚举,请查看 IntDef。@droppin_science - David Darias
1
@DavidDarias 个人而言,我认为枚举是一种更清晰的方法,即使它们有一些额外的开销(在这里开始争论... :-) - droppin_science
显示剩余2条评论

108

这个解决方案对我来说完美地检测了 AppBarLayout 的折叠或展开状态。

appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {

            if (Math.abs(verticalOffset)-appBarLayout.getTotalScrollRange() == 0)
            {
                //  Collapsed


            }
            else
            {
                //Expanded


            }
        }
    });

AppBarLayout上使用addOnOffsetChangedListener方法。


3
这些火力点在多个场合下倒塌,还有在扩张时。 - Jacek Kwiecień

38

将一个 OnOffsetChangedListener 挂载到你的 AppBarLayout 上。当 verticalOffset 达到 0 或小于 Toolbar 高度时,意味着 CollapsingToolbarLayout 已经折叠,否则它正在扩展或已经扩展。

mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                if(verticalOffset == 0 || verticalOffset <= mToolbar.getHeight() && !mToolbar.getTitle().equals(mCollapsedTitle)){
                    mCollapsingToolbar.setTitle(mCollapsedTitle);
                }else if(!mToolbar.getTitle().equals(mExpandedTitle)){
                    mCollapsingToolbar.setTitle(mExpandedTitle);
                }

            }
        });

1
它对我不起作用。在折叠时,我想启用主页按钮,在展开时隐藏主页按钮。 - Maheshwar Ligade
9
当工具栏完全展开时,verticalOffset值似乎为零,然后在收缩时变为负数。当工具栏被收缩时,verticalOffset等于负的工具栏高度(-mToolbar.getHeight())。所以,如果(verticalOffset > -mToolbar.getHeight()),则工具栏部分展开。 - Mike
如果有人想知道appBarLayout.getVerticalOffset()方法在哪里,可以调用appBarLayout.getY()来检索回调中使用的相同值。 - Jarett Millard
很遗憾,Jarett Millard是不正确的。根据您的fitsSystemWindow配置和StatusBar配置(透明),appBarLayout.getY()可能是verticalOffset = appBarLayout.getY() + statusBarHeight - Capricorn
1
有没有人注意到,即使我们实际上没有与应用栏交互,mAppBarLayout.addOnOffsetChangedListener(listener)是否会被重复调用?或者这是我布局/应用程序中观察到的行为异常。请帮忙! - Rahul Shukla
显示剩余3条评论

18

这段代码对我有效

mAppBarLayout.addOnOffsetChangedListener(new   AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            if (verticalOffset == -mCollapsingToolbarLayout.getHeight() + mToolbar.getHeight()) {
                //toolbar is collapsed here
                //write your code here
            }
        }
    });

比Nikola Despotoski更好的答案 - GOBINATH.M
似乎不是一个可靠的解决方案。我测试了一下,在我的设备上,值如下:mCollapsingToolbarLayout.getHeight() = 1013,mToolbar.getHeight() = 224。因此,根据您的解决方案,在折叠状态下的verticalOffset必须为-789,但实际上它等于-693。 - Leo DroidCoder

17
private enum State {
    EXPANDED,
    COLLAPSED,
    IDLE
}

private void initViews() {
    final String TAG = "AppBarTest";
    final AppBarLayout mAppBarLayout = findViewById(R.id.appbar);
    mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        private State state;

        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            if (verticalOffset == 0) {
                if (state != State.EXPANDED) {
                    Log.d(TAG,"Expanded");
                }
                state = State.EXPANDED;
            } else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) {
                if (state != State.COLLAPSED) {
                    Log.d(TAG,"Collapsed");
                }
                state = State.COLLAPSED;
            } else {
                if (state != State.IDLE) {
                    Log.d(TAG,"Idle");
                }
                state = State.IDLE;
            }
        }
    });
}

14

您可以使用以下方法获取CollapsingToolbar的Alpha百分比:

appbarLayout.addOnOffsetChangedListener( new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            float percentage = ((float)Math.abs(verticalOffset)/appBarLayout.getTotalScrollRange());
            fadedView.setAlpha(percentage);
    });

参考链接:link


2
这是一个很好的答案,因为它提供了一个标准化的偏移量。在我看来,API 应该直接提供这个标准化的偏移量,而不是 verticalOffset 像素距离。 - dbm
我想要实现这样的效果:当AppBarLayout折叠时,View被隐藏,展开时则显示。因此,我使用了val percentage = 1 - abs(verticalOffset).toFloat() / appBarLayout.totalScrollRange。这对我来说非常有效。 - PhongBM

13

以下是一个使用Kotlin编写的解决方案。将OnOffsetChangedListener添加到AppBarLayout

解决方案A:

AppBarStateChangeListener.kt添加到您的项目中:

import com.google.android.material.appbar.AppBarLayout
import kotlin.math.abs

abstract class AppBarStateChangeListener : AppBarLayout.OnOffsetChangedListener {

    enum class State {
        EXPANDED, COLLAPSED, IDLE
    }

    private var mCurrentState = State.IDLE

    override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) {
        if (i == 0 && mCurrentState != State.EXPANDED) {
            onStateChanged(appBarLayout, State.EXPANDED)
            mCurrentState = State.EXPANDED
        }
        else if (abs(i) >= appBarLayout.totalScrollRange && mCurrentState != State.COLLAPSED) {
            onStateChanged(appBarLayout, State.COLLAPSED)
            mCurrentState = State.COLLAPSED
        }
        else if (mCurrentState != State.IDLE) {
            onStateChanged(appBarLayout, State.IDLE)
            mCurrentState = State.IDLE
        }
    }

    abstract fun onStateChanged(
        appBarLayout: AppBarLayout?,
        state: State?
    )

}
在你的appBarLayout上添加监听器:
appBarLayout.addOnOffsetChangedListener(object: AppBarStateChangeListener() {
        override fun onStateChanged(appBarLayout: AppBarLayout?, state: State?) {
            Log.d("State", state.name)
            when(state) {
                State.COLLAPSED -> { /* Do something */ }
                State.EXPANDED -> { /* Do something */ }
                State.IDLE -> { /* Do something */ }
            }
        }
    }
)

方案B:

appBarLayout.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
        if (abs(verticalOffset) - appBarLayout.totalScrollRange == 0) { 
            // Collapsed
        } else if (verticalOffset == 0) {
            // Expanded
        } else {
            // Idle
        }
    }
)

3
这个解决方案适用于我:
```

这个解决方案适用于我:

```
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
  if (i == 0) {
    if (onStateChangeListener != null && state != State.EXPANDED) {
      onStateChangeListener.onStateChange(State.EXPANDED);
    }
    state = State.EXPANDED;
  } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
    if (onStateChangeListener != null && state != State.COLLAPSED) {
      onStateChangeListener.onStateChange(State.COLLAPSED);
    }
    state = State.COLLAPSED;
  } else {
    if (onStateChangeListener != null && state != State.IDLE) {
      onStateChangeListener.onStateChange(State.IDLE);
    }
    state = State.IDLE;
  }
}

使用addOnOffsetChangedListener在AppBarLayout上。

你能分享你的完整代码吗?State.EXPANDED是什么? - Chetna

2
这段代码对我来说完美运行。您可以按照自己的喜好使用百分比比例尺。
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
    double percentage = (double) Math.abs(verticalOffset) / collapsingToolbar.getHeight();
    if (percentage > 0.8) {
        collapsingToolbar.setTitle("Collapsed");
    } else {
        collapsingToolbar.setTitle("Expanded");
    }
}

2
这是 Kotlin 的解决方案:
abstract class AppBarStateChangeListener : OnOffsetChangedListener {
    enum class State {
        EXPANDED, COLLAPSED, IDLE
    }

    private var mCurrentState =
        State.IDLE

    override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) {
        mCurrentState = if (i == 0) {
            if (mCurrentState != State.EXPANDED) {
                onStateChanged(appBarLayout, State.EXPANDED)
            }
            State.EXPANDED
        } else if (Math.abs(i) >= appBarLayout.totalScrollRange) {
            if (mCurrentState != State.COLLAPSED) {
                onStateChanged(appBarLayout, State.COLLAPSED)
            }
            State.COLLAPSED
        } else {
            if (mCurrentState != State.IDLE) {
                onStateChanged(appBarLayout, State.IDLE)
            }
            State.IDLE
        }
    }

    abstract fun onStateChanged(
        appBarLayout: AppBarLayout?,
        state: State?
    )
}

这里是监听器:

  appbar.addOnOffsetChangedListener(object : AppBarStateChangeListener() {
        override fun onStateChanged(
            appBarLayout: AppBarLayout?,
            state: State?
        ) {
            if(state == State.COLLAPSED){
                LayoutBottom.visibility = View.GONE
            }else if(state == State.EXPANDED){
                LayoutBottom.visibility = View.VISIBLE
            }
        }
    })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接