展开/折叠棒棒糖工具栏动画(电报应用程序)
Posted
技术标签:
【中文标题】展开/折叠棒棒糖工具栏动画(电报应用程序)【英文标题】:Expand/Collapse Lollipop toolbar animation (Telegram app) 【发布时间】:2015-01-20 02:43:31 【问题描述】:我试图弄清楚工具栏的展开/折叠动画是如何完成的。如果您查看 Telegram 应用程序设置,您会看到有一个列表视图和工具栏。当您向下滚动时,工具栏会折叠,当您向上滚动时,它会展开。还有个人资料图片和FAB的动画。有人对此有任何线索吗?你认为他们在它之上构建了所有的动画吗?也许我在新 API 或支持库中遗漏了一些东西。
我在 Google 日历应用程序上注意到相同的行为,当您打开 Spinner 时(我不认为它是一个微调器,但它看起来像):工具栏展开,当您向上滚动时,它会折叠。
只是为了澄清:我不需要 QuickReturn 方法。我知道 Telegram 应用程序可能正在使用类似的东西。我需要的确切方法是 Google 日历应用效果。我试过了
android:animateLayoutChanges="true"
expand 方法效果很好。但显然,如果我向上滚动 ListView,工具栏不会折叠。
我也考虑过添加GestureListener
,但我想知道是否有任何 API 或更简单的方法可以实现这一点。
如果没有,我想我会选择GestureListener
。希望能有一个流畅的动画效果。
谢谢!
【问题讨论】:
【参考方案1】:编辑:
自从 Android 设计支持库发布以来,就有了一个更简单的解决方案。检查joaquin's answer
--
这就是我的做法,可能还有很多其他解决方案,但这个对我有用。
首先,您必须使用带有透明背景的Toolbar
。展开和折叠的Toolbar
实际上是一个fake,位于透明的Toolbar
之下。 (您可以在下面的第一个屏幕截图中看到 - 带有边距的屏幕 - 这也是他们在 Telegram 中的做法)。
我们只为NavigationIcon
和溢出的MenuItem
保留实际的Toolbar
。
第二个屏幕截图上红色矩形中的所有内容(即假的Toolbar
和FloatingActionButton
)实际上是您添加到设置ListView
(或@987654337)的标题 @)。
所以你必须在一个单独的文件中为这个标题创建一个布局,看起来像这样:
<!-- The headerView layout. Includes the fake Toolbar & the FloatingActionButton -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_>
<RelativeLayout
android:id="@+id/header_container"
android:layout_
android:layout_
android:layout_marginBottom="3dp"
android:background="@android:color/holo_blue_dark">
<RelativeLayout
android:id="@+id/header_infos_container"
android:layout_
android:layout_
android:layout_alignParentBottom="true"
android:padding="16dp">
<ImageView
android:id="@+id/header_picture"
android:layout_
android:layout_
android:layout_centerVertical="true"
android:layout_marginRight="8dp"
android:src="@android:drawable/ic_dialog_info" />
<TextView
android:id="@+id/header_title"
style="@style/TextAppearance.AppCompat.Title"
android:layout_
android:layout_
android:layout_toRightOf="@+id/header_picture"
android:text="Toolbar Title"
android:textColor="@android:color/white" />
<TextView
android:id="@+id/header_subtitle"
style="@style/TextAppearance.AppCompat.Subhead"
android:layout_
android:layout_
android:layout_below="@+id/header_title"
android:layout_toRightOf="@+id/header_picture"
android:text="Toolbar Subtitle"
android:textColor="@android:color/white" />
</RelativeLayout>
</RelativeLayout>
<FloatingActionButton
android:id="@+id/header_fab"
android:layout_
android:layout_
android:layout_gravity="bottom|right"
android:layout_margin="10dp"
android:src="@drawable/ic_open_in_browser"/>
</FrameLayout>
(请注意,您可以使用负边距/填充使晶圆厂跨越 2 Views
)
现在是有趣的部分。为了使我们的假Toolbar
的扩展动画化,我们实现了ListView
onScrollListener
。
// The height of your fully expanded header view (same than in the xml layout)
int headerHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
// The height of your fully collapsed header view. Actually the Toolbar height (56dp)
int minHeaderHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height);
// The left margin of the Toolbar title (according to specs, 72dp)
int toolbarTitleLeftMargin = getResources().getDimensionPixelSize(R.dimen.toolbar_left_margin);
// Added after edit
int minHeaderTranslation;
private ListView listView;
// Header views
private View headerView;
private RelativeLayout headerContainer;
private TextView headerTitle;
private TextView headerSubtitle;
private FloatingActionButton headerFab;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
View rootView = inflater.inflate(R.layout.listview_fragment, container, false);
listView = rootView.findViewById(R.id.listview);
// Init the headerHeight and minHeaderTranslation values
headerHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
minHeaderTranslation = -headerHeight +
getResources().getDimensionPixelOffset(R.dimen.action_bar_height);
// Inflate your header view
headerView = inflater.inflate(R.layout.header_view, listview, false);
// Retrieve the header views
headerContainer = (RelativeLayout) headerView.findViewById(R.id.header_container);
headerTitle = (TextView) headerView.findViewById(R.id.header_title);
headerSubtitle = (TextView) headerView.findViewById(R.id.header_subtitle);
headerFab = (TextView) headerView.findViewById(R.id.header_fab);;
// Add the headerView to your listView
listView.addHeaderView(headerView, null, false);
// Set the onScrollListener
listView.setOnScrollListener(this);
// ...
return rootView;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
// Do nothing
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
Integer scrollY = getScrollY(view);
// This will collapse the header when scrolling, until its height reaches
// the toolbar height
headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
// Scroll ratio (0 <= ratio <= 1).
// The ratio value is 0 when the header is completely expanded,
// 1 when it is completely collapsed
float offset = 1 - Math.max(
(float) (-minHeaderTranslation - scrollY) / -minHeaderTranslation, 0f);
// Now that we have this ratio, we only have to apply translations, scales,
// alpha, etc. to the header views
// For instance, this will move the toolbar title & subtitle on the X axis
// from its original position when the ListView will be completely scrolled
// down, to the Toolbar title position when it will be scrolled up.
headerTitle.setTranslationX(toolbarTitleLeftMargin * offset);
headerSubtitle.setTranslationX(toolbarTitleLeftMargin * offset);
// Or we can make the FAB disappear when the ListView is scrolled
headerFab.setAlpha(1 - offset);
// Method that allows us to get the scroll Y position of the ListView
public int getScrollY(AbsListView view)
View c = view.getChildAt(0);
if (c == null)
return 0;
int firstVisiblePosition = view.getFirstVisiblePosition();
int top = c.getTop();
int headerHeight = 0;
if (firstVisiblePosition >= 1)
headerHeight = this.headerHeight;
return -top + firstVisiblePosition * c.getHeight() + headerHeight;
请注意,此代码的某些部分我没有测试,因此请随时突出显示错误。但总的来说,我知道这个解决方案是有效的,尽管我确信它可以改进。
编辑 2:
上面的代码有一些错误(直到今天我才测试...),所以我改了几行让它工作:
-
我引入了另一个变量 minHeaderTranslation,它取代了 minHeaderHeight;
我更改了应用于标题视图的 Y 平移值:
headerView.setTranslationY(Math.max(-scrollY, minHeaderTranslation));
到:
headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
之前的表达方式根本不起作用,对此我很抱歉......
比率计算也发生了变化,现在它从工具栏底部(而不是屏幕顶部)演变为完整的展开标题。
【讨论】:
向下滚动太远,标题不会消失吗? Afaik 标题也被回收,所以即使你翻译你的标题,使它看起来像固定在顶部,我认为一旦实际位置移出视线它就会消失。 你说得对,库诺。我没有尝试,但这是预期的行为。这就是我在 FrameLayout 中使用工具栏的原因。主要内容位于工具栏上方,marginTop 为 x。要播放动画,我只需在 Y 轴之间平移主要内容 @FedeAmura Sweet,但我认为 FAB 在屏幕底部时太低了 :) 现在有了新的Android Design Support Library,一切都变得更简单了。根据 Chris Banes 为 Cheesesquare sample app 所做的工作,您可以通过遵循 this tutorial from Suleiman user 来实现此效果。 **EDIT** 一些用户询问他们是否可以使用相同的想法,但带有一个图标。 [Saulmm Github 用户尝试过类似的事情](github.com/saulmm 这个库是获取基本动画和行为的好方法,但 CollapsingToolbarLayout 目前只支持字符串作为标题。没有图标或字幕。链接的问题:***.com/questions/31069107/…【参考方案2】:还可以查看由 Android 团队的 Chris Banes 编写的 CollapsingTitleLayout
:
https://plus.google.com/+ChrisBanes/posts/J9Fwbc15BHN
代码:https://gist.github.com/chrisbanes/91ac8a20acfbdc410a68
【讨论】:
优秀的链接我的朋友。【参考方案3】:使用设计支持库http://android-developers.blogspot.in/2015/05/android-design-support-library.html
在 build.gradle 中包含这个
compile 'com.android.support:design:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.+'
对于回收站视图也包括此
compile 'com.android.support:recyclerview-v7:22.2.0'
<!-- AppBarLayout allows your Toolbar and other views (such as tabs provided by TabLayout)
to react to scroll events in a sibling view marked with a ScrollingViewBehavior.-->
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_
android:layout_
android:fitsSystemWindows="true">
<!-- specify tag app:layout_scrollFlags -->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_
android:layout_
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"/>
<!-- specify tag app:layout_scrollFlags -->
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:scrollbars="horizontal"
android:layout_below="@+id/toolbar"
android:layout_
android:layout_
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"/>
<!-- app:layout_collapseMode="pin" will help to pin this view at top when scroll -->
<TextView
android:layout_
android:layout_
android:text="Title"
android:gravity="center"
app:layout_collapseMode="pin" />
</android.support.design.widget.AppBarLayout>
<!-- This will be your scrolling view.
app:layout_behavior="@string/appbar_scrolling_view_behavior" tag connects this features -->
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_
android:layout_>
</android.support.v7.widget.RecyclerView>
</android.support.design.widget.CoordinatorLayout>
您的活动应该扩展 AppCompatActivity
public class YourActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
//set toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
你的应用主题应该是这样的
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
</style>
</resources>
【讨论】:
非常好,喜欢这个设计支持库 我讨厌它。无时无刻不在变化。不幸的是,我将不得不遵循它。【参考方案4】:这是我的实现:
collapsedHeaderHeight
和 expandedHeaderHeight
在其他地方定义,使用函数 getAnimationProgress
我可以获得展开/折叠进度,基于此值我制作动画并显示/隐藏真实标题。
listForumPosts.setOnScrollListener(new AbsListView.OnScrollListener()
/**
* @return [0,1], 0 means header expanded, 1 means header collapsed
*/
private float getAnimationProgress(AbsListView view, int firstVisibleItem)
if (firstVisibleItem > 0)
return 1;
// should not exceed 1
return Math.min(
-view.getChildAt(0).getTop() / (float) (expandedHeaderHeight - collapsedHeaderHeight), 1);
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
// at render beginning, the view could be empty!
if (view.getChildCount() > 0)
float animationProgress = getAnimationProgress(view, firstVisibleItem);
imgForumHeaderAvatar.setAlpha(1-animationProgress);
if (animationProgress == 1)
layoutForumHeader.setVisibility(View.VISIBLE);
else
layoutForumHeader.setVisibility(View.GONE);
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
// do nothing
【讨论】:
以上是关于展开/折叠棒棒糖工具栏动画(电报应用程序)的主要内容,如果未能解决你的问题,请参考以下文章