现在很多应用在页面里都使用了一些动画效果,前两天玩了玩人人和百合网客户端,实现了其中使用的几个动画效果。主要有以下几种:
1. 人人和百合登录页面动画。
这两款客户端登录页都有。首先看效果图。Gif做的比较烂,大家多发挥下想象力→_→。有兴趣的可以下载下这两个客户端体验一下,效果稍有不同,但大同小异。
貌似outlook不支持gif,所以放到附件里,见“登录动画”。
基本的效果是这样的,以百合网为例,两张图片,第一张图片逐渐放大,继而逐渐变小,变小的同时清晰度发生变化,逐渐模糊,类似淡入淡出的透明度变化效果。在第一张图逐渐透明的过程中,第二张图淡入,越来越清晰,同时逐渐放大。由于两张图左上角都有logo,所以还会有个logo有个影子叠在一起的感觉。第一张图消失之后,第二张图重复第一张图的渐变动画,消失时第一张图再次出现,如此反复。
实现这样的效果其实很简单,定义四种动画,放大,缩小,淡入,淡出。然后用AnimationSet组合。对动画实施监听,比如第一张图的消失动画,在动画开始时启动一个线程,此线程首先sleep一半的动画时间,然后启动第二张图的淡入动画,从而实现两张图的交互效果。第二张图调用第一张,第一张调用第二张,也很容易就达到了循环播放的效果。关键实现如下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_main); mImg = (ImageView) findViewById(R.id.img); mImg2 = (ImageView) findViewById(R.id.img2); mAniSet1 = new AnimationSet(true); mAniSet2 = new AnimationSet(true); mAniSet3 = new AnimationSet(true); mAniSet4 = new AnimationSet(true); mAniSet5 = new AnimationSet(true); ScaleAnimation sa1 = new ScaleAnimation(1.0F, 1.3F, 1.0F, 1.3F, Animation.RELATIVE_TO_SELF, 0.5F, Animation.RELATIVE_TO_SELF, 0.5F);//放大动画 ScaleAnimation sa2 = new ScaleAnimation(1.3F, 1.0F, 1.3F, 1.0F, Animation.RELATIVE_TO_SELF, 0.5F, Animation.RELATIVE_TO_SELF, 0.5F); //缩小动画 AlphaAnimation aa1 = new AlphaAnimation(1.0F, 0.0F); //淡出动画 AlphaAnimation aa2 = new AlphaAnimation(0.0F, 1.0F); //淡入动画 AlphaAnimation aa3 = new AlphaAnimation(0.1F, 1.0F);
mAniSet5.addAnimation(aa1); mAniSet5.setDuration(8000L); mAniSet1.addAnimation(sa1); mAniSet1.addAnimation(aa3); mAniSet1.setDuration(8000L); mAniSet2.addAnimation(sa2); mAniSet2.addAnimation(aa1); mAniSet2.setDuration(8000L); mAniSet3.addAnimation(sa1); mAniSet3.addAnimation(aa2); mAniSet3.setDuration(8000L); mAniSet4.addAnimation(sa2); mAniSet4.addAnimation(aa1); mAniSet4.setDuration(8000L);
mImg.startAnimation(mAniSet5); //首先一个淡出动画,动画结束时,切换图片,采用handle刷新页面。
mAniSet5.setAnimationListener(new AnimationListener() {
@Override public void onAnimationStart(Animation animation) { mImg.setBackgroundResource(R.drawable.index_bg_3); }
@Override public void onAnimationEnd(Animation animation) { mImg.setBackgroundDrawable(null); new MyThread().start(); }
@Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub
}
}); mAniSet1.setAnimationListener(new AnimationListener() {
@Override public void onAnimationStart(Animation animation) { mImg2.setBackgroundResource(R.drawable.index_bg_2);
}
@Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub
}
@Override public void onAnimationEnd(Animation animation) { mImg2.startAnimation(mAniSet2);
} });//动画逐渐放大,同时逐渐清晰,结束后启动下一个动画。 mAniSet2.setAnimationListener(new AnimationListener() {
@Override public void onAnimationEnd(Animation animation) { mImg2.setBackgroundDrawable(null);
}
@Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub
}
@Override public void onAnimationStart(Animation animation) { new MyThread2().start();
}
});//动画逐渐变小,清晰度越来越差,直至消失。动画开始时启动一个线程,用于启动下一个图片的动画。 mAniSet3.setAnimationListener(new AnimationListener() {
@Override public void onAnimationStart(Animation animation) { mImg.setBackgroundResource(R.drawable.index_bg_3);
}
@Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub
}
@Override public void onAnimationEnd(Animation animation) { mImg.startAnimation(mAniSet4);
} }); mAniSet4.setAnimationListener(new AnimationListener() {
@Override public void onAnimationStart(Animation animation) { new MyThread().start();
}
@Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub
}
@Override public void onAnimationEnd(Animation animation) { mImg.setBackgroundDrawable(null);
} });
mBtnEnter = (Button) findViewById(R.id.btnEnter); mBtnEnter.setOnClickListener(this); }
class MyThread extends Thread { @Override public void run() { try { if (mSleep) { Thread.sleep(4000L); } mSleep = true; Message localMessage = new Message(); localMessage.what = 102; mHandler.sendMessage(localMessage); return; } catch (Exception localInterruptedException) { localInterruptedException.printStackTrace(); } } }
class MyThread2 extends Thread { @Override public void run() { try { Thread.sleep(4000L); Message localMessage = new Message(); localMessage.what = 101; mHandler.sendMessage(localMessage); return; } catch (Exception localInterruptedException) { localInterruptedException.printStackTrace(); } } }
@Override public boolean handleMessage(Message msg) { switch (msg.what) { case 102: mImg2.startAnimation(mAniSet1); break; case 101: mImg.startAnimation(mAniSet3); break; } return false; } |
2. 百合网个人信息页的抽屉效果。
动画效果见附件“抽屉动画”。
简单描述一下,就是点击一个按钮,按钮下方出来内容,高度逐渐变大,好像一个抽屉拉出来一样。再次点击,下方内容收起,好像抽屉拉上一般。为了让大家看清楚,gif每帧的时间很长,所以一顿一顿的,实际很流畅。
很多应用都有这个效果。我采用了三种方式实现。
第一种使用提供的动画TranslateAnimation,这种实现方式很简单,代码如下:
mAniSet = new AnimationSet(true); Animation showAni = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f); mAniSet.addAnimation(showAni); mAniSet.setDuration(600);
mAniSet2 = new AnimationSet(true); Animation closeAni = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, -1.0f); mAniSet2.addAnimation(closeAni); mAniSet2.setDuration(600); mAniSet2.setAnimationListener(new AnimationListener() {
@Override public void onAnimationStart(Animation animation) {
}
@Override public void onAnimationRepeat(Animation animation) {
}
@Override public void onAnimationEnd(Animation animation) { mBtnSlide .setBackgroundResource(R.drawable.other_person_profile_detail_off); LayoutParams params1 = new LayoutParams( LayoutParams.FILL_PARENT, 0); params1.height = 0; mTxtSlide.setLayoutParams(params1); mBtnSlide.setText("点击展开");
} }); |
这种实现方式有一个弊端,就是下方抽屉的长度是固定的,实际上只是做了一个平移动画,因此启动动画的按钮下方如果有内容,那在动画开始时,下方的内容会立刻移动到动画结束时的位置,不能跟随上方的抽屉动画平滑移动。除非抽屉的高度一直在变化。于是想到了第二种办法,改变抽屉的长度。因为涉及到刷新页面,采用了一个AsyncTask,实现如下:
/** * 通过高度改变,实现抽屉效果,不流畅,不应sleep,且帧数过多。 * * @author meng.chen * */ private class showMenuTask extends AsyncTask<Boolean, Integer, Void> { private boolean mIsShow;
@Override protected Void doInBackground(Boolean... params) { mIsShow = params[0]; if (mIsShow) { for (int i = 0; i < 200; i++) { publishProgress(i); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } else { for (int i = 200; i >= 0; i--) { publishProgress(i); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
return null; }
@Override protected void onProgressUpdate(Integer... values) { LayoutParams params1 = new LayoutParams(LayoutParams.FILL_PARENT, 0); params1.height = values[0]; mTxtSlide.setLayoutParams(params1); if (values[0] <= 0 && !mIsShow) { mBtnSlide .setBackgroundResource(R.drawable.other_person_profile_detail_off); } super.onProgressUpdate(values); }
@Override protected void onPostExecute(Void result) {
super.onPostExecute(result); }
} |
在onProgressUpdate中刷新页面,使用sleep方法控制动画时间。但是实际效果很不流畅,且刷新次数过多,比较耗资源。所以采用第三种方法,用handle刷新页面,采用timer定时刷新,帧数放大,整个动画一共也就几帧。动画效果也非常流畅。实现如下:
public boolean showHideAnimate(final Activity activity, final View view, final int height, int duration, final boolean show) { if (mTimer != null) { return false; } mTimer = new Timer(); final int frame = 20; final Handler handler = new Handler() {
@Override public void handleMessage(Message msg) { activity.runOnUiThread(new Runnable() { public void run() { int h = mTxtSlide.getHeight(); if (show) { view.setVisibility(View.VISIBLE); h += height / frame; if (h >= height) h = height; } else { h -= height / frame; if (h <= 0) { h = 0; mBtnSlide .setBackgroundResource(R.drawable.other_person_profile_detail_off);
} } LinearLayout.LayoutParams params = (android.widget.LinearLayout.LayoutParams) view .getLayoutParams(); LinearLayout.LayoutParams newParams = new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, h); newParams.bottomMargin = params.bottomMargin; newParams.topMargin = params.topMargin; view.setLayoutParams(newParams); if (show) { if (mTimer != null && h == height) { mTimer.cancel(); mTimer = null; } } else { if (mTimer != null && h == 0) { mTimer.cancel(); mTimer = null; view.setVisibility(View.GONE); } } } }); super.handleMessage(msg); } };
mTimer.schedule(new TimerTask() { public void run() { handler.dispatchMessage(new Message()); } }, 0, duration / frame); return true; } |
3. 顶部浮层
动画效果见附件“顶部浮层”这个动画也是人人和百合都有。人人的稍微复杂一些。以百合为例,在其个人信息页面有个透明浮层,如gif中的“动画演示”。当页面内容过长时,可向上推动,当动画演示按钮滑动到页面顶部时,停止在页面顶端,其余内容继续向上滑动。人人网此效果体现在手机联系人页面,在“a-z”分组栏滑动到顶部时,固定在页面顶端,下一个分组过来,把之前的分组顶上去。
实现方案为准备两个按钮或布局,外观一模一样,实现同样的功能。一个在页面中的正常位置,如中间,一个在页面顶部,采用framelayout。初始只显示中间的控件,顶部的隐藏。当中间的控件滑动到顶部时,隐藏此控件,显示始终固定在顶部的控件。当上方内容逐渐滑下时,顶部控件隐藏,原中间的控件显示。代码实现如下:
mScroll = (ScrollView) findViewById(R.id.scroll); mScroll.setOnTouchListener(new OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) { if (mScroll.getScrollY() >= mBtnScroll2Top.getTop()) { mBtnTop.setVisibility(View.VISIBLE); mBtnScroll2Top.setVisibility(View.INVISIBLE); } else { mBtnTop.setVisibility(View.INVISIBLE); mBtnScroll2Top.setVisibility(View.VISIBLE); } return false; } }); |
4. 页间切换动画
动画效果见附件“页面切换”。当切换页面时,目标页面从下方逐渐拉上来,原页面逐渐变暗消失。当目标页面返回时,目标页面再向着页面底端逐渐移出,原页面逐渐显示。很多app都有这个效果,人人客户端更是大量采用。实现非常简单,只需要一句代码。调用overridePendingTransition方法,需要传两个参数,第一个参数是目标页面的动画,第二个参数是原页面动画。代码实现如下:
anim_in_from_bottom.Xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- <alpha android:duration="1000" android:fromAlpha="0.0" android:toAlpha="1.0" /> -->
<translate android:duration="500" android:fromYDelta="100%" android:toYDelta="0" />
</set> |
anim_disappear.xml |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" >
<alpha android:duration="500" android:fromAlpha="1.0" android:toAlpha="0.0" />
<!-- <translate android:duration="1000" android:fromYDelta="0" android:toYDelta="-100%" /> --> </set><!-- 透明度控制动画效果 alpha 浮点型值: fromAlpha 属性为动画起始时透明度 toAlpha 属性为动画结束时透明度 说明: 0.0表示完全透明 1.0表示完全不透明 以上值取0.0-1.0之间的float数据类型的数字
长整型值: duration 属性为动画持续时间 说明: 时间以毫秒为单位
--> |
调用代码,在startAcitivity方法或者finish方法之后调用。 |
。。。 startActivity(new Intent(mContext, TabPagerActivity.class)); overridePendingTransition(R.anim.anim_in_from_bottom, R.anim.anim_disappear); 。。。 |
5. viewpager
动画效果见附件“选项卡游标”。这个效果体现在tab下面的游标上,会跟着tab的切换而移动。Viewpager的应用非常广泛,主要用处有三个。一个是帮助页面。一个是优酷那种视频客户端在页面顶端的热门电影推荐,如
既定时切换,又可以手动滑动。一个是和tab页面结合。
帮助页面很简单,是最基本的viewpager的一个利用。第二种效果是viewpager和timer的结合。第三种和tab host的联合使用方式参照supportv4 demo中的实例,游标动画的实现代码如下:
@Override public void onPageSelected(int position) { mTabHost.setCurrentTab(position);
Animation animation = null; switch (position) { case 0: if (mCurrentIndex == 1) { animation = new TranslateAnimation(oneTabWidth, 0, 0, 0); } else if (mCurrentIndex == 2) { animation = new TranslateAnimation(twoTabWidth, 0, 0, 0); } break; case 1: if (mCurrentIndex == 0) { animation = new TranslateAnimation(mOffset, oneTabWidth, 0, 0); } else if (mCurrentIndex == 2) { animation = new TranslateAnimation(twoTabWidth, oneTabWidth, 0, 0); } break; case 2: if (mCurrentIndex == 0) { animation = new TranslateAnimation(mOffset, twoTabWidth, 0, 0); } else if (mCurrentIndex == 1) { animation = new TranslateAnimation(oneTabWidth, twoTabWidth, 0, 0); } break; } mCurrentIndex = position; animation.setFillAfter(true);// True:图片停在动画结束位置 animation.setDuration(300); mCursor.startAnimation(animation); } 。。。 |
完整的实现我已经上传到svn AndroidResearch\Project\AnimationTest目录下。