Java tutorial
/* * Copyright (C) 2013 Martin Hochstrasser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ch.bretscherhochstrasser.android.stickyviewpager; import android.content.Context; import android.content.res.TypedArray; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.View; import java.util.HashSet; import java.util.Set; /** * Simple extension to {@link ViewPager} that allows the protection of paged * views against {@link ViewPager}'s default horizontal swipe detection and * gesture handling by declaring them "sticky". * <p/> * The {@link StickyViewPager} allows for placing of any views that require * touch input(e.g. map fragment) within its paged views and selectively * overriding {@link ViewPager}'s default swipe detection behavior by declaring * the view's position as sticky. The positions that should be treated sticky * can be set through {@link #addStickyPosition(int)} or declared through XML. * <p/> * Swipe gestures for navigation on sticky positions are only accepted when * initiated within a certain margin on the left and right of the pager's inner * edge. The with of the margin can either be set through * {@link #setSwipeMarginWidth(int)} or declared as XML attribute. The default * margin width is 40dip. * <p/> * Other view pager positions, not declared as sticky, are handled by * {@link ViewPager}'s default swipe detection. * * @author Martin Hochstrasser * @attr ref R.styleable.StickyViewPager_stickyPositions * @attr ref R.styleable.StickyViewPager_swipeMarginWidth */ public class StickyViewPager extends ViewPager { /** * Default swipe margin width: 40dip */ private static final int DEFAULT_SWIPE_MARGIN_WIDTH_DIP = 40; private static final String TAG = "StickyViewPager"; private OnPageChangeListener internalOnPageChangeListener; private int currentPosition; private final Set<Integer> stickyPositions = new HashSet<>(); private int swipeMarginWidth; /** * Constructor without XML attributes. * * @param context the activity context */ public StickyViewPager(final Context context) { super(context); setDefaultSwipeMargin(context); wireUpInternalListener(); } /** * Constructor with XML attributes. * * @param context the activity context * @param attrs the attributes to set */ public StickyViewPager(final Context context, final AttributeSet attrs) { super(context, attrs); setDefaultSwipeMargin(context); wireUpInternalListener(); readAttributes(context, attrs); } private void setDefaultSwipeMargin(final Context context) { swipeMarginWidth = (int) (DEFAULT_SWIPE_MARGIN_WIDTH_DIP * context.getResources().getDisplayMetrics().density); } private void wireUpInternalListener() { // We add our own OnPageChangeListener to know what page we're on internalOnPageChangeListener = new OnPageChangeListener() { @Override public void onPageSelected(final int position) { currentPosition = position; } @Override public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { // nothing to do } @Override public void onPageScrollStateChanged(final int state) { // nothing to do } }; addOnPageChangeListener(internalOnPageChangeListener); } private void readAttributes(final Context context, final AttributeSet attrs) { final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.StickyViewPager); for (int i = 0; i < styledAttributes.getIndexCount(); ++i) { final int attr = styledAttributes.getIndex(i); if (attr == R.styleable.StickyViewPager_swipeMarginWidth) { swipeMarginWidth = styledAttributes.getDimensionPixelSize(attr, swipeMarginWidth); } else if (attr == R.styleable.StickyViewPager_stickyPositions) { addStickyPositions(styledAttributes.getString(attr)); } } styledAttributes.recycle(); } private void addStickyPositions(final String positionList) { if ((positionList != null) && (positionList.length() != 0)) { final String[] rawPositions = positionList.split(","); for (final String rawPosition : rawPositions) { try { addStickyPosition(Integer.parseInt(rawPosition.trim())); } catch (final NumberFormatException e) { Log.w(TAG, String.format("Cannot parse sticky position value from '%s'", rawPosition)); } } } } @Override protected boolean canScroll(final View v, final boolean checkV, final int dx, final int x, final int y) { // handle here if current position is declared sticky and must be // protected, otherwise let ViewPager handle it normally. if (stickyPositions.contains(currentPosition)) { return !isAllowedSwipe(x, dx); } return super.canScroll(v, checkV, dx, x, y); } /** * Determines if the pointer movement event at x and moved pixels is * considered an allowed swipe movement overriding the inner horizontal * scroll content protection. * * @param x X coordinate of the active touch point * @param dx Delta scrolled in pixels * @return true if the movement should start a page swipe */ protected boolean isAllowedSwipe(final float x, final float dx) { return ((x < swipeMarginWidth) && (dx > 0)) || ((x > (getWidth() - swipeMarginWidth)) && (dx < 0)); } @Override public void clearOnPageChangeListeners() { super.clearOnPageChangeListeners(); // we want to keep our internal page change listener, so we add it again addOnPageChangeListener(internalOnPageChangeListener); } /** * Returns the width of the left and right margin that allows swipe input. * * @return the width in pixels */ public int getSwipeMarginWidth() { return swipeMarginWidth; } /** * Sets the width of the left and right margin that allows swipe input. * * @param width the width in pixels */ public void setSwipeMarginWidth(final int width) { swipeMarginWidth = width; } /** * Adds the position to the set of sticky positions. * * @param position the position to be sticky */ public void addStickyPosition(final int position) { stickyPositions.add(position); } /** * Removes the position from the set of sticky positions. * * @param position the position to no longer be sticky */ public void removeStickyPosition(final int position) { stickyPositions.remove(position); } }