Material 设计:在工具栏之间切换时添加圆形显示动画
Material Design: Add circular reveal animation while switching between toolbars
我正在阅读 material 设计指南 (https://material.io/guidelines/patterns/selection.html) 中的 "Selection" 部分,我想在我的应用程序中添加的一个效果是在应用栏和 ActionMode ?另一个工具栏 ?
我没有找到有关如何执行此操作的任何说明。
我什至不知道他们是否使用 ActionMode 或其他东西...
有没有人可以给我好的指导?
编辑:
minSDK 21
编辑 2:
查看也会自行更改的状态栏...
谢谢
弗朗索瓦
您可以为此类动画使用自定义波纹库。可以找到here.
然后,为了达到相同的效果,您的工具栏将驻留在 rippleview 中,如自定义库的说明中所述。您需要从涟漪效应开始的位置开始坐标,对于您的情况,此坐标是带有加号的浮动操作按钮的中心。然后,在项目选择上,运行这个波纹动画和浮动动作按钮淡入淡出,你会得到同样的效果。
在上面提到的图书馆页面上可以看到一些类似的例子。
希望对您有所帮助!
好的,我终于找到了解决办法。
这不是一个很好的...但我没有其他想法可以使用其他东西,所以如果您有其他建议可以分享...不客气!
下面是最终结果和代码:
GitHub
上的示例项目
https://github.com/fbourlieux/android-material-circular_reveal_animation
目标与想法
使用 "smooth" 循环显示动画从一个工具栏切换到另一个工具栏。该动画需要更新应用栏和状态栏。
为此,首先我们需要通过在主布局容器上使用 android:fitsSystemWindows=false
属性 和 [=14= 来强制 activity 在状态栏下显示内容] 在 App 主题中。在此基础上,我们不仅会创建一个 Toolbar
,还会创建一个显示在状态栏下方的视图,只是为了在动画期间绘制一个漂亮的背景。这是我的示例中我不喜欢的一点,但我没有找到任何其他解决方案。
来看代码
styles.xml
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowTranslucentStatus">true</item>
</style>
我们刚刚添加了 android:windowTranslucentStatus
属性。
app_bar_main.xml
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
tools:context="sample.test.fbo.circularrevealanimation.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<!-- used to force the two toolbars to display above each other -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- initial toolbar layout with the status bar
and the original toolbar. That layout need to have a
background to show the elevation even if it will never
be visible (because of inner component backgrounds) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:elevation="4dp"
android:orientation="vertical">
<!-- status bar background: height of 24dp
and initial color darker than the toolbar color -->
<View
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="@color/colorPrimaryDark" />
<!-- main toolbar. A very basic one.-->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</LinearLayout>
<!-- reveal section layout. Here is our second toolbar
section which will be animated. It contains a view to
fake the status bar background and the second toolbar
to display. -->
<LinearLayout
android:id="@+id/revealedToolBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccentDark"
android:elevation="4dp"
android:orientation="vertical"
android:visibility="invisible">
<!-- revealed status bar. Just to change it background. -->
<View
android:id="@+id/revealBackgroundStatus"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="@color/colorAccentDark" />
<!-- revealed toolbar. The second one with in our case
a simple button and text inside. -->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar2"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorAccent"
app:popupTheme="@style/AppTheme.PopupOverlay">
<!-- a click on that button will trigger
the animation close event -->
<ImageButton
android:id="@+id/toolbar_arrow"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@android:color/transparent"
android:src="@drawable/arrow_left" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="24dp"
android:fontFamily="sans-serif-regular"
android:gravity="center_vertical"
android:text="Foo Bar Baz"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Foo Bar Baz" />
</android.support.v7.widget.Toolbar>
</LinearLayout>
</RelativeLayout>
</android.support.design.widget.AppBarLayout>
<!-- content_main just contains a ToggleButton to trigger
the animation-->
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>
创建 2 个重叠布局,其中包含一个用于绘制状态栏的视图和一个用于绘制工具栏的视图。默认情况下,动画布局设置为不可见。
MainActivity.java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {
private final static int ANIMATION_DURATION = 400;
private ToggleButton mActionButton;
private View mRevealedToolBar;
private ImageButton mArrowButton;
private boolean mIsHidden = true;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// main toolbar
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
final ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
// trigger circular reveal animation
mActionButton = (ToggleButton) findViewById(R.id.actionButton);
mActionButton.setOnClickListener(this);
// toolbar view to reveal. Inivisible by default
mRevealedToolBar = findViewById(R.id.revealedToolBar);
mRevealedToolBar.setVisibility(View.INVISIBLE);
// button in revealed toolbar to dismiss it
mArrowButton = (ImageButton) findViewById(R.id.toolbar_arrow);
mArrowButton.setOnClickListener(this);
}
@Override
public void onBackPressed() {
final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(final MenuItem item) {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
@Override
public void onClick(final View view) {
if (view == mActionButton || view == mArrowButton) {
// compute started X and Y co-ordinates for the animation + radius
int x = mRevealedToolBar.getLeft();
int y = mRevealedToolBar.getBottom();
int startRadius = 0;
int endRadius = Math.max(mRevealedToolBar.getWidth(), mRevealedToolBar.getHeight());
int reverseStartRadius = endRadius;
int reverseEndRadius = startRadius;
if (mIsHidden) {
// show secondary toolbar
// performing circular reveal when icon will be tapped
Animator animator = ViewAnimationUtils.createCircularReveal(mRevealedToolBar, x, y, startRadius, endRadius);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(ANIMATION_DURATION);
mRevealedToolBar.setVisibility(View.VISIBLE);
animator.start();
mIsHidden = false;
} else {
// dismiss secondary toolbar
// performing circular reveal for reverse animation
Animator animate = ViewAnimationUtils.createCircularReveal(mRevealedToolBar, x, y, reverseStartRadius, reverseEndRadius);
animate.setInterpolator(new AccelerateDecelerateInterpolator());
animate.setDuration(ANIMATION_DURATION);
// to hide layout on animation end
animate.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mRevealedToolBar.setVisibility(View.INVISIBLE);
mIsHidden = true;
}
});
mRevealedToolBar.setVisibility(View.VISIBLE);
animate.start();
}
}
}
}
所以在 MainActivity
中,在监听了 ToggleButton
的 onclick 事件后,我使用 ViewAnimationUtils.createCircularReveal
方法触发了第二个工具栏组(状态视图 + 工具栏)的动画.第一个参数是要设置动画的视图,然后是动画的起始坐标,然后是半径。
在 onClick 方法中,当我点击箭头或第二次点击 ToggleButton 时,我也会启动一个 reverse
动画。
最后这是一个非常简单的解决方案,即使我们需要伪造状态栏背景。
希望我的解决方案可以帮助到别人。
弗朗索瓦
有用链接:
我正在阅读 material 设计指南 (https://material.io/guidelines/patterns/selection.html) 中的 "Selection" 部分,我想在我的应用程序中添加的一个效果是在应用栏和 ActionMode ?另一个工具栏 ?
我没有找到有关如何执行此操作的任何说明。 我什至不知道他们是否使用 ActionMode 或其他东西...
有没有人可以给我好的指导?
编辑: minSDK 21
编辑 2: 查看也会自行更改的状态栏...
谢谢 弗朗索瓦
您可以为此类动画使用自定义波纹库。可以找到here.
然后,为了达到相同的效果,您的工具栏将驻留在 rippleview 中,如自定义库的说明中所述。您需要从涟漪效应开始的位置开始坐标,对于您的情况,此坐标是带有加号的浮动操作按钮的中心。然后,在项目选择上,运行这个波纹动画和浮动动作按钮淡入淡出,你会得到同样的效果。
在上面提到的图书馆页面上可以看到一些类似的例子。
希望对您有所帮助!
好的,我终于找到了解决办法。
这不是一个很好的...但我没有其他想法可以使用其他东西,所以如果您有其他建议可以分享...不客气!
下面是最终结果和代码:
GitHub
上的示例项目https://github.com/fbourlieux/android-material-circular_reveal_animation
目标与想法
使用 "smooth" 循环显示动画从一个工具栏切换到另一个工具栏。该动画需要更新应用栏和状态栏。
为此,首先我们需要通过在主布局容器上使用 android:fitsSystemWindows=false
属性 和 [=14= 来强制 activity 在状态栏下显示内容] 在 App 主题中。在此基础上,我们不仅会创建一个 Toolbar
,还会创建一个显示在状态栏下方的视图,只是为了在动画期间绘制一个漂亮的背景。这是我的示例中我不喜欢的一点,但我没有找到任何其他解决方案。
来看代码
styles.xml
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowTranslucentStatus">true</item>
</style>
我们刚刚添加了 android:windowTranslucentStatus
属性。
app_bar_main.xml
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
tools:context="sample.test.fbo.circularrevealanimation.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<!-- used to force the two toolbars to display above each other -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- initial toolbar layout with the status bar
and the original toolbar. That layout need to have a
background to show the elevation even if it will never
be visible (because of inner component backgrounds) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:elevation="4dp"
android:orientation="vertical">
<!-- status bar background: height of 24dp
and initial color darker than the toolbar color -->
<View
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="@color/colorPrimaryDark" />
<!-- main toolbar. A very basic one.-->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</LinearLayout>
<!-- reveal section layout. Here is our second toolbar
section which will be animated. It contains a view to
fake the status bar background and the second toolbar
to display. -->
<LinearLayout
android:id="@+id/revealedToolBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccentDark"
android:elevation="4dp"
android:orientation="vertical"
android:visibility="invisible">
<!-- revealed status bar. Just to change it background. -->
<View
android:id="@+id/revealBackgroundStatus"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="@color/colorAccentDark" />
<!-- revealed toolbar. The second one with in our case
a simple button and text inside. -->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar2"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorAccent"
app:popupTheme="@style/AppTheme.PopupOverlay">
<!-- a click on that button will trigger
the animation close event -->
<ImageButton
android:id="@+id/toolbar_arrow"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@android:color/transparent"
android:src="@drawable/arrow_left" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="24dp"
android:fontFamily="sans-serif-regular"
android:gravity="center_vertical"
android:text="Foo Bar Baz"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Foo Bar Baz" />
</android.support.v7.widget.Toolbar>
</LinearLayout>
</RelativeLayout>
</android.support.design.widget.AppBarLayout>
<!-- content_main just contains a ToggleButton to trigger
the animation-->
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>
创建 2 个重叠布局,其中包含一个用于绘制状态栏的视图和一个用于绘制工具栏的视图。默认情况下,动画布局设置为不可见。
MainActivity.java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {
private final static int ANIMATION_DURATION = 400;
private ToggleButton mActionButton;
private View mRevealedToolBar;
private ImageButton mArrowButton;
private boolean mIsHidden = true;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// main toolbar
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
final ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
// trigger circular reveal animation
mActionButton = (ToggleButton) findViewById(R.id.actionButton);
mActionButton.setOnClickListener(this);
// toolbar view to reveal. Inivisible by default
mRevealedToolBar = findViewById(R.id.revealedToolBar);
mRevealedToolBar.setVisibility(View.INVISIBLE);
// button in revealed toolbar to dismiss it
mArrowButton = (ImageButton) findViewById(R.id.toolbar_arrow);
mArrowButton.setOnClickListener(this);
}
@Override
public void onBackPressed() {
final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(final MenuItem item) {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
@Override
public void onClick(final View view) {
if (view == mActionButton || view == mArrowButton) {
// compute started X and Y co-ordinates for the animation + radius
int x = mRevealedToolBar.getLeft();
int y = mRevealedToolBar.getBottom();
int startRadius = 0;
int endRadius = Math.max(mRevealedToolBar.getWidth(), mRevealedToolBar.getHeight());
int reverseStartRadius = endRadius;
int reverseEndRadius = startRadius;
if (mIsHidden) {
// show secondary toolbar
// performing circular reveal when icon will be tapped
Animator animator = ViewAnimationUtils.createCircularReveal(mRevealedToolBar, x, y, startRadius, endRadius);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(ANIMATION_DURATION);
mRevealedToolBar.setVisibility(View.VISIBLE);
animator.start();
mIsHidden = false;
} else {
// dismiss secondary toolbar
// performing circular reveal for reverse animation
Animator animate = ViewAnimationUtils.createCircularReveal(mRevealedToolBar, x, y, reverseStartRadius, reverseEndRadius);
animate.setInterpolator(new AccelerateDecelerateInterpolator());
animate.setDuration(ANIMATION_DURATION);
// to hide layout on animation end
animate.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mRevealedToolBar.setVisibility(View.INVISIBLE);
mIsHidden = true;
}
});
mRevealedToolBar.setVisibility(View.VISIBLE);
animate.start();
}
}
}
}
所以在 MainActivity
中,在监听了 ToggleButton
的 onclick 事件后,我使用 ViewAnimationUtils.createCircularReveal
方法触发了第二个工具栏组(状态视图 + 工具栏)的动画.第一个参数是要设置动画的视图,然后是动画的起始坐标,然后是半径。
在 onClick 方法中,当我点击箭头或第二次点击 ToggleButton 时,我也会启动一个 reverse
动画。
最后这是一个非常简单的解决方案,即使我们需要伪造状态栏背景。
希望我的解决方案可以帮助到别人。
弗朗索瓦
有用链接: