转载请附原文地址:http://www.jianshu.com/p/f9c0b00efd14
前言
因为公司UI需要撸一个动效,然后我又一个人没有人约,别人在嗨嗨嗨,嘿嘿嘿,呵呵呵的时候,我只能在家里默默撸代码。一个很简单的位移动画,因为嵌套布局,出现了一些蛋疼的坑。记录一下,以后碰就可以绕过了。
在这里感谢洋葱司机提供思路,帮我解决了这个问题
需求
需求是,这四个分享控件按照比例,分布在屏幕靠近中央的矩形范围内,每个图标左右,上下的间距都是按权重来分配,避免在大屏幕太靠中间,小屏幕又太靠边。弹窗弹出时,四个图标依次从屏幕底部飞入。
布局
根布局为LinearLayout,orientation="vertical"
每一排图标为一个水平的LinearLayout
动画
布局写完了,开始撸动画。
这里很容易想到,用TranslateAnimation,so easy
- 第一个坑来了。(子控件被限制视图)
因为四个控件的父布局是水平的LinearLayout(简称二级布局),当控件移动到二级布局的边缘时就没有视图了,看不见了~坑爹啊。
于是我去求助,见多识广的洋葱老司机给了我一个思路,在window上加一个布局用来做动画,这个布局只用来做动画,动画完成后消失,布局背景设置为透明。
先写一个动画布局,把需要做动效的控件copy一份到这个布局中
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#00ffffff"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!-- 这里放你的控件-->
</RelativeLayout>
又踩了个坑(布局特性)
一开始根布局用的是线性布局,结果有两个控件被挤出去了。。。
这是个小问题在代码中添加动画图层
View animWindow=LayoutInflater.from(mActivity).inflate(R.layout.popupwindow_anim, null);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
0, 0,
WindowManager.LayoutParams.TYPE_TOAST,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.RGBA_8888);
windowManager.addView(animWindow, params);
拿到四个子控件
LinearLayout weixinCircle = (LinearLayout) animWindow.findViewById(R.id.ll_share_wx_circle);
LinearLayout weixin = (LinearLayout) animWindow.findViewById(R.id.ll_share_wx);
LinearLayout sina = (LinearLayout) animWindow.findViewById(R.id.ll_share_sina);
LinearLayout copy = (LinearLayout) animWindow.findViewById(R.id.ll_share_url);
- 获取子控件的坐标
为了让动画图层中的控件位置和实际位置精确重合,需要计算一下每个控件的位置,getLocationInWindow(location);可以获取在窗口内的坐标,具体用什么方法获取看具体需求,这里我的popwindow已经对statusbar做了适配处理,所以直接获取window坐标就可以了
getLocationInWindow(location);
getLocationOnScreen(location);
getGlobalVisibleRect(rect);
不一一列举了,搜一下一堆。
这里有个坑,如果绘制还没有完成的情况下去获取坐标,会得到0,并没有什么卵用
怎么判断绘制完毕,也有很多方法,也不一一列举了,我用的 ViewTreeObserver
ViewTreeObserver viewTreeObserver = mWeixinCircle.getViewTreeObserver();
viewTreeObserver
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mWeixinCircle.getViewTreeObserver().removeGlobalOnLayoutListener(this);
startEnterAnimation();
}
});
别忘了remove
- 绘制完成后获取坐标启动动画,并监听
动画的代码就不写,几行代码。原来的popwindow初始化后,将四个控件隐藏
常用的监听方法,在onAnimationEnd中,将四个控件显示出来,并remove动画图层
ta.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) { }
@Override
public void onAnimationEnd(Animation animation) {
mWeixinCircle.setVisibility(View.VISIBLE);
mWeixin.setVisibility(View.VISIBLE);
mSina.setVisibility(View.VISIBLE);
mCopy.setVisibility(View.VISIBLE);
windowManager.removeView(animWindow);
}
@Override
public void onAnimationRepeat(Animation animation) { }
});
坑爹的又来了
测试中,这个方法在进入动画时正常,退出动画时没有被调用!
查阅官方文档,当动画被设置为无限重复时,不会被调用。
This callback is not invoked for animations with repeat count set to INFINITE.
WTF?我没有设置重复啊?
上stackoverflow找到一个相关问题,这哥们儿是这么回答的
原文链接:http://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-called-onanimationstart-works-fine
AnimationEnd
is not reliable. If you don't want to rewrite your code with custom views that override OnAnimationEnd, use postDelayed.
While it MAY seem ugly, I can guarantee it's very reliable. I use it for ListViews that are inserting new rows while removing with animation to other rows. Stress testing a listener with AnimationEnd proved unreliable. Sometimes AnimationEnd
was never triggered. You might want to reapply any transformation in the postDelayed
function in case the animation didn't fully finish, but that really depends on what type of animation you're using.
坑爹啊,用postDelayed吧,起码靠谱~
handler.postDelayed(new Runnable() {
@Override
public void run() {
ULog.d("isEnter=" + isEnter);
if (isEnter) {
mWeixinCircle.setVisibility(View.VISIBLE);
mWeixin.setVisibility(View.VISIBLE);
mSina.setVisibility(View.VISIBLE);
mCopy.setVisibility(View.VISIBLE);
} else {
CustomShareBoard.this.dismiss();
}
windowManager.removeView(animWindow);
}
}, 700);
最后祝大家节日快乐