Skip to content

Instantly share code, notes, and snippets.

@franciscojunior
Created August 3, 2011 15:43
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save franciscojunior/1122947 to your computer and use it in GitHub Desktop.
Save franciscojunior/1122947 to your computer and use it in GitHub Desktop.
Sample of viewflow with fragments. Note that you have to use the custom file TitleFlowIndicator below because it contains the support for the FragmentPagerAdapter
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/test.viewflowfragments"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent"
android:gravity="center_horizontal" android:id="@+id/header_layout"
android:orientation="vertical" android:layout_height="wrap_content">
<org.taptwo.android.widget.TitleFlowIndicator
android:id="@+id/viewflowindic" android:layout_height="wrap_content"
android:layout_width="fill_parent"
app:footerLineHeight="1"
app:footerTriangleHeight="8" app:textColor="#FFFFFFFF" app:selectedColor="#FFFFC445" app:footerColor="#FFFFC445" app:titlePadding="5" app:textSize="20" android:layout_marginTop="5dip" ></org.taptwo.android.widget.TitleFlowIndicator>
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
/*
* Copyright (C) 2011 Patrik Åkerfeldt
*
* 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 org.taptwo.android.widget;
import java.util.ArrayList;
import org.taptwo.android.widget.viewflow.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.support.v4.view.PagerAdapter;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
/**
* A TitleFlowIndicator is a FlowIndicator which displays the title of left view
* (if exist), the title of the current select view (centered) and the title of
* the right view (if exist). When the user scrolls the ViewFlow then titles are
* also scrolled.
*
*/
public class TitleFlowIndicator extends TextView implements FlowIndicator {
private static final int TITLE_PADDING = 10;
private static final int SELECTED_COLOR = 0xFFFFC445;
private static final int TEXT_COLOR = 0xFFAAAAAA;
private static final int TEXT_SIZE = 15;
private static final int FOOTER_LINE_HEIGHT = 4;
private static final int FOOTER_COLOR = 0xFFFFC445;
private static final int FOOTER_TRIANGLE_HEIGHT = 10;
private ViewFlow viewFlow;
private int currentScroll = 0;
private TitleProvider titleProvider = null;
private int currentPosition = 0;
private Paint paintText;
private Paint paintSelected;
private Path path;
private Paint paintFooterLine;
private Paint paintFooterTriangle;
private int footerTriangleHeight;
private int titlePadding;
private int footerLineHeight;
private PagerAdapter pagerAdapter;
/**
* Default constructor
*/
public TitleFlowIndicator(Context context) {
super(context);
initDraw(TEXT_COLOR, TEXT_SIZE, SELECTED_COLOR, FOOTER_LINE_HEIGHT, FOOTER_COLOR);
}
/**
* The contructor used with an inflater
*
* @param context
* @param attrs
*/
public TitleFlowIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
// Retrieve styles attributs
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitleFlowIndicator);
// Retrieve the colors to be used for this view and apply them.
int footerColor = a.getColor(R.styleable.TitleFlowIndicator_footerColor, FOOTER_COLOR);
footerLineHeight = a.getInt(R.styleable.TitleFlowIndicator_footerLineHeight, FOOTER_LINE_HEIGHT);
footerTriangleHeight = a.getInt(R.styleable.TitleFlowIndicator_footerTriangleHeight, FOOTER_TRIANGLE_HEIGHT);
int selectedColor = a.getColor(R.styleable.TitleFlowIndicator_selectedColor, SELECTED_COLOR);
int textColor = a.getColor(R.styleable.TitleFlowIndicator_textColor, TEXT_COLOR);
float textSize = a.getFloat(R.styleable.TitleFlowIndicator_textSize, TEXT_SIZE);
titlePadding = a.getInt(R.styleable.TitleFlowIndicator_titlePadding, TITLE_PADDING);
initDraw(textColor, textSize, selectedColor, footerLineHeight, footerColor);
}
/**
* Initialize draw objects
*/
private void initDraw(int textColor, float textSize, int selectedColor, int footerLineHeight, int footerColor) {
paintText = new Paint();
paintText.setColor(textColor);
paintText.setTextSize(textSize);
paintText.setAntiAlias(true);
paintSelected = new Paint();
paintSelected.setColor(selectedColor);
paintSelected.setTextSize(textSize);
paintSelected.setAntiAlias(true);
paintFooterLine = new Paint();
paintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE);
paintFooterLine.setStrokeWidth(footerLineHeight);
paintFooterLine.setColor(FOOTER_COLOR);
paintFooterTriangle = new Paint();
paintFooterTriangle.setStyle(Paint.Style.FILL_AND_STROKE);
paintFooterTriangle.setColor(footerColor);
}
/*
* (non-Javadoc)
*
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Calculate views bounds
ArrayList<Rect> bounds = calculateAllBounds(paintText);
// If no value then add a fake one
//int count = (viewFlow != null && viewFlow.getAdapter() != null) ? viewFlow.getAdapter().getCount() : 1;
int count = (pagerAdapter != null) ? pagerAdapter.getCount() : 1;
// Verify if the current view must be clipped to the screen
Rect curViewBound = bounds.get(currentPosition);
int curViewWidth = curViewBound.right - curViewBound.left;
if (curViewBound.left < 0) {
// Try to clip to the screen (left side)
curViewBound.left = 0;
curViewBound.right = curViewWidth;
}
if (curViewBound.right > getLeft() + getWidth()) {
// Try to clip to the screen (right side)
curViewBound.right = getLeft() + getWidth();
curViewBound.left = curViewBound.right - curViewWidth;
}
// Left views starting from the current position
if (currentPosition > 0) {
for (int iLoop = currentPosition - 1; iLoop >= 0; iLoop--) {
Rect bound = bounds.get(iLoop);
int w = bound.right - bound.left;
// Si left side is outside the screen
if (bound.left < 0) {
// Try to clip to the screen (left side)
bound.left = 0;
bound.right = w;
// Except if there's an intersection with the right view
if (iLoop < count - 1 && currentPosition != iLoop) {
Rect rightBound = bounds.get(iLoop + 1);
// Intersection
if (bound.right + TITLE_PADDING > rightBound.left) {
bound.left = rightBound.left - (w + titlePadding);
}
}
}
}
}
// Right views starting from the current position
if (currentPosition < count - 1) {
for (int iLoop = currentPosition + 1 ; iLoop < count; iLoop++) {
Rect bound = bounds.get(iLoop);
int w = bound.right - bound.left;
// If right side is outside the screen
if (bound.right > getLeft() + getWidth()) {
// Try to clip to the screen (right side)
bound.right = getLeft() + getWidth();
bound.left = bound.right - w;
// Except if there's an intersection with the left view
if (iLoop > 0 && currentPosition != iLoop) {
Rect leftBound = bounds.get(iLoop - 1);
// Intersection
if (bound.left - TITLE_PADDING < leftBound.right) {
bound.left = leftBound.right + titlePadding;
}
}
}
}
}
// Now draw views
for (int iLoop = 0; iLoop < count; iLoop++) {
// Get the title
String title = getTitle(iLoop);
Rect bound = bounds.get(iLoop);
// Only if one side is visible
if ((bound.left > getLeft() && bound.left < getLeft() + getWidth()) || (bound.right > getLeft() && bound.right < getLeft() + getWidth())) {
Paint paint = paintText;
// Change the color is the title is closed to the center
int middle = (bound.left + bound.right) / 2;
if (Math.abs(middle - (getWidth() / 2)) < 20) {
paint = paintSelected;
}
canvas.drawText(title, bound.left, bound.bottom, paint);
}
}
// Draw the footer line
path = new Path();
path.moveTo(0, getHeight()-footerLineHeight);
path.lineTo(getWidth(), getHeight()-footerLineHeight);
path.close();
canvas.drawPath(path, paintFooterLine);
// Draw the footer triangle
path = new Path();
path.moveTo(getWidth() / 2, getHeight()-footerLineHeight-footerTriangleHeight);
path.lineTo(getWidth() / 2 + footerTriangleHeight, getHeight()-footerLineHeight);
path.lineTo(getWidth() / 2 - footerTriangleHeight, getHeight()-footerLineHeight);
path.close();
canvas.drawPath(path, paintFooterTriangle);
}
/**
* Calculate views bounds and scroll them according to the current index
*
* @param paint
* @param currentIndex
* @return
*/
private ArrayList<Rect> calculateAllBounds(Paint paint) {
ArrayList<Rect> list = new ArrayList<Rect>();
// For each views (If no values then add a fake one)
//int count = (viewFlow != null && viewFlow.getAdapter() != null) ? viewFlow.getAdapter().getCount() : 1;
int count = (pagerAdapter != null) ? pagerAdapter.getCount() : 1;
for (int iLoop = 0; iLoop < count; iLoop++) {
Rect bounds = calcBounds(iLoop, paint);
int w = (bounds.right - bounds.left);
int h = (bounds.bottom - bounds.top);
bounds.left = (getWidth() / 2) - (w / 2) - currentScroll + (iLoop * getWidth());
bounds.right = bounds.left + w;
bounds.top = 0;
bounds.bottom = h;
list.add(bounds);
}
return list;
}
/**
* Calculate the bounds for a view's title
*
* @param index
* @param paint
* @return
*/
private Rect calcBounds(int index, Paint paint) {
// Get the title
String title = getTitle(index);
// Calculate the text bounds
Rect bounds = new Rect();
bounds.right = (int) paint.measureText(title);
bounds.bottom = (int) (paint.descent()-paint.ascent());
return bounds;
}
/**
* Returns the title
*
* @param pos
* @return
*/
private String getTitle(int pos) {
// Set the default title
String title = "title " + pos;
// If the TitleProvider exist
if (titleProvider != null) {
title = titleProvider.getTitle(pos);
}
return title;
}
/*
* (non-Javadoc)
*
* @see org.taptwo.android.widget.FlowIndicator#onScrolled(int, int, int,
* int)
*/
public void onScrolled(int h, int v, int oldh, int oldv) {
currentScroll = h;
invalidate();
}
/*
* (non-Javadoc)
*
* @see
* org.taptwo.android.widget.ViewFlow.ViewSwitchListener#onSwitched(android
* .view.View, int)
*/
public void onSwitched(View view, int position) {
currentPosition = position;
invalidate();
}
/*
* (non-Javadoc)
*
* @see
* org.taptwo.android.widget.FlowIndicator#setViewFlow(org.taptwo.android
* .widget.ViewFlow)
*/
public void setViewFlow(ViewFlow view) {
viewFlow = view;
invalidate();
}
public void setPagerAdapter(PagerAdapter pager) {
pagerAdapter = pager;
}
/**
* Set the title provider
*
* @param provider
*/
public void setTitleProvider(TitleProvider provider) {
titleProvider = provider;
}
/*
* (non-Javadoc)
*
* @see android.view.View#onMeasure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* Determines the width of this view
*
* @param measureSpec
* A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"ViewFlow can only be used in EXACTLY mode.");
}
result = specSize;
return result;
}
/**
* Determines the height of this view
*
* @param measureSpec
* A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
// We were told how big to be
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
// Measure the height
else {
// Calculate the text bounds
Rect bounds = new Rect();
bounds.bottom = (int) (paintText.descent()-paintText.ascent());
result = bounds.bottom - bounds.top + footerTriangleHeight + footerLineHeight + 10;
return result;
}
return result;
}
}
/*
* Copyright (C) 2011 Francisco Figueiredo Jr.
*
* 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 test.viewflowfragments;
import org.taptwo.android.widget.TitleFlowIndicator;
import org.taptwo.android.widget.TitleProvider;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.ListFragment;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class ViewFlowFragmentsActivity extends FragmentActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
TitleFlowIndicator tfi = (TitleFlowIndicator) findViewById(R.id.viewflowindic);
final TextIndicatorAdapter a = new TextIndicatorAdapter(this, mViewPager, tfi);
//a.setCurrentView(0);
tfi.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
a.setCurrentView(2);
}
});
}
public static final String[] TITLES =
{
"Henry IV (1)",
"Henry V",
"Henry VIII",
"Richard II",
"Richard III",
"Merchant of Venice",
"Othello",
"King Lear"
};
public static class ArrayListFragment extends ListFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, TITLES));
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Log.i("FragmentList", "Item clicked: " + id);
}
}
public static class TextIndicatorAdapter extends FragmentPagerAdapter
implements ViewPager.OnPageChangeListener, TitleProvider {
public TextIndicatorAdapter(FragmentActivity activity, ViewPager pager,
TitleFlowIndicator tfi) {
super(activity.getSupportFragmentManager());
mContext = activity;
mViewPager = pager;
mFlowIndicator = tfi;
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
tfi.setTitleProvider(this);
tfi.setPagerAdapter(this);
// TODO Auto-generated constructor stub
}
private final Context mContext;
private final ViewPager mViewPager;
private final TitleFlowIndicator mFlowIndicator;
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
// TODO Auto-generated method stub
int hPerceived = positionOffsetPixels + position
* mViewPager.getWidth();
mFlowIndicator.onScrolled(hPerceived, 0, 0, 0);
// Log.d("viewpager", String.format("%d %d %d %f %d",
// mViewPager.getWidth(), hPerceived, position,
// positionOffset, positionOffsetPixels));
}
@Override
public void onPageSelected(int position) {
// TODO Auto-generated method stub
mFlowIndicator.onSwitched(null, position);
Log.d("viewpager", String.valueOf(position));
}
@Override
public void onPageScrollStateChanged(int state) {
// TODO Auto-generated method stub
}
@Override
public Fragment getItem(int position) {
// TODO Auto-generated method stub
if (position == 0)
return Fragment.instantiate(mContext,
ArrayListFragment.class.getName(), null);
else
return Fragment.instantiate(mContext,
ArrayListFragment.class.getName(), null);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return 2;
}
public void setCurrentView(int id) {
mViewPager.setCurrentItem(id);
}
@Override
public String getTitle(int position) {
// TODO Auto-generated method stub
if (position == 0)
return "List 1";
else
return "List 2";
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment