概述:
动画可以为我们的App增加精细的视觉提示,并且能改进App界面的思维模型。当界面改变其状态时(例如加载内容或新操作可用时),动画特别有帮助。另外,动画也能让我们的App外观更加优雅,为用户提供一种更好的使用体验。
View间渐变
渐变动画(也叫消失)通常指渐渐的淡出某个UI组件,同时同步地淡入另一个。当App想切换内容或View的情况下,这种动画很有用。渐变简短不易察觉,同时又提供从一个界面到下一个之间流畅的转换。如果在需要转换的时候没有使用任何动画效果,这会使得转换看上去感到生硬而仓促。
下面是一个利用进度指示渐变到一些文本内容的例子。
创建View
创建两个我们想相互渐变的View。下面的例子创建了一个进度提示圈和可滑动文本View。
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView style="?android:textAppearanceMedium"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lorem_ipsum"
android:padding="16dp" />
</ScrollView>
<ProgressBar
android:id="@+id/loading_spinner"
style="?android:progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
设置动画
为设置动画,我们需要按照如下步骤来做:
- 为我们想渐变的View 创建成员变量。在之后动画应用途中修改View的时候我们会需要这些引用。
- 对于被淡入的View,设置它的visibility为GONE。这样防止view再占据布局的空间,而且也能在布局计算中将其忽略,加速处理过程。
- 将config_shortAnimTime系统属性暂存到一个成员变量里。这个属性为动画定义了一个标准的“短”持续时间。对于细微或者快速发生的动画,这是个很理想的持续时段。也可以根据实际需求使用config_longAnimTime或config_mediumAnimTime。
下面的例子使用了前文提到的布局文件:
public class MainActivity extends AppCompatActivity {
private boolean mContentLoaded;
private View mContentView;
private View mLoadingView;
private int mShortAnimationDuration;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContentView = findViewById(R.id.content);
mLoadingView = findViewById(R.id.loading_spinner);
// Initially hide the content view.
mContentView.setVisibility(View.GONE);
// Retrieve and cache the system's default "short" animation time.
mShortAnimationDuration = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
...
}
渐变View
进行了上述配置之后,接下来就让我们实现渐变动画吧:
- 对于正在淡入的View,设置它的alpha值为0并且设置visibility为 VISIBLE(记住他起初被设置成了GONE)。这样View就变成可见的了,但是此时它是透明的。
- 对于正在淡入的View,把alpha值从0动态改变到1。同时,对于淡出的View,把alpha值从1动态变到0。
- 使用Animator.AnimatorListener中的 onAnimationEnd(),设置淡出View的visibility为GONE。即使alpha值为0,也要把View的visibility设置成GONE来防止view 占据布局空间,还能把它从布局计算中忽略,加速处理过程。
详见下面的例子:
private void crossfade(boolean contentLoaded) {
final View showView = contentLoaded ? mContentView : mLoadingView;
final View hideView = contentLoaded ? mLoadingView : mContentView;
// Set the content view to 0% opacity but visible, so that it is visible
// (but fully transparent) during the animation.
mContentView.setAlpha(0f);
mContentView.setVisibility(View.VISIBLE);
// Animate the content view to 100% opacity, and clear any animation
// listener set on the view.
mContentView.animate()
.alpha(1f)
.setDuration(mShortAnimationDuration)
.setListener(null);
// Animate the loading view to 0% opacity. After the animation ends,
// set its visibility to GONE as an optimization step (it won't
// participate in layout passes, etc.)
mLoadingView.animate()
.alpha(0f)
.setDuration(mShortAnimationDuration)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoadingView.setVisibility(View.GONE);
}
});
}
在 XML 文件中定义菜单
创建菜单定义文件并放置在res/menu目录下, Android会自动生成相应的资源ID。随后,在代码中实例化菜单时,就可以直接使用。
在项目工具窗口中,右键单击res目录,选择New → Android resource file菜单项。 在弹出的窗口界面,
选择Menu资源类型,并命名资源文件为activity_view_menu,点击OK按钮确认。
打开新建的activity_view_menu.xml文件 :
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_item_new_crime"
android:icon="@drawable/ic_action_name"
android:title="@string/show_text"
app:showAsAction="ifRoom|withText"/>
</menu>
showAsAction属性用于指定菜单选项是显示在工具栏上,还是隐藏于溢出菜单(overflow
menu) 。该属性当前设置为ifRoom和withText的组合值。
因此,只要空间足够,菜单项图标及其文字描述都会显示在工具栏上。如空间仅够显示菜单项图标,文字描述就不会显示。如空间大小不够显示任何项,菜单项就会隐藏到溢出菜单中。
创建菜单
在代码中, Activity类提供了管理菜单的回调函数。需要选项菜单时, Android会调用Activity的onCreateOptionsMenu(Menu)方法。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.activity_view_menu,menu);
return true;
}
在以上方法中,调用MenuInflater.inflate(int, Menu)方法并传入菜单文件的资源ID,将布局文件中定义的菜单项目填充到Menu实例中。
响应菜单项选择
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_item_new_crime:
mContentLoaded = !mContentLoaded;
crossfade(mContentLoaded);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
效果图如下:
使用ViewPager实现屏幕滑动
屏幕划动是在两个完整界面间的转换,它在一些UI中很常见,比如设置向导和幻灯放映。这节课将告诉你怎样通过support library提供的ViewPager实现屏幕滑动。ViewPager能自动实现屏幕滑动动画。下面展示了从一个内容界面到一下界面的屏幕滑动转换是什么样子的。
创建View
创建Fragment所使用的布局文件。
下面的例子包含一个显示文本的TextView:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView style="?android:textAppearanceMedium"
android:padding="16dp"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lorem_ipsum" />
</ScrollView>
与此同时我们还定义了一个字符串作为该Fragment的内容。
创建Fragment
创建一个 Fragment 子类,它从 onCreateView()方法中返回之前创建的布局。无论何时如果我们需要为用户展示一个新的页面,可以在它的父Activity中创建该Fragment的实例:
public class ScreenSlidePageFragment extends Fragment {
public static final String ARG_PAGE = "page";
/**
* The fragment's page number, which is set to the argument value for {@link #ARG_PAGE}.
*/
private int mPageNumber;
@Nullable
//附加 argument 给 fragment
public static ScreenSlidePageFragment newInstance(int pagerNumber) {
ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
Bundle args = new Bundle();
args.putInt(ARG_PAGE,pagerNumber);
fragment.setArguments(args);
return fragment;
}
public ScreenSlidePageFragment() {
}
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//获取 argument
mPageNumber = getArguments().getInt(ARG_PAGE);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.fragment_screen_slide_page, container, false);
return rootView;
}
...
}
添加ViewPager
ViewPager 有内建的滑动手势用来在页面间转换,并且它默认使用滑屏动画,所以我们不用自己为其创建。ViewPager使用PagerAdapter来补充新页面,所以PagerAdapter会用到你之前新建的Fragment类。
开始之前,创建一个包含ViewPager的布局:
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
创建一个Activity来做下面这些事情:
- 把ContentView设置成这个包含ViewPager的布局。
- 创建一个继承自FragmentStatePagerAdapter抽象类的类,然后实现getItem()方法来把ScreenSlidePageFragment实例作为新页面补充进来。PagerAdapter还需要实现getCount()方法,它返回Adapter将要创建页面的总数(例如5个)。
- 把PagerAdapter和ViewPager关联起来。
- 处理Back按钮,按下变为在虚拟的Fragment栈中回退。如果用户已经在第一个页面了,则在Activity的回退栈(back stack)中回退。
public class ScreenSlidePagerActivity extends FragmentActivity {
private static final int NUM_PAGES = 5;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen_slide);
mPager = (ViewPager)findViewById(R.id.pager);
FragmentManager fragmentManager = getSupportFragmentManager();
mPagerAdapter = new ScreenSlidePagerAdapter(fragmentManager);
mPager.setAdapter(mPagerAdapter);
mPager.setPageTransformer(true, new DepthPageTransformer());
}
public void onBackPressed() {
if (mPager.getCurrentItem() == 0) {
// If the user is currently looking at the first step, allow the system to handle the
// Back button. This calls finish() on this activity and pops the back stack.
super.onBackPressed();
} else {
// Otherwise, select the previous step.
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
@Override
public Fragment getItem(int position) {
return ScreenSlidePageFragment.newInstance(position);
}
@Override
public int getCount() {
return NUM_PAGES;
}
}
}
效果图如下:
用PageTransformer自定义动画
要展示不同于默认滑屏效果的动画,我们需要实现ViewPager.PageTransformer接口,然后把它补充到ViewPager里就行了。这个接口只暴露了一个方法,transformPage()。每次界面切换,这个方法都会为每个可见页面(通常只有一个页面可见)和刚消失的相邻页面调用一次。例如,第三页可见而且用户向第四页拖动,transformPage()在操作的各个阶段为第二,三,四页分别调用。
在transformPage()的实现中,基于当前屏幕显示的页面position(position 由transformPage()方法的参数给出)决定哪些页面需要被动画转换,这样我们就能创建自己的动画。
position参数表示特定页面相对于屏幕中的页面的位置。它的值在用户滑动页面过程中动态变化。当某一页面填充屏幕,它的值为0。当页面刚向屏幕右侧方向被拖走,它的值为1。如果用户在页面1和页面2间滑动到一半,那么页面1的position为-0.5并且页面2的position为0.5。根据屏幕上页面的position,我们可以通过setAlpha(),setTranslationX()或setScaleY()这些方法设定页面属性来自定义滑动动画。
当我们实现了PageTransformer后,用我们的实现调用setPageTransformer()来应用这些自定义动画。例如,如果我们有一个叫做ZoomOutPageTransformer的PageTransformer,可以这样设置自定义动画:
ViewPager mPager = (ViewPager) findViewById(R.id.pager);
...
mPager.setPageTransformer(true, new ZoomOutPageTransformer());
Zoom-out Page Transformer
当在相邻界面滑动时,这个Page Transformer使页面收缩并褪色。当页面越靠近中心,它将渐渐还原到正常大小并且图像渐入。
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA +
(scaleFactor - MIN_SCALE) /
(1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
Depth Page Transformer
这个Page Transformer使用默认动画的屏幕左滑动画。但是为右滑使用一种“潜藏”效果的动画。潜藏动画将page淡出,并且线性缩小它。
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}