├── .gitignore ├── README.md ├── viewflow-example ├── .classpath ├── .project ├── AndroidManifest.xml ├── assets │ └── fonts │ │ └── Antic.ttf ├── default.properties ├── res │ ├── drawable-hdpi │ │ └── icon.png │ ├── drawable-ldpi │ │ └── icon.png │ ├── drawable-mdpi │ │ └── icon.png │ ├── drawable │ │ ├── android.jpg │ │ ├── cupcake.jpg │ │ ├── donut.jpg │ │ ├── eclair.png │ │ ├── froyo.png │ │ ├── gingerbread.png │ │ ├── honeycomb.png │ │ └── icecream.png │ ├── layout │ │ ├── circle_layout.xml │ │ ├── day_view.xml │ │ ├── diff_view1.xml │ │ ├── diff_view2.xml │ │ ├── flow_item.xml │ │ ├── image_item.xml │ │ ├── main.xml │ │ └── title_layout.xml │ └── values │ │ ├── color.xml │ │ └── strings.xml ├── screen.png ├── screen2.png └── src │ └── org │ └── taptwo │ └── android │ └── widget │ └── viewflow │ └── example │ ├── AndroidVersionAdapter.java │ ├── AsyncAdapter.java │ ├── AsyncDataFlowExample.java │ ├── CircleViewFlowExample.java │ ├── DiffAdapter.java │ ├── DiffViewFlowExample.java │ ├── ImageAdapter.java │ ├── TitleViewFlowExample.java │ └── ViewFlowExample.java └── viewflow ├── .classpath ├── .project ├── AndroidManifest.xml ├── build.xml ├── default.properties ├── res ├── drawable │ └── icon.png └── values │ ├── attrs.xml │ └── strings.xml └── src └── org └── taptwo └── android └── widget ├── CircleFlowIndicator.java ├── FlowIndicator.java ├── TitleFlowIndicator.java ├── TitleProvider.java └── ViewFlow.java /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .settings 3 | bin 4 | gen 5 | target 6 | local.properties 7 | proguard.cfg 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # View Flow for Android 2 | 3 | ViewFlow is an Android UI widget providing a horizontally scrollable [ViewGroup](http://developer.android.com/reference/android/view/ViewGroup.html) with items populated from an [Adapter](http://developer.android.com/reference/android/widget/Adapter.html). Scroll down to the bottom of the page for a screen shot. 4 | 5 | The component is a [Library Project](http://developer.android.com/guide/developing/eclipse-adt.html#libraryProject). This means that there's no need to copy-paste resources into your own project, simply add the viewflow component as a reference to any project. 6 | 7 | ## When to use 8 | This library might be suitable if you have an indeterminate number of views in your viewflow, if instead you have a static numbers of views you ought to look at Fragments and the ViewPager in the Compatibility Library instead. 9 | 10 | ## Usage 11 | 12 | ### In your layout 13 | 14 | 18 | 19 | The use of `app:sidebuffer` is optional. It defines the number of Views to buffer on each side of the currently shown View. The default sidebuffer is 3, making up a grand total of 7 (3 * 2 + 1) Views loaded at a time (at max). 20 | To be able to use the more convenient `app:sidebuffer` attribute, the application namespace must be included in the same manner as the android namespace is. Please refer to the layout main.xml in the example project for a full example. Again, note that it's the application namespace and *not* the viewflow namespace that must be referred like `xmlns:app="http://schemas.android.com/apk/res/your.application.package.here"`. 21 | 22 | ### In your activity 23 | 24 | ViewFlow viewFlow = (ViewFlow) findViewById(R.id.viewflow); 25 | viewFlow.setAdapter(myAdapter); 26 | 27 | Setting a different initial position (0 being default) is as easy as: 28 | 29 | viewFlow.setAdapter(myAdapter, 8); 30 | 31 | Although possible, you should not call `setSelection(...)` immediately after calling `setAdapter(myAdapter)` as that might load unnecessary views giving you a decrease in performance. 32 | 33 | ### Listen on screen change events 34 | 35 | If you need to listen to screen change events you would want to implement your own `ViewFlow.ViewSwitchListener` and pass it to the `setOnViewSwitchListener()` method. 36 | 37 | viewFlow.setOnViewSwitchListener(new ViewSwitchListener() { 38 | public void onSwitched(View v, int position) { 39 | // Your code here 40 | } 41 | }); 42 | 43 | ### Listen on initialize view events 44 | 45 | If you need a lazy View initialization you would want to implement your own `ViewFlow.ViewLazyInitializeListener` and pass it to the `setOnViewLazyInitializeListener()` method. 46 | 47 | viewFlow.setOnViewLazyInitializeListener(new ViewLazyInitializeListener() { 48 | public void onViewLazyInitialize(View view, int position) { 49 | // Your code here e.g. 50 | ((MyAdapter)((AbsListView)view).getAdapter()).initializeData(); 51 | } 52 | }); 53 | 54 | ### Flow Indicator 55 | It is also possible to add a flow view indicator to your layout. The purpose of a `FlowIndicator` is to present a visual representation of where in the item list focus is at. You may either implement a `FlowIndicator` yourself or use an implementation provided by the View Flow library. The View Flow library currently supports the following indicators: 56 | 57 | #### Circle Flow Indicator #### 58 | This indicator shows a circle for each `View` in the adapter with a special circle representing the currently selected view (see screenshot below). 59 | 60 | 64 | 65 | And then you'll need to connect your `ViewFlow` with the `FlowIndicator`: 66 | 67 | CircleFlowIndicator indic = (CircleFlowIndicator) findViewById(R.id.viewflowindic); 68 | viewFlow.setFlowIndicator(indic); 69 | 70 | By default, the 'active' indicator moves smoothly from one 'inactive' indicator 71 | to the next, as the user scrolls. If you set the `snap` attribute to `true`, it 72 | will instead jump to the next position when the flow settles at the next page. 73 | 74 | The following attributes are supported: `activeColor`, `inactiveColor`, 75 | `activeType` (either fill or stroke), `inactiveType` (either fill or stroke), 76 | `fadeOut` (time in ms until indicator fades out, 0 = never), `radius`, `sync` 77 | (see above). 78 | 79 | #### Title Flow Indicator #### 80 | This indicator presents the title of the previous, current and next `View` in the adapter (see screenshot below). 81 | 82 | 90 | 91 | And then you'll need to connect your `ViewFlow` with the `FlowIndicator`: 92 | 93 | TitleFlowIndicator indicator = (TitleFlowIndicator) findViewById(R.id.viewflowindic); 94 | indicator.setTitleProvider(myTitleProvider); 95 | viewFlow.setFlowIndicator(indicator); 96 | 97 | ## Building a jar file 98 | If you rather want a jar file instead of a including the project as an android library, run `ant jar` in the `android-viewflow/viewflow` folder, to build a jar file. 99 | 100 | ## Caveats ## 101 | The manifest states a min sdk version of 4, which is true. But in any case you want to support an api level < 8 you will have to forward an `onConfigurationChanged` event to the `ViewFlow` from your `Activity`. I know this isn't a very nice solution, feel free to propose better ones! 102 | 103 | @Override 104 | public void onConfigurationChanged(Configuration newConfig) { 105 | super.onConfigurationChanged(newConfig); 106 | viewFlow.onConfigurationChanged(newConfig); 107 | } 108 | 109 | ## Contributions 110 | The following persons deserves a mention for their contributions: 111 | 112 | * Eric Taix 113 | * Marc Reichelt, 114 | 115 | ### Want to contribute? 116 | 117 | GitHub has some great articles on [how to get started with Git and GitHub](http://help.github.com/) and how to [fork a project](http://help.github.com/forking/). 118 | 119 | Contributers are recommended to fork the app on GitHub (but don't have too). Create a feature branch, push the branch to git hub, press Pull Request and write a simple explanation. 120 | 121 | One fix per commit. If let's say a commit closes the open issue 12. Just add `closes #12` in your commit message to close that issue automagically. 122 | 123 | If you still feel uncomfortable contributing the project github-wise, don't hesistate to send a regular patch. 124 | 125 | All code that is contributed must be compliant with [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). 126 | 127 | ## License 128 | Copyright (c) 2011 [Patrik Åkerfeldt](http://about.me/pakerfeldt) 129 | 130 | Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) 131 | 132 | ![ViewFlow for Android](https://github.com/pakerfeldt/android-viewflow/raw/master/viewflow-example/screen.png "ViewFlow for Android")    ![ViewFlow for Android](https://github.com/pakerfeldt/android-viewflow/raw/master/viewflow-example/screen2.png "ViewFlow for Android") 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /viewflow-example/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /viewflow-example/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | viewflow-example 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | 35 | org_taptwo_android_widget_viewflow_src 36 | 2 37 | _android_org_taptwo_android_widget_viewflow_7cfa4d63/src 38 | 39 | 40 | viewflow_src 41 | 2 42 | _android_viewflow_474f6153/src 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /viewflow-example/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /viewflow-example/assets/fonts/Antic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/assets/fonts/Antic.ttf -------------------------------------------------------------------------------- /viewflow-example/default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-8 12 | android.library.reference.1=../viewflow/ 13 | -------------------------------------------------------------------------------- /viewflow-example/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /viewflow-example/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /viewflow-example/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /viewflow-example/res/drawable/android.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable/android.jpg -------------------------------------------------------------------------------- /viewflow-example/res/drawable/cupcake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable/cupcake.jpg -------------------------------------------------------------------------------- /viewflow-example/res/drawable/donut.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable/donut.jpg -------------------------------------------------------------------------------- /viewflow-example/res/drawable/eclair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable/eclair.png -------------------------------------------------------------------------------- /viewflow-example/res/drawable/froyo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable/froyo.png -------------------------------------------------------------------------------- /viewflow-example/res/drawable/gingerbread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable/gingerbread.png -------------------------------------------------------------------------------- /viewflow-example/res/drawable/honeycomb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable/honeycomb.png -------------------------------------------------------------------------------- /viewflow-example/res/drawable/icecream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/res/drawable/icecream.png -------------------------------------------------------------------------------- /viewflow-example/res/layout/circle_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /viewflow-example/res/layout/day_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /viewflow-example/res/layout/diff_view1.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /viewflow-example/res/layout/diff_view2.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /viewflow-example/res/layout/flow_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /viewflow-example/res/layout/image_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /viewflow-example/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /viewflow-example/res/layout/title_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 15 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /viewflow-example/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #58567D 4 | 5 | -------------------------------------------------------------------------------- /viewflow-example/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World, ViewFlowExample! 4 | Viewflow examples 5 | TitleFlowIndicator 6 | CircleFlowIndicator 7 | DifferentViewFlow 8 | AsyncDataLoading 9 | 10 | -------------------------------------------------------------------------------- /viewflow-example/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/screen.png -------------------------------------------------------------------------------- /viewflow-example/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/screen2.png -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/AndroidVersionAdapter.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/src/org/taptwo/android/widget/viewflow/example/AndroidVersionAdapter.java -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/AsyncAdapter.java: -------------------------------------------------------------------------------- 1 | package org.taptwo.android.widget.viewflow.example; 2 | 3 | import java.text.DateFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | 8 | import org.taptwo.android.widget.TitleProvider; 9 | import org.taptwo.android.widget.viewflow.example.R; 10 | 11 | import android.content.Context; 12 | import android.os.AsyncTask; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.BaseAdapter; 17 | import android.widget.ProgressBar; 18 | import android.widget.TextView; 19 | 20 | 21 | public class AsyncAdapter extends BaseAdapter implements TitleProvider { 22 | 23 | private LayoutInflater mInflater; 24 | 25 | private static final DateFormat dfTitle = new SimpleDateFormat("E, dd MMM"); 26 | 27 | private static final int daysDepth = 10; 28 | private static final int daysSize = daysDepth * 2 + 1; 29 | 30 | private static Date[] dates = new Date[ daysSize ]; 31 | private static String[] content = new String[ daysSize ]; 32 | 33 | 34 | private class ViewHolder { 35 | ProgressBar mProgressBar; 36 | View mContent; 37 | TextView mDate; 38 | } 39 | 40 | 41 | public AsyncAdapter(Context context) { 42 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 43 | prepareDates(); 44 | } 45 | 46 | @Override 47 | public String getItem(int position) { 48 | return content[position]; 49 | } 50 | 51 | @Override 52 | public long getItemId(int position) { 53 | return position; 54 | } 55 | 56 | @Override 57 | public View getView(int position, View convertView, ViewGroup parent) { 58 | return drawView(position, convertView); 59 | } 60 | 61 | private View drawView(int position, View view) { 62 | ViewHolder holder = null; 63 | 64 | if(view == null) { 65 | view = mInflater.inflate(R.layout.day_view, null); 66 | 67 | holder = new ViewHolder(); 68 | 69 | holder.mProgressBar = (ProgressBar) view.findViewById(R.id.progress); 70 | holder.mDate = (TextView) view.findViewById(R.id.date); 71 | holder.mContent = (View) view.findViewById(R.id.content); 72 | 73 | view.setTag(holder); 74 | } else { 75 | holder = (ViewHolder) view.getTag(); 76 | } 77 | 78 | 79 | final String o = getItem(position); 80 | if (o != null) { 81 | holder.mProgressBar.setVisibility(View.GONE); 82 | holder.mDate.setText(o); 83 | holder.mContent.setVisibility(View.VISIBLE); 84 | } 85 | else { 86 | new LoadContentTask().execute(position, view); 87 | 88 | holder.mContent.setVisibility(View.GONE); 89 | holder.mProgressBar.setVisibility(View.VISIBLE); 90 | } 91 | 92 | return view; 93 | } 94 | 95 | @Override 96 | public String getTitle(int position) { 97 | return dfTitle.format( dates[position] ); 98 | } 99 | 100 | @Override 101 | public int getCount() { 102 | return dates.length; 103 | } 104 | 105 | public int getTodayId() { 106 | return daysDepth; 107 | } 108 | 109 | public Date getTodayDate() { 110 | return dates[daysDepth]; 111 | } 112 | 113 | /** 114 | * Prepare dates for navigation, to past and to future 115 | */ 116 | private void prepareDates() { 117 | Date today = new Date(); 118 | 119 | Calendar calPast = Calendar.getInstance(); 120 | Calendar calFuture = Calendar.getInstance(); 121 | 122 | calPast.setTime(today); 123 | calFuture.setTime(today); 124 | 125 | dates[ daysDepth ] = calPast.getTime(); 126 | for (int i = 1; i <= daysDepth; i++) { 127 | calPast.add( Calendar.DATE, -1 ); 128 | dates[ daysDepth - i ] = calPast.getTime(); 129 | 130 | calFuture.add( Calendar.DATE, 1 ); 131 | dates[ daysDepth + i ] = calFuture.getTime(); 132 | } 133 | } 134 | 135 | 136 | private class LoadContentTask extends AsyncTask { 137 | 138 | private Integer position; 139 | private View view; 140 | 141 | @Override 142 | protected Object doInBackground(Object... arg) { 143 | position = (Integer) arg[0]; 144 | view = (View) arg[1]; 145 | 146 | // long-term task is here 147 | try { 148 | Thread.sleep(3000); // do nothing for 3000 miliseconds (3 second) 149 | } catch (InterruptedException e) { 150 | e.printStackTrace(); 151 | } 152 | 153 | return getTitle(position); 154 | } 155 | 156 | protected void onPostExecute(Object result) { 157 | // process result 158 | content[position] = (String) result; 159 | 160 | drawView(position, view); 161 | 162 | view.postInvalidate(); 163 | } 164 | 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/AsyncDataFlowExample.java: -------------------------------------------------------------------------------- 1 | package org.taptwo.android.widget.viewflow.example; 2 | 3 | import org.taptwo.android.widget.TitleFlowIndicator; 4 | import org.taptwo.android.widget.ViewFlow; 5 | 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | 9 | public class AsyncDataFlowExample extends Activity { 10 | 11 | private ViewFlow viewFlow; 12 | 13 | @Override 14 | public void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setTitle(R.string.async_title); 17 | setContentView(R.layout.title_layout); 18 | 19 | viewFlow = (ViewFlow) findViewById(R.id.viewflow); 20 | AsyncAdapter adapter = new AsyncAdapter(this); 21 | viewFlow.setAdapter(adapter, adapter.getTodayId()); 22 | 23 | TitleFlowIndicator indicator = (TitleFlowIndicator) findViewById(R.id.viewflowindic); 24 | indicator.setTitleProvider(adapter); 25 | 26 | viewFlow.setFlowIndicator(indicator); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/CircleViewFlowExample.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/src/org/taptwo/android/widget/viewflow/example/CircleViewFlowExample.java -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/DiffAdapter.java: -------------------------------------------------------------------------------- 1 | package org.taptwo.android.widget.viewflow.example; 2 | 3 | import org.taptwo.android.widget.TitleProvider; 4 | 5 | import android.content.Context; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.BaseAdapter; 10 | 11 | public class DiffAdapter extends BaseAdapter implements TitleProvider { 12 | 13 | private static final int VIEW1 = 0; 14 | private static final int VIEW2 = 1; 15 | private static final int VIEW_MAX_COUNT = VIEW2 + 1; 16 | private final String[] names = {"View1","View2"}; 17 | 18 | private LayoutInflater mInflater; 19 | 20 | public DiffAdapter(Context context) { 21 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 22 | } 23 | 24 | @Override 25 | public int getItemViewType(int position) { 26 | return position; 27 | } 28 | 29 | @Override 30 | public int getViewTypeCount() { 31 | return VIEW_MAX_COUNT; 32 | } 33 | 34 | @Override 35 | public int getCount() { 36 | return 2; 37 | } 38 | 39 | @Override 40 | public Object getItem(int position) { 41 | return position; 42 | } 43 | 44 | @Override 45 | public long getItemId(int position) { 46 | return position; 47 | } 48 | 49 | @Override 50 | public View getView(int position, View convertView, ViewGroup parent) { 51 | int view = getItemViewType(position); 52 | if (convertView == null) { 53 | switch (view) { 54 | case VIEW1: 55 | convertView = mInflater.inflate(R.layout.diff_view1, null); 56 | break; 57 | case VIEW2: 58 | convertView = mInflater.inflate(R.layout.diff_view2, null); 59 | break; 60 | } 61 | } 62 | return convertView; 63 | } 64 | 65 | 66 | 67 | /* (non-Javadoc) 68 | * @see org.taptwo.android.widget.TitleProvider#getTitle(int) 69 | */ 70 | public String getTitle(int position) { 71 | return names[position]; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/DiffViewFlowExample.java: -------------------------------------------------------------------------------- 1 | package org.taptwo.android.widget.viewflow.example; 2 | 3 | import org.taptwo.android.widget.TitleFlowIndicator; 4 | import org.taptwo.android.widget.ViewFlow; 5 | 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | import android.widget.ArrayAdapter; 9 | import android.widget.ListView; 10 | 11 | public class DiffViewFlowExample extends Activity { 12 | 13 | private ViewFlow viewFlow; 14 | private ListView listView; 15 | 16 | /** Called when the activity is first created. */ 17 | @Override 18 | public void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setTitle(R.string.diff_title); 21 | setContentView(R.layout.title_layout); 22 | 23 | viewFlow = (ViewFlow) findViewById(R.id.viewflow); 24 | DiffAdapter adapter = new DiffAdapter(this); 25 | viewFlow.setAdapter(adapter); 26 | TitleFlowIndicator indicator = (TitleFlowIndicator) findViewById(R.id.viewflowindic); 27 | indicator.setTitleProvider(adapter); 28 | viewFlow.setFlowIndicator(indicator); 29 | 30 | /** To populate ListView in diff_view1.xml */ 31 | listView = (ListView) findViewById(R.id.listView1); 32 | String[] names = new String[] { "Cupcake", "Donut", "Eclair", "Froyo", 33 | "Gingerbread", "Honeycomb", "IceCream Sandwich"}; 34 | // Create an ArrayAdapter, that will actually make the Strings above 35 | // appear in the ListView 36 | listView.setAdapter(new ArrayAdapter(this, 37 | android.R.layout.simple_list_item_1, names)); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/ImageAdapter.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/src/org/taptwo/android/widget/viewflow/example/ImageAdapter.java -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/TitleViewFlowExample.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow-example/src/org/taptwo/android/widget/viewflow/example/TitleViewFlowExample.java -------------------------------------------------------------------------------- /viewflow-example/src/org/taptwo/android/widget/viewflow/example/ViewFlowExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik �kerfeldt 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.taptwo.android.widget.viewflow.example; 17 | 18 | import android.app.Activity; 19 | import android.content.Intent; 20 | import android.os.Bundle; 21 | import android.view.View; 22 | import android.widget.AdapterView; 23 | import android.widget.ArrayAdapter; 24 | import android.widget.ListView; 25 | import android.widget.AdapterView.OnItemClickListener; 26 | 27 | public class ViewFlowExample extends Activity { 28 | 29 | ListView listView; 30 | 31 | @Override 32 | public void onCreate(Bundle savedInstanceState) { 33 | 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.main); 36 | listView = (ListView) findViewById(R.id.menu); 37 | String[] listeStrings = { "Circle indicator...", "Title indicator...", "Different Views...", "Async Data Loading..." }; 38 | listView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, listeStrings)); 39 | listView.setOnItemClickListener(new OnItemClickListener() { 40 | @Override 41 | public void onItemClick(AdapterView adapterView, View view, int position, long arg3) { 42 | switch (position) { 43 | case 0: 44 | startActivity(new Intent(ViewFlowExample.this, CircleViewFlowExample.class)); 45 | break; 46 | case 1: 47 | startActivity(new Intent(ViewFlowExample.this, TitleViewFlowExample.class)); 48 | break; 49 | case 2: 50 | startActivity(new Intent(ViewFlowExample.this, DiffViewFlowExample.class)); 51 | break; 52 | case 3: 53 | startActivity(new Intent(ViewFlowExample.this, AsyncDataFlowExample.class)); 54 | break; 55 | } 56 | } 57 | }); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /viewflow/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /viewflow/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | viewflow 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /viewflow/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /viewflow/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | A view switcher library for Android 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /viewflow/default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-8 12 | android.library=true 13 | -------------------------------------------------------------------------------- /viewflow/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pakerfeldt/android-viewflow/3da74fa32a935bcbb37e5ebeb270477cde1985d4/viewflow/res/drawable/icon.png -------------------------------------------------------------------------------- /viewflow/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /viewflow/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World! 4 | ViewFlow 5 | 6 | -------------------------------------------------------------------------------- /viewflow/src/org/taptwo/android/widget/CircleFlowIndicator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik Åkerfeldt 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.taptwo.android.widget; 17 | 18 | import android.content.Context; 19 | import android.content.res.TypedArray; 20 | import android.graphics.Canvas; 21 | import android.graphics.Paint; 22 | import android.graphics.Paint.Style; 23 | import android.os.AsyncTask; 24 | import android.util.AttributeSet; 25 | import android.view.View; 26 | import android.view.animation.Animation; 27 | import android.view.animation.Animation.AnimationListener; 28 | import android.view.animation.AnimationUtils; 29 | import org.taptwo.android.widget.viewflow.R; 30 | 31 | /** 32 | * A FlowIndicator which draws circles (one for each view). 33 | *
34 | * Available attributes are:
35 | *
    36 | *
  • 37 | * activeColor: Define the color used to draw the active circle (default to white) 38 | *
  • 39 | *
  • 40 | * inactiveColor: Define the color used to draw the inactive circles (default to 0x44FFFFFF) 41 | *
  • 42 | *
  • 43 | * inactiveType: Define how to draw the inactive circles, either stroke or fill (default to stroke) 44 | *
  • 45 | *
  • 46 | * activeType: Define how to draw the active circle, either stroke or fill (default to fill) 47 | *
  • 48 | *
  • 49 | * fadeOut: Define the time (in ms) until the indicator will fade out (default to 0 = never fade out) 50 | *
  • 51 | *
  • 52 | * radius: Define the circle outer radius (default to 4.0) 53 | *
  • 54 | *
  • 55 | * spacing: Define the circle spacing (default to 4.0) 56 | *
  • 57 | *
  • 58 | * snap: If true, the 'active' indicator snaps from one page to the next; otherwise, it moves smoothly. 59 | *
  • 60 | *
