├── LICENSE ├── README.md ├── controllers └── widget.js ├── docs └── screenshot1.png ├── styles └── widget.tss ├── views └── widget.xml └── widget.json /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Apra Informatica 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ti.ImageViewer 2 | Image viewer widget for Appcelerator Titanium 3 | 4 | With this image viewer it's possible to show a single - zoomable, scrollable and pinchable - local image on iOS and Android (on iOS also remote images). 5 | 6 | It uses module org.iotashan.TiTouchImageView (on Android), and installation of this module is necessary if you mean to use the widget on Android. 7 | 8 | ![image](docs/screenshot1.png?raw=true) 9 | 10 | ## Installation 11 | 12 | Add in your *config.json*, under `dependencies`: 13 | 14 | ``` 15 | "dependencies": { 16 | "it.apra.tiimageviewer": "*" 17 | } 18 | ``` 19 | 20 | ## Usage 21 | ```javascript 22 | widget = Alloy.createWidget('it.apra.tiimageviewer', { 23 | 'image' : filepath, 24 | 'title' : filename, 25 | 'subtitle' : des 26 | }); 27 | ``` 28 | 29 | ## Usage with Alloy 30 | ```xml 31 | 32 | ``` 33 | 34 | **Args** 35 | * **image**: image filepath 36 | * **title**: optional image title (showed in lower panel) 37 | * **subtitle**: optional image subtitle (showed in lower panel) 38 | * **lowerInfoHidden**: if true hides lower info panel 39 | * **lowerGradientHidden**: if true hides lower info panel gradient 40 | * **backgroundColor**: backgroundColor (default is 'black') 41 | 42 | **Functions** 43 | * **removeEventListeners**: removes all listeners on controls 44 | * **reloadImage**: reloads the image 45 | * **showLowerInfo**: showes lower info 46 | * **hideLowerInfo**: hides lower info 47 | 48 | ##Notes 49 | * Could be useful a compatibility on Android without the native module too (using a webview?) 50 | * The pinch-to-zoom and scroll on iOS are based on the widget https://github.com/CaffeinaLab/Ti.TiltImageView. 51 | -------------------------------------------------------------------------------- /controllers/widget.js: -------------------------------------------------------------------------------- 1 | var args = arguments[0] || {}; 2 | $.image = args.image; 3 | var title = args.title || "", 4 | subtitle = args.subtitle || "", 5 | lowerInfoHidden = args.lowerInfoHidden || args.lowerInfoHided || false, 6 | lowerGradientHidden = args.lowerGradientHidden || args.lowerGradientHided || false, 7 | backgroundColor = args.backgroundColor || 'black'; 8 | 9 | var lastValidOrientation, imageView, imageLoad, orientationChange, removeEventListeners, showLowerInfo; 10 | 11 | lastValidOrientation = null; 12 | 13 | showLowerInfo = function(){ 14 | if (!lowerInfoHidden){ 15 | $.title.visible = true; 16 | $.description.visible = true; 17 | } 18 | }; 19 | hideLowerInfo = function(){ 20 | $.title.visible = false; 21 | $.description.visible = false; 22 | }; 23 | 24 | if (OS_IOS) { 25 | (function(){ 26 | var getMaxOffset, IMGS, OUTS, SS, LAYOUT, MINZOOMSCALE; 27 | 28 | imageView = Ti.UI.createImageView({ 29 | 'image' : $.image, 30 | 'height': Ti.UI.SIZE, 31 | 'width': Ti.UI.SIZE, 32 | 'touchEnabled': false 33 | }); 34 | $.imageViewContainer.add(imageView); 35 | 36 | getMaxOffset = function() { 37 | return (IMGS.width*$.imageViewContainer.zoomScale)-OUTS.width; 38 | }; 39 | 40 | imageLoad = function(){ 41 | Ti.API.debug('Loading imagetView ' + title); 42 | 43 | var size, rect, OR, IR, zoomScaleToFit; 44 | 45 | if (imageView.image != $.image) { 46 | imageView.image = $.image; 47 | } 48 | size = imageView.size; 49 | rect = $.index.size; 50 | IMGS = { width: size.width, height: size.height }; 51 | OUTS = { width: rect.width, height: rect.height }; 52 | OR = OUTS.width/OUTS.height; 53 | IR = IMGS.width/IMGS.height; 54 | 55 | // Recalculate if the min-height is not sufficient 56 | if (IMGS.heightOR) { 67 | LAYOUT = 'H'; 68 | if (OUTS.height > 0 && IMGS.height > 0){ 69 | MINZOOMSCALE = OUTS.height/IMGS.height; 70 | } 71 | SS = OUTS.width-40; 72 | $.scrollBar.applyProperties({ 73 | bottom: 90, left: 20, right: 20, top: null, 74 | height: 1, width: Ti.UI.FILL 75 | }); 76 | $.scrollBarInset.width = 60; 77 | } else { 78 | LAYOUT = 'V'; 79 | if (OUTS.width > 0 && IMGS.width > 0){ 80 | MINZOOMSCALE = OUTS.width/IMGS.width; 81 | } 82 | SS = OUTS.height-20; 83 | $.scrollBar.applyProperties({ 84 | right: 20, left: null, top: 10, bottom: 10, 85 | height: Ti.UI.FILL, width: 1 86 | }); 87 | $.scrollBarInset.height = 60; 88 | } 89 | 90 | // scalesToFit 91 | if (IMGS.width > OUTS.width || IMGS.height > OUTS.height) { 92 | zoomScaleToFit = Math.min(OUTS.height / IMGS.height, OUTS.width / IMGS.width); 93 | if (zoomScaleToFit > 0 && zoomScaleToFit < 1){ 94 | MINZOOMSCALE = zoomScaleToFit; 95 | } 96 | } 97 | MINZOOMSCALE *= 0.95; 98 | 99 | $.imageViewContainer.minZoomScale = MINZOOMSCALE; 100 | $.imageViewContainer.zoomScale = MINZOOMSCALE; 101 | $.imageViewContainer.maxZoomScale = 5; 102 | }; 103 | 104 | $.imageViewContainer.addEventListener('scroll', function(){ 105 | if (LAYOUT==='H') { 106 | $.scrollBarInset.animate({ 107 | left: (SS-60)*($.imageViewContainer.contentOffset.x/getMaxOffset()) 108 | }); 109 | } else { 110 | $.scrollBarInset.animate({ 111 | top: (SS-60)*($.imageViewContainer.contentOffset.y/getMaxOffset()) 112 | }); 113 | } 114 | }); 115 | 116 | imageView.addEventListener('load', imageLoad); 117 | $.index.addEventListener('singletap', function(e) { 118 | imageLoad(); 119 | }); 120 | }()); 121 | } else if (OS_ANDROID){ 122 | (function(){ 123 | imageView = require('org.iotashan.TiTouchImageView').createView({ 124 | 'image' : $.image, 125 | 'width': Ti.UI.FILL, 126 | 'height' : Ti.UI.FILL 127 | }); 128 | $.imageViewContainer.add(imageView); 129 | 130 | imageLoad = function(){ 131 | Ti.API.debug('Loading imagetView ' + title); 132 | 133 | if (imageView.image != $.image) { 134 | imageView.image = $.image; 135 | } 136 | 137 | imageView.resetZoom(); 138 | }; 139 | }()); 140 | } 141 | 142 | (function(){ 143 | var lastValidOrientation; 144 | 145 | orientationChange = function(e){ 146 | if (e){ 147 | Ti.API.debug('Orientation: ' + e.orientation); 148 | switch(e.orientation){ 149 | case Ti.UI.PORTRAIT: 150 | case Ti.UI.UPSIDE_PORTRAIT: 151 | case Ti.UI.LANDSCAPE_LEFT: 152 | case Ti.UI.LANDSCAPE_RIGHT: 153 | if (e.orientation === lastValidOrientation){ 154 | return; 155 | } 156 | lastValidOrientation = e.orientation; 157 | break; 158 | default: 159 | return; 160 | } 161 | } 162 | 163 | imageLoad(); 164 | }; 165 | Ti.Gesture.addEventListener('orientationchange', orientationChange); 166 | }()); 167 | 168 | removeEventListeners = function(){ 169 | Ti.API.debug('tiImageViewer: removingEventListeners ' + title); 170 | 171 | if (orientationChange) { Ti.Gesture.removeEventListener('orientationchange', orientationChange); } 172 | if (imageView && imageLoad) { imageView.removeEventListener('load', imageLoad); } 173 | }; 174 | 175 | 176 | $.title.text = title; 177 | $.description.text = subtitle; 178 | $.index.backgroundColor = backgroundColor; 179 | 180 | if (lowerInfoHidden){ 181 | hideLowerInfo(); 182 | } else { 183 | showLowerInfo(); 184 | } 185 | 186 | $.gradientImage.visible = !lowerGradientHidden; 187 | 188 | exports.reloadImage = imageLoad; 189 | exports.removeEventListeners = removeEventListeners; 190 | exports.showLowerInfo = showLowerInfo; 191 | exports.hideLowerInfo = hideLowerInfo; 192 | -------------------------------------------------------------------------------- /docs/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apra-informatica/Ti.ImageViewer/7efcc9eb845d73187f7157af64901c4bebcc30da/docs/screenshot1.png -------------------------------------------------------------------------------- /styles/widget.tss: -------------------------------------------------------------------------------- 1 | "#index":{ 2 | width: Ti.UI.FILL, 3 | height: Ti.UI.FILL 4 | }, 5 | "#imageViewContainer":{ 6 | width: Ti.UI.FILL, 7 | height: Ti.UI.FILL 8 | }, 9 | "#imageViewContainer[platform=ios]":{ 10 | disableBounce: true 11 | }, 12 | "#title":{ 13 | touchEnabled: false, 14 | bottom: 40, 15 | left: 20, 16 | right: 20, 17 | height: 34, 18 | color: '#fff', 19 | shadow: { 20 | shadowColor: '#000', 21 | shadowOffset: { x: 0, y: 1 }, 22 | shadowRadius: 4 23 | }, 24 | font: { 25 | fontWeight: 'bold', 26 | fontSize: 24, 27 | } 28 | }, 29 | "#description":{ 30 | touchEnabled: false, 31 | bottom: 20, 32 | left: 20, 33 | right: 20, 34 | height: 18, 35 | color: '#fff', 36 | shadow: { 37 | shadowColor: '#000', 38 | shadowOffset: { x: 0, y: 1 }, 39 | shadowRadius: 2 40 | }, 41 | font: { 42 | fontSize: 14, 43 | } 44 | }, 45 | "#scrollBar":{ 46 | touchEnabled: false, 47 | bottom: 90, 48 | height: 1, 49 | left: 20, 50 | right: 20, 51 | backgroundColor: '#4fff' 52 | }, 53 | "#scrollBarInset":{ 54 | touchEnabled: false, 55 | width: 60, 56 | left: 0, 57 | top: 0, 58 | backgroundColor: '#fff' 59 | }, 60 | "#gradientImage":{ 61 | touchEnabled: false, 62 | bottom: 0, 63 | height: 260, 64 | backgroundGradient: { 65 | type: 'linear', 66 | startPoint: { x: 0, y: 0 }, 67 | endPoint: { x: 0, y: '100%' }, 68 | colors: [ { color: '#0000', offset: 0.0}, { color: '#000', offset: 1 } ], 69 | } 70 | } -------------------------------------------------------------------------------- /views/widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /widget.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "it.apra.tiimageviewer", 3 | "name": "Ti.ImageViewer", 4 | "description" : "Image viewer widget for Appcelerator Titanium", 5 | "author": "Mauro Piccotti", 6 | "version": "1.0.1", 7 | "copyright":"Copyright (c) 2015", 8 | "min-alloy-version": "1.0", 9 | "min-titanium-version":"4.0", 10 | "tags":"fullscreen,image,view,viewer", 11 | "platforms":"android,ios" 12 | } --------------------------------------------------------------------------------