<|start_filename|>TextViewPagerExample/src/co/paulburke/android/textviewpagerexample/ColumnsActivity.java<|end_filename|>
/*
 * Copyright (C) 2013 <NAME>
 *
 * 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 co.paulburke.android.textviewpagerexample;

import android.app.Activity;
import android.os.Bundle;

import co.paulburke.android.textviewpager.ColumnedTextView;

public class ColumnsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ColumnedTextView view = new ColumnedTextView(this);
        view.setNumColumns(getResources().getInteger(R.integer.num_columns));
        view.setText(R.string.lipsum);
        setContentView(view);
    }
}


<|start_filename|>TextViewPager/src/co/paulburke/android/textviewpager/TextViewPagerIndicator.java<|end_filename|>
/*
 * Copyright (C) 2012 <NAME>
 *
 * 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 co.paulburke.android.textviewpager;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.DirectionalViewPager;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * Customized UnderlinePageIndicator from the ViewPageIndicator library that
 * emulates a scroll bar based on the orientation of the
 * {@link DirectionalViewPager} it's connected to.
 *
 * @author <NAME>
 * @author paulburke (ipaulpro) Modified to support {@link DirectionalViewPager}
 *         and better resemble a scroll bar.
 */
public class TextViewPagerIndicator extends View implements ViewPager.OnPageChangeListener {
    private static final String TAG = "TextViewPagerIndicator";

    private static final int FADE_FRAME_MS = 30;

    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private boolean mFades;
    private int mFadeDelay;
    private int mFadeLength;
    private int mFadeBy;

    private DirectionalViewPager mViewPager;
    private DirectionalViewPager.OnPageChangeListener mListener;
    private int mScrollState;
    private int mCurrentPage;
    private float mPositionOffset;

    private final Runnable mFadeRunnable = new Runnable() {
        @Override
        public void run() {
            if (!mFades)
                return;

            final int alpha = Math.max(mPaint.getAlpha() - mFadeBy, 0);
            mPaint.setAlpha(alpha);
            setBackgroundAlpha(alpha);
            invalidate();
            if (alpha > 0) {
                postDelayed(this, FADE_FRAME_MS);
            }
        }
    };

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

