android - 展开/折叠 Lollipop 工具栏动画(电报应用程序)

标签 android android-animation android-6.0-marshmallow android-toolbar android-7.0-nougat

我试图弄清楚工具栏的展开/折叠动画是如何完成的。如果您查看 Telegram 应用程序设置,您会看到有一个 ListView 和工具栏。当您向下滚动时,工具栏会折叠,当您向上滚动时,它会展开。还有个人资料图片和FAB的动画。有人对此有任何线索吗?你认为他们在它之上构建了所​​有的动画吗?也许我在新 API 或支持库中遗漏了一些东西。

我在 Google 日历应用程序上注意到相同的行为,当您打开 Spinner 时(我不认为它是一个 Spinner,但它看起来像):工具栏展开,当您向上滚动时,它会折叠。

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

android:animateLayoutChanges="true"

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

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

如果没有,我想我会使用 GestureListener。希望能有一个流畅的动画效果。

谢谢!

最佳答案

编辑:

自从 Android 设计支持库发布以来,就有了一个更简单的解决方案。查看 joaquin's answer

--

这就是我的做法,可能还有很多其他解决方案,但这个对我有用。

  1. 首先,您必须使用具有透明背景的Toolbar。展开和折叠的Toolbar实际上是一个fake,它位于透明的Toolbar之下。 (您可以在下面的第一个屏幕截图中看到 - 带有边距的屏幕 - 这也是他们在 Telegram 中的做法)。

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

    1. Transparent Toolbar - 2. Expanded header - 3. Collapsed header

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

    所以你必须在一个单独的文件中为这个标题创建一个布局,看起来像这样:

     <!-- 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. 现在是有趣的部分。为了使我们的假 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:

上面的代码有一些错误(直到今天我才测试...),所以我改了几行让它工作:

  1. 我引入了另一个变量 minHeaderTranslation,它取代了 minHeaderHeight;
  2. 我将应用于标题 View 的 Y 平移值更改为:

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

    到:

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

    以前的表达方式根本不起作用,对此我很抱歉......

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

关于android - 展开/折叠 Lollipop 工具栏动画(电报应用程序),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27070079/

相关文章:

Android Studio 无法识别我导入的 Github 项目

android - 动画android View 的高度

android - 结束动画事件android

启动位置服务时出现java.lang.SecurityException

java - 使用邮箱和密码注册成功后,我可以将邮箱自动添加到实时数据库吗?

android - React Native 将 apk 文件的大小增加了 6.5 MB

android - 制作一个从上到下的可滑动 View ,类似于Android中的

android - 某些权限的保护级别似乎错误记录或实现

android - android Marshmallow 应用程序中位置的权限问题

android - 在 LinearLayout 中获取 View 的位置 (Android)