Skip to content

Instantly share code, notes, and snippets.

@dmitriy-chernysh
Last active June 25, 2020 15:09
Show Gist options
  • Save dmitriy-chernysh/a3904813ba2d303e43f59bc520d1efc3 to your computer and use it in GitHub Desktop.
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
/**
* 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;
}
}
}
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