    public TextViewPagerIndicator(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.scrollPageIndicatorStyle);
    }

    public TextViewPagerIndicator(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        if (isInEditMode())
            return;

        final Resources res = getResources();

        // Load defaults from resources
        final boolean defaultFades = res.getBoolean(R.bool.scroll_indicator_fades);
        final int defaultFadeDelay = res.getInteger(R.integer.scroll_indicator_fade_delay);
        final int defaultFadeLength = res.getInteger(R.integer.scroll_indicator_fade_length);
        final int defaultSelectedColor = res.getColor(R.color.scroll_indicator_selected_color);

        // Retrieve styles attributes
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextViewPagerIndicator,
                defStyle, 0);

        setFades(a.getBoolean(R.styleable.TextViewPagerIndicator_fades, defaultFades));
        setSelectedColor(a.getColor(R.styleable.TextViewPagerIndicator_selectedColor,
                defaultSelectedColor));
        setFadeDelay(a.getInteger(R.styleable.TextViewPagerIndicator_fadeDelay, defaultFadeDelay));
        setFadeLength(a
                .getInteger(R.styleable.TextViewPagerIndicator_fadeLength, defaultFadeLength));

        Drawable background = a.getDrawable(R.styleable.TextViewPagerIndicator_android_background);
        if (background != null)
            setBackgroundDrawable(background);

        a.recycle();
    }

    public boolean getFades() {
        return mFades;
    }

    public void setFades(boolean fades) {
        if (fades != mFades) {
            mFades = fades;
            if (fades) {
                post(mFadeRunnable);
            } else {
                removeCallbacks(mFadeRunnable);
                mPaint.setAlpha(0xFF);
                setBackgroundAlpha(0xFF);
                invalidate();
            }
        }
    }

    public int getFadeDelay() {
        return mFadeDelay;
    }

    public void setFadeDelay(int fadeDelay) {
        mFadeDelay = fadeDelay;
    }

    public int getFadeLength() {
        return mFadeLength;
    }

    public void setFadeLength(int fadeLength) {
        mFadeLength = fadeLength;
        mFadeBy = 0xFF / (mFadeLength / FADE_FRAME_MS);
    }

    public int getSelectedColor() {
        return mPaint.getColor();
    }

    public void setSelectedColor(int selectedColor) {
        mPaint.setColor(selectedColor);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mViewPager == null)
            return;

        final int count = mViewPager.getAdapter().getCount();
        if (count == 0)
            return;

        if (mCurrentPage >= count) {
            setCurrentItem(count - 1);
            return;
        }

        float left = 0f, top = 0f, right = 0f, bottom = 0f;

        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        final int paddingRight = getPaddingRight();
        final int paddingLeft = getPaddingLeft();

        if (isHorizontal()) {
            final float pageWidth = (getWidth() - paddingLeft - paddingRight) / (1f * count);
            left = paddingLeft + pageWidth * (mCurrentPage + mPositionOffset);
            right = left + pageWidth;
            top = 0f;
            bottom = getHeight();
        }
        else if (isVertical()) {
            final float pageHeight = (getHeight() - paddingTop - paddingBottom) / (1f * count);
            top = paddingTop + pageHeight * (mCurrentPage + mPositionOffset);
            bottom = top + pageHeight;
            left = 0f;
            right = getWidth();
        }

        canvas.drawRect(left, top, right, bottom, mPaint);
    }

    private boolean isHorizontal() {
        return mViewPager.getOrientation() == DirectionalViewPager.HORIZONTAL;
    }

    private boolean isVertical() {
        return mViewPager.getOrientation() == DirectionalViewPager.VERTICAL;
    }

    public void setViewPager(ViewPager viewPager) {
        if (mViewPager == viewPager) {
            return;
        }
        if (mViewPager != null) {
            // Clear us from the old pager.
            mViewPager.setOnPageChangeListener(null);
        }
        if (viewPager.getAdapter() == null) {
            throw new IllegalStateException("ViewPager does not have adapter instance.");
        }
        mViewPager = (DirectionalViewPager) viewPager;
        mViewPager.setOnPageChangeListener(this);
        mViewPager.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    show();
                }
                return false;
            }
        });
        invalidate();
        post(new Runnable() {
            @Override
            public void run() {
                if (mFades) {
                    post(mFadeRunnable);
                }
            }
        });
    }

    public void show() {
        removeCallbacks(mFadeRunnable);
        mPaint.setAlpha(0xFF);
        setBackgroundAlpha(0xFF);
        postDelayed(mFadeRunnable, mFadeDelay);
    }

    public void hide() {
        removeCallbacks(mFadeRunnable);
        post(mFadeRunnable);
    }

    public void setViewPager(ViewPager view, int initialPosition) {
        setViewPager(view);
        setCurrentItem(initialPosition);
    }

    public void setCurrentItem(int item) {
        if (mViewPager == null) {
            throw new IllegalStateException("ViewPager has not been bound.");
        }
        mViewPager.setCurrentItem(item);
        mCurrentPage = item;
        invalidate();
    }

    public void notifyDataSetChanged() {
        invalidate();
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        mScrollState = state;

        if (mListener != null) {
            mListener.onPageScrollStateChanged(state);
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        mCurrentPage = position;
        mPositionOffset = positionOffset;
        if (mFades) {
            if (positionOffsetPixels > 0) {
                removeCallbacks(mFadeRunnable);
                mPaint.setAlpha(0xFF);
                setBackgroundAlpha(0xFF);
            } else if (mScrollState != ViewPager.SCROLL_STATE_DRAGGING) {
                postDelayed(mFadeRunnable, mFadeDelay);
            }
        }
        invalidate();

        if (mListener != null) {
            mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
        }
    }

    @Override
    public void onPageSelected(int position) {
        if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
            mCurrentPage = position;
            mPositionOffset = 0;
            invalidate();
            mFadeRunnable.run();
        }
        if (mListener != null) {
            mListener.onPageSelected(position);
        }
    }

    public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
        mListener = listener;
    }

    private void setBackgroundAlpha(int alpha) {
        Drawable background = getBackground();
        if (background != null)
            background.setAlpha(alpha);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        mCurrentPage = savedState.currentPage;
        requestLayout();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState savedState = new SavedState(superState);
        savedState.currentPage = mCurrentPage;
        return savedState;
    }

    static class SavedState extends BaseSavedState {
        int currentPage;

        public SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            currentPage = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(currentPage);
        }

        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}


