{"id":1416,"date":"2013-04-09T11:16:15","date_gmt":"2013-04-09T05:46:15","guid":{"rendered":"http:\/\/judepereira.com\/blog\/?p=1416"},"modified":"2013-07-30T21:37:29","modified_gmt":"2013-07-30T16:07:29","slug":"multi-touch-in-android-translate-scale-and-rotate","status":"publish","type":"post","link":"https:\/\/judepereira.com\/blog\/multi-touch-in-android-translate-scale-and-rotate\/","title":{"rendered":"Multi Touch in Android &#8211; Translate, Scale, and Rotate"},"content":{"rendered":"<p>Here&#8217;s a quick and easy implementation of Android&#8217;s multi touch feature &#8211; one finger to move, two to zoom, and three to rotate the image.<\/p>\n<p>Assuming you have a basic understanding of 2D matrix transformations, the <a title=\"Matrix | Android Developers\" href=\"http:\/\/developer.android.com\/reference\/android\/graphics\/Matrix.html\" target=\"_blank\">Matrix<\/a> class in Android uses a 3&#215;3 matrix to achieve all of the 2D transformations.<\/p>\n<p>The source code and pre -built APK is available. See the end of this post.<\/p>\n<p>Main activity &#8211; MultiTouch.java<br \/>\n[code language=&#8221;java&#8221;]<br \/>\npackage com.multitouch.example;<\/p>\n<p>import android.app.Activity;<br \/>\nimport android.graphics.Matrix;<br \/>\nimport android.graphics.PointF;<br \/>\nimport android.os.Bundle;<br \/>\nimport android.util.FloatMath;<br \/>\nimport android.view.MotionEvent;<br \/>\nimport android.view.View;<br \/>\nimport android.view.View.OnTouchListener;<br \/>\nimport android.widget.ImageView;<\/p>\n<p>public class MultiTouch extends Activity implements OnTouchListener {<\/p>\n<p>    \/\/ these matrices will be used to move and zoom image<br \/>\n    private Matrix matrix = new Matrix();<br \/>\n    private Matrix savedMatrix = new Matrix();<br \/>\n    \/\/ we can be in one of these 3 states<br \/>\n    private static final int NONE = 0;<br \/>\n    private static final int DRAG = 1;<br \/>\n    private static final int ZOOM = 2;<br \/>\n    private int mode = NONE;<br \/>\n    \/\/ remember some things for zooming<br \/>\n    private PointF start = new PointF();<br \/>\n    private PointF mid = new PointF();<br \/>\n    private float oldDist = 1f;<br \/>\n    private float d = 0f;<br \/>\n    private float newRot = 0f;<br \/>\n    private float[] lastEvent = null;<\/p>\n<p>    @Override<br \/>\n    public void onCreate(Bundle savedInstanceState) {<br \/>\n        super.onCreate(savedInstanceState);<br \/>\n        setContentView(R.layout.main);<br \/>\n        ImageView view = (ImageView) findViewById(R.id.imageView);<br \/>\n        view.setOnTouchListener(this);<br \/>\n    }<\/p>\n<p>    public boolean onTouch(View v, MotionEvent event) {<br \/>\n        \/\/ handle touch events here<br \/>\n        ImageView view = (ImageView) v;<br \/>\n        switch (event.getAction() &#038; MotionEvent.ACTION_MASK) {<br \/>\n            case MotionEvent.ACTION_DOWN:<br \/>\n                savedMatrix.set(matrix);<br \/>\n                start.set(event.getX(), event.getY());<br \/>\n                mode = DRAG;<br \/>\n                lastEvent = null;<br \/>\n                break;<br \/>\n            case MotionEvent.ACTION_POINTER_DOWN:<br \/>\n                oldDist = spacing(event);<br \/>\n                if (oldDist > 10f) {<br \/>\n                    savedMatrix.set(matrix);<br \/>\n                    midPoint(mid, event);<br \/>\n                    mode = ZOOM;<br \/>\n                }<br \/>\n                lastEvent = new float[4];<br \/>\n                lastEvent[0] = event.getX(0);<br \/>\n                lastEvent[1] = event.getX(1);<br \/>\n                lastEvent[2] = event.getY(0);<br \/>\n                lastEvent[3] = event.getY(1);<br \/>\n                d = rotation(event);<br \/>\n                break;<br \/>\n            case MotionEvent.ACTION_UP:<br \/>\n            case MotionEvent.ACTION_POINTER_UP:<br \/>\n                mode = NONE;<br \/>\n                lastEvent = null;<br \/>\n                break;<br \/>\n            case MotionEvent.ACTION_MOVE:<br \/>\n                if (mode == DRAG) {<br \/>\n                    matrix.set(savedMatrix);<br \/>\n                    float dx = event.getX() &#8211; start.x;<br \/>\n                    float dy = event.getY() &#8211; start.y;<br \/>\n                    matrix.postTranslate(dx, dy);<br \/>\n                } else if (mode == ZOOM) {<br \/>\n                    float newDist = spacing(event);<br \/>\n                    if (newDist > 10f) {<br \/>\n                        matrix.set(savedMatrix);<br \/>\n                        float scale = (newDist \/ oldDist);<br \/>\n                        matrix.postScale(scale, scale, mid.x, mid.y);<br \/>\n                    }<br \/>\n                    if (lastEvent != null &#038;&#038; event.getPointerCount() == 3) {<br \/>\n                        newRot = rotation(event);<br \/>\n                        float r = newRot &#8211; d;<br \/>\n                        float[] values = new float[9];<br \/>\n                        matrix.getValues(values);<br \/>\n                        float tx = values[2];<br \/>\n                        float ty = values[5];<br \/>\n                        float sx = values[0];<br \/>\n                        float xc = (view.getWidth() \/ 2) * sx;<br \/>\n                        float yc = (view.getHeight() \/ 2) * sx;<br \/>\n                        matrix.postRotate(r, tx + xc, ty + yc);<br \/>\n                    }<br \/>\n                }<br \/>\n                break;<br \/>\n        }<\/p>\n<p>        view.setImageMatrix(matrix);<br \/>\n        return true;<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * Determine the space between the first two fingers<br \/>\n     *\/<br \/>\n    private float spacing(MotionEvent event) {<br \/>\n        float x = event.getX(0) &#8211; event.getX(1);<br \/>\n        float y = event.getY(0) &#8211; event.getY(1);<br \/>\n        return FloatMath.sqrt(x * x + y * y);<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * Calculate the mid point of the first two fingers<br \/>\n     *\/<br \/>\n    private void midPoint(PointF point, MotionEvent event) {<br \/>\n        float x = event.getX(0) + event.getX(1);<br \/>\n        float y = event.getY(0) + event.getY(1);<br \/>\n        point.set(x \/ 2, y \/ 2);<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * Calculate the degree to be rotated by.<br \/>\n     *<br \/>\n     * @param event<br \/>\n     * @return Degrees<br \/>\n     *\/<br \/>\n    private float rotation(MotionEvent event) {<br \/>\n        double delta_x = (event.getX(0) &#8211; event.getX(1));<br \/>\n        double delta_y = (event.getY(0) &#8211; event.getY(1));<br \/>\n        double radians = Math.atan2(delta_y, delta_x);<br \/>\n        return (float) Math.toDegrees(radians);<br \/>\n    }<br \/>\n}<br \/>\n[\/code]<br \/>\nAn important function that is always used is postXx(). This function concats a new matrix of the type Xx to the existing matrix object. Using setXx() will reset the matrix&#8217;s Xx property.<br \/>\nMain layout &#8211; main.xml<br \/>\n[code language=&#8221;xml&#8221;]<br \/>\n<?xml version=\"1.0\" encoding=\"utf-8\"?><\/p>\n<p><FrameLayout\nxmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\nandroid:layout_width=\"fill_parent\"\nandroid:layout_height=\"fill_parent\"><\/p>\n<p><ImageView android:id=\"@+id\/imageView\"\nandroid:layout_width=\"fill_parent\"\nandroid:layout_height=\"fill_parent\"\nandroid:src=\"@drawable\/butterfly\"\nandroid:scaleType=\"matrix\" \/><\/p>\n<p><\/FrameLayout><br \/>\n[\/code]<\/p>\n<p>Browse and download the source code on <a title=\"Multi Touch example on GitHub\" href=\"https:\/\/github.com\/judepereira\/android-multitouch\" target=\"_blank\">GitHub.<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Here&#8217;s a quick and easy implementation of Android&#8217;s multi touch feature &#8211; one finger to move, two to zoom, and three to rotate the image. Assuming you have a basic understanding of 2D matrix transformations, the Matrix class in Android uses a 3&#215;3 matrix to achieve all of the 2D transformations. The source code and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[401],"tags":[446,556,445,447,75,442,443,444,435,437,440,438,436,439,441],"class_list":["post-1416","post","type-post","status-publish","format-standard","hentry","category-android","tag-2d-transformation","tag-android","tag-biitmap","tag-example","tag-image","tag-image-view","tag-imageview","tag-matrix","tag-multi","tag-multi-touch","tag-rotate","tag-scale","tag-touch","tag-translate","tag-view"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pqtyx-mQ","jetpack-related-posts":[{"id":1713,"url":"https:\/\/judepereira.com\/blog\/a-letter-to-tim-cook-regarding-ios-10-with-love-for-apple\/","url_meta":{"origin":1416,"position":0},"title":"A letter to Tim Cook regarding iOS 10, with love for Apple","author":"Jude Pereira","date":"June 17, 2016","format":false,"excerpt":"Tim, iOS has always been known for it's user interface, until recently. Your new take on notifications have not only made them very loud, and unsettling, but do not flow with the entire look and feel of Apple: No Mr. Tim, no. My notifications aren't waiting for me. They're crying\u2026","rel":"","context":"In &quot;life!&quot;","block_context":{"text":"life!","link":"https:\/\/judepereira.com\/blog\/category\/living-a-wonder\/"},"img":{"alt_text":"No Mr. Tim, no. My notifications will not be waiting for me. They're crying out for my attention.","src":"https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screen-Shot-2016-06-17-at-08.49.59-614x1024.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screen-Shot-2016-06-17-at-08.49.59-614x1024.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/Screen-Shot-2016-06-17-at-08.49.59-614x1024.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":1778,"url":"https:\/\/judepereira.com\/blog\/letstuneup-a-music-chart-with-arjit-singh-in-the-lead\/","url_meta":{"origin":1416,"position":1},"title":"LetsTuneup: A music chart with Arjit Singh in the lead","author":"Jude Pereira","date":"April 28, 2017","format":false,"excerpt":"LetsTuneup has grown tremendously, and with it, we've introduced new features too. We identified that a few of our users couldn't use the app to it's full extent because they didn't have music on their devices. We've solved that. Users can now pick their favourite artists, powered by a location\u2026","rel":"","context":"In &quot;android&quot;","block_context":{"text":"android","link":"https:\/\/judepereira.com\/blog\/category\/code\/android\/"},"img":{"alt_text":"Arjit Singh in the lead, with Eminem, Linking Park, Coldplay and Pink Floyd following close","src":"https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/artists.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1734,"url":"https:\/\/judepereira.com\/blog\/why-matchbox-and-how-it-connects-people-through-music\/","url_meta":{"origin":1416,"position":2},"title":"Why Matchbox, and how it connects people through music","author":"Jude Pereira","date":"January 21, 2017","format":false,"excerpt":"There's no doubt that music defines us. It influences our moods, for example, making us happy by releasing a chemical named dopamine. It can affect what we wear, what we eat, and perhaps even who we enjoy being together with. It affects\u00a0our thought process too (it's well known that ambient\u2026","rel":"","context":"In &quot;android&quot;","block_context":{"text":"android","link":"https:\/\/judepereira.com\/blog\/category\/code\/android\/"},"img":{"alt_text":"Matchbox showing the top 10 artists","src":"https:\/\/i0.wp.com\/judepereira.com\/blog\/wp-content\/uploads\/IMG_7195-300x210.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":893,"url":"https:\/\/judepereira.com\/blog\/coloured-varlogmessages-at-tty12\/","url_meta":{"origin":1416,"position":3},"title":"Coloured \/var\/log\/messages at tty12","author":"Jude Pereira","date":"April 26, 2011","format":false,"excerpt":"Reading logs could never become any more easier, at just a keystroke, you have your logs displayed where you want, in some fancy colour. They look great too. TTY's can be accessed by pressing Alt + Ctrl + F[1 - 12] simultaneously. In the following, you'll get a decent, colourized\u2026","rel":"","context":"In &quot;another snippet | code&quot;","block_context":{"text":"another snippet | code","link":"https:\/\/judepereira.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/posts\/1416","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/comments?post=1416"}],"version-history":[{"count":9,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/posts\/1416\/revisions"}],"predecessor-version":[{"id":1505,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/posts\/1416\/revisions\/1505"}],"wp:attachment":[{"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/media?parent=1416"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/categories?post=1416"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/judepereira.com\/blog\/wp-json\/wp\/v2\/tags?post=1416"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}