为了响应用户的输入和其它的事件,用户的UI activity经常需要改变。比如,一个activity含有用户可以查找的表格,当用户查找完成之后,这个表格应该被隐藏还应该含有一系列的搜索到的信息。
为了给这些场景提供连续的可视化,你可以让你的view在变化的时候动起来。这些动画效果可以给用户回馈并且让他们知道app是怎么工作的。
android包括哦一个变化的框架,可以让你很容易在两个view之间动画变换。通过在运行的时候改变它们的属性可以使他们有动起来的效果。这些动画包括一些内置的变化场景可以让你创建本地的动画以及变化回调。
这篇文章教你怎么使用内置的变化框架,这篇文章也包括创建自定义的动画。
第一章 变换框架
学习主要的框架以及框架的组件
第二章 创建一个场景
学习怎么在view中创建场景
第三章 增加变换
在两个场景中增加变化
第四章 创建自定义变换
创建变换框架中没有的动画效果
使app的UI动画不仅仅是一个视觉上的感受,动画可以重点突出变化,可以帮助用户更好地理解app的工作。
android提供变换框架,
这个框架有下面的特性:
可以让变化作用于一些列的组件
可以让变化只作用于开始和结束的组件的属性值
已经内置了一些常见的变化
可以从资源文件中加载变化
生命周期的回调函数
1. 概览
有一个搜索的例子,用户输入关键字之后点击搜索会跳转到搜索界面上,会慢慢淡出不需要的界面,慢慢弹出搜索到的结果。
这个例子使用了transitions framework.这个框架改变了布局中所有的组件。一个布局继承可可以简单到只有一个组件,也可以很复杂还有一个ViewGroup.
这个变幻框架改变了所有的组件的属性。
下面是一张图片,很明显了表达了这样的例子。
第三章 增加动画
增加动画可以提醒用户将会发生什么,改善UI的界面。动画对于内容加载来说非常重要。
增加动画也会带来ui延迟,所以使用的时候需要谨慎考虑。
第一节 两个view切换
在两个view之间切换,像你展示切换的过程。
两个view之间平滑切换代码。
main_activity.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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: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>
package com.example.www.animation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
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);
// Retrive and cache the system's default "short" animation time.
mShortAnimationDuration = getResources().getInteger(android.R.integer.config_longAnimTime);
crossfade();
}
private void crossfade() {
// set the contenct 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 contenctview 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% opcacity. After the animation ends,
// set its visibility to GONE as an optimization setp (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);
}
});
}
}
第二节 使用ViewPager功能
使用ViewPager增加动画效果
翻页功能,很重要的。
学会。
ViewPager可以滑动,很重要的一个UI组件。
需要四个文件
src/ScreenSlidePageFragment.java
src/ScreenSlideActivity.java
layout/activity_screen_slide.java
layout/fragment_screen_slide_page.xml
一个activity,一个fragment
ViewPager有一个内置的手动滑动转换页面的功能,所以默认的就是会产生动画效果,因此你不需要创建任何的东西。
ViewPager使用PagerAdapter进行新的页面绘制,因此PagerAdapter会使用之前创建的fragment,进行填充。
1. 设置一个ViewPager的xml
2.创建一个类它继承FragmentStatePagerAdapter抽象类,实现getItem方法,它会使用ScreenSlidePageFragment作为新的page。还需要实现getCount()方法,它会返回多少个pager.
3. Hooks up the PagerAdapter to the ViewPager.
4. 处理device's back按键事件,当用户已经在第一页的时候就返回到上一个activity中。
一个简单的viewpager已经写完
O(∩_∩)O哈哈哈~
效果还不错。
package com.example.www.animation;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
/**
* Created by wang on 17-8-31.
*/
public class ScreenSlidePagerActivity extends FragmentActivity{
/**
* The number of pages (wizard steps) to show in this demo.
*/
private static final int NUM_PAGES = 5;
/**
* the pager widget, which handles animation and allows swiping to access previous and next wizard steps.
*/
private ViewPager mPager;
/**
* The pager adapter, which provides the pages to the view pager widget.
*/
private PagerAdapter mPagerAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen_slide);
// Instantiate a ViewPager and a PagerAdapter
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
/**
* A simple pager adapter that represent 5 ScreenSlidePageFragment objects, in sequence.
*/
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
@Override
public Fragment getItem(int position) {
return new ScreenSlidePageFragment();
}
@Override
public int getCount() {
return NUM_PAGES;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"/>
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
android:id="@id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
style="?android:textAppearanceMedium"
android:padding="16dp"
android:lineSpacingMultiplier="1.2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lorem_ipsum"/>
</ScrollView>
package com.example.www.animation;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by wang on 17-8-31.
*/
public class ScreenSlidePageFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.fragment_screen_slide_page, container, false);
return rootView;
}
}
定制化页面变换
可以通过实现ViewPager.PageTransformer接口实现,提供了一个方法叫做transformPage()方法。
The position parameter方法告诉了pager的位置,它会随着用户滑动界面而动态的改变。
用户可以创建动态的界面属性来设置变换,比如调用setAlpha(), setTranslationX()。
新建一个内部类
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);
}
}
}
记得需要设置类似的东西
mPager.setPageTransformer(true, new ZoomOutPageTransformer());
card类型变换
放大缩小一个view进行显示
这小节也很重要,需要掌握。
点击小图显示大图。
加油
package com.example.www.animation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
/**
* Created by wang on 17-8-31.
*/
class ZoomActivity extends FragmentActivity{
// Hold a reference to the current animator
// so that it can be canceled mid-way
private Animator mCurrentAnimator;
private int mShortAnimationDuration;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_zoom);
final View thumb1View = findViewById(R.id.thumb_button_1);
thumb1View.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
zoomImageFromThumb(thumb1View, R.drawable.liuye);
}
});
mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
}
private void zoomImageFromThumb(final View thumbView, int imageResId) {
// If there's an animation in progress, cancel it
// immediately and proceed with this one.
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Load the high-resolution "zoomed-in" image.
final ImageView expandedImageView = (ImageView) findViewById(
R.id.expanded_image);
expandedImageView.setImageResource(imageResId);
// Calculate the starting and ending bounds for the zoomed-in image.
// This step involves lots of math. Yay, math.
final Rect startBounds = new Rect();
final Rect finalBounds = new Rect();
final Point globalOffset = new Point();
// The start bounds are the global visible rectangle of the thumbnail,
// and the final bounds are the global visible rectangle of the container
// view. Also set the container view's offset as the origin for the
// bounds, since that's the origin for the positioning animation
// properties (X, Y).
thumbView.getGlobalVisibleRect(startBounds);
findViewById(R.id.container)
.getGlobalVisibleRect(finalBounds, globalOffset);
startBounds.offset(-globalOffset.x, -globalOffset.y);
finalBounds.offset(-globalOffset.x, -globalOffset.y);
// Adjust the start bounds to be the same aspect ratio as the final
// bounds using the "center crop" technique. This prevents undesirable
// stretching during the animation. Also calculate the start scaling
// factor (the end scaling factor is always 1.0).
float startScale;
if ((float) finalBounds.width() / finalBounds.height()
> (float) startBounds.width() / startBounds.height()) {
// Extend start bounds horizontally
startScale = (float) startBounds.height() / finalBounds.height();
float startWidth = startScale * finalBounds.width();
float deltaWidth = (startWidth - startBounds.width()) / 2;
startBounds.left -= deltaWidth;
startBounds.right += deltaWidth;
} else {
// Extend start bounds vertically
startScale = (float) startBounds.width() / finalBounds.width();
float startHeight = startScale * finalBounds.height();
float deltaHeight = (startHeight - startBounds.height()) / 2;
startBounds.top -= deltaHeight;
startBounds.bottom += deltaHeight;
}
// Hide the thumbnail and show the zoomed-in view. When the animation
// begins, it will position the zoomed-in view in the place of the
// thumbnail.
thumbView.setAlpha(0f);
expandedImageView.setVisibility(View.VISIBLE);
// Set the pivot point for SCALE_X and SCALE_Y transformations
// to the top-left corner of the zoomed-in view (the default
// is the center of the view).
expandedImageView.setPivotX(0f);
expandedImageView.setPivotY(0f);
// Construct and run the parallel animation of the four translation and
// scale properties (X, Y, SCALE_X, and SCALE_Y).
AnimatorSet set = new AnimatorSet();
set
.play(ObjectAnimator.ofFloat(expandedImageView, View.X,
startBounds.left, finalBounds.left))
.with(ObjectAnimator.ofFloat(expandedImageView, View.Y,
startBounds.top, finalBounds.top))
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X,
startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView,
View.SCALE_Y, startScale, 1f));
set.setDuration(mShortAnimationDuration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCurrentAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
mCurrentAnimator = null;
}
});
set.start();
mCurrentAnimator = set;
// Upon clicking the zoomed-in image, it should zoom back down
// to the original bounds and show the thumbnail instead of
// the expanded image.
final float startScaleFinal = startScale;
expandedImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Animate the four positioning/sizing properties in parallel,
// back to their original values.
AnimatorSet set = new AnimatorSet();
set.play(ObjectAnimator
.ofFloat(expandedImageView, View.X, startBounds.left))
.with(ObjectAnimator
.ofFloat(expandedImageView,
View.Y,startBounds.top))
.with(ObjectAnimator
.ofFloat(expandedImageView,
View.SCALE_X, startScaleFinal))
.with(ObjectAnimator
.ofFloat(expandedImageView,
View.SCALE_Y, startScaleFinal));
set.setDuration(mShortAnimationDuration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
thumbView.setAlpha(1f);
expandedImageView.setVisibility(View.GONE);
mCurrentAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
thumbView.setAlpha(1f);
expandedImageView.setVisibility(View.GONE);
mCurrentAnimator = null;
}
});
set.start();
mCurrentAnimator = set;
}
});
}
}
第三节 翻转
使用翻转动画
第四节 触摸放大效果
使用触摸放大view
第五节 layout变化
重点还是方法的实现,花哨的接口其实没有什么东西,只是死记硬背传递一点参数而已,没什么新鲜。
加油,下一章节。