<|start_filename|>TextViewPager/src/co/paulburke/android/textviewpager/TextViewUtils.java<|end_filename|>
/*
 * Copyright (C) 2013 <NAME>
 *
 * 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 co.paulburke.android.textviewpager;

import android.graphics.Paint;
import android.text.TextUtils;
import android.widget.TextView;

import java.util.ArrayList;

/**
 * Experimental
 */
public class TextViewUtils {

    private static final String TAG = "TextViewUtils";

    private TextViewUtils() {
    }

    /**
     * Not ready for prime time.
     */
    public static String getJustifiedText(CharSequence text, TextView view) {
        ArrayList<String> lineList = lineBreak(String.valueOf(text), view.getPaint(),
                view.getLayout().getWidth());

        return TextUtils.join(" ", lineList).replaceFirst("\\s", "");
    }

    /**
     * TODO Respect \n
     */
    private static ArrayList<String> lineBreak(String text, Paint paint, float contentWidth) {
        String[] wordArray = text.split("\\s");
        ArrayList<String> lineList = new ArrayList<String>();
        String myText = "";

        for (String word : wordArray) {
            if (paint.measureText(myText + " " + word) <= contentWidth)
                myText = myText + " " + word;
            else {
                int totalSpacesToInsert = (int) ((contentWidth - paint.measureText(myText)) / paint
                        .measureText(" "));
                lineList.add(justifyLine(myText, totalSpacesToInsert));
                myText = word;
            }
        }
        lineList.add(myText);
        return lineList;
    }

    private static String justifyLine(String text, int totalSpacesToInsert) {
        String[] wordArray = text.split("\\s");
        String toAppend = " ";

        while (totalSpacesToInsert >= wordArray.length - 1 && wordArray.length > 1) {
            toAppend = toAppend + " ";
            totalSpacesToInsert = totalSpacesToInsert - (wordArray.length - 1);
        }
        int i = 0;
        String justifiedText = "";
        for (String word : wordArray) {
            if (i < totalSpacesToInsert)
                justifiedText = justifiedText + word + " " + toAppend;

            else
                justifiedText = justifiedText + word + toAppend;

            i++;
        }

        return justifiedText;
    }
}


<|start_filename|>TextViewPager/src/co/paulburke/android/textviewpager/TextViewPager.java<|end_filename|>
/*
 * Copyright (C) 2013 <NAME>
 *
 * 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 co.paulburke.android.textviewpager;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.DirectionalViewPager;
import android.support.v4.view.PagerAdapter;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

/**
 * A ViewPager that pages the provided text based on the available space. The
 * {@link PagerAdapter} is automatically created, as well as the necessary
 * {@link TextView}s. You can specify the style to be used by overriding the
 * {@link R.attr#textViewPagerStyle} in the Activity Theme, or by specifying a
 * layout to be used with {@link #setTextViewLayout(int)};
 *
 * @see TextViewPagerAdapter
 * @see #setText(CharSequence)
 * @see #setText(int)
 * @author paulburke (ipaulpro)
 */
public class TextViewPager extends DirectionalViewPager {

    private static final String TAG = "TextViewPager";
    private static final boolean DEBUG = false;

    private static final String INSTANCE_STATE = "instanceState";
    private static final String STATE_OFFSET = "stateToSave";

    /**
     * Listener to be notified of when the adapter has measured and created the
     * necessary pages.
     */
    public interface OnPageCreatedListener {
        /**
         * Called when the adapter has measured and created the necessary pages.
         * Calls to {@link PagerAdapter#getCount()} will not be correct until
         * after this fires.
         *
         * @param count the total number of pages created.
         */
        public void onPageCreated(int count);
    }

