展开/折叠棒棒糖工具栏animation(电报应用程序)

我想弄清楚工具栏的展开/折叠animation是如何完成的。 如果你看看电报应用程序设置,你会看到有一个列表视图和工具栏。 向下滚动时,工具栏折叠,当您向上滚动时展开。 还有configuration文件图片和FAB的animation。 有没有人有任何线索呢? 你认为他们把所有的animation制作在上面吗? 也许我从新的API或支持库丢失了一些东西。

我注意到Google日历应用程序中的相同行为,当您打开微调器(我不认为它是一个微调,但它看起来像):工具栏展开,当你向上滚动,它崩溃。

只是为了清除:我不需要QuickReturn方法。 我知道可能电报应用程序正在使用类似的东西。 我需要的确切方法是Google日历应用效果。 我试过了

android:animateLayoutChanges="true" 

扩展方法工作得很好。 但显然,如果我向上滚动ListView,工具栏不会折叠。

我也想过添加一个GestureListener但我想知道是否有任何API或更简单的方法来实现这一点。

如果没有,我想我会和GestureListener一起去的。 希望能有一个stream畅的animation效果。

谢谢!

编辑:

自Androiddevise支持库发布以来,有一个更简单的解决scheme。 检查joaquin的答案

这是我做的,可能有很多其他的解决scheme,但是这个为我工作。

  1. 首先,你必须使用透明背景的Toolbar 。 展开和折叠Toolbar实际上是一个在透明Toolbar下的 Toolbar 。 (你可以在下面的第一个屏幕截图中看到 – 这是一个带边缘的截图 – 这也是他们在电报中的做法)。

    我们只保留NavigationIcon的实际Toolbar和溢出MenuItem

    1.透明工具栏 -  2.展开标题 -  3.折叠标题

  2. 第二个屏幕截图上的红色矩形(即假ToolbarFloatingActionButton )中的所有内容实际上都是您添加到设置ListView (或ScrollView的标题

    所以你必须在一个单独的文件中为这个头文件创build一个布局,如下所示:

      <!-- The headerView layout. Includes the fake Toolbar & the FloatingActionButton --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <RelativeLayout android:id="@+id/header_container" android:layout_width="match_parent" android:layout_height="@dimen/header_height" android:layout_marginBottom="3dp" android:background="@android:color/holo_blue_dark"> <RelativeLayout android:id="@+id/header_infos_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:padding="16dp"> <ImageView android:id="@+id/header_picture" android:layout_width="wrap_content" android:layout_height="wrap_content" 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_width="wrap_content" android:layout_height="wrap_content" 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_width="wrap_content" android:layout_height="wrap_content" 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_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_margin="10dp" android:src="@drawable/ic_open_in_browser"/> </FrameLayout> 

    (请注意,您可以使用负边距/填充工厂在2个Views上跨越)

  3. 现在来了有趣的部分。 为了animation扩展我们的假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; } 

请注意,这段代码中有一部分我没有testing,所以请随时突出显示错误。 但总的来说,我知道这个解决scheme是有效的,尽pipe我确信它可以被改进。

编辑2:

在上面的代码中有一些错误(我直到今天才testing…),所以我改了几行使其工作:

  1. 我介绍了另一个variables,minHeaderTranslation,它取代了minHeaderHeight;
  2. 我更改了应用于标题View的Y翻译值:

      headerView.setTranslationY(Math.max(-scrollY, minHeaderTranslation)); 

    至 :

      headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation)); 

    以前的表情根本不起作用,我很抱歉…

  3. 比率计算也发生了变化,所以它现在从工具栏的底部(而不是屏幕的顶部)发展到完整扩展的标题。

另外请查看Chris Banes在Android团队中撰写的CollapsingTitleLayout : https : //plus.google.com/+ChrisBanes/posts/J9Fwbc15BHN

在这里输入图像描述

代码: https : //gist.github.com/chrisbanes/91ac8a20acfbdc410a68

使用devise支持库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_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true"> <!-- specify tag app:layout_scrollFlags --> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" 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_width="match_parent" android:layout_height="wrap_content" 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_width="match_parent" android:layout_height="50dp" 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_width="match_parent" android:layout_height="match_parent"> </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> 

这是我的实现:

collapsedHeaderHeightexpandedHeaderHeight定义在其他地方,用函数getAnimationProgress我可以得到展开/折叠的进展,基于这个值我做我的animation和显示/隐藏真正的头。

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