1.需求
viewpager中嵌套了scollview实现fragment的纵向滑动,scollview嵌套了viewpager实现轮播形式。已有代码使用viewpager的 android:clipChildren="false"实现一页显示3个view的效果,但是视觉效果是3个,而实际viewpager大小并没有改变,先需要按触摸到视觉效果的左右2个view时响应子viewpager的滑动事件,而不是父viewpager的滑动事件。
2.实现
自定义2个viewpager,父viewpager处理当有子viewpager时不拦截touchevent,子viewpager实现响应区域扩大逻辑(TouchDelegate)。
3.代码
父viewpager:
public class ShrinkTouchViewPager extends ViewPager {
private final int TOUCH_SHRINK = 0;
private int mTouchShrinkBottom = 0;
private int mTouchShrinkLeft = 0;
private int mTouchShrinkRight = 0;
private int mTouchShrinkTop = 0;
public ShrinkTouchViewPager(Context context) {
super(context);
init(context, null);
}
public ShrinkTouchViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.LargeTouchableAreaView);
int shrink = (int) a.getDimension(
R.styleable.ShrinkTouchViewPager_shrink, TOUCH_SHRINK);
mTouchShrinkBottom = shrink;
mTouchShrinkLeft = shrink;
mTouchShrinkRight = shrink;
mTouchShrinkTop = shrink;
mTouchShrinkBottom = (int) a.getDimension(
R.styleable.ShrinkTouchViewPager_shrinkBottom,
mTouchShrinkBottom);
mTouchShrinkLeft = (int) a.getDimension(
R.styleable.ShrinkTouchViewPager_shrinkLeft,
mTouchShrinkLeft);
mTouchShrinkRight = (int) a.getDimension(
R.styleable.ShrinkTouchViewPager_shrinkRight,
mTouchShrinkRight);
mTouchShrinkTop = (int) a.getDimension(
R.styleable.ShrinkTouchViewPager_shrinkTop,
mTouchShrinkTop);
a.recycle();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getY() > mTouchShrinkTop && ev.getY() < mTouchShrinkBottom && getCurrentItem() == 0) {
return false;
}
return super.onInterceptTouchEvent(ev);
}
}
attrs:
<declare-styleable name="LargeTouchableAreaView"> <attr name="addition" format="dimension" /> <attr name="additionBottom" format="dimension" /> <attr name="additionLeft" format="dimension" /> <attr name="additionRight" format="dimension" /> <attr name="additionTop" format="dimension" /> </declare-styleable>
子viewpager:
public class EnlargeTouchViewPager extends ViewPager { private final int TOUCH_ADDITION = 0; private int mTouchAdditionBottom = 0; private int mTouchAdditionLeft = 0; private int mTouchAdditionRight = 0; private int mTouchAdditionTop = 0; private int mPreviousLeft = -1; private int mPreviousRight = -1; private int mPreviousBottom = -1; private int mPreviousTop = -1; public EnlargeTouchViewPager(Context context) { super(context); init(context, null); } public EnlargeTouchViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } private void init(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LargeTouchableAreaView); int addition = (int) a.getDimension( R.styleable.LargeTouchableAreaView_addition, TOUCH_ADDITION); mTouchAdditionBottom = addition; mTouchAdditionLeft = addition; mTouchAdditionRight = addition; mTouchAdditionTop = addition; mTouchAdditionBottom = (int) a.getDimension( R.styleable.LargeTouchableAreaView_additionBottom, mTouchAdditionBottom); mTouchAdditionLeft = (int) a.getDimension( R.styleable.LargeTouchableAreaView_additionLeft, mTouchAdditionLeft); mTouchAdditionRight = (int) a.getDimension( R.styleable.LargeTouchableAreaView_additionRight, mTouchAdditionRight); mTouchAdditionTop = (int) a.getDimension( R.styleable.LargeTouchableAreaView_additionTop, mTouchAdditionTop); a.recycle(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (left != mPreviousLeft || top != mPreviousTop || right != mPreviousRight || bottom != mPreviousBottom) { mPreviousLeft = left; mPreviousTop = top; mPreviousRight = right; mPreviousBottom = bottom; final View parent = (View) this.getParent(); parent.setTouchDelegate(new TouchDelegate(new Rect(left - mTouchAdditionLeft, top - mTouchAdditionTop, right + mTouchAdditionRight, bottom + mTouchAdditionBottom), this)); } } class TouchDelegate extends android.view.TouchDelegate { /** * View that should receive forwarded touch events */ private View mDelegateView; /** * Bounds in local coordinates of the containing view that should be mapped to the delegate * view. This rect is used for initial hit testing. */ private Rect mBounds; /** * mBounds inflated to include some slop. This rect is to track whether the motion events * should be considered to be be within the delegate view. */ private Rect mSlopBounds; /** * True if the delegate had been targeted on a down event (intersected mBounds). */ private boolean mDelegateTargeted; /** * The touchable region of the View extends above its actual extent. */ public static final int ABOVE = 1; /** * The touchable region of the View extends below its actual extent. */ public static final int BELOW = 2; /** * The touchable region of the View extends to the left of its * actual extent. */ public static final int TO_LEFT = 4; /** * The touchable region of the View extends to the right of its * actual extent. */ public static final int TO_RIGHT = 8; private int mSlop; private boolean isStartLeft = false; /** * Constructor * * @param bounds Bounds in local coordinates of the containing view that should be mapped to * the delegate view * @param delegateView The view that should receive motion events */ public TouchDelegate(Rect bounds, View delegateView) { super(bounds, delegateView); mBounds = bounds; mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop(); mSlopBounds = new Rect(bounds); mSlopBounds.inset(-mSlop, -mSlop); mDelegateView = delegateView; } /** * Will forward touch events to the delegate view if the event is within the bounds * specified in the constructor. * * @param event The touch event to forward * @return True if the event was forwarded to the delegate, false otherwise. */ public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); boolean sendToDelegate = false; boolean hit = true; boolean handled = false; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Rect bounds = mBounds; if (bounds.contains(x, y)) { mDelegateTargeted = true; sendToDelegate = true; if (event.getX() < mDelegateView.getX()) { isStartLeft = true; } else { isStartLeft = false; } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_MOVE: sendToDelegate = mDelegateTargeted; if (sendToDelegate) { Rect slopBounds = mSlopBounds; if (!slopBounds.contains(x, y)) { hit = false; } } break; case MotionEvent.ACTION_CANCEL: sendToDelegate = mDelegateTargeted; mDelegateTargeted = false; break; } if (sendToDelegate) { final View delegateView = mDelegateView; if (hit) { // Offset event coordinates to be inside the target view if (isStartLeft) { event.setLocation(event.getX() + mTouchAdditionLeft, delegateView.getY() + delegateView.getHeight() / 2); } else { event.setLocation(event.getX() - mTouchAdditionRight, delegateView.getY() + delegateView.getHeight() / 2); } } else { // Offset event coordinates to be outside the target view (in case it does // something like tracking pressed state) int slop = mSlop; event.setLocation(-(slop * 2), -(slop * 2)); } handled = delegateView.dispatchTouchEvent(event); } return handled; } }
}
attrs:
<declare-styleable name="ShrinkTouchViewPager"> <attr name="shrink" format="dimension" /> <attr name="shrinkBottom" format="dimension" /> <attr name="shrinkLeft" format="dimension" /> <attr name="shrinkRight" format="dimension" /> <attr name="shrinkTop" format="dimension" /> </declare-styleable>