    private final PagingLayoutListener.OnPageMeasureListener mMeasureListener = new PagingLayoutListener.OnPageMeasureListener() {
        @Override
        public void onPageMeasure(int[] offsets, int totalLines, int linesPerPage) {
            if (DEBUG) Log.i(TAG, "onMeasure pages count = " + offsets.length+", totalLines = "+totalLines+", linesPerPage = "+linesPerPage);

            if (mPagerAdapter != null) {
                mPagerAdapter.setOffsets(offsets);
            }

            if (mRestoredOffset > 0) {
                // Find the restored offset page
                for (int i = 0; i < offsets.length; i++) {
                    if (mRestoredOffset < offsets[i]) {
                        setCurrentItem(i - 1, false);
                        break;
                    } else if (i == offsets.length - 1) {
                        setCurrentItem(i, false);
                    }
                }
            }

            // Let the listener know that new pages were created
            if (mPageCreatedListener != null)
                mPageCreatedListener.onPageCreated(offsets.length);
        }
    };

    private TextViewPagerAdapter mPagerAdapter;
    private OnPageCreatedListener mPageCreatedListener;

    private int mRestoredOffset;

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

    public TextViewPager(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.textViewPagerStyle);
    }

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

        mPagerAdapter = new TextViewPagerAdapter(context, mMeasureListener);

        if (attrs != null) {
            TypedArray attributes = context
                    .obtainStyledAttributes(attrs, R.styleable.TextViewPager);

            int layout = attributes.getResourceId(R.styleable.TextViewPager_textViewLayout, -1);
            CharSequence text = attributes.getText(R.styleable.TextViewPager_android_text);

            mPagerAdapter.setTextViewLayout(layout);
            mPagerAdapter.setText(text);

            attributes.recycle();
        }

        setAdapter(mPagerAdapter);
    }

    /**
     * @param text the text to be paged.
     * @attr {@link R.styleable#TextViewPager_android_text}
     */
    public void setText(CharSequence text) {
        mPagerAdapter.setText(text);
    }

    /**
     * @param resId the resource identifier of the text to be paged.
     * @attr {@link R.styleable#TextViewPager_android_text}
     */
    public void setText(int resId) {
        mPagerAdapter.setText(getContext().getResources().getText(resId));
    }

    /**
     * @param resId the resource identifier of the layout to use for the
     *            TextView
     * @attr {@link R.styleable#TextViewPager_textViewLayout}
     */
    public void setTextViewLayout(int resId) {
        mPagerAdapter.setTextViewLayout(resId);
    }

    /**
     * @return the current {@link OnPageCreatedListener}.
     */
    public OnPageCreatedListener getOnPageCreatedListener() {
        return mPageCreatedListener;
    }

    /**
     * @param mPageCreatedListener the listener to be notified when the pages
     *            are created.
     */
    public void setOnPageCreatedListener(OnPageCreatedListener mPageCreatedListener) {
        this.mPageCreatedListener = mPageCreatedListener;
    }

    @Override
    public Parcelable onSaveInstanceState() {

        Bundle bundle = new Bundle();
        bundle.putParcelable(INSTANCE_STATE, super.onSaveInstanceState());

        // Keep tabs of the current page offset, since the number of lines will
        // change with configurations
        int offset = mPagerAdapter.getOffsetForPosition(getCurrentItem());
        bundle.putInt(STATE_OFFSET, offset);

        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {

        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            state = bundle.getParcelable(INSTANCE_STATE);
            mRestoredOffset = bundle.getInt(STATE_OFFSET);
        }

        super.onRestoreInstanceState(state);
    }
}


<|start_filename|>TextViewPager/src/co/paulburke/android/textviewpager/ColumnedTextView.java<|end_filename|>
/*
 * Copyright (C) 2013 <NAME>
 *
 * 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 co.paulburke.android.textviewpager;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * Experimental layout that resembles newspaper columns.
 *
 * @author paulburke (ipaulpro)
 */