61 | */ 62 | public class CircleFlowIndicator extends View implements FlowIndicator, 63 | AnimationListener { 64 | private static final int STYLE_STROKE = 0; 65 | private static final int STYLE_FILL = 1; 66 | 67 | private float mRadius = 4; 68 | private float mRadiusInactive = 4; 69 | private float mRadiusActive = 4; 70 | private float spacing = 4; 71 | private int fadeOutTime = 0; 72 | private final Paint mPaintInactive = new Paint(Paint.ANTI_ALIAS_FLAG); 73 | private final Paint mPaintActive = new Paint(Paint.ANTI_ALIAS_FLAG); 74 | private ViewFlow viewFlow; 75 | private int currentScroll = 0; 76 | private int currentPosition = 0; 77 | private int flowWidth = 0; 78 | private FadeTimer timer; 79 | public AnimationListener animationListener = this; 80 | private Animation animation; 81 | private boolean mCentered = false; 82 | private boolean mSnap = false; 83 | 84 | /** 85 | * Default constructor 86 | * 87 | * @param context 88 | */ 89 | public CircleFlowIndicator(Context context) { 90 | super(context); 91 | initColors(0xFFFFFFFF, 0xFFFFFFFF, STYLE_FILL, STYLE_STROKE); 92 | } 93 | 94 | /** 95 | * The contructor used with an inflater 96 | * 97 | * @param context 98 | * @param attrs 99 | */ 100 | public CircleFlowIndicator(Context context, AttributeSet attrs) { 101 | super(context, attrs); 102 | // Retrieve styles attributs 103 | TypedArray a = context.obtainStyledAttributes(attrs, 104 | R.styleable.CircleFlowIndicator); 105 | 106 | // Gets the active circle type, defaulting to "fill" 107 | int activeType = a.getInt(R.styleable.CircleFlowIndicator_activeType, 108 | STYLE_FILL); 109 | 110 | int activeDefaultColor = 0xFFFFFFFF; 111 | 112 | // Get a custom active color if there is one 113 | int activeColor = a 114 | .getColor(R.styleable.CircleFlowIndicator_activeColor, 115 | activeDefaultColor); 116 | 117 | // Gets the inactive circle type, defaulting to "stroke" 118 | int inactiveType = a.getInt( 119 | R.styleable.CircleFlowIndicator_inactiveType, STYLE_STROKE); 120 | 121 | int inactiveDefaultColor = 0x44FFFFFF; 122 | // Get a custom inactive color if there is one 123 | int inactiveColor = a.getColor( 124 | R.styleable.CircleFlowIndicator_inactiveColor, 125 | inactiveDefaultColor); 126 | 127 | // Retrieve the radius 128 | mRadius = a.getDimension(R.styleable.CircleFlowIndicator_radius, 4.0f); 129 | mRadiusActive = mRadius; 130 | mRadiusInactive = mRadius; 131 | 132 | // Retrieve the spacing 133 | spacing = a.getDimension(R.styleable.CircleFlowIndicator_spacing, 4.0f); 134 | // We want the spacing to be center-to-center 135 | spacing += 2 * mRadiusActive; 136 | 137 | // Retrieve the fade out time 138 | fadeOutTime = a.getInt(R.styleable.CircleFlowIndicator_fadeOut, 0); 139 | 140 | mCentered = a.getBoolean(R.styleable.CircleFlowIndicator_centered, false); 141 | 142 | mSnap = a.getBoolean(R.styleable.CircleFlowIndicator_snap, false); 143 | 144 | initColors(activeColor, inactiveColor, activeType, inactiveType); 145 | } 146 | 147 | private void initColors(int activeColor, int inactiveColor, int activeType, 148 | int inactiveType) { 149 | // Select the paint type given the type attr 150 | switch (inactiveType) { 151 | case STYLE_FILL: 152 | mPaintInactive.setStyle(Style.FILL); 153 | break; 154 | default: 155 | mPaintInactive.setStyle(Style.STROKE); 156 | float strokeWidth = mPaintInactive.getStrokeWidth(); 157 | if (strokeWidth == 0.0f) { 158 | // It draws in "hairline mode", which is 1 px wide. 159 | strokeWidth = 1.0f / getResources().getDisplayMetrics().density; 160 | } 161 | mRadiusInactive -= strokeWidth / 2.0f; 162 | } 163 | mPaintInactive.setColor(inactiveColor); 164 | 165 | // Select the paint type given the type attr 166 | switch (activeType) { 167 | case STYLE_STROKE: 168 | mPaintActive.setStyle(Style.STROKE); 169 | float strokeWidth = mPaintInactive.getStrokeWidth(); 170 | if (strokeWidth == 0.0f) { 171 | // It draws in "hairline mode", which is 1 px wide. 172 | strokeWidth = 1.0f / getResources().getDisplayMetrics().density; 173 | } 174 | mRadiusActive -= strokeWidth / 2.0f; 175 | break; 176 | default: 177 | mPaintActive.setStyle(Style.FILL); 178 | } 179 | mPaintActive.setColor(activeColor); 180 | } 181 | 182 | /* 183 | * (non-Javadoc) 184 | * 185 | * @see android.view.View#onDraw(android.graphics.Canvas) 186 | */ 187 | @Override 188 | protected void onDraw(Canvas canvas) { 189 | super.onDraw(canvas); 190 | int count = 3; 191 | if (viewFlow != null) { 192 | count = viewFlow.getViewsCount(); 193 | } 194 | 195 | //this is the amount the first circle should be offset to make the entire thing centered 196 | float centeringOffset = 0; 197 | 198 | int leftPadding = getPaddingLeft(); 199 | 200 | // Draw stroked circles 201 | for (int iLoop = 0; iLoop < count; iLoop++) { 202 | canvas.drawCircle(leftPadding + mRadius 203 | + (iLoop * spacing) + centeringOffset, 204 | getPaddingTop() + mRadius, mRadiusInactive, mPaintInactive); 205 | } 206 | float cx = 0; 207 | if (mSnap) { 208 | cx = currentPosition * spacing; 209 | } else { 210 | if (flowWidth != 0) { 211 | // Draw the filled circle according to the current scroll 212 | cx = (currentScroll * spacing) / flowWidth; 213 | } 214 | // else, the flow width hasn't been updated yet. Draw the default position. 215 | } 216 | canvas.drawCircle(leftPadding + mRadius + cx+centeringOffset, getPaddingTop() 217 | + mRadius, mRadiusActive, mPaintActive); 218 | } 219 | 220 | /* 221 | * (non-Javadoc) 222 | * 223 | * @see 224 | * org.taptwo.android.widget.ViewFlow.ViewSwitchListener#onSwitched(android 225 | * .view.View, int) 226 | */ 227 | @Override 228 | public void onSwitched(View view, int position) { 229 | currentPosition = position; 230 | if (mSnap) { 231 | setVisibility(View.VISIBLE); 232 | resetTimer(); 233 | invalidate(); 234 | } 235 | } 236 | 237 | /* 238 | * (non-Javadoc) 239 | * 240 | * @see 241 | * org.taptwo.android.widget.FlowIndicator#setViewFlow(org.taptwo.android 242 | * .widget.ViewFlow) 243 | */ 244 | @Override 245 | public void setViewFlow(ViewFlow view) { 246 | resetTimer(); 247 | viewFlow = view; 248 | flowWidth = viewFlow.getChildWidth(); 249 | invalidate(); 250 | } 251 | 252 | /* 253 | * (non-Javadoc) 254 | * 255 | * @see org.taptwo.android.widget.FlowIndicator#onScrolled(int, int, int, 256 | * int) 257 | */ 258 | @Override 259 | public void onScrolled(int h, int v, int oldh, int oldv) { 260 | currentScroll = h; 261 | flowWidth = viewFlow.getChildWidth(); 262 | if (!mSnap) { 263 | setVisibility(View.VISIBLE); 264 | resetTimer(); 265 | invalidate(); 266 | } 267 | } 268 | 269 | /* 270 | * (non-Javadoc) 271 | * 272 | * @see android.view.View#onMeasure(int, int) 273 | */ 274 | @Override 275 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 276 | setMeasuredDimension(measureWidth(widthMeasureSpec), 277 | measureHeight(heightMeasureSpec)); 278 | } 279 | 280 | /** 281 | * Determines the width of this view 282 | * 283 | * @param measureSpec 284 | * A measureSpec packed into an int 285 | * @return The width of the view, honoring constraints from measureSpec 286 | */ 287 | private int measureWidth(int measureSpec) { 288 | int result = 0; 289 | int specMode = MeasureSpec.getMode(measureSpec); 290 | int specSize = MeasureSpec.getSize(measureSpec); 291 | 292 | // We were told how big to be 293 | if (specMode == MeasureSpec.EXACTLY) { 294 | result = specSize; 295 | } 296 | // Calculate the width according the views count 297 | else { 298 | int count = 3; 299 | if (viewFlow != null) { 300 | count = viewFlow.getViewsCount(); 301 | } 302 | // Remember that spacing is centre-to-centre 303 | result = (int) (getPaddingLeft() + getPaddingRight() 304 | + (2 * mRadius) + (count - 1) * spacing); 305 | // Respect AT_MOST value if that was what is called for by 306 | // measureSpec 307 | if (specMode == MeasureSpec.AT_MOST) { 308 | result = Math.min(result, specSize); 309 | } 310 | } 311 | return result; 312 | } 313 | 314 | /** 315 | * Determines the height of this view 316 | * 317 | * @param measureSpec 318 | * A measureSpec packed into an int 319 | * @return The height of the view, honoring constraints from measureSpec 320 | */ 321 | private int measureHeight(int measureSpec) { 322 | int result = 0; 323 | int specMode = MeasureSpec.getMode(measureSpec); 324 | int specSize = MeasureSpec.getSize(measureSpec); 325 | 326 | // We were told how big to be 327 | if (specMode == MeasureSpec.EXACTLY) { 328 | result = specSize; 329 | } 330 | // Measure the height 331 | else { 332 | result = (int) (2 * mRadius + getPaddingTop() + getPaddingBottom() + 1); 333 | // Respect AT_MOST value if that was what is called for by 334 | // measureSpec 335 | if (specMode == MeasureSpec.AT_MOST) { 336 | result = Math.min(result, specSize); 337 | } 338 | } 339 | return result; 340 | } 341 | 342 | /** 343 | * Sets the fill color 344 | * 345 | * @param color 346 | * ARGB value for the text 347 | */ 348 | public void setFillColor(int color) { 349 | mPaintActive.setColor(color); 350 | invalidate(); 351 | } 352 | 353 | /** 354 | * Sets the stroke color 355 | * 356 | * @param color 357 | * ARGB value for the text 358 | */ 359 | public void setStrokeColor(int color) { 360 | mPaintInactive.setColor(color); 361 | invalidate(); 362 | } 363 | 364 | /** 365 | * Resets the fade out timer to 0. Creating a new one if needed 366 | */ 367 | private void resetTimer() { 368 | // Only set the timer if we have a timeout of at least 1 millisecond 369 | if (fadeOutTime > 0) { 370 | // Check if we need to create a new timer 371 | if (timer == null || timer._run == false) { 372 | // Create and start a new timer 373 | timer = new FadeTimer(); 374 | timer.execute(); 375 | } else { 376 | // Reset the current tiemr to 0 377 | timer.resetTimer(); 378 | } 379 | } 380 | } 381 | 382 | /** 383 | * Counts from 0 to the fade out time and animates the view away when 384 | * reached 385 | */ 386 | private class FadeTimer extends AsyncTask { 387 | // The current count 388 | private int timer = 0; 389 | // If we are inside the timing loop 390 | private boolean _run = true; 391 | 392 | public void resetTimer() { 393 | timer = 0; 394 | } 395 | 396 | @Override 397 | protected Void doInBackground(Void... arg0) { 398 | while (_run) { 399 | try { 400 | // Wait for a millisecond 401 | Thread.sleep(1); 402 | // Increment the timer 403 | timer++; 404 | 405 | // Check if we've reached the fade out time 406 | if (timer == fadeOutTime) { 407 | // Stop running 408 | _run = false; 409 | } 410 | } catch (InterruptedException e) { 411 | // TODO Auto-generated catch block 412 | e.printStackTrace(); 413 | } 414 | } 415 | return null; 416 | } 417 | 418 | @Override 419 | protected void onPostExecute(Void result) { 420 | animation = AnimationUtils.loadAnimation(getContext(), 421 | android.R.anim.fade_out); 422 | animation.setAnimationListener(animationListener); 423 | startAnimation(animation); 424 | } 425 | } 426 | 427 | @Override 428 | public void onAnimationEnd(Animation animation) { 429 | setVisibility(View.GONE); 430 | } 431 | 432 | @Override 433 | public void onAnimationRepeat(Animation animation) { 434 | } 435 | 436 | @Override 437 | public void onAnimationStart(Animation animation) { 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /viewflow/src/org/taptwo/android/widget/FlowIndicator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik Åkerfeldt 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.taptwo.android.widget; 17 | 18 | import org.taptwo.android.widget.ViewFlow.ViewSwitchListener; 19 | 20 | /** 21 | * An interface which defines the contract between a ViewFlow and a 22 | * FlowIndicator.
23 | * A FlowIndicator is responsible to show an visual indicator on the total views 24 | * number and the current visible view.
25 | * 26 | */ 27 | public interface FlowIndicator extends ViewSwitchListener { 28 | 29 | /** 30 | * Set the current ViewFlow. This method is called by the ViewFlow when the 31 | * FlowIndicator is attached to it. 32 | * 33 | * @param view 34 | */ 35 | public void setViewFlow(ViewFlow view); 36 | 37 | /** 38 | * The scroll position has been changed. A FlowIndicator may implement this 39 | * method to reflect the current position 40 | * 41 | * @param h 42 | * @param v 43 | * @param oldh 44 | * @param oldv 45 | */ 46 | public void onScrolled(int h, int v, int oldh, int oldv); 47 | } 48 | -------------------------------------------------------------------------------- /viewflow/src/org/taptwo/android/widget/TitleFlowIndicator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik Åkerfeldt 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.taptwo.android.widget; 17 | 18 | import java.util.ArrayList; 19 | 20 | import org.taptwo.android.widget.viewflow.R; 21 | 22 | import android.content.Context; 23 | import android.content.res.TypedArray; 24 | import android.graphics.Canvas; 25 | import android.graphics.Paint; 26 | import android.graphics.Path; 27 | import android.graphics.Rect; 28 | import android.graphics.Typeface; 29 | import android.util.AttributeSet; 30 | import android.view.View; 31 | import android.widget.TextView; 32 | 33 | /** 34 | * A TitleFlowIndicator is a FlowIndicator which displays the title of left view 35 | * (if exist), the title of the current select view (centered) and the title of 36 | * the right view (if exist). When the user scrolls the ViewFlow then titles are 37 | * also scrolled. 38 | * 39 | */ 40 | public class TitleFlowIndicator extends TextView implements FlowIndicator { 41 | 42 | private static final float TITLE_PADDING = 10.0f; 43 | private static final float CLIP_PADDING = 0.0f; 44 | private static final int SELECTED_COLOR = 0xFFFFC445; 45 | private static final boolean SELECTED_BOLD = false; 46 | private static final int TEXT_COLOR = 0xFFAAAAAA; 47 | private static final int TEXT_SIZE = 15; 48 | private static final float FOOTER_LINE_HEIGHT = 4.0f; 49 | private static final int FOOTER_COLOR = 0xFFFFC445; 50 | private static final float FOOTER_TRIANGLE_HEIGHT = 10; 51 | private ViewFlow viewFlow; 52 | private int currentScroll = 0; 53 | private TitleProvider titleProvider = null; 54 | private int currentPosition = 0; 55 | private Paint paintText; 56 | private Paint paintSelected; 57 | private Path path; 58 | private Paint paintFooterLine; 59 | private Paint paintFooterTriangle; 60 | private float footerTriangleHeight; 61 | private float titlePadding; 62 | /** 63 | * Left and right side padding for not active view titles. 64 | */ 65 | private float clipPadding; 66 | private float footerLineHeight; 67 | 68 | /* These are hardcoded just like in TextView */ 69 | private static final int SANS = 1; 70 | private static final int SERIF = 2; 71 | private static final int MONOSPACE = 3; 72 | 73 | private Typeface typeface; 74 | 75 | /** 76 | * Default constructor 77 | */ 78 | public TitleFlowIndicator(Context context) { 79 | super(context); 80 | initDraw(TEXT_COLOR, TEXT_SIZE, SELECTED_COLOR, SELECTED_BOLD, TEXT_SIZE, FOOTER_LINE_HEIGHT, FOOTER_COLOR); 81 | } 82 | 83 | /** 84 | * The contructor used with an inflater 85 | * 86 | * @param context 87 | * @param attrs 88 | */ 89 | public TitleFlowIndicator(Context context, AttributeSet attrs) { 90 | super(context, attrs); 91 | // Retrieve styles attributs 92 | 93 | int typefaceIndex = attrs.getAttributeIntValue("http://schemas.android.com/apk/res/android", "typeface", 0); 94 | int textStyleIndex = attrs.getAttributeIntValue("http://schemas.android.com/apk/res/android", "textStyle", 0); 95 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitleFlowIndicator); 96 | 97 | String customTypeface = a.getString(R.styleable.TitleFlowIndicator_customTypeface); 98 | // Retrieve the colors to be used for this view and apply them. 99 | int footerColor = a.getColor(R.styleable.TitleFlowIndicator_footerColor, FOOTER_COLOR); 100 | footerLineHeight = a.getDimension(R.styleable.TitleFlowIndicator_footerLineHeight, FOOTER_LINE_HEIGHT); 101 | footerTriangleHeight = a.getDimension(R.styleable.TitleFlowIndicator_footerTriangleHeight, FOOTER_TRIANGLE_HEIGHT); 102 | int selectedColor = a.getColor(R.styleable.TitleFlowIndicator_selectedColor, SELECTED_COLOR); 103 | boolean selectedBold = a.getBoolean(R.styleable.TitleFlowIndicator_selectedBold, SELECTED_BOLD); 104 | int textColor = a.getColor(R.styleable.TitleFlowIndicator_textColor, TEXT_COLOR); 105 | float textSize = a.getDimension(R.styleable.TitleFlowIndicator_textSize, TEXT_SIZE); 106 | float selectedSize = a.getDimension(R.styleable.TitleFlowIndicator_selectedSize, textSize); 107 | titlePadding = a.getDimension(R.styleable.TitleFlowIndicator_titlePadding, TITLE_PADDING); 108 | clipPadding = a.getDimension(R.styleable.TitleFlowIndicator_clipPadding, CLIP_PADDING); 109 | initDraw(textColor, textSize, selectedColor, selectedBold, selectedSize, footerLineHeight, footerColor); 110 | 111 | if (customTypeface != null) 112 | typeface = Typeface.createFromAsset(context.getAssets(), customTypeface); 113 | else 114 | typeface = getTypefaceByIndex(typefaceIndex); 115 | typeface = Typeface.create(typeface, textStyleIndex); 116 | 117 | } 118 | 119 | /** 120 | * Initialize draw objects 121 | */ 122 | private void initDraw(int textColor, float textSize, int selectedColor, boolean selectedBold, float selectedSize, float footerLineHeight, int footerColor) { 123 | paintText = new Paint(); 124 | paintText.setColor(textColor); 125 | paintText.setTextSize(textSize); 126 | paintText.setAntiAlias(true); 127 | paintSelected = new Paint(); 128 | paintSelected.setColor(selectedColor); 129 | paintSelected.setTextSize(selectedSize); 130 | paintSelected.setFakeBoldText(selectedBold); 131 | paintSelected.setAntiAlias(true); 132 | paintFooterLine = new Paint(); 133 | paintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE); 134 | paintFooterLine.setStrokeWidth(footerLineHeight); 135 | paintFooterLine.setColor(footerColor); 136 | paintFooterTriangle = new Paint(); 137 | paintFooterTriangle.setStyle(Paint.Style.FILL_AND_STROKE); 138 | paintFooterTriangle.setColor(footerColor); 139 | } 140 | 141 | /* 142 | * (non-Javadoc) 143 | * 144 | * @see android.view.View#onDraw(android.graphics.Canvas) 145 | */ 146 | @Override 147 | protected void onDraw(Canvas canvas) { 148 | super.onDraw(canvas); 149 | 150 | // Calculate views bounds 151 | ArrayList bounds = calculateAllBounds(paintText); 152 | 153 | // If no value then add a fake one 154 | int count = (viewFlow != null && viewFlow.getAdapter() != null) ? viewFlow.getAdapter().getCount() : 1; 155 | 156 | // Verify if the current view must be clipped to the screen 157 | Rect curViewBound = bounds.get(currentPosition); 158 | int curViewWidth = curViewBound.right - curViewBound.left; 159 | if (curViewBound.left < 0) { 160 | // Try to clip to the screen (left side) 161 | clipViewOnTheLeft(curViewBound, curViewWidth); 162 | } 163 | if (curViewBound.right > getLeft() + getWidth()) { 164 | // Try to clip to the screen (right side) 165 | clipViewOnTheRight(curViewBound, curViewWidth); 166 | } 167 | 168 | // Left views starting from the current position 169 | if (currentPosition > 0) { 170 | for (int iLoop = currentPosition - 1; iLoop >= 0; iLoop--) { 171 | Rect bound = bounds.get(iLoop); 172 | int w = bound.right - bound.left; 173 | // Si left side is outside the screen 174 | if (bound.left < 0) { 175 | // Try to clip to the screen (left side) 176 | clipViewOnTheLeft(bound, w); 177 | // Except if there's an intersection with the right view 178 | if (iLoop < count - 1 && currentPosition != iLoop) { 179 | Rect rightBound = bounds.get(iLoop + 1); 180 | // Intersection 181 | if (bound.right + TITLE_PADDING > rightBound.left) { 182 | bound.left = rightBound.left - (w + (int) titlePadding); 183 | } 184 | } 185 | } 186 | } 187 | } 188 | // Right views starting from the current position 189 | if (currentPosition < count - 1) { 190 | for (int iLoop = currentPosition + 1; iLoop < count; iLoop++) { 191 | Rect bound = bounds.get(iLoop); 192 | int w = bound.right - bound.left; 193 | // If right side is outside the screen 194 | if (bound.right > getLeft() + getWidth()) { 195 | // Try to clip to the screen (right side) 196 | clipViewOnTheRight(bound, w); 197 | // Except if there's an intersection with the left view 198 | if (iLoop > 0 && currentPosition != iLoop) { 199 | Rect leftBound = bounds.get(iLoop - 1); 200 | // Intersection 201 | if (bound.left - TITLE_PADDING < leftBound.right) { 202 | bound.left = leftBound.right + (int) titlePadding; 203 | } 204 | } 205 | } 206 | } 207 | } 208 | 209 | // Now draw views 210 | for (int iLoop = 0; iLoop < count; iLoop++) { 211 | // Get the title 212 | String title = getTitle(iLoop); 213 | Rect bound = bounds.get(iLoop); 214 | // Only if one side is visible 215 | if ((bound.left > getLeft() && bound.left < getLeft() + getWidth()) || (bound.right > getLeft() && bound.right < getLeft() + getWidth())) { 216 | Paint paint = paintText; 217 | // Change the color is the title is closed to the center 218 | int middle = (bound.left + bound.right) / 2; 219 | if (Math.abs(middle - (getWidth() / 2)) < 20) { 220 | paint = paintSelected; 221 | } 222 | paint.setTypeface(typeface); 223 | canvas.drawText(title, bound.left, bound.bottom, paint); 224 | } 225 | } 226 | 227 | // Draw the footer line 228 | path = new Path(); 229 | int coordY = getHeight() - 1; 230 | coordY -= (footerLineHeight % 2 == 1) ? footerLineHeight / 2 : footerLineHeight / 2 - 1; 231 | path.moveTo(0, coordY); 232 | path.lineTo(getWidth(), coordY); 233 | path.close(); 234 | canvas.drawPath(path, paintFooterLine); 235 | // Draw the footer triangle 236 | path = new Path(); 237 | path.moveTo(getWidth() / 2, getHeight() - footerLineHeight - footerTriangleHeight); 238 | path.lineTo(getWidth() / 2 + footerTriangleHeight, getHeight() - footerLineHeight); 239 | path.lineTo(getWidth() / 2 - footerTriangleHeight, getHeight() - footerLineHeight); 240 | path.close(); 241 | canvas.drawPath(path, paintFooterTriangle); 242 | 243 | } 244 | 245 | /** 246 | * Set bounds for the right textView including clip padding. 247 | * 248 | * @param curViewBound 249 | * current bounds. 250 | * @param curViewWidth 251 | * width of the view. 252 | */ 253 | private void clipViewOnTheRight(Rect curViewBound, int curViewWidth) { 254 | curViewBound.right = getLeft() + getWidth() - (int) clipPadding; 255 | curViewBound.left = curViewBound.right - curViewWidth; 256 | } 257 | 258 | /** 259 | * Set bounds for the left textView including clip padding. 260 | * 261 | * @param curViewBound 262 | * current bounds. 263 | * @param curViewWidth 264 | * width of the view. 265 | */ 266 | private void clipViewOnTheLeft(Rect curViewBound, int curViewWidth) { 267 | curViewBound.left = 0 + (int) clipPadding; 268 | curViewBound.right = curViewWidth; 269 | } 270 | 271 | /** 272 | * Calculate views bounds and scroll them according to the current index 273 | * 274 | * @param paint 275 | * @param currentIndex 276 | * @return 277 | */ 278 | private ArrayList calculateAllBounds(Paint paint) { 279 | ArrayList list = new ArrayList(); 280 | // For each views (If no values then add a fake one) 281 | int count = (viewFlow != null && viewFlow.getAdapter() != null) ? viewFlow.getAdapter().getCount() : 1; 282 | for (int iLoop = 0; iLoop < count; iLoop++) { 283 | Rect bounds = calcBounds(iLoop, paint); 284 | int w = (bounds.right - bounds.left); 285 | int h = (bounds.bottom - bounds.top); 286 | bounds.left = (getWidth() / 2) - (w / 2) - currentScroll + (iLoop * getWidth()); 287 | bounds.right = bounds.left + w; 288 | bounds.top = 0; 289 | bounds.bottom = h; 290 | list.add(bounds); 291 | } 292 | 293 | return list; 294 | } 295 | 296 | /** 297 | * Calculate the bounds for a view's title 298 | * 299 | * @param index 300 | * @param paint 301 | * @return 302 | */ 303 | private Rect calcBounds(int index, Paint paint) { 304 | // Get the title 305 | String title = getTitle(index); 306 | // Calculate the text bounds 307 | Rect bounds = new Rect(); 308 | bounds.right = (int) paint.measureText(title); 309 | bounds.bottom = (int) (paint.descent() - paint.ascent()); 310 | return bounds; 311 | } 312 | 313 | /** 314 | * Returns the title 315 | * 316 | * @param pos 317 | * @return 318 | */ 319 | private String getTitle(int pos) { 320 | // Set the default title 321 | String title = "title " + pos; 322 | // If the TitleProvider exist 323 | if (titleProvider != null) { 324 | title = titleProvider.getTitle(pos); 325 | } 326 | return title; 327 | } 328 | 329 | /* 330 | * (non-Javadoc) 331 | * 332 | * @see org.taptwo.android.widget.FlowIndicator#onScrolled(int, int, int, 333 | * int) 334 | */ 335 | @Override 336 | public void onScrolled(int h, int v, int oldh, int oldv) { 337 | currentScroll = h; 338 | invalidate(); 339 | } 340 | 341 | /* 342 | * (non-Javadoc) 343 | * 344 | * @see 345 | * org.taptwo.android.widget.ViewFlow.ViewSwitchListener#onSwitched(android 346 | * .view.View, int) 347 | */ 348 | @Override 349 | public void onSwitched(View view, int position) { 350 | currentPosition = position; 351 | invalidate(); 352 | } 353 | 354 | /* 355 | * (non-Javadoc) 356 | * 357 | * @see 358 | * org.taptwo.android.widget.FlowIndicator#setViewFlow(org.taptwo.android 359 | * .widget.ViewFlow) 360 | */ 361 | @Override 362 | public void setViewFlow(ViewFlow view) { 363 | viewFlow = view; 364 | currentPosition = view.getSelectedItemPosition(); 365 | invalidate(); 366 | } 367 | 368 | /** 369 | * Set the title provider 370 | * 371 | * @param provider 372 | */ 373 | public void setTitleProvider(TitleProvider provider) { 374 | titleProvider = provider; 375 | } 376 | 377 | /* 378 | * (non-Javadoc) 379 | * 380 | * @see android.view.View#onMeasure(int, int) 381 | */ 382 | @Override 383 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 384 | setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); 385 | } 386 | 387 | /** 388 | * Determines the width of this view 389 | * 390 | * @param measureSpec 391 | * A measureSpec packed into an int 392 | * @return The width of the view, honoring constraints from measureSpec 393 | */ 394 | private int measureWidth(int measureSpec) { 395 | int result = 0; 396 | int specMode = MeasureSpec.getMode(measureSpec); 397 | int specSize = MeasureSpec.getSize(measureSpec); 398 | 399 | if (specMode != MeasureSpec.EXACTLY) { 400 | throw new IllegalStateException("ViewFlow can only be used in EXACTLY mode."); 401 | } 402 | result = specSize; 403 | return result; 404 | } 405 | 406 | /** 407 | * Determines the height of this view 408 | * 409 | * @param measureSpec 410 | * A measureSpec packed into an int 411 | * @return The height of the view, honoring constraints from measureSpec 412 | */ 413 | private int measureHeight(int measureSpec) { 414 | int result = 0; 415 | int specMode = MeasureSpec.getMode(measureSpec); 416 | int specSize = MeasureSpec.getSize(measureSpec); 417 | 418 | // We were told how big to be 419 | if (specMode == MeasureSpec.EXACTLY) { 420 | result = specSize; 421 | } 422 | // Measure the height 423 | else { 424 | // Calculate the text bounds 425 | Rect bounds = new Rect(); 426 | bounds.bottom = (int) (paintText.descent() - paintText.ascent()); 427 | result = bounds.bottom - bounds.top + (int) footerTriangleHeight + (int) footerLineHeight + 10; 428 | return result; 429 | } 430 | return result; 431 | } 432 | 433 | private Typeface getTypefaceByIndex(int typefaceIndex) { 434 | switch (typefaceIndex) { 435 | case SANS: 436 | return Typeface.SANS_SERIF; 437 | 438 | case SERIF: 439 | return Typeface.SERIF; 440 | 441 | case MONOSPACE: 442 | return Typeface.MONOSPACE; 443 | default: 444 | return Typeface.DEFAULT; 445 | } 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /viewflow/src/org/taptwo/android/widget/TitleProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik Åkerfeldt 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.taptwo.android.widget; 17 | 18 | /** 19 | * A TitleProvider provides the title to display according to a view. 20 | */ 21 | public interface TitleProvider { 22 | 23 | /** 24 | * Returns the title of the view at position 25 | * @param position 26 | * @return 27 | */ 28 | public String getTitle(int position); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /viewflow/src/org/taptwo/android/widget/ViewFlow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik Åkerfeldt 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.taptwo.android.widget; 17 | 18 | import android.content.Context; 19 | import android.content.res.Configuration; 20 | import android.content.res.TypedArray; 21 | import android.database.DataSetObserver; 22 | import android.util.AttributeSet; 23 | import android.util.Log; 24 | import android.view.*; 25 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 26 | import android.widget.Adapter; 27 | import android.widget.AdapterView; 28 | import android.widget.Scroller; 29 | import org.taptwo.android.widget.viewflow.R; 30 | 31 | import java.util.EnumSet; 32 | import java.util.LinkedList; 33 | 34 | /** 35 | * A horizontally scrollable {@link ViewGroup} with items populated from an 36 | * {@link Adapter}. The ViewFlow uses a buffer to store loaded {@link View}s in. 37 | * The default size of the buffer is 3 elements on both sides of the currently 38 | * visible {@link View}, making up a total buffer size of 3 * 2 + 1 = 7. The 39 | * buffer size can be changed using the {@code sidebuffer} xml attribute. 40 | * 41 | */ 42 | public class ViewFlow extends AdapterView { 43 | 44 | private static final int SNAP_VELOCITY = 1000; 45 | private static final int INVALID_SCREEN = -1; 46 | private final static int TOUCH_STATE_REST = 0; 47 | private final static int TOUCH_STATE_SCROLLING = 1; 48 | 49 | private LinkedList mLoadedViews; 50 | private LinkedList mRecycledViews; 51 | private int mCurrentBufferIndex; 52 | private int mCurrentAdapterIndex; 53 | private int mSideBuffer = 2; 54 | private Scroller mScroller; 55 | private VelocityTracker mVelocityTracker; 56 | private int mTouchState = TOUCH_STATE_REST; 57 | private float mLastMotionX; 58 | private int mTouchSlop; 59 | private int mMaximumVelocity; 60 | private int mCurrentScreen; 61 | private int mNextScreen = INVALID_SCREEN; 62 | private boolean mFirstLayout = true; 63 | private ViewSwitchListener mViewSwitchListener; 64 | private ViewLazyInitializeListener mViewInitializeListener; 65 | private EnumSet mLazyInit = EnumSet.allOf(LazyInit.class); 66 | private Adapter mAdapter; 67 | private int mLastScrollDirection; 68 | private AdapterDataSetObserver mDataSetObserver; 69 | private FlowIndicator mIndicator; 70 | private int mLastOrientation = -1; 71 | /** Extra return value from obtainView: tells you whether the item it returned on the last call was recycled rather than created by the adapter. 72 | * This is a member because getting a second return value requires an allocation. */ 73 | private boolean mLastObtainedViewWasRecycled = false; 74 | 75 | private OnGlobalLayoutListener orientationChangeListener = new OnGlobalLayoutListener() { 76 | 77 | @Override 78 | public void onGlobalLayout() { 79 | getViewTreeObserver().removeGlobalOnLayoutListener( 80 | orientationChangeListener); 81 | setSelection(mCurrentAdapterIndex); 82 | } 83 | }; 84 | 85 | /** 86 | * Receives call backs when a new {@link View} has been scrolled to. 87 | */ 88 | public static interface ViewSwitchListener { 89 | 90 | /** 91 | * This method is called when a new View has been scrolled to. 92 | * 93 | * @param view 94 | * the {@link View} currently in focus. 95 | * @param position 96 | * The position in the adapter of the {@link View} currently in focus. 97 | */ 98 | void onSwitched(View view, int position); 99 | 100 | } 101 | 102 | public static interface ViewLazyInitializeListener { 103 | void onViewLazyInitialize(View view, int position); 104 | } 105 | 106 | enum LazyInit { 107 | LEFT, RIGHT 108 | } 109 | 110 | public ViewFlow(Context context) { 111 | super(context); 112 | mSideBuffer = 3; 113 | init(); 114 | } 115 | 116 | public ViewFlow(Context context, int sideBuffer) { 117 | super(context); 118 | mSideBuffer = sideBuffer; 119 | init(); 120 | } 121 | 122 | public ViewFlow(Context context, AttributeSet attrs) { 123 | super(context, attrs); 124 | TypedArray styledAttrs = context.obtainStyledAttributes(attrs, 125 | R.styleable.ViewFlow); 126 | mSideBuffer = styledAttrs.getInt(R.styleable.ViewFlow_sidebuffer, 3); 127 | init(); 128 | } 129 | 130 | private void init() { 131 | mLoadedViews = new LinkedList(); 132 | mRecycledViews = new LinkedList(); 133 | mScroller = new Scroller(getContext()); 134 | final ViewConfiguration configuration = ViewConfiguration 135 | .get(getContext()); 136 | mTouchSlop = configuration.getScaledTouchSlop(); 137 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 138 | } 139 | 140 | public void onConfigurationChanged(Configuration newConfig) { 141 | if (newConfig.orientation != mLastOrientation) { 142 | mLastOrientation = newConfig.orientation; 143 | getViewTreeObserver().addOnGlobalLayoutListener(orientationChangeListener); 144 | } 145 | } 146 | 147 | public int getViewsCount() { 148 | return mAdapter.getCount(); 149 | } 150 | 151 | @Override 152 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 153 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 154 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 155 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 156 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 157 | 158 | int childWidth = 0; 159 | int childHeight = 0; 160 | int childState = 0; 161 | 162 | final int widthPadding = getWidthPadding(); 163 | final int heightPadding = getHeightPadding(); 164 | 165 | int count = mAdapter == null ? 0 : mAdapter.getCount(); 166 | if (count > 0) { 167 | final View child = obtainView(0); 168 | measureChild(child, widthMeasureSpec, heightMeasureSpec); 169 | childWidth = child.getMeasuredWidth(); 170 | childHeight = child.getMeasuredHeight(); 171 | childState = child.getMeasuredState(); 172 | mRecycledViews.add(child); 173 | } 174 | 175 | switch (widthMode) { 176 | case MeasureSpec.UNSPECIFIED: 177 | widthSize = childWidth + widthPadding; 178 | break; 179 | case MeasureSpec.AT_MOST: 180 | widthSize = (childWidth + widthPadding) | childState; 181 | break; 182 | case MeasureSpec.EXACTLY: 183 | if (widthSize < childWidth + widthPadding) 184 | widthSize |= MEASURED_STATE_TOO_SMALL; 185 | break; 186 | } 187 | switch (heightMode) { 188 | case MeasureSpec.UNSPECIFIED: 189 | heightSize = childHeight + heightPadding; 190 | break; 191 | case MeasureSpec.AT_MOST: 192 | heightSize = (childHeight + heightPadding) | (childState >> MEASURED_HEIGHT_STATE_SHIFT); 193 | break; 194 | case MeasureSpec.EXACTLY: 195 | if (heightSize < childHeight + heightPadding) 196 | heightSize |= MEASURED_STATE_TOO_SMALL; 197 | break; 198 | } 199 | 200 | if (heightMode == MeasureSpec.UNSPECIFIED) { 201 | heightSize = heightPadding + childHeight; 202 | } else { 203 | heightSize |= (childState&MEASURED_STATE_MASK); 204 | } 205 | 206 | setMeasuredDimension(widthSize, heightSize); 207 | } 208 | 209 | private int getWidthPadding() { 210 | return getPaddingLeft() + getPaddingRight() + getHorizontalFadingEdgeLength() * 2; 211 | } 212 | 213 | public int getChildWidth() { 214 | return getWidth() - getWidthPadding(); 215 | } 216 | 217 | private int getHeightPadding() { 218 | return getPaddingTop() + getPaddingBottom(); 219 | } 220 | 221 | public int getChildHeight() { 222 | return getHeight() - getHeightPadding(); 223 | } 224 | 225 | @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { 226 | super.onSizeChanged(w, h, oldw, oldh); 227 | 228 | final int count = getChildCount(); 229 | for (int i = 0; i < count ; ++i) { 230 | final View child = getChildAt(i); 231 | child.measure(MeasureSpec.makeMeasureSpec(getChildWidth(), MeasureSpec.EXACTLY), 232 | MeasureSpec.makeMeasureSpec(getChildHeight(), MeasureSpec.EXACTLY)); 233 | } 234 | 235 | if (mFirstLayout) { 236 | mScroller.startScroll(0, 0, mCurrentScreen * getChildWidth(), 0, 0); 237 | mFirstLayout = false; 238 | } 239 | } 240 | 241 | @Override 242 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 243 | int childLeft = getPaddingLeft() + getHorizontalFadingEdgeLength(); 244 | 245 | final int count = getChildCount(); 246 | for (int i = 0; i < count; i++) { 247 | final View child = getChildAt(i); 248 | if (child.getVisibility() != View.GONE) { 249 | final int childWidth = child.getMeasuredWidth(); 250 | child.layout(childLeft, getPaddingTop(), childLeft + childWidth, 251 | getPaddingTop() + child.getMeasuredHeight()); 252 | childLeft += childWidth; 253 | } 254 | } 255 | } 256 | 257 | @Override protected float getTopFadingEdgeStrength() { 258 | return 0.0f; 259 | } 260 | 261 | @Override protected float getBottomFadingEdgeStrength() { 262 | return 0.0f; 263 | } 264 | 265 | @Override protected float getLeftFadingEdgeStrength() { 266 | // always do the fading edge 267 | return 1.0f; 268 | } 269 | 270 | @Override protected float getRightFadingEdgeStrength() { 271 | // always do the fading edge 272 | return 1.0f; 273 | } 274 | 275 | @Override 276 | public boolean onInterceptTouchEvent(MotionEvent ev) { 277 | if (getChildCount() == 0) 278 | return false; 279 | 280 | if (mVelocityTracker == null) { 281 | mVelocityTracker = VelocityTracker.obtain(); 282 | } 283 | mVelocityTracker.addMovement(ev); 284 | 285 | final int action = ev.getAction(); 286 | final float x = ev.getX(); 287 | 288 | switch (action) { 289 | case MotionEvent.ACTION_DOWN: 290 | /* 291 | * If being flinged and user touches, stop the fling. isFinished 292 | * will be false if being flinged. 293 | */ 294 | if (!mScroller.isFinished()) { 295 | mScroller.abortAnimation(); 296 | } 297 | 298 | // Remember where the motion event started 299 | mLastMotionX = x; 300 | 301 | mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST 302 | : TOUCH_STATE_SCROLLING; 303 | 304 | break; 305 | 306 | case MotionEvent.ACTION_MOVE: 307 | final int deltaX = (int) (mLastMotionX - x); 308 | 309 | boolean xMoved = Math.abs(deltaX) > mTouchSlop; 310 | 311 | if (xMoved) { 312 | // Scroll if the user moved far enough along the X axis 313 | mTouchState = TOUCH_STATE_SCROLLING; 314 | 315 | if (mViewInitializeListener != null) 316 | initializeView(deltaX); 317 | } 318 | 319 | if (mTouchState == TOUCH_STATE_SCROLLING) { 320 | // Scroll to follow the motion event 321 | 322 | mLastMotionX = x; 323 | 324 | final int scrollX = getScrollX(); 325 | if (deltaX < 0) { 326 | if (scrollX > 0) { 327 | scrollBy(Math.max(-scrollX, deltaX), 0); 328 | } 329 | } else if (deltaX > 0) { 330 | final int availableToScroll = getChildAt( 331 | getChildCount() - 1).getRight() 332 | - getPaddingRight() - getHorizontalFadingEdgeLength() 333 | - scrollX - getWidth(); 334 | if (availableToScroll > 0) { 335 | scrollBy(Math.min(availableToScroll, deltaX), 0); 336 | } 337 | } 338 | return true; 339 | } 340 | break; 341 | 342 | case MotionEvent.ACTION_UP: 343 | if (mTouchState == TOUCH_STATE_SCROLLING) { 344 | final VelocityTracker velocityTracker = mVelocityTracker; 345 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 346 | int velocityX = (int) velocityTracker.getXVelocity(); 347 | 348 | if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { 349 | // Fling hard enough to move left 350 | snapToScreen(mCurrentScreen - 1); 351 | } else if (velocityX < -SNAP_VELOCITY 352 | && mCurrentScreen < getChildCount() - 1) { 353 | // Fling hard enough to move right 354 | snapToScreen(mCurrentScreen + 1); 355 | } else { 356 | snapToDestination(); 357 | } 358 | 359 | if (mVelocityTracker != null) { 360 | mVelocityTracker.recycle(); 361 | mVelocityTracker = null; 362 | } 363 | } 364 | 365 | mTouchState = TOUCH_STATE_REST; 366 | 367 | break; 368 | case MotionEvent.ACTION_CANCEL: 369 | mTouchState = TOUCH_STATE_REST; 370 | } 371 | return false; 372 | } 373 | 374 | @Override 375 | public boolean onTouchEvent(MotionEvent ev) { 376 | if (getChildCount() == 0) 377 | return false; 378 | 379 | if (mVelocityTracker == null) { 380 | mVelocityTracker = VelocityTracker.obtain(); 381 | } 382 | mVelocityTracker.addMovement(ev); 383 | 384 | final int action = ev.getAction(); 385 | final float x = ev.getX(); 386 | 387 | switch (action) { 388 | case MotionEvent.ACTION_DOWN: 389 | /* 390 | * If being flinged and user touches, stop the fling. isFinished 391 | * will be false if being flinged. 392 | */ 393 | if (!mScroller.isFinished()) { 394 | mScroller.abortAnimation(); 395 | } 396 | 397 | // Remember where the motion event started 398 | mLastMotionX = x; 399 | 400 | mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST 401 | : TOUCH_STATE_SCROLLING; 402 | 403 | break; 404 | 405 | case MotionEvent.ACTION_MOVE: 406 | final int deltaX = (int) (mLastMotionX - x); 407 | 408 | boolean xMoved = Math.abs(deltaX) > mTouchSlop; 409 | 410 | if (xMoved) { 411 | // Scroll if the user moved far enough along the X axis 412 | mTouchState = TOUCH_STATE_SCROLLING; 413 | 414 | if (mViewInitializeListener != null) 415 | initializeView(deltaX); 416 | } 417 | 418 | if (mTouchState == TOUCH_STATE_SCROLLING) { 419 | // Scroll to follow the motion event 420 | 421 | mLastMotionX = x; 422 | 423 | final int scrollX = getScrollX(); 424 | if (deltaX < 0) { 425 | if (scrollX > 0) { 426 | scrollBy(Math.max(-scrollX, deltaX), 0); 427 | } 428 | } else if (deltaX > 0) { 429 | final int availableToScroll = getChildAt( 430 | getChildCount() - 1).getRight() 431 | - getPaddingRight() - getHorizontalFadingEdgeLength() 432 | - scrollX - getChildWidth(); 433 | if (availableToScroll > 0) { 434 | scrollBy(Math.min(availableToScroll, deltaX), 0); 435 | } 436 | } 437 | return true; 438 | } 439 | break; 440 | 441 | case MotionEvent.ACTION_UP: 442 | if (mTouchState == TOUCH_STATE_SCROLLING) { 443 | final VelocityTracker velocityTracker = mVelocityTracker; 444 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 445 | int velocityX = (int) velocityTracker.getXVelocity(); 446 | 447 | if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { 448 | // Fling hard enough to move left 449 | snapToScreen(mCurrentScreen - 1); 450 | } else if (velocityX < -SNAP_VELOCITY 451 | && mCurrentScreen < getChildCount() - 1) { 452 | // Fling hard enough to move right 453 | snapToScreen(mCurrentScreen + 1); 454 | } else { 455 | snapToDestination(); 456 | } 457 | 458 | if (mVelocityTracker != null) { 459 | mVelocityTracker.recycle(); 460 | mVelocityTracker = null; 461 | } 462 | } 463 | 464 | mTouchState = TOUCH_STATE_REST; 465 | 466 | break; 467 | case MotionEvent.ACTION_CANCEL: 468 | snapToDestination(); 469 | mTouchState = TOUCH_STATE_REST; 470 | } 471 | return true; 472 | } 473 | 474 | private void initializeView(final float direction) { 475 | if (direction > 0) { 476 | if (mLazyInit.contains(LazyInit.RIGHT)) { 477 | mLazyInit.remove(LazyInit.RIGHT); 478 | if (mCurrentBufferIndex+1 < mLoadedViews.size()) 479 | mViewInitializeListener.onViewLazyInitialize(mLoadedViews.get(mCurrentBufferIndex + 1), mCurrentAdapterIndex + 1); 480 | } 481 | } else { 482 | if (mLazyInit.contains(LazyInit.LEFT)) { 483 | mLazyInit.remove(LazyInit.LEFT); 484 | if (mCurrentBufferIndex > 0) 485 | mViewInitializeListener.onViewLazyInitialize(mLoadedViews.get(mCurrentBufferIndex - 1), mCurrentAdapterIndex - 1); 486 | } 487 | } 488 | } 489 | 490 | @Override 491 | protected void onScrollChanged(int h, int v, int oldh, int oldv) { 492 | super.onScrollChanged(h, v, oldh, oldv); 493 | if (mIndicator != null) { 494 | /* 495 | * The actual horizontal scroll origin does typically not match the 496 | * perceived one. Therefore, we need to calculate the perceived 497 | * horizontal scroll origin here, since we use a view buffer. 498 | */ 499 | int hPerceived = h + (mCurrentAdapterIndex - mCurrentBufferIndex) 500 | * getChildWidth(); 501 | mIndicator.onScrolled(hPerceived, v, oldh, oldv); 502 | } 503 | } 504 | 505 | private void snapToDestination() { 506 | final int screenWidth = getChildWidth(); 507 | final int whichScreen = (getScrollX() + (screenWidth / 2)) 508 | / screenWidth; 509 | 510 | snapToScreen(whichScreen); 511 | } 512 | 513 | private void snapToScreen(int whichScreen) { 514 | mLastScrollDirection = whichScreen - mCurrentScreen; 515 | if (!mScroller.isFinished()) 516 | return; 517 | 518 | whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); 519 | 520 | mNextScreen = whichScreen; 521 | 522 | final int newX = whichScreen * getChildWidth(); 523 | final int delta = newX - getScrollX(); 524 | mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2); 525 | invalidate(); 526 | } 527 | 528 | @Override 529 | public void computeScroll() { 530 | if (mScroller.computeScrollOffset()) { 531 | scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 532 | postInvalidate(); 533 | } else if (mNextScreen != INVALID_SCREEN) { 534 | mCurrentScreen = Math.max(0, 535 | Math.min(mNextScreen, getChildCount() - 1)); 536 | mNextScreen = INVALID_SCREEN; 537 | post(new Runnable() { 538 | @Override public void run() { 539 | postViewSwitched(mLastScrollDirection); 540 | } 541 | }); 542 | } 543 | } 544 | 545 | /** 546 | * Scroll to the {@link View} in the view buffer specified by the index. 547 | * 548 | * @param indexInBuffer 549 | * Index of the view in the view buffer. 550 | */ 551 | private void setVisibleView(int indexInBuffer, boolean uiThread) { 552 | mCurrentScreen = Math.max(0, 553 | Math.min(indexInBuffer, getChildCount() - 1)); 554 | int dx = (mCurrentScreen * getChildWidth()) - mScroller.getCurrX(); 555 | mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), dx, 556 | 0, 0); 557 | if(dx == 0) 558 | onScrollChanged(mScroller.getCurrX() + dx, mScroller.getCurrY(), mScroller.getCurrX() + dx, mScroller.getCurrY()); 559 | if (uiThread) 560 | invalidate(); 561 | else 562 | postInvalidate(); 563 | } 564 | 565 | /** 566 | * Set the listener that will receive notifications every time the {code 567 | * ViewFlow} scrolls. 568 | * 569 | * @param l 570 | * the scroll listener 571 | */ 572 | public void setOnViewSwitchListener(ViewSwitchListener l) { 573 | mViewSwitchListener = l; 574 | } 575 | 576 | public void setOnViewLazyInitializeListener(ViewLazyInitializeListener l) { 577 | mViewInitializeListener = l; 578 | } 579 | 580 | @Override 581 | public Adapter getAdapter() { 582 | return mAdapter; 583 | } 584 | 585 | @Override 586 | public void setAdapter(Adapter adapter) { 587 | setAdapter(adapter, 0); 588 | } 589 | 590 | public void setAdapter(Adapter adapter, int initialPosition) { 591 | if (mAdapter != null) { 592 | mAdapter.unregisterDataSetObserver(mDataSetObserver); 593 | } 594 | 595 | mAdapter = adapter; 596 | 597 | if (mAdapter != null) { 598 | mDataSetObserver = new AdapterDataSetObserver(); 599 | mAdapter.registerDataSetObserver(mDataSetObserver); 600 | 601 | } 602 | if (mAdapter == null || mAdapter.getCount() == 0) 603 | return; 604 | 605 | setSelection(initialPosition); 606 | } 607 | 608 | @Override 609 | public View getSelectedView() { 610 | return (mCurrentBufferIndex < mLoadedViews.size() ? mLoadedViews 611 | .get(mCurrentBufferIndex) : null); 612 | } 613 | 614 | @Override 615 | public int getSelectedItemPosition() { 616 | return mCurrentAdapterIndex; 617 | } 618 | 619 | /** 620 | * Set the FlowIndicator 621 | * 622 | * @param flowIndicator 623 | */ 624 | public void setFlowIndicator(FlowIndicator flowIndicator) { 625 | mIndicator = flowIndicator; 626 | mIndicator.setViewFlow(this); 627 | } 628 | 629 | protected void recycleViews() { 630 | while (!mLoadedViews.isEmpty()) 631 | recycleView(mLoadedViews.remove()); 632 | } 633 | 634 | protected void recycleView(View v) { 635 | if (v == null) 636 | return; 637 | mRecycledViews.addFirst(v); 638 | detachViewFromParent(v); 639 | } 640 | 641 | protected View getRecycledView() { 642 | return (mRecycledViews.isEmpty() ? null : mRecycledViews.remove()); 643 | } 644 | 645 | @Override 646 | public void setSelection(int position) { 647 | mNextScreen = INVALID_SCREEN; 648 | mScroller.forceFinished(true); 649 | if (mAdapter == null) 650 | return; 651 | 652 | position = Math.max(position, 0); 653 | position = Math.min(position, mAdapter.getCount()-1); 654 | 655 | recycleViews(); 656 | 657 | View currentView = makeAndAddView(position, true); 658 | mLoadedViews.addLast(currentView); 659 | 660 | if (mViewInitializeListener != null) 661 | mViewInitializeListener.onViewLazyInitialize(currentView, position); 662 | 663 | for(int offset = 1; mSideBuffer - offset >= 0; offset++) { 664 | int leftIndex = position - offset; 665 | int rightIndex = position + offset; 666 | if(leftIndex >= 0) 667 | mLoadedViews.addFirst(makeAndAddView(leftIndex, false)); 668 | if(rightIndex < mAdapter.getCount()) 669 | mLoadedViews.addLast(makeAndAddView(rightIndex, true)); 670 | } 671 | 672 | mCurrentBufferIndex = mLoadedViews.indexOf(currentView); 673 | mCurrentAdapterIndex = position; 674 | 675 | requestLayout(); 676 | setVisibleView(mCurrentBufferIndex, false); 677 | if (mIndicator != null) { 678 | mIndicator.onSwitched(currentView, mCurrentAdapterIndex); 679 | } 680 | if (mViewSwitchListener != null) { 681 | mViewSwitchListener.onSwitched(currentView, mCurrentAdapterIndex); 682 | } 683 | } 684 | 685 | private void resetFocus() { 686 | logBuffer(); 687 | recycleViews(); 688 | removeAllViewsInLayout(); 689 | mLazyInit.addAll(EnumSet.allOf(LazyInit.class)); 690 | 691 | for (int i = Math.max(0, mCurrentAdapterIndex - mSideBuffer); i < Math 692 | .min(mAdapter.getCount(), mCurrentAdapterIndex + mSideBuffer 693 | + 1); i++) { 694 | mLoadedViews.addLast(makeAndAddView(i, true)); 695 | if (i == mCurrentAdapterIndex) { 696 | mCurrentBufferIndex = mLoadedViews.size() - 1; 697 | if (mViewInitializeListener != null) 698 | mViewInitializeListener.onViewLazyInitialize(mLoadedViews.getLast(), mCurrentAdapterIndex); 699 | } 700 | } 701 | logBuffer(); 702 | requestLayout(); 703 | } 704 | 705 | private void postViewSwitched(int direction) { 706 | if (direction == 0) 707 | return; 708 | 709 | if (direction > 0) { // to the right 710 | mCurrentAdapterIndex++; 711 | mCurrentBufferIndex++; 712 | mLazyInit.remove(LazyInit.LEFT); 713 | mLazyInit.add(LazyInit.RIGHT); 714 | 715 | // Recycle view outside buffer range 716 | if (mCurrentAdapterIndex > mSideBuffer) { 717 | recycleView(mLoadedViews.removeFirst()); 718 | mCurrentBufferIndex--; 719 | } 720 | 721 | // Add new view to buffer 722 | int newBufferIndex = mCurrentAdapterIndex + mSideBuffer; 723 | if (newBufferIndex < mAdapter.getCount()) 724 | mLoadedViews.addLast(makeAndAddView(newBufferIndex, true)); 725 | 726 | } else { // to the left 727 | mCurrentAdapterIndex--; 728 | mCurrentBufferIndex--; 729 | mLazyInit.add(LazyInit.LEFT); 730 | mLazyInit.remove(LazyInit.RIGHT); 731 | 732 | // Recycle view outside buffer range 733 | if (mAdapter.getCount() - 1 - mCurrentAdapterIndex > mSideBuffer) { 734 | recycleView(mLoadedViews.removeLast()); 735 | } 736 | 737 | // Add new view to buffer 738 | int newBufferIndex = mCurrentAdapterIndex - mSideBuffer; 739 | if (newBufferIndex > -1) { 740 | mLoadedViews.addFirst(makeAndAddView(newBufferIndex, false)); 741 | mCurrentBufferIndex++; 742 | } 743 | 744 | } 745 | 746 | requestLayout(); 747 | setVisibleView(mCurrentBufferIndex, true); 748 | if (mIndicator != null) { 749 | mIndicator.onSwitched(mLoadedViews.get(mCurrentBufferIndex), 750 | mCurrentAdapterIndex); 751 | } 752 | if (mViewSwitchListener != null) { 753 | mViewSwitchListener 754 | .onSwitched(mLoadedViews.get(mCurrentBufferIndex), 755 | mCurrentAdapterIndex); 756 | } 757 | logBuffer(); 758 | } 759 | 760 | @Override protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { 761 | LayoutParams lp = child.getLayoutParams(); 762 | final int childWidthSpec = getChildMeasureSpec(parentWidthMeasureSpec, getWidthPadding(), lp.width); 763 | final int childHeightSpec = getChildMeasureSpec(parentHeightMeasureSpec, getHeightPadding(), lp.height); 764 | child.measure(childWidthSpec, childHeightSpec); 765 | } 766 | 767 | private View setupChild(View child, boolean addToEnd, boolean recycle) { 768 | final LayoutParams lp = child.getLayoutParams(); 769 | child.measure(MeasureSpec.makeMeasureSpec(getChildWidth(), MeasureSpec.EXACTLY), 770 | MeasureSpec.makeMeasureSpec(getChildHeight(), MeasureSpec.EXACTLY)); 771 | if (recycle) 772 | attachViewToParent(child, (addToEnd ? -1 : 0), lp); 773 | else 774 | addViewInLayout(child, (addToEnd ? -1 : 0), lp, true); 775 | return child; 776 | } 777 | 778 | private View makeAndAddView(int position, boolean addToEnd) { 779 | View view = obtainView(position); 780 | return setupChild(view, addToEnd, mLastObtainedViewWasRecycled); 781 | } 782 | 783 | private View obtainView(int position) { 784 | View convertView = getRecycledView(); 785 | View view = mAdapter.getView(position, convertView, this); 786 | if(view != convertView && convertView != null) 787 | mRecycledViews.add(convertView); 788 | mLastObtainedViewWasRecycled = (view == convertView); 789 | ViewGroup.LayoutParams p = view.getLayoutParams(); 790 | if (p == null) { 791 | p = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); 792 | view.setLayoutParams(p); 793 | } 794 | return view; 795 | } 796 | 797 | class AdapterDataSetObserver extends DataSetObserver { 798 | 799 | @Override 800 | public void onChanged() { 801 | View v = getChildAt(mCurrentBufferIndex); 802 | if (v != null) { 803 | for (int index = 0; index < mAdapter.getCount(); index++) { 804 | if (v.equals(mAdapter.getItem(index))) { 805 | mCurrentAdapterIndex = index; 806 | break; 807 | } 808 | } 809 | } 810 | resetFocus(); 811 | } 812 | 813 | @Override 814 | public void onInvalidated() { 815 | // Not yet implemented! 816 | } 817 | 818 | } 819 | 820 | private void logBuffer() { 821 | 822 | Log.d("viewflow", "Size of mLoadedViews: " + mLoadedViews.size() + 823 | ", Size of mRecycledViews: " + mRecycledViews.size() + 824 | ", X: " + mScroller.getCurrX() + ", Y: " + mScroller.getCurrY()); 825 | Log.d("viewflow", "IndexInAdapter: " + mCurrentAdapterIndex 826 | + ", IndexInBuffer: " + mCurrentBufferIndex); 827 | } 828 | } 829 | --------------------------------------------------------------------------------