墨迹天气新版的开机介绍很漂亮,上下滚动翻页,翻页结束后元素会有动画效果,分析一下动画元素都是基本的Animation,没有用到最新的属性动画;上下翻页滚动的控件android没有提供,只有横向的Viewpager,这里有一种实现->点击打开链接,用到了开源的控件ViewPager-Android,我们这里试着手动实现一个上下滚动的翻页控件。
前期准备
首先我们用apktool把墨迹天气的安装包解压出来,取出其中的图片资源和布局文件,一共4个布局
翻页控件实现
要实现自定义布局,需要继承ViewGroup,然后实现onMeasure、onLayout方法
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); int height = getMeasuredHeight(); int top = 0; for (int i = 0; i < count; ++i) { View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { childView.layout(l, top, r, top + height); top += height; } } mTotalHeight = height * (count - 1); mTolerance = height / 2; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); for (int i = 0; i < count; ++i) { View childView = getChildAt(i); measureChild(childView, widthMeasureSpec, heightMeasureSpec); } }
这里在onLayout方法中将页面排下来,每页内容都充满控件,垂直排列。
这时候在如果在activity中将View inflate出来再通过addview添加到控件中就会看到第一页的内容,但此时还不能滑动。下面我们就来实现上下滑动翻页。
要响应控件上的手势操作需要实现onTouchEvent方法:
@Override public boolean onTouchEvent(MotionEvent event) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } mLastY = (int) event.getY(); mStartYPosition = getScrollY(); break; case MotionEvent.ACTION_MOVE: int y = (int) event.getY(); int distance = mLastY - y; int scrollY = getScrollY(); // 边界检查 if (distance < 0 && scrollY + distance < 0) { distance = 0 - scrollY; } else if (distance > 0 && scrollY + distance > mTotalHeight) { distance = mTotalHeight - scrollY; } scrollBy(0, distance); mLastY = y; break; case MotionEvent.ACTION_UP: mEndYPosition = getScrollY(); int posDiff = mEndYPosition - mStartYPosition; mVelocityTracker.computeCurrentVelocity(1000); int velocityY = (int) mVelocityTracker.getYVelocity(); mVelocityTracker.recycle(); mVelocityTracker = null; if (Math.abs(velocityY) >= 600 || Math.abs(posDiff) > mTolerance) { int dis = 0; if (posDiff > 0) { dis = getMeasuredHeight() - posDiff; } else if (posDiff < 0) { dis = -(getMeasuredHeight() + posDiff); } mScroller.startScroll(0, 0, 0, dis); } else { mScroller.startScroll(0, 0, 0, -posDiff); } postInvalidate(); break; default: break; } return true; }
上面的这些操作包括了滚动、边界检查(避免滑出边界)和完成翻页的功能,最开始其实是这样的
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: int y = (int) event.getY(); int distance = mLastY - y; scrollBy(0, distance); mLastY = y; break; case MotionEvent.ACTION_UP: break; default: break; } return true; }
页面可以随着手指上下滚动,但是会超出边界,再添加上边界检查,就是修改一下ACTION_MOVE,
case MotionEvent.ACTION_MOVE: int y = (int) event.getY(); int distance = mLastY - y; int scrollY = getScrollY(); // 边界检查 if (distance < 0 && scrollY + distance < 0) { distance = 0 - scrollY; } else if (distance > 0 && scrollY + distance > mTotalHeight) { distance = mTotalHeight - scrollY; } scrollBy(0, distance); mLastY = y; break;
这时候再滚动的时候就会发现到达边界的时候就无法再滑动了,下面再添加滑动半屏后自动完成翻页的功能,就是最上面的那个完整的代码,里面用到了scroller,在抬起手指的时候计算滚动剩余距离,然后开始滚动,scroller只负责完成滚动过程位置的计算,真正控制页面的是在computeScroll()方法里:
@Override public void computeScroll() { if (mScroller.computeScrollOffset()) { int scroll = mScroller.getCurrY(); if (scroll > 0 && mEndYPosition + scroll > mTotalHeight) { scroll = mTotalHeight - mEndYPosition; } else if (scroll < 0 && mEndYPosition + scroll < 0) { scroll = -mEndYPosition; } scrollTo(0, mEndYPosition + scroll); mIsScrolling = true; postInvalidate(); } else if (mIsScrolling) { if (mPageScrollListener != null) { int position = getScrollY() / getMeasuredHeight(); if (position != mCurrentPage) { mCurrentPage = position; mPageScrollListener.onPageChanged(mCurrentPage); } } mIsScrolling = false; } }
每次页面重绘都会调用computeScroll方法,然后通过scroller得到此时的滚动值,再次重绘,直到滚动结束,这里也做了下边界检测,防止滚过头了。
滚动结束后要通知控件的使用者翻页已完成,所以定义一个翻页完成的接口
public void setOnPageScrollListener(OnPageScrollListener listener) { mPageScrollListener = listener; } public interface OnPageScrollListener { public void onPageChanged(int position); }
在computeScroll()中发现翻页完成了就调用这个接口。
剩下的就是在activity中加载动画了,每当翻页结束就播放相应页面的动画并清除上一页的动画效果
class MyPageScrollListener implements OnPageScrollListener { @Override public void onPageChanged(int position) { switch (position) { case 0: layout1AnimStart(); break; case 1: layout2AnimStart(); break; case 2: layout3AnimStart(); break; case 3: layout4AnimStart(); break; } } }
演示效果
代码在这里->http://download.csdn.net/detail/xu_fu/7185403