public class ColumnedTextView extends LinearLayout {

    protected static final String TAG = "ColumnedTextView";

    private static final String XML_NS = "http://schemas.android.com/apk/res/android";

    public interface OnColumnMeasureListener {
        public void onColumnMeasure(int[] offsets, int totalColumns, int columnsPerPage);
    }

    private static int DEFAULT_COLUMN_COUNT = 2;
    private CharSequence mText;
    private int mNumColumns = DEFAULT_COLUMN_COUNT;
    private OnColumnMeasureListener mListener;

    private PagingLayoutListener.OnPageMeasureListener mOnMeasureListener = new PagingLayoutListener.OnPageMeasureListener() {
        @Override
        public void onPageMeasure(int[] offsets, int totalLines, int linesPerPage) {
            Log.i(TAG, "onPageMeasure offsets count = " + offsets.length + ", totalLines = "
                    + totalLines + ", linesPerPage = " + linesPerPage);
            fillViews(offsets);
        }
    };

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

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

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

        if (attrs != null) {
            mNumColumns = attrs.getAttributeIntValue(XML_NS, "numColumns", DEFAULT_COLUMN_COUNT);
            mText = attrs.getAttributeValue(XML_NS, "text");
        }

        if (mText != null) {
            setText(mText);
        }
    }

    public void setText(CharSequence text) {
        addColumns();

        mText = text;
        TextView textView = (TextView) getChildAt(0);
        textView.setText(mText);
    }

    public void setText(int resId) {
        setText(getContext().getText(resId));
    }

    public int getNumColumns() {
        return mNumColumns;
    }

    public void setNumColumns(int numColumns) {
        mNumColumns = numColumns;
        if (mText != null)
            setText(mText);
    }

    public OnColumnMeasureListener getOnColumnMeasureListener() {
        return mListener;
    }

    public void setOnColumnMeasureListener(OnColumnMeasureListener listener) {
        mListener = listener;
    }

    private void addColumns() {
        removeAllViews();

        LayoutParams layoutParams = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
        layoutParams.setMargins(48, 48, 48, 48);

        TextView textView = new TextView(getContext(), null, R.attr.textViewPagerStyle);
        textView.getViewTreeObserver().addOnGlobalLayoutListener(
                new PagingLayoutListener(textView, mOnMeasureListener));
        addView(textView, 0, layoutParams);

        for (int i = 1; i < mNumColumns; i++) {
            addView(new TextView(getContext(), null, R.attr.textViewPagerStyle), i, layoutParams);
        }
    }

    private void fillViews(int[] offsets) {
        int childCount = getChildCount();
        Log.i(TAG, "onMeasure childCount = " + childCount);
        for (int i = 0; i < offsets.length && i < childCount; i++) {
            TextView child = (TextView) getChildAt(i);
            int start = offsets[i];
            int end = (i < offsets.length - 1) ? offsets[i + 1] : mText.length();
            Log.i(TAG, "onMeasure child " + i + " start = " + start + " end  =" + end);
            String text = TextViewUtils.getJustifiedText(mText.subSequence(start, end), child);
            child.setText(text);
        }
    }

}


<|start_filename|>TextViewPagerExample/src/co/paulburke/android/textviewpagerexample/BasicHorizontalActivity.java<|end_filename|>

package co.paulburke.android.textviewpagerexample;

import android.app.Activity;
import android.os.Bundle;

import co.paulburke.android.textviewpager.TextViewPager;

public class BasicHorizontalActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextViewPager pager = new TextViewPager(this);
        // Needed for restoring state
        pager.setId(R.id.pager);
        // Set the text on the pager, itself
        pager.setText(R.string.lipsum);

        setContentView(pager);
    }
}


