├── Dec-04-2016 21-36-53.gif
├── Demo App
├── config.json
├── controllers
│ └── index.js
├── styles
│ └── index.tss
└── views
│ └── index.xml
├── README.md
└── in.prashant.scrollableViews
├── controllers
└── widget.js
├── styles
└── widget.tss
├── views
└── widget.xml
└── widget.json
/Dec-04-2016 21-36-53.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashantsaini1/titanium-scrollable-views/5234617c5ab5df4788a0b02124406cfffef8d1d1/Dec-04-2016 21-36-53.gif
--------------------------------------------------------------------------------
/Demo App/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "global": {},
3 | "env:development": {},
4 | "env:test": {},
5 | "env:production": {},
6 | "os:android": {},
7 | "os:blackberry": {},
8 | "os:ios": {},
9 | "os:mobileweb": {},
10 | "dependencies": {
11 | "in.prashant.scrollableViews" : "*"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Demo App/controllers/index.js:
--------------------------------------------------------------------------------
1 |
2 | $.index.open();
3 |
4 | function add() {
5 | $.scrollWidget.add([Ti.UI.createView(), Ti.UI.createView(), Ti.UI.createView()], ["#a00", '#0a0', '#aa0'], true);
6 | }
7 |
8 | function remove() {
9 | $.scrollWidget.remove(0);
10 | $.scrollWidget.remove(0);
11 | $.scrollWidget.remove(0);
12 | }
13 |
14 | // master
15 | // another pull test
16 |
--------------------------------------------------------------------------------
/Demo App/styles/index.tss:
--------------------------------------------------------------------------------
1 | "#scrollWidget": {
2 | // height : Ti.UI.FILL,
3 | width: Ti.UI.FILL,
4 | top: 50,
5 | bottom: 50,
6 | pagingPosition : 'top',
7 | pagingColor : 'white',
8 | pagingEffect : true,
9 | backdropEffect : true,
10 | backdropColors : ['teal', 'silver', 'cyan', 'pink']
11 | }
12 |
13 | "Button" : {
14 | color : 'white'
15 | }
16 |
--------------------------------------------------------------------------------
/Demo App/views/index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # titanium-scrollable-views
2 | A cross platform Alloy - Titanium scrollable widget with background color transition effect and animated paging controls.
3 |
4 | 
5 |
6 | This widget provides you a customizable scrollable view container with background color transition effect (optional) and nice paging controls for iOS & Android.
7 |
8 | ## Installation
9 |
10 | Add in your *config.json*, under `dependencies`:
11 |
12 | ```
13 | "dependencies": {
14 | "in.prashant.scrollableViews" : "*"
15 | }
16 | ```
17 |
18 | ## Usage Titanium Classic
19 | ```javascript
20 | var widget = Alloy.createWidget('in.prashant.scrollableViews', {
21 | width: Ti.UI.FILL,
22 | top: 50,
23 | bottom: 50,
24 | pagingPosition : 'top',
25 | pagingColor : 'white',
26 | pagingEffect : true,
27 | backdropEffect : true,
28 | backdropColors : ['teal', 'silver', 'cyan', 'pink'],
29 | views : [Ti.UI.createView(), Ti.UI.createView(), Ti.UI.createView(), Ti.UI.createView()]
30 | });
31 | ```
32 |
33 | ## Usage Alloy
34 | *index.xml*
35 | ```xml
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ```
45 |
46 | *index.tss*
47 | ```tss
48 | "#scrollWidget": {
49 | width: Ti.UI.FILL,
50 | top: 50,
51 | bottom: 50,
52 | pagingPosition : 'top',
53 | pagingColor : 'white',
54 | pagingEffect : true,
55 | backdropEffect : true,
56 | backdropColors : ['teal', 'silver', 'cyan', 'pink']
57 | }
58 |
59 | "Button" : {
60 | color : 'white'
61 | }
62 | ```
63 |
64 | Arguments
65 | * **backdropEffect** : set the background transition effect (true/false) - defaults to false - do not set background colors of Ti.UI.View to take effect of __backdropEffect__ property
66 | * **backdropColors** : array of colors to use with backdropEffect - defaults to transparent
67 | * **pagingEffect** : shows the paging control (true/false) - defaults to true
68 | * **pagerStyle** : pager style - pass 1 for ring type pagers or pass 2 for solid colored pagers, if value is 2 then optionally pass **pagingBackColor** for unselected pagers
69 | * **pagingPosition** : position of paging control - pass string "top" or "bottom"
70 | * **pagingPadding** : padding between pagers in paging control - defaults to 7dp
71 | * **pagingColor** : color of paging control - defaults to white
72 | * **pagingBackColor** : sets the color of unselected pagers if pagerStyle is 2, otherwise not effective
73 | * **clip** : iOS only - clip views to show them adjacently - defaults to false
74 | * **clipPadding** : padding of views when clip is true
75 | * **index** : index of the current page to set in widget
76 |
77 | Methods
78 | * **add**: Add view(s) at once with below arguments
79 | * *argument 1* : pass single view (Ti.UI.View) or an array of views to add.
80 | * *argument 2* : color or an array of colors for **backdropEffect**. _(optional)_
81 | * *argument 3* : boolean to scroll to last view/page added. _(optional)_
82 | * **remove**: Remove view by passing either View or index of the view - *iOS + Android* ( :stuck_out_tongue_winking_eye: but Ti SDK allows to remove the view by index on iOS only)
83 | * *argument 1* : Ti.UI.View object or index of the view to remove.
84 |
85 | Exposer Properties
86 | * **scrollableView**: instance of the Ti.UI.ScrollableView used in this widget.
87 |
88 |
89 | ## Contributions
90 |
91 | If you enjoy this module, feel free to contribute with your PR or [donate](https://paypal.me/prashantsainii) :-)
92 |
--------------------------------------------------------------------------------
/in.prashant.scrollableViews/controllers/widget.js:
--------------------------------------------------------------------------------
1 | var args = $.args;
2 | var lastPage = 0,
3 | totalPages = 0,
4 | currentPage = 0,
5 | isRemoving = false, // to stop calling `onScrollAnimate` while removing the view
6 | pagerPadding = 5,
7 | pagerStyle = ((args.pagerStyle === undefined) || (args.pagerStyle == 1)) ? 1 : 2, // 1 for ring type pagers, 2 for solid pagers
8 | opacityEffect = ((args.backdropEffect === undefined) || (args.backdropEffect == false)) ? false : true,
9 | pagingEffect = ((args.pagingEffect === undefined) || (args.pagingEffect == true)) ? true : false,
10 | pagingTopBottom = (args.pagingPosition === undefined) || (args.pagingPosition === "bottom") ? 'bottom' : 'top',
11 | pagingColor = args.pagingColor || 'white',
12 | pagingBackColor = (pagerStyle == 1) ? pagingColor : (args.pagingBackColor || '#aaa'),
13 | defaultPadding = parseInt(args.pagingPadding) || 7,
14 | pagerPosition = 12 + defaultPadding; // default left position of animated pager control
15 |
16 |
17 | (function constructor() {
18 | var views = [];
19 |
20 | // get children if Alloy is used, or use `views` key to get pages views
21 | if ((args.views !== undefined) && _.isArray(args.views)) {
22 | views = _.filter(args.views, function(view) {
23 | return view.apiName == 'Ti.UI.View';
24 | });
25 |
26 | } else if (args.children !== undefined) {
27 | views = _.filter(args.children, function(view) {
28 | return view.apiName == 'Ti.UI.View';
29 | });
30 | }
31 |
32 | totalPages = views.length;
33 |
34 | // only iOS supports clipMode to show adjacent views
35 | if (OS_IOS && (args.clip !== undefined) && (args.clip != false)) {
36 | $.SCROLLABLE_VIEW.left = $.SCROLLABLE_VIEW.right = args.clipPadding || 0;
37 | $.SCROLLABLE_VIEW.clipViews = false;
38 | }
39 |
40 | // set pager controls - at bottom or top
41 | if (pagingTopBottom === 'bottom') {
42 | $.scrollView.bottom = pagerPadding;
43 | } else {
44 | $.scrollView.top = pagerPadding;
45 | }
46 |
47 | // set pager controls visible or invisible
48 | $.scrollView.visible = pagingEffect;
49 | $.pagerControl.borderColor = pagingColor;
50 |
51 | // set properties on main container
52 | var props = _.pick(args, 'width', 'height', 'top', 'bottom', 'left', 'right', 'backgroundColor', 'opacity', 'cacheSize');
53 | $.container.applyProperties(props);
54 |
55 | if (totalPages > 0) {
56 | // set views
57 | $.SCROLLABLE_VIEW.views = views;
58 |
59 | // set lastPage to zero or defined one
60 | if ((args.index === undefined) || (args.index > (args.children.length - 1))) {
61 | currentPage = 0;
62 |
63 | } else {
64 | currentPage = parseInt(args.index) || 0; // parse valid index of current page
65 | currentPage = (currentPage < 0) ? 0 : currentPage; // if index is -ve, use default 0 index
66 | currentPage = (currentPage > (totalPages - 1)) ? 0 : currentPage; // if greater than total views, set to 0
67 | }
68 |
69 | $.SCROLLABLE_VIEW.currentPage = currentPage;
70 | }
71 |
72 | // create paging controls or remove pager from its parent container
73 | if (pagingEffect) {
74 | createPager(totalPages);
75 | $.pagerControl.left = currentPage * pagerPosition;
76 | $.SCROLLABLE_VIEW.addEventListener('scroll', animateControl);
77 | }
78 |
79 | // create backdrop views
80 | if (opacityEffect) {
81 | for (var i = 0; i < totalPages; ++i) {
82 | $.backdropViews.add(Ti.UI.createView({
83 | backgroundColor : (args.backdropColors == undefined) ? 'transparent' : args.backdropColors[i],
84 | opacity : (i <= currentPage) ? 1 : 0
85 | }));
86 | }
87 | $.SCROLLABLE_VIEW.addEventListener('scroll', onScrollAnimate);
88 |
89 | } else {
90 | $.SCROLLABLE_VIEW.addEventListener('scrollend', function (e) {
91 | if (e.source.id !== "SCROLLABLE_VIEW") { return; }
92 | currentPage = e.currentPage;
93 | });
94 | }
95 |
96 | })();
97 |
98 | function createPager(_total) {
99 | var totalTempPagers = $.PAGING_VIEW.children.length;
100 | totalTempPagers = (totalTempPagers == 0) ? true : false;
101 |
102 | for (var i = 0; i < _total; ++i) {
103 | $.PAGING_VIEW.add(Ti.UI.createView({
104 | width: 12,
105 | height: 12,
106 | borderRadius: 6,
107 | touchEnabled: false,
108 | left : totalTempPagers ? 0 : defaultPadding,
109 | borderWidth : (pagerStyle == 1) ? 1.2 : 6.5,
110 | borderColor : pagingBackColor
111 | }));
112 |
113 | totalTempPagers = false;
114 | }
115 | }
116 |
117 | function animateControl(e) {
118 | if ((totalPages <= 1) || (e.currentPageAsFloat < 0) || (e.currentPageAsFloat > (totalPages - 1))) {
119 | return;
120 | }
121 |
122 | $.pagerControl.left = e.currentPageAsFloat * pagerPosition;
123 | }
124 |
125 | function onScrollAnimate(e) {
126 | if (e.source.id !== "SCROLLABLE_VIEW") { return; }
127 |
128 | if (isRemoving) { return; }
129 |
130 | var cFloat = e.currentPageAsFloat;
131 |
132 | if ((totalPages <= 1) || (cFloat < 0) || (cFloat > $.SCROLLABLE_VIEW.views.length - 1)) {
133 | return;
134 | }
135 |
136 | var delta = cFloat - Math.floor(cFloat);
137 |
138 | // require('log')('Delta = ' + delta + ' : Float = ' + cFloat + ' : Page = ' + e.currentPage);
139 |
140 | if (cFloat > currentPage) {
141 | // on iOS, scrolling can be jumped slightly to next page when swiped very fastly
142 | // it gives currentPageAsFloat greater one page ahead and produces blink effect
143 | // so current float, while moving next, should not be greater than currentPage + 1
144 | // READ COMMENTS at bottom most of this file to see the above require('log') output
145 | if (cFloat > (currentPage + 1)) {
146 | return;
147 | }
148 |
149 | if (delta == 0) {
150 | delta = 1;
151 | }
152 | var nextPage = currentPage + 1;
153 | $.backdropViews.children[nextPage].opacity = delta;
154 |
155 | } else if (cFloat < currentPage) {
156 | // on iOS, scrolling can be jumped slightly to previous page slightly when swiped very fastly
157 | // it gives currentPageAsFloat lesser one page behind and produces blink effect
158 | // so current float, while moving to previous page, should not be less than (currentPage - 1)
159 | if (cFloat < (currentPage - 1)) {
160 | return;
161 | }
162 |
163 | var prevPage = !currentPage ? 0 : currentPage;
164 | $.backdropViews.children[prevPage].opacity = delta;
165 | }
166 |
167 | if (cFloat === e.currentPage) {
168 | // when the scrolling is finished
169 | currentPage = e.currentPage;
170 | }
171 | }
172 |
173 | function addView(_views, _backdropColors, _scrollToView) {
174 | // make an array of views and backdropcolors
175 | _views = _.isArray(_views) ? _views : [_views];
176 |
177 | if (_backdropColors !== undefined) {
178 | _backdropColors = _.isArray(_backdropColors) ? _backdropColors : [_backdropColors];
179 | }
180 |
181 | _.each(_views, function(_view, i) {
182 | if (_view.apiName == 'Ti.UI.View') {
183 | $.SCROLLABLE_VIEW.addView(_view);
184 |
185 | ++totalPages;
186 |
187 | if (pagingEffect) {
188 | $.scrollView.visible = true;
189 | createPager(1);
190 | }
191 |
192 | if (opacityEffect) {
193 | $.backdropViews.add(Ti.UI.createView({
194 | backgroundColor : _backdropColors[i] || 'transparent',
195 | opacity : 0
196 | }));
197 | }
198 | }
199 | });
200 |
201 | if ((_scrollToView !== undefined) && _.isBoolean(_scrollToView) && _scrollToView) {
202 | currentPage = totalPages - 1;
203 |
204 | $.SCROLLABLE_VIEW.currentPage = currentPage;
205 |
206 | // set opacity to 1 of all backdrop views, reverse views to set opacity of topmost view first to avoid blinking
207 | opacityEffect && _.each($.backdropViews.children.reverse(), function(_backView) { _backView.opacity = 1; });
208 |
209 | // set pager control to last page
210 | if (pagingEffect) { $.pagerControl.left = currentPage * pagerPosition; }
211 | }
212 | }
213 |
214 | function removeView(_viewOrIndex) {
215 | if (totalPages === 0) {
216 | Ti.API.warn('No view to remove');
217 | return;
218 | }
219 |
220 | var tempViewIndex = null;
221 |
222 | if (typeof _viewOrIndex === 'object') {
223 | if (_viewOrIndex.apiName !== 'Ti.UI.View') {
224 | Ti.API.warn('Cannot remove view. Invalid view type passed');
225 | return;
226 |
227 | } else {
228 | // find the index of the view to be removed
229 | _.each($.SCROLLABLE_VIEW.views, function (_tempView, i) {
230 | (_tempView === _viewOrIndex) && (tempViewIndex = i);
231 | });
232 | }
233 |
234 | } else if (typeof _viewOrIndex === 'number' && (_viewOrIndex !== NaN)) {
235 | tempViewIndex = _viewOrIndex;
236 |
237 | } else {
238 | Ti.API.warn('Cannot remove view. Pass either View or index');
239 | return;
240 | }
241 |
242 | if ( (tempViewIndex < 0) || tempViewIndex > (totalPages - 1) ) {
243 | Ti.API.warn('Index/View is not valid');
244 | return;
245 | }
246 |
247 |
248 | try {
249 | isRemoving = true;
250 |
251 | $.SCROLLABLE_VIEW.removeView($.SCROLLABLE_VIEW.views[tempViewIndex]);
252 |
253 | if ( (tempViewIndex < currentPage) || ((tempViewIndex == currentPage) && (tempViewIndex == (totalPages - 1))) ) {
254 | --currentPage;
255 | $.SCROLLABLE_VIEW.currentPage = currentPage;
256 | }
257 |
258 | --totalPages;
259 |
260 | // remove backdrop view and set opacity to 1 of all views till currentPage
261 | if (opacityEffect) {
262 | $.backdropViews.remove($.backdropViews.children[tempViewIndex]);
263 | var childs = $.backdropViews.children;
264 | for (var i=0; i<=currentPage; i++) { childs[i].opacity = 1; }
265 | childs = null;
266 | }
267 |
268 | // remove last pager, set left position of animated pager and if no views are then set it visible false
269 | if (pagingEffect) {
270 | if (totalPages == 0) {
271 | $.PAGING_VIEW.removeAllChildren();
272 | $.scrollView.visible = false;
273 |
274 | } else {
275 | $.PAGING_VIEW.remove($.PAGING_VIEW.children[totalPages - 1]);
276 | if (currentPage == 0) { $.PAGING_VIEW.children[0].left = 0; }
277 | $.pagerControl.left = currentPage * pagerPosition;
278 | }
279 | }
280 |
281 | isRemoving = false;
282 |
283 | } catch(ex) {
284 | isRemoving = false;
285 | }
286 | }
287 |
288 | function setPage(_index) {
289 | currentPage = _index;
290 | $.SCROLLABLE_VIEW.currentPage = currentPage;
291 |
292 | if (pagingEffect) {
293 | $.pagerControl.left = currentPage * pagerPosition;
294 | }
295 |
296 | if (opacityEffect) {
297 | _.each($.backdropViews.children, function (child, i) {
298 | childs[i].opacity = (i <= currentPage) ? 1 : 0;
299 | });
300 | }
301 | }
302 |
303 |
304 | // expose scrollable view pager
305 | $.scrollableView = $.SCROLLABLE_VIEW;
306 | $.add = addView;
307 | $.remove = removeView;
308 | $.currentPage = function () { return currentPage; };
309 | $.totalPages = function () { return totalPages; };
310 | $.setCurrentPage = setPage;
311 |
312 |
313 |
314 |
315 | // iOS swiping fast issue
316 | /*
317 | *
318 | 1 - ******* FAST *******
319 | Delta = 0.208 : Float = 0.208 : Page = 0
320 | Delta = 0.538 : Float = 0.538 : Page = 1
321 | Delta = 0.745 : Float = 0.745 : Page = 1
322 | Delta = 0.991 : Float = 0.991 : Page = 1
323 | Delta = 0.033 : Float = 1.033 : Page = 1
324 | Delta = 0.045 : Float = 1.045 : Page = 1
325 | Delta = 0.005 : Float = 1.005 : Page = 1
326 | Delta = 0.001 : Float = 1.001 : Page = 1
327 |
328 | Delta = 0 : Float = 1 : Page = 1
329 |
330 | ******* SLOW *******
331 | Delta = 0.030 : Float = 0.030 : Page = 0
332 | Delta = 0.326 : Float = 0.326 : Page = 0
333 | Delta = 0.463 : Float = 0.463 : Page = 0
334 | Delta = 0.620 : Float = 0.620 : Page = 1
335 | Delta = 0.761 : Float = 0.761 : Page = 1
336 | Delta = 0.998 : Float = 0.998 : Page = 1
337 | Delta = 0 : Float = 1 : Page = 1
338 | */
339 |
--------------------------------------------------------------------------------
/in.prashant.scrollableViews/styles/widget.tss:
--------------------------------------------------------------------------------
1 | "#pagerContainer": {
2 | width: Ti.UI.SIZE,
3 | height: Ti.UI.SIZE,
4 | }
5 |
6 | "#PAGING_VIEW": {
7 | width: Ti.UI.SIZE,
8 | layout: "horizontal",
9 | height: Ti.UI.SIZE,
10 | horizontalWrap: false
11 | }
12 |
13 | "#pagerControl": {
14 | width: 12,
15 | height: 12,
16 | borderRadius: 6,
17 | borderWidth: 7,
18 | touchEnabled: false
19 | }
20 |
21 | "#SCROLLABLE_VIEW" : {
22 | disableBounce : true,
23 | showPagingControl : false
24 | }
25 |
26 | "#scrollView": {
27 | left: 15,
28 | right: 15,
29 | height: Ti.UI.SIZE,
30 | scrollType: 'horizontal',
31 | disableBounce: true
32 | }
33 |
34 | "#backdropViews" : {
35 | width : Titanium.UI.FILL,
36 | height : Titanium.UI.FILL
37 | }
38 |
--------------------------------------------------------------------------------
/in.prashant.scrollableViews/views/widget.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/in.prashant.scrollableViews/widget.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "in.prashant.scrollableViews",
3 | "name": "Scrollable Views",
4 | "description" : "A cross platform Alloy scrollable widget with nice background effect and paging controls",
5 | "author": "Prashant Saini",
6 | "version": "1.0.0",
7 | "copyright":"",
8 | "license":"",
9 | "min-alloy-version": "1.0",
10 | "min-titanium-version":"5.3.1",
11 | "tags":"",
12 | "platforms":"ios, android"
13 | }
14 |
--------------------------------------------------------------------------------