Развернуть / свернуть анимацию панели инструментов Lollipop (приложение Telegram)
Я пытаюсь выяснить, как делается анимация развертывания / сворачивания панели инструментов. Если вы посмотрите на настройки приложения Telegram, вы увидите, что есть список и панель инструментов. При прокрутке вниз панель инструментов сворачивается, а при прокрутке вверх она расширяется. Существует также анимация аватара и FAB. Кто-нибудь имеет какое-либо представление об этом? Как вы думаете, они построили все анимации поверх этого? Может быть, мне чего-то не хватает в новых API или в библиотеке поддержки.
Я заметил такое же поведение в приложении календаря Google, когда вы открываете Spinner (я не думаю, что это спиннер, но выглядит так): панель инструментов расширяется, а когда вы прокручиваете ее вверх, она сворачивается.
Просто чтобы прояснить: мне не нужен метод QuickReturn. Я знаю, что, вероятно, приложение Telegram использует нечто подобное. Точный метод, который мне нужен, - это эффект приложения Google Calendar. Я пробовал с
android:animateLayoutChanges="true"
и метод расширения работает довольно хорошо. Но очевидно, что если прокрутить ListView вверх, панель инструментов не рухнет.
Я также думал о добавлении GestureListener
но я хочу знать, есть ли какие-либо API или более простые методы достижения этого.
Если их нет, думаю, я пойду с GestureListener
, Надеемся, что эффект анимации будет плавным.
Спасибо!
4 ответа
Редактировать:
С момента выпуска библиотеки поддержки Android Design существует более простое решение. Проверьте ответ Хоакина
-
Вот как я это сделал, возможно, есть много других решений, но это сработало для меня.
Прежде всего, вы должны использовать
Toolbar
с прозрачным фоном. Расширяющееся и разрушающеесяToolbar
на самом деле подделка, которая под прозрачнымToolbar
, (вы можете увидеть на первом скриншоте ниже - тот, что с полями - это также, как они сделали это в Telegram).Мы сохраняем только
Toolbar
дляNavigationIcon
и переполнениеMenuItem
,Все, что находится в красном прямоугольнике на втором скриншоте (т.е. подделка
Toolbar
иFloatingActionButton
) на самом деле это заголовок, который вы добавляете в настройки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
)Теперь начинается интересная часть. Для того, чтобы оживить расширение нашей подделки
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, примененное к заголовку View from:
headerView.setTranslationY(Math.max(-scrollY, minHeaderTranslation));
к:
headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
Предыдущее выражение не работало вообще, я сожалею об этом...
Вычисление коэффициента также изменилось, так что теперь оно переходит от нижней панели инструментов (вместо верхней части экрана) к полностью расширенному заголовку.
Также проверьте CollapsingTitleLayout
написанный Крисом Бейнсом в команде Android: https://plus.google.com/+ChrisBanes/posts/J9Fwbc15BHN
Код: https://gist.github.com/chrisbanes/91ac8a20acfbdc410a68
Используйте библиотеку поддержки проектирования 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>
Это моя реализация:
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
}
}