<|start_filename|>TextViewPagerExample/src/co/paulburke/android/textviewpagerexample/StyledVerticalActivity.java<|end_filename|>
/*
 * Copyright (C) 2013 <NAME>
 *
 * 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 co.paulburke.android.textviewpagerexample;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.ViewPager.SimpleOnPageChangeListener;
import android.widget.TextView;

import co.paulburke.android.textviewpager.TextViewPager;
import co.paulburke.android.textviewpager.TextViewPager.OnPageCreatedListener;
import co.paulburke.android.textviewpager.TextViewPagerIndicator;

public class StyledVerticalActivity extends Activity {

    private TextView mNumber;
    private TextViewPager mPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_styled_vertical);

        mNumber = (TextView) findViewById(R.id.page_number);

        mPager = (TextViewPager) findViewById(R.id.pager);
        // We don't know how many pages there are until we've measured
        mPager.setOnPageCreatedListener(new OnPageCreatedListener() {
            @Override
            public void onPageCreated(int count) {
                setPageNumber(mPager.getCurrentItem());
            }
        });

        final TextViewPagerIndicator indicator = (TextViewPagerIndicator) findViewById(R.id.indicator);
        indicator.setViewPager(mPager);
        // If not using an indicator, you'd set this on the TextViewPager itself
        indicator.setOnPageChangeListener(new SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                setPageNumber(position);
            }
        });
    }

    private void setPageNumber(int position) {
        mNumber.setText(getString(R.string.page_number, position + 1,
                mPager.getAdapter().getCount()));
    }
}


<|start_filename|>TextViewPager/src/co/paulburke/android/textviewpager/PagingLayoutListener.java<|end_filename|>
/*
 * Copyright (C) 2013 <NAME>
 *
 * 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 co.paulburke.android.textviewpager;

import android.text.Layout;
import android.util.Log;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.TextView;

/**
 * An {@link OnGlobalLayoutListener} that determines how many "pages" the
 * current text should be split into by calculating the maximum number of lines
 * that the view can display without clipping.<br>
 * <br>
 * Set an {@link OnPageMeasureListener} to be notified of the character offsets
 * calculated for each page.
 *
 * @author paulburke (ipaulpro)
 */
public class PagingLayoutListener implements OnGlobalLayoutListener {

    private static final String TAG = "PagingLayoutListener";
    private static final boolean DEBUG = true;

    /**
     * Listener used to notify of a completed layout measurement.
     */
    public interface OnPageMeasureListener {
        /**
         * Called when the character offsets have been determined.
         *
         * @param offsets character offsets for the current text.
         * @param totalLines total number of lines in the layout.
         * @param linesPerPage number of lines that fit on this view without
         *            clipping.
         */
        public void onPageMeasure(int[] offsets, int totalLines, int linesPerPage);
    }

    private TextView mView;
    private OnPageMeasureListener mListener;

    /**
     * Creates a new PreDrawListener for the given view.
     *
     * @param view the view to intercept drawing.
     * @param listener the {@link OnPageMeasureListener} to listen for offset
     *            calculations.
     */
    public PagingLayoutListener(TextView view, OnPageMeasureListener listener) {
        mView = view;
        mListener = listener;
    }

    @Override
    public void onGlobalLayout() {
        final Layout layout = mView.getLayout();
        if (layout != null) {
            final int height = mView.getHeight() - mView.getPaddingTop()
                    - mView.getPaddingBottom();
            final int lineCount = layout.getLineCount();

            // Last visible line
            int lastLine = layout.getLineForVertical(height);
            // Bottom of the last visible line
            int lastLineBottom = layout.getLineBottom(lastLine);

            // Check if the layout is taller than the page
            if (lineCount > 0 && lastLineBottom > height) {
                // Determine the number of pages needed
                int pagesCount = (int) Math.ceil(lineCount / (double) lastLine);
                if (DEBUG) Log.d(TAG, "onPreDraw text is too tall! Should be "+pagesCount+" pages.");

                // Determine offsets for each page
                int[] offsets = new int[pagesCount];
                for (int i = 0; i < pagesCount; i++) {
                    offsets[i] = layout.getLineStart(i * lastLine);
                    if (DEBUG) Log.d(TAG, "onPreDraw new page at " + i + ", starting char offset = " + offsets[i]);
                }

                // Clip the text in this view now
                CharSequence text = layout.getText().subSequence(0, offsets[1]);
                mView.setText(text);

                if (mListener != null)
                    mListener.onPageMeasure(offsets, lineCount, lastLine);
            }
        }
    }

}


