Last active
June 25, 2020 15:09
-
-
Save dmitriy-chernysh/a3904813ba2d303e43f59bc520d1efc3 to your computer and use it in GitHub Desktop.
[Android, Java] HeightWrappingViewPager - a custom ViewPager that can be used as a child view in ScrollView. With custom swipe listener
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A custom scrollable ViewPager | |
* NOTE: it uses in a production app. | |
* <p> | |
* Created by Dmitriy V. Chernysh on 2/7/19. | |
* <p> | |
* https://instagr.am/mobiledevpro | |
* #MobileDevPro | |
*/ | |
public class HeightWrappingViewPager extends ViewPager { | |
public static final int CUSTOM_EVENT_SWIPE_LEFT = 1; | |
public static final int CUSTOM_EVENT_SWIPE_RIGHT = 2; | |
public static final float CUSTOM_SWIPE_MIN_DISTANCE = 50F; | |
private boolean isSwipable = true; | |
private boolean isHeightMatchParent; | |
private CustomSwipePagerListener customSwipePagerListener; | |
private float motionEventStartX = 0F; | |
private boolean isCustomSwipeDone; | |
public interface CustomSwipePagerListener { | |
void onNext(); | |
void onPrev(); | |
} | |
public HeightWrappingViewPager(Context context) { | |
super(context); | |
} | |
public HeightWrappingViewPager(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
} | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
int mode = MeasureSpec.getMode(heightMeasureSpec); | |
// Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT. | |
// At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT. | |
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
if (isHeightMatchParent) return; | |
int childCount = getChildCount(); | |
int height = 0; | |
for (int i = 0; i < childCount; i++) { | |
final View child = getChildAt(i); | |
//don't set height if RecyclerView is a first child. | |
//in some cases RecyclerView should be scrollable. Example, fragment_exam_questions_breakdown.xml | |
if (child instanceof RecyclerView) { | |
return; | |
} | |
final ViewPager.LayoutParams lp = (ViewPager.LayoutParams) child.getLayoutParams(); | |
int position = 0; | |
//find position of visible page | |
try { | |
Field f = lp.getClass().getDeclaredField("position"); | |
f.setAccessible(true); | |
position = f.getInt(lp); //IllegalAccessException | |
} catch (NoSuchFieldException | IllegalAccessException ex) { | |
ex.printStackTrace(); | |
} | |
//if page found position and current item position are equals | |
if (position == getCurrentItem()) { | |
//change height of visible page | |
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); | |
int h = child.getMeasuredHeight(); | |
if (h > height) height = h; | |
break; | |
} | |
} | |
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); | |
} | |
// super has to be called again so the new specs are treated as exact measurements | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
} | |
@Override | |
public boolean onInterceptTouchEvent(MotionEvent ev) { | |
if (!isSwipable) { | |
//handle custom events | |
if (customSwipePagerListener != null) { | |
int swipeDirection = swipeDirection(ev); | |
if (swipeDirection == CUSTOM_EVENT_SWIPE_LEFT) { | |
isCustomSwipeDone = true; | |
customSwipePagerListener.onPrev(); | |
} else if (swipeDirection == CUSTOM_EVENT_SWIPE_RIGHT) { | |
isCustomSwipeDone = true; | |
customSwipePagerListener.onNext(); | |
} | |
} | |
return false; | |
} else { | |
return super.onInterceptTouchEvent(ev); | |
} | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent ev) { | |
if (!isSwipable) { | |
return false; | |
} else { | |
return super.onTouchEvent(ev); | |
} | |
} | |
public void onChangePagerIndex() { | |
//it lets to optimize a height of viewpager | |
requestLayout(); | |
} | |
public void setSwipable(boolean isSwipable) { | |
this.isSwipable = isSwipable; | |
requestLayout(); | |
} | |
public void setHeightMatchParent(boolean b) { | |
this.isHeightMatchParent = b; | |
requestLayout(); | |
} | |
public void setCustomSwipeListener(CustomSwipePagerListener listener) { | |
customSwipePagerListener = listener; | |
} | |
private int swipeDirection(MotionEvent event) { | |
switch (event.getAction()) { | |
case MotionEvent.ACTION_DOWN: | |
isCustomSwipeDone = false; | |
motionEventStartX = event.getX(); | |
return 0; | |
case MotionEvent.ACTION_MOVE: | |
case MotionEvent.ACTION_UP: | |
//prevent callbacks doubling | |
if (isCustomSwipeDone) return 0; | |
float deltaX = motionEventStartX - event.getX(); | |
if (deltaX > 0 && deltaX > CUSTOM_SWIPE_MIN_DISTANCE) { | |
return CUSTOM_EVENT_SWIPE_RIGHT; | |
} else if (deltaX < 0 && -deltaX > CUSTOM_SWIPE_MIN_DISTANCE) { | |
return CUSTOM_EVENT_SWIPE_LEFT; | |
} else | |
return 0; | |
default: | |
return 0; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
viewPager.setAdapter(adapter); | |
// fit to parent view height | |
viewPager.setHeightMatchParent(true); | |
viewPager.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); | |
viewPager.setSwipable(false); | |
viewPager.setCustomSwipeListener(new HeightWrappingViewPager.CustomSwipePagerListener() { | |
@Override | |
public void onNext() { | |
//do something | |
} | |
@Override | |
public void onPrev() { | |
//do something | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment