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>