<|start_filename|>TextViewPager/src/co/paulburke/android/textviewpager/TextViewPagerAdapter.java<|end_filename|>
/*
 * Copyright (C) 2013 <NAME>
 *
 * 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 co.paulburke.android.textviewpager;

import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
 * A {@link PagerAdapter} that creates pages of text based on the available
 * space for each view.
 *
 * @see PagingLayoutListener
 * @author paulburke (ipaulpro)
 */
public class TextViewPagerAdapter extends PagerAdapter {

    private static final String TAG = "TextViewPagerAdapter";
    static final boolean DEBUG = false;

    private final Context mContext;
    private final PagingLayoutListener.OnPageMeasureListener mMeasureListener;

    private int[] mOffsets = new int[] {};

    private CharSequence mText;
    private int mLayoutRes = -1;
    private int mCount = 1;

    LayoutInflater mInflater;

    public TextViewPagerAdapter(Context context, PagingLayoutListener.OnPageMeasureListener listener) {
        this(context, listener, null);
    }

    public TextViewPagerAdapter(Context context,
            PagingLayoutListener.OnPageMeasureListener listener, CharSequence text) {
        mContext = context;
        mMeasureListener = listener;
        mText = text;

        mInflater = LayoutInflater.from(mContext);
    }

    @Override
    public TextView instantiateItem(final ViewGroup container, final int position) {
        TextView view = null;

        if (mLayoutRes > 0)
            view = (TextView) mInflater.inflate(mLayoutRes, container, false);
        else
            view = new TextView(mContext, null, R.attr.textViewPagerStyle);

        if (mText != null) {
            int offset = 0;
            int end = mText.length();
            int size = mOffsets.length;

            if (size == 0) {
                // Add the OnGlobalLayoutListener, which measures the text and
                // reports paged character offsets if the text layout is taller
                // than the container.
                PagingLayoutListener listener = new PagingLayoutListener(view, mMeasureListener);
                view.getViewTreeObserver().addOnGlobalLayoutListener(listener);

            } else {

                offset = mOffsets[position];
                int lastPage = size - 1;

                // Don't consider the last page measured, in case there is more
                // text to be displayed.
                if (position < lastPage)
                    end = mOffsets[position + 1];
            }

            final CharSequence sub = mText.subSequence(offset, end);
            view.setText(sub != null ? sub : mContext.getText(R.string.unable_to_load_text));
            container.addView(view, 0);

            if (DEBUG) Log.d(TAG, "instantiateItem position = " + position + ", offset = " + offset + ", end = " + end + ", text = " + sub);
        }

        return view;
    }

    @Override
    public int getCount() {
        return mCount;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        final View view = (View) object;
        container.removeView(view);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    /**
     * @return the text that is being displayed.
     */
    public CharSequence getText() {
        return mText;
    }

    /**
     * @param text the text to display.
     */
    public void setText(CharSequence text) {
        if (DEBUG) Log.i(TAG, "setText length = " + text.length());
        mText = text;
        notifyDataSetChanged();
    }

    /**
     * @return the layout resource identifier used to inflate the TextViews.
     */
    public int getTextViewLayout() {
        return mLayoutRes;
    }

    /**
     * @param resId the layout resource identifier to use to inflate the
     *            TextViews.
     */
    public void setTextViewLayout(int resId) {
        mLayoutRes = resId;
        notifyDataSetChanged();
    }

    /**
     * @param offsets array containing the character offsets for each page.
     */
    public void setOffsets(int[] offsets) {
        mOffsets = offsets;
        mCount = mOffsets.length;
        notifyDataSetChanged();
    }

    /**
     * @return array containing the character offsets for each page.
     */
    public int[] getOffsets() {
        return mOffsets;
    }

    /**
     * @param position position in the adapter to get the character offset from.
     * @return the character offset from the current page.
     */
    public int getOffsetForPosition(int position) {
        return mOffsets[position];
    }

}