{"id":872,"date":"2011-02-08T19:43:47","date_gmt":"2011-02-08T17:43:47","guid":{"rendered":"http:\/\/192.168.1.2:8080\/?p=872"},"modified":"2011-02-08T19:43:47","modified_gmt":"2011-02-08T17:43:47","slug":"uso-de-gif-animado-en-android","status":"publish","type":"post","link":"https:\/\/nauj27.com\/blog\/?p=872","title":{"rendered":"Uso de gif animado en Android"},"content":{"rendered":"<p>En Android se suele usar un <a href=\"http:\/\/developer.android.com\/reference\/android\/widget\/ImageView.html\">ImageView<\/a> para dibujar una imagen. La imagen puede provenir de un recurso interno, de una imagen externa, o de un objeto drawable que se le pase directamente al m\u00e9todo <a href=\"http:\/\/developer.android.com\/reference\/android\/widget\/ImageView.html#setImageDrawable(android.graphics.drawable.Drawable)\">.setImageDrawable()<\/a>.<\/p>\n<p>En cualquier caso, en la mayor\u00eda de dispositivos no ser\u00e1 posible mostrar en este tipo de contenedor un gif animado, ya que seg\u00fan la documentaci\u00f3n tan solo se representar\u00e1 el primer frame de la imagen animada.<\/p>\n<p>El modo de representar correctamente una animaci\u00f3n en android es usando un <a href=\"http:\/\/developer.android.com\/guide\/topics\/resources\/animation-resource.html\">recurso creado espec\u00edficamente para ello<\/a>. De los dos tipos de animaciones que se pueden crear en Android la que nos interesa en esta ocasi\u00f3n es la que se conoce como <a href=\"http:\/\/developer.android.com\/guide\/topics\/resources\/animation-resource.html#Frame\">Frame Animation<\/a>. La idea es sencilla y es muy similar a la de los GIF animados: un archivo xml define qu\u00e9 im\u00e1genes se muestran y durante cu\u00e1nto tiempo, las im\u00e1genes deben de encontrarse accesibles como recursos internos de la aplicaci\u00f3n. Para m\u00e1s informaci\u00f3n v\u00e9ase la documentaci\u00f3n sobre <a href=\"http:\/\/developer.android.com\/guide\/topics\/resources\/animation-resource.html#Frame\">Frame Animation<\/a>.<\/p>\n<p>La cuesti\u00f3n es que hay un mont\u00f3n de GIFs animados que podemos querer incluir en nuestra aplicaci\u00f3n para Android, as\u00ed que es necesario hacer una conversi\u00f3n de un formato a otro de un modo m\u00e1s o menos automatizado.<\/p>\n<p>El siguiente gui\u00f3n en python extrae los frames completos as\u00ed como la informaci\u00f3n temporal y espacial de cada uno de ellos regenerando im\u00e1genes en caso de tratarse de un GIF optimizado, para formar un recurso de imagen completo para Android. La <a href=\"http:\/\/developer.android.com\/reference\/android\/graphics\/drawable\/AnimationDrawable.html\">animaci\u00f3n as\u00ed creada<\/a> funcionar\u00e1 correctamente en cualquier dispositivo en cualquier versi\u00f3n de Android. Para m\u00e1s informaci\u00f3n v\u00e9ase la clase <a href=\"http:\/\/developer.android.com\/reference\/android\/graphics\/drawable\/AnimationDrawable.html\">AnimationDrawable<\/a>.<\/p>\n<pre lang=\"python\" line=\"1\">\r\n#!\/usr\/bin\/env python\r\n# -*- encoding: utf-8 -*-\r\n\r\nimport sys, os\r\nimport subprocess\r\n\r\n# When no delay is specified in a gif file, 100ms is default\r\nDEFAULT_DELAY_MS = 100\r\n\r\ninput_file = None\r\noutput_file = None\r\n\r\ntry:\r\n    input_file = sys.argv[1]\r\n    output_file = \"%s.xml\" % (input_file.split(\".\")[0],)\r\nexcept:\r\n    print \"Usage: %s <input.gif>\" % (sys.argv[0],)\r\n    sys.exit(1)\r\n\r\n    \r\nclass Anim(object):\r\n    def __init__(self):\r\n        self.content = []\r\n        \r\n    def add_header(self):\r\n        self.content.append('<?xml version=\"1.0\" encoding=\"utf-8\"?>\\n')\r\n        self.content.append('<animation-list\\n')\r\n        self.content.append('    xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\\n')\r\n        self.content.append('    android:oneshot=\"false\">\\n')\r\n        \r\n    def add_item(self, resource, duration):\r\n        self.content.append(\r\n            '    <item android:drawable=\"@drawable\/%s\" android:duration=\"%d\" \/>\\n' %\r\n            (resource, duration))\r\n        \r\n    def add_footer(self):\r\n        self.content.append('<\/animation-list>\\n')\r\n        \r\n    def write_file(self):\r\n        file_descriptor = open(output_file, \"w\")\r\n        file_descriptor.writelines(self.content)\r\n        file_descriptor.close()\r\n\r\n    \r\nclass Gif(object):\r\n    def __init__(self, gif):\r\n        self.input_file = gif\r\n        gif_info = subprocess.Popen(\r\n            [\"gifsicle\", \"--info\", input_file],\r\n            stdout=subprocess.PIPE,\r\n            stderr=subprocess.PIPE)\r\n\r\n        (gif_info_out, gif_info_error) = gif_info.communicate()\r\n        \r\n        self.gif_info_lines = gif_info_out.splitlines()\r\n        \r\n    def get_info(self):\r\n        return self.gif_info_lines\r\n                \r\n    def explode(self):    \r\n        return_code = subprocess.call(\r\n            [\"gifsicle\", \"--explode\", \"--unoptimize\", self.input_file])\r\n        \r\n        return not return_code\r\n        \r\n    def to_png(self, gif_frame):\r\n        return_code = subprocess.call([\"gif2png\", \"-d\", \"-s\", \"-O\", gif_frame])\r\n        \r\n        return return_code\r\n\r\n\r\ndef main():\r\n    print \"Processing %s...\" % (input_file,)\r\n    \r\n    anim = Anim()\r\n    anim.add_header() \r\n        \r\n    gif = Gif(input_file)\r\n\r\n    if not gif.explode():\r\n        print \"Error exploding gif input file %s\" % (input_file,)\r\n        #sys.exit(1)\r\n\r\n    info_lines = gif.get_info()\r\n\r\n    counter = 0\r\n    for line in info_lines:\r\n        line = line.strip()\r\n\r\n        if line.startswith(\"disposal\"):\r\n            line_items = line.split()\r\n            \r\n            if len(line_items) == 4:\r\n                milliseconds = int(round(float(line_items[3][:-1]) * 1000))\r\n            else:\r\n                milliseconds = DEFAULT_DELAY_MS\r\n        \r\n            # Rename and convert gif to optimized png\r\n            renamed_gif = \"%s_%03d.gif\" % (input_file.split(\".\")[0], counter)\r\n            os.rename(\"%s.%03d\" % (input_file, counter), renamed_gif)\r\n            result = gif.to_png(renamed_gif)\r\n            \r\n            anim.add_item(renamed_gif.split(\".\")[0], milliseconds)\r\n            counter = counter + 1\r\n    \r\n    anim.add_footer()\r\n    anim.write_file()\r\n    \r\n    os.unlink(input_file)\r\n    sys.exit(0)\r\n\r\nif __name__ == \"__main__\":\r\n    main()\r\n<\/pre>\n<p>Es necesario tener instalado <a href=\"http:\/\/www.lcdf.org\/gifsicle\/\">gifsicle<\/a>, y para la optimizaci\u00f3n en tama\u00f1o y conversi\u00f3n a png de las im\u00e1genes resultantes es necesario tener tambi\u00e9n instalado, y en el <strong>$PATH<\/strong>, <a href=\"http:\/\/catb.org\/~esr\/gif2png\/\">gif2png<\/a>.<\/p>\n<p>Descargar <a href='http:\/\/192.168.1.2:8080\/wp-content\/uploads\/2011\/02\/getanim.zip'>getanim.py<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>En Android se suele usar un ImageView para dibujar una imagen. La imagen puede provenir de un recurso interno, de una imagen externa, o de un objeto drawable que se le pase directamente al m\u00e9todo .setImageDrawable(). En cualquier caso, en la mayor\u00eda de dispositivos no ser\u00e1 posible mostrar en este tipo de contenedor un gif [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[151],"tags":[85,187,186,153,196],"_links":{"self":[{"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/872"}],"collection":[{"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=872"}],"version-history":[{"count":6,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/872\/revisions"}],"predecessor-version":[{"id":880,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/872\/revisions\/880"}],"wp:attachment":[{"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=872"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=872"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nauj27.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=872"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}