com.astuetz.viewpager.extensions.SwipeyTabsView.java Source code

Java tutorial

Introduction

Here is the source code for com.astuetz.viewpager.extensions.SwipeyTabsView.java

Source

/*
 * Copyright (C) 2011 Andreas Stuetz <andreas.stuetz@gmail.com>
 *
 * 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 com.astuetz.viewpager.extensions;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.RelativeLayout;

import java.util.ArrayList;

public class SwipeyTabsView extends RelativeLayout implements OnPageChangeListener, OnTouchListener {

    @SuppressWarnings("unused")
    private static final String TAG = "com.astuetz.viewpager.extensions";
    // Length of the horizontal fading edges
    private static final int SHADOW_WIDTH = 20;
    private int mPosition;

    // This ArrayList stores the positions for each tab.
    private ArrayList<TabPosition> mPositions = new ArrayList<TabPosition>();
    private ViewPager mPager;
    private TabsAdapter mAdapter;
    private int mTabsCount = 0;
    private int mWidth = 0;
    private int mCenter = 0;
    private int mHighlightOffset = 0;
    // store measure specs for manual re-measuring after
    // data has changed
    private int mWidthMeasureSpec = 0;
    private int mHeightMeasureSpec = 0;
    // The offset at which tabs are going to
    // be moved, if they are outside the screen
    private int mOutsideOffset = -1;
    private float mDragX = 0.0f;

    public SwipeyTabsView(Context context) {
        this(context, null);
    }

    public SwipeyTabsView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SwipeyTabsView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerExtensions, defStyle, 0);

        mOutsideOffset = (int) a.getDimension(R.styleable.ViewPagerExtensions_outsideOffset, -1);

        a.recycle();

        setHorizontalFadingEdgeEnabled(true);
        setFadingEdgeLength((int) (getResources().getDisplayMetrics().density * SHADOW_WIDTH));
        setWillNotDraw(false);

        setOnTouchListener(this);
    }

    @Override
    protected float getLeftFadingEdgeStrength() {
        return 1.0f;
    }

    @Override
    protected float getRightFadingEdgeStrength() {
        return 1.0f;
    }

    /**
     * Notify the view that new data is available.
     */
    public void notifyDatasetChanged() {
        if (mPager != null && mAdapter != null) {
            initTabs();
            measure(mWidthMeasureSpec, mHeightMeasureSpec);
            calculateNewPositions(true);
        }
    }

    public void setAdapter(TabsAdapter adapter) {
        this.mAdapter = adapter;

        if (mPager != null && mAdapter != null)
            initTabs();
    }

    /**
     * Binds the {@link ViewPager} to this instance
     *
     * @param pager An instance of {@link ViewPager}
     */
    public void setViewPager(ViewPager pager) {
        this.mPager = pager;
        mPager.setOnPageChangeListener(this);

        if (mPager != null && mAdapter != null)
            initTabs();
    }

    /**
     * Initialize and add all tabs to the Layout
     */
    private void initTabs() {

        // Remove all old child views
        removeAllViews();

        mPositions.clear();

        if (mAdapter == null || mPager == null)
            return;

        for (int i = 0; i < mPager.getAdapter().getCount(); i++) {
            addTab(mAdapter.getView(i), i);
            mPositions.add(new TabPosition());
        }

        mTabsCount = getChildCount();

        mPosition = mPager.getCurrentItem();
    }

    /**
     * Adds a new {@link SwipeyTabButton} to the layout
     *
     * @param index The index from the Pagers adapter
     * @param title The title which should be used
     */
    public void addTab(View tab, final int index) {
        if (tab == null)
            return;

        addView(tab);

        tab.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mPager.setCurrentItem(index);
            }
        });

        tab.setOnTouchListener(this);
    }

    /**
     *
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        // Set a default outsideOffset
        if (mOutsideOffset < 0)
            mOutsideOffset = w;

        mWidth = w;
        mCenter = w / 2;
        mHighlightOffset = w / 5;

        if (mPager != null)
            mPosition = mPager.getCurrentItem();
        calculateNewPositions(true);

    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int maxTabHeight = 0;

        mWidthMeasureSpec = widthMeasureSpec;
        mHeightMeasureSpec = heightMeasureSpec;

        final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec),
                MeasureSpec.AT_MOST);
        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec),
                MeasureSpec.AT_MOST);

        for (int i = 0; i < mTabsCount; i++) {
            final View child = getChildAt(i);

            if (child.getVisibility() == GONE)
                continue;

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            mPositions.get(i).width = child.getMeasuredWidth();
            mPositions.get(i).height = child.getMeasuredHeight();

            maxTabHeight = Math.max(maxTabHeight, mPositions.get(i).height);
        }

        setMeasuredDimension(resolveSize(0, widthMeasureSpec),
                resolveSize(maxTabHeight + getPaddingTop() + getPaddingBottom(), heightMeasureSpec));

    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        final int paddingTop = getPaddingTop();

        for (int i = 0; i < mTabsCount; i++) {

            final View tab = getChildAt(i);
            TabPosition pos = mPositions.get(i);

            if (tab instanceof SwipeyTab) {

                final int tabCenter = mPositions.get(i).currentPos + tab.getMeasuredWidth() / 2;
                final int diff = Math.abs(mCenter - tabCenter);
                final int p = (int) 100 * diff / mHighlightOffset;

                ((SwipeyTab) tab).setHighlightPercentage(diff <= mHighlightOffset ? 100 - p : 0);

            }

            tab.layout(pos.currentPos, paddingTop, pos.currentPos + pos.width, paddingTop + pos.height);

        }

    }

    /**
     * This method calculates the previous, current and next position for each
     * tab
     * <p/>
     * -5 -4 -3 /-2 |-1 0 +1| +2\ +3 +4 +5
     * <p/>
     * There are the following cases:
     * <p/>
     * [1] -5 to -3 are outside the screen
     * [2] -2 is outside the screen, may come into the screen when swiping right
     * [3] -1 is inside the screen, aligned at the left
     * [4] 0 is inside the screen, aligned at the center
     * [5] +1 is inside the screen, aligned at the right
     * [6] +2 is outside the screen, may come into the screen when swiping left
     * [7] +3 to +5 are outside the screen
     *
     * @param layout If true, all tabs will be aligned at their initial position
     */
    private void calculateNewPositions(boolean layout) {

        if (mTabsCount == 0)
            return;

        final int currentItem = mPosition;

        for (int i = 0; i < mTabsCount; i++) {

            if (i < currentItem - 2)
                alignLeftOutside(i, false);
            else if (i == currentItem - 2)
                alignLeftOutside(i, true);
            else if (i == currentItem - 1)
                alignLeft(i);
            else if (i == currentItem)
                alignCenter(i);
            else if (i == currentItem + 1)
                alignRight(i);
            else if (i == currentItem + 2)
                alignRightOutside(i, true);
            else if (i > currentItem + 2)
                alignRightOutside(i, false);

        }

        preventFromOverlapping();

        if (layout) {
            for (TabPosition p : mPositions) {
                p.currentPos = p.oldPos;
            }
            requestLayout();
        }

    }

    private int leftOutside(int position) {
        View tab = getChildAt(position);
        final int width = tab.getMeasuredWidth();
        return width * (-1) - mOutsideOffset;
    }

    private int left(int position) {
        View tab = getChildAt(position);
        return 0 - tab.getPaddingLeft();
    }

    private int center(int position) {
        View tab = getChildAt(position);
        final int width = tab.getMeasuredWidth();
        return mWidth / 2 - width / 2;
    }

    private int right(int position) {
        View tab = getChildAt(position);
        final int width = tab.getMeasuredWidth();
        return mWidth - width + tab.getPaddingRight();
    }

    private int rightOutside(int position) {
        return mWidth + mOutsideOffset;
    }

    private void alignLeftOutside(int position, boolean canComeToLeft) {
        TabPosition pos = mPositions.get(position);

        pos.oldPos = leftOutside(position);
        pos.leftPos = pos.oldPos;
        pos.rightPos = canComeToLeft ? left(position) : pos.oldPos;
    }

    private void alignLeft(int position) {
        TabPosition pos = mPositions.get(position);

        pos.leftPos = leftOutside(position);
        pos.oldPos = left(position);
        pos.rightPos = center(position);
    }

    private void alignCenter(int position) {
        TabPosition pos = mPositions.get(position);

        pos.leftPos = left(position);
        pos.oldPos = center(position);
        pos.rightPos = right(position);
    }

    private void alignRight(int position) {
        TabPosition pos = mPositions.get(position);

        pos.leftPos = center(position);
        pos.oldPos = right(position);
        pos.rightPos = rightOutside(position);
    }

    private void alignRightOutside(int position, boolean canComeToRight) {
        TabPosition pos = mPositions.get(position);

        pos.oldPos = rightOutside(position);
        pos.rightPos = pos.oldPos;
        pos.leftPos = canComeToRight ? right(position) : pos.oldPos;
    }

    /**
     *
     */
    private void preventFromOverlapping() {

        final int currentItem = mPosition;

        TabPosition leftOutside = currentItem > 1 ? mPositions.get(currentItem - 2) : null;
        TabPosition left = currentItem > 0 ? mPositions.get(currentItem - 1) : null;
        TabPosition center = mPositions.get(currentItem);
        TabPosition right = currentItem < mTabsCount - 1 ? mPositions.get(currentItem + 1) : null;
        TabPosition rightOutside = currentItem < mTabsCount - 2 ? mPositions.get(currentItem + 2) : null;

        if (leftOutside != null) {
            if (leftOutside.rightPos + leftOutside.width >= left.rightPos) {
                leftOutside.rightPos = left.rightPos - leftOutside.width;
            }
        }

        if (left != null) {
            if (left.oldPos + left.width >= center.oldPos) {
                left.oldPos = center.oldPos - left.width;
            }
            if (center.rightPos <= left.rightPos + left.width) {
                center.rightPos = left.rightPos + left.width;
            }
        }

        if (right != null) {
            if (right.oldPos <= center.oldPos + center.width) {
                right.oldPos = center.oldPos + center.width;
            }
            if (center.leftPos + center.width >= right.leftPos) {
                center.leftPos = right.leftPos - center.width;
            }
        }

        if (rightOutside != null) {
            if (rightOutside.leftPos <= right.leftPos + right.width) {
                rightOutside.leftPos = right.leftPos + right.width;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onPageScrollStateChanged(int state) {
    }

    /**
     * At this point the scrolling direction is determined and every child is
     * interpolated to its previous or next position
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

        Direction dir = Direction.None;

        if (position != mPosition) {
            mPosition = position;
            calculateNewPositions(true);
        }

        final int currentScrollX = mPosition * (mPager.getWidth() + mPager.getPageMargin());

        // Check if the user is swiping to the left or to the right

        if (mPager.getScrollX() < currentScrollX)
            dir = Direction.Left;
        else if (mPager.getScrollX() > currentScrollX)
            dir = Direction.Right;

        float x = 0.0f;
        if (dir == Direction.Left)
            x = 1 - positionOffset;
        else if (dir == Direction.Right)
            x = positionOffset;

        // Iterate over all tabs and set their current positions

        for (int i = 0; i < mTabsCount; i++) {
            TabPosition pos = mPositions.get(i);

            final float y0 = pos.oldPos;
            float y1 = 0.0f;

            if (dir == Direction.Left)
                y1 = pos.rightPos;
            else if (dir == Direction.Right)
                y1 = pos.leftPos;
            else
                y1 = pos.oldPos;

            if (y1 != y0)
                pos.currentPos = (int) (y0 + (y1 * x - y0 * x));
        }

        requestLayout();

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onPageSelected(int position) {
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        float x = event.getRawX();

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mDragX = x;
            mPager.beginFakeDrag();
            break;
        case MotionEvent.ACTION_MOVE:
            if (!mPager.isFakeDragging())
                break;
            mPager.fakeDragBy((mDragX - x) * (-1));
            mDragX = x;
            break;
        case MotionEvent.ACTION_UP:
            if (!mPager.isFakeDragging())
                break;
            mPager.endFakeDrag();
            break;
        }

        return v.equals(this) ? true : super.onTouchEvent(event);
    }

    // Scrolling direction
    private enum Direction {
        None, Left, Right
    }

    /**
     * Helper class which holds different positions (and the width) for a tab
     */
    private class TabPosition {

        public int oldPos;

        public int leftPos;
        public int rightPos;

        public int currentPos;

        public int width;
        public int height;

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();

            sb.append("oldPos: ").append(oldPos).append(", ");
            sb.append("leftPos: ").append(leftPos).append(", ");
            sb.append("rightPos: ").append(rightPos).append(", ");
            sb.append("currentPos: ").append(currentPos);

            return sb.toString();
        }
    }

}