TV Future is Apps tvOS vs AndroidTV by @pablito_az @hugojperal @jr_salazares MADRID · NOV 18-19 · 2016
TV Future is AppstvOS vs AndroidTVby @pablito_az @hugojperal @jr_salazares
MADRID · NOV 18-19 · 2016
Agenda ¿Quiénes somos? Introducción a la plataforma Dinámica del workshop. Primeros pasos ¿Cómo buscar contenido dentro de la app? Vayamos al detalle. Cosas que nos dejamos en el tintero
¿Quiénes somos?
http://www.edreamsodigeocareers.com
Introducción. Experiencia
Introducción. Interacción
Introducción. UX
Introducción. Foco
Dinámica del workshop
Repo AndroidTV: https://github.com/odigeoteam/AndroidTV-workshop
Repo tvOS y TVML: https://github.com/odigeoteam/tvOS-workshop
Primeros pasos
Step 1 - 2
Para que sea compatible en AndroidTV ...… tenemos que hacer algunas cosas:
● Nuestras activity principal CATEGORY_LEANBACK_LAUNCHER
● <uses-feature android:name = “android.software.leanback”android:required = “false”>
● <uses-feature android:name = “android.hardware.touchscreen”android:required = “false”>
… y nuestro dispositivo tiene limitaciones:
● Touchscreen● Telephony● Camera
● GPS● Microphone● Sensors
Adaptando el comportamiento ... … en tiempo de ejecución:
if (getPackageManager().hasSystemFeature("android.hardware.camera")) {
// Hago algo con la cámara
} else {
// Adapto mi comportamiento
}
Algunas características de los layoutsDebemos tener en cuenta:
● Layouts en Landscape.● UI en secciones fácilmente navegables.● Suficiente margen entre elementos.● Cuidado con el Overscan● Textos suficientemente visibles.
v17 Leanback to the rescue
compile 'com.android.support:leanback-v17:24.2.1'
Nuestra primera vista: BrowseFragment
Configurando el BrowseFragmentPodemos configurar elementos en la interfaz muy fácil:
setBadgeDrawable(...);setOnSearchClickedListener(...);setSearchAffordanceColor(...);
Pintando contenido
Pintando contenidoclass ContentAdapter extends ArrayObjectAdapter {
for (CategoryList) {ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardItemPresenter());for (awesomePlaces) {
listRowAdapter.add(places);}HeaderItem header = new HeaderItem(Category);add(new ListRow(header, listRowAdapter));
}}
Nuestro CardItemPresenterpublic class CardItemPresenter extends Presenter{
@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent) {
// Creo mi ImageCardView aquí}
@Overridepublic void onBindViewHolder(ViewHolder viewHolder,
Object item) {// Pinto la información en la ImageCardView
}}
Configurando ImageCardViewTiene 3 elementos principales, además de la imágen de fondo:
Se pueden configurar, mediante un Style combinando estos elementos:
<item name="lbImageCardViewType">Title|Content|IconOnLeft</item>
● Título● Contenido● Badge
Step 1 - 2
Limitaciones 200MB No LocalStorage No WebViews No Custom Video Player No Contacts, HandOff...
Universal PurchaseMisma entrada en iTunes ConnectMismo bundle IDSubidas a iTunes Connect separadasDistintas versionesDistintos procesos de revisiónEsta unión es para… siempre
Construyamos un layout
Grids, grids everywhere¿puedo tener el foco? -> canBecomeFocusedUIButton, UIControl, UISegmentedControl, UITabBar, UITextField, UISearchBar(UITextField)collectionView(_:canFocusItemAtIndexPath:)tableView(_:canFocusRowAtIndexPath:)
y yo, ¿tengo el foco? -> focuseddidUpdateFocusInContext(_:withAnimationCoordinator:)
Step 1-2
TVMLTVML (TV Markup Language)TVJS (TV Javascript)TVMLKit
TVML
Templates, templates everywhere18 templates a elegirElementos simplesElementos compuestos
carousel, collectionList, shelf, lockup
TVML
JS Principalesapplication.js:
JS Inicial. Carga otros JS y la primera pantallaDocumentLoader:
Encargado de cargar documentosDocumentController:
Es el controller y tiene asociado un loader y un template. Nos abstrae de la navegación
TVML
Aspecto template● Para navegar entre pantallas
usaremos el atributo “documentURL” en cualquier elemento focusable
TVML
Step 1 - 2TVML
¿Cómo buscamos dentro de la app?
Step 3
In-app search: SearchFragment
SearchFragmentclass SearchViewImp extends SearchFragment implements SearchResultProvider {
@Overridepublic ObjectAdapter getResultsAdapter() {}
@Overridepublic boolean onQueryTextChange(String newQuery) {}
@Overridepublic boolean onQueryTextSubmit(String query) {}
}
Cards interaction: BackgroundManager
private BackgroundManager mBackgroundManager;
// Inicializamos el BackgroundManagermBackgroundManager = BackgroundManager.getInstance(getActivity());mBackgroundManager.attach(getActivity().getWindow());
// Cambiamos el fondo cada vez que seleccione una cardmBackgroundManager.setDrawable(backgroundDrawable);
Step 3
¿Cómo buscar dentro de la app?//Swiftlet searchResultsController = SearchResultsViewController()
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController let searchContainer = UISearchContainerViewController(searchController: searchController)
Gestos
Veamos los styles//Con el Appearance ProxyUIButton.appearance().setTitleColor(.red, for: [])let light = UITraitCollection(userInterfaceStyle: .light)let dark = UITraitCollection(userInterfaceStyle: .dark)UIButton.appearance(for: light).setTitleColor(.red, for:[])UIButton.appearance(for: dark).setTitleColor(.blue, for:[])
// UIScreen, UIWindow, UIViewController, UIPresentationController, UIViewfunc traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
Step 3
TVJSEventos:Document → load, unload, appear, disappear…Focusable Elements → select, highlight, holdselect, play
event.target vs event.currentTarget
TVML
SearchTemplateTVML
Step 3TVML
Vayamos al detalle
Step 4
Construyendo cualquier layoutLo más importante, la navegación
Controlando la navegación
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {// Vamos abajo
} else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {// Vamos arriba
} else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
// Vamos a la derecha} else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
// Vamos a la izquiera}
Controlando la navegación
<TextView android:id="@+id/Category1"
android:nextFocusDown="@+id/Category2"\>
Manejando recursos pesados
● Carga imágenes solo cuando se muestren en la pantalla.
● Recicla los Bitmaps cuando ya no sean necesarios.
● Si traes imágenes de red, hazlo siempre en un hilo aparte.
● Escala las imágenes al tamaño que realmente necesitas.
Step 4
Cargar un videoLo más fácil es usar la clase AVPlayerViewController y el resto sería así://Creamos el player con la URL del videoplayer = AVPlayer(url: url)//configuramos el payer con las opciones deseadasplayer?.play()
Step 4
TVML. Elementos Custom I TVML
TVML. Elementos Custom II 1.Implementar protocolo TVInterfaceCreating2.Asignar el extendedInterfaceCreator del objeto
singleton TVInterfaceFactory3.Registrar elemento4.Usar en nuestros TVMLs
TVML
Custom template
Step 4TVML
Cosas que nos dejamos en el tintero
Finding content outside the app
● Recommendation Row:○ Continuation content○ New content○ Related content
● Hacer que el contenido de tu aplicación sea encontrable desde fuera de ella.
Más vistas reutilizables
Modo picture-in-picture
Cosas que nos dejamos en el tintero
Testing. Unit Test y UI Test Top Shelf Dinámico Parallax Artwork Más Focus Engine:
∘ UIFocusGuide∘ var preferredFocusEnvironments: [UIFocusEnvironment]
Cosas que nos dejamos en el tintero
Video features Now playing Animar DOM updates
Algunos enlaces Primero pasos con AndroidTV Building AndroidTV Apps Guías de estilo AndroidTV Distribución en AndroidTV
¡Gracias!