在android开发技术周报看到了小红书引导页的开源实现,觉得做的很不错,在这里分析一下它的实现,共同学习~
效果
页面组成
Touch事件传递
如果还不理解touch事件的分派、拦截、处理机制,强烈推荐下面的文章:
Android TouchEvent事件传递机制
Android:30分钟弄明白Touch事件分发机制
要实现的需求:
- Req-1 ChildViewPager#image 和 ChildViewPager#text能同时响应滑动事件。
- Req-2 点击“跳过”按钮进入LoginAnimFragment。
- Req-3 Text1滑到Text2时,ChildViewPager#image不滑动,FirstFragment播放动画。
- Req-4 Text2滑到Text3时,切换到SecondFragment并播放动画。
- Req-5 Text3滑到Text4时,切换到ThirdFragment并播放动画。
- Req-6 从Text4再向右滑,切换到LoginAnimFragment。
- Req-7 从LoginAnimFragment向左滑不会滑到WelcomeAnimFragment。
触摸事件拦截
为了实现Req-1(ChildViewPager#image 和 ChildViewPager#text能同时响应滑动事件),需要在ParentViewPager拦截触摸事件自己来处理touch事件。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mLoginPageLock) {
requestDisallowInterceptTouchEvent(true);
return super.onInterceptTouchEvent(ev);
}
return true;
}
requestDisallowInterceptTouchEvent(true);的作用是禁止调用onInterceptTouchEvent方法,也就是不再拦截触摸事件。实现了Req-7(从LoginAnimFragment向左滑不会滑到WelcomeAnimFragment)。
那么问题来了,可以把
requestDisallowInterceptTouchEvent(true);
return super.onInterceptTouchEvent(ev);
这两句能换成一句return false吗?试了一下效果跟上面两句是一样的,会不会出现负面影响未知。
处理触摸事件
ParentViewPager将事件拦截后,在onTouchEvent里做处理。
Req-2:“跳过”按钮
/**
* 由于子View被拦截,这里通过计算跳过按钮的坐标手动处理跳过click事件
*/
if (mTvSkipLocation == null) {
mTvSkipLocation = new int[2];
mWelcomAnimFragment.tv_skip.getLocationOnScreen(mTvSkipLocation);
}
if (left == 0) {
margin = DisplayUtil.dip2px(getContext(), 10);
left = mTvSkipLocation[0] - margin;
top = mTvSkipLocation[1] - margin;
right = mTvSkipLocation[0] + mWelcomAnimFragment.tv_skip.getWidth() + margin;
bottom = mTvSkipLocation[1] + mWelcomAnimFragment.tv_skip.getHeight() + margin;
}
if (mCx - left > 0 && right - mCx > 0 && mCy - top > 0 && bottom - mCy > 0 && !mLoginPageLock) {
} else {
mSkipFlag = false;
}
if (ev.getAction() == MotionEvent.ACTION_UP) {
if (mCx - left > 0 && right - mCx > 0 && mCy - top > 0 && bottom - mCy > 0 && !mLoginPageLock && mSkipFlag) {
if (mWelcomAnimFragment.mWelcomAnimFragmentInterface != null) {
mWelcomAnimFragment.mWelcomAnimFragmentInterface.onSkip();
}
}
mSkipFlag = true;
mCx = 0;
mCy = 0;
}
ViewPager控件
/**
* touch事件由顶层viewpager捕捉,手动分发到两个子viewpager
*/
if (mWelcomAnimFragment.imageViewPager != null && !mWelcomAnimFragment.imageViewPager.mIsLockScoll) {
mWelcomAnimFragment.imageViewPager.onTouchEvent(ev);
}
if (mWelcomAnimFragment.textViewPager != null) {
mWelcomAnimFragment.textViewPager.onTouchEvent(ev);
}
if (mWelcomAnimFragment.mIsMoveParent) {
return super.onTouchEvent(ev);
}
通过mIsLockScoll控制imageViewPager是否响应触摸事件,实现了Req-3(Text1滑到Text2时,ChildViewPager#image不滑动,FirstFragment播放动画);
通过mIsMoveParent实现了Req-6(从Text4再向右滑,切换到LoginAnimFragment)。
动画
下面来看一看酷炫的动画是怎么实现的。
这三个Fragment分别是LoginAnimImageFristFragment,LoginAnimImageSecondFragment,LoginAnimImageThridFragment。
以LoginAnimImageFristFragment为例做分析。
先看资源:
需要从第一个item滚动到第三个item的头部,涉及到如下问题:
- 图片缩放到恰当的比例;
精确滚动到第三个item头部;
经过一系列的计算,播放动画的代码如下:
@Override
public void playInAnim() {
if (mAnimStartY < 0) {
mAnimStartY = ViewHelper.getY(iv_scroll);
}
if(mObjectAnimator == null){
mObjectAnimator = ObjectAnimator.ofFloat(iv_scroll, "y", mMarginTopHeigth, -scrollMarginTopHeigth+mMarginTopHeigth);
}
if(mAnimatorSet == null){
mAnimatorSet = new AnimatorSet();
}
if(mAnimatorSet.isRunning()){
mAnimatorSet.cancel();
}
mAnimatorSet.play(mObjectAnimator);
mAnimatorSet.setDuration(3000);
mAnimatorSet.start();
}
第二、三页的动画实现类似(看到各种长宽计算有没有晕晕的感觉???刚看到代码我是晕了,找到一种更好的开发动画的方式就好了。。。)
总结
- 这个例子可以帮助你深入理解Touch事件的传递机制;
- 为了精确控制动画要进行繁杂的计算,无非是缩放、移动、旋转、隐藏,理解了就不觉得难;
感谢小红书的分享,感谢github,感谢所有支持开源的个人和组织~