FlowHelper工程源码
最后就来实现 跟着 viewpager 的效果,如下:
可以看到 ,上面实现了几个效果:
1、子控件的背景跟着自身大小自动变化
2、背景跟着viewpager的滚动自动滑动
3、当移动到中间,如果后面有多余的数据,则让背景保持在中间,内容移动
首先,实现一个红色背景框框;首先,思考一下,在 viewgroup 实现 canvas , 是在 onDraw(Canvas canvas) 绘制,还是在 dispatchDraw(Canvas canvas) 呢?
答案肯定是 dispatchDraw 绘制了,为什么呢?这里解释几个概念:
onDraw 绘制内容
onDraw 为实际要关心的东西,即所有绘制都在这里。
dispatchDraw 只对ViewGroup有意义
dispatchDraw 通常来讲,可以解释成绘制 子 View
View 继承drawable,view 组件的绘制会先调用 draw(Canvas canvas) 方法,然后先绘制 Drawable背景,接着才是调用 onDraw ,然后调用 dispatchDraw方法。dispatchDraw 会分发给组件去绘制。
不过 View 是没有子 view 的,所以dispatchDraw对它来说没意义。
所以,当自定义 ViewGroup 时,加入 ViewGroup 没有背景,是不会回调 onDraw 方法的,只会回调dispatchDraw,有背景才会走正常顺序。(不信? 你可以把你的 tabflowlayout 背景去掉,在 onDraw 绘制,看看有没有用)
这样,当在自定义 ViewGroup 时,比如添加一下斑点,如果不想添加完子 view 之后,斑点消失,只需要在 子 view 的 onDraw 执行完,再执行 dispatchDraw 即可,就是在 ViewGroup的 dispatchDraw 绘制即可。
这样,我们先拿到,第一个子 view 的大小,确定 rect:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
View child = getChildAt(0);
if (child != null) {
//拿到第一个数据
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
mRect.set(getPaddingLeft()+params.leftMargin,
getPaddingTop()+params.topMargin,
child.getMeasuredWidth()-params.rightMargin,
child.getMeasuredHeight() - params.bottomMargin);
}
}
接着在 dispatchDraw 中绘制圆角矩形:
@Override
protected void dispatchDraw(Canvas canvas) {
//绘制一个矩形
canvas.drawRoundRect(mRect, 10, 10, mPaint);
super.dispatchDraw(canvas);
}
效果如下:
接着,怎么让这个背景跟着 viewpager 移动呢?
可以从 viewpager 的页面监听中拿到 onPageScrolled 方法:
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from the page at position.
* @param positionOffsetPixels Value in pixels indicating the offset from position.
*/
@UnsupportedAppUsage
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
三个参数解释如下:
从上面可以看到,我们只需要 position 和 positionOffset 即可,即上一个 左边为要移动的偏移量,加上 子 view 的宽度变化即可:
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
/**
* position 当前第一页的索引,比较有意思的是,当右滑时,position 表示当前页面,当左滑时,为当前页面减1;
* positionOffset 当前页面移动的百分比
* positionOffsetPixels 当前页面移动的像素
*/
if (position < getChildCount() - 1) {
//上一个view
final View lastView = getChildAt(position);
//当前view
final View curView = getChildAt(position + 1);
//左边偏移量
float left = lastView.getLeft() + positionOffset * (curView.getLeft() - lastView.getLeft());
//右边表示宽度变化
float right = lastView.getRight() + positionOffset * (curView.getRight() - lastView.getRight());
mRect.left = left;
mRect.right = right;
postInvalidate();
}
}
但移动发现,左边和右边都会被滑出,中间也没滚动,所以,最后代码改为:
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
/**
* position 当前第一页的索引,比较有意思的是,当右滑时,position 表示当前页面,当左滑时,为当前页面减1;
* positionOffset 当前页面移动的百分比
* positionOffsetPixels 当前页面移动的像素
*/
//居中代码
float offset = getChildAt(position).getWidth() * positionOffset;
int scrollX = (int) (getChildAt(position).getLeft() + offset);
if (position < getChildCount() - 1) {
//上一个view
final View lastView = getChildAt(position);
//当前view
final View curView = getChildAt(position + 1);
//左边偏移量
float left = lastView.getLeft() + positionOffset * (curView.getLeft() - lastView.getLeft());
//右边表示宽度变化
float right = lastView.getRight() + positionOffset * (curView.getRight() - lastView.getRight());
mRect.left = left;
mRect.right = right;
postInvalidate();
//超过中间了,让父控件也跟着移动
if (scrollX > mScreenWidth / 2 - getPaddingLeft() ) {
scrollX -= mScreenWidth / 2 - getPaddingLeft();
//有边界提醒
if (scrollX < mRightBound - mScreenWidth) {
scrollTo(scrollX, 0);
}
}else{
scrollTo(0,0);
}
}
}
看看效果;
这样,我们就实现了效果,至于最后要改成什么样,就靠大家的自定义想象了;
在子 view 的点击事件那里,只需要切 mViewPager.setCurrentItem(position); 就可以了;
上面实现了viewpager 的功能,但我没有 viewpager 怎么办?那肯定也要一个效果呀。
从上面可以看到,只需要子控件的,左边坐标和 宽度变化即可。
那么,可以使用 ValueAnimator,但 ofFloat 方法只能放一个参数呀;那这个时候,就可以用ObjectAnimator.ofObject() 方法了,然后再继承 TypeEvaluator 实现一个x,y 都是匀速的插值器就可以了。
好了,思路给你了;如果没实现出来,可以看看下面这个代码: