Showing posts with label Android code sample: detect touch. Show all posts
Showing posts with label Android code sample: detect touch. Show all posts

Thursday, December 3, 2015

Open image, free draw something on bitmap, and save bitmap to ExternalStorage

Example to load image with intent of Intent.ACTION_PICK, free draw something on the image, and save the bitmap to storage.


I have a series example of "Something about processing images in Android", but have show how to save the result bitmap to SD Card. This example modify from one of the example "Detect touch and free draw on Bitmap", add the function to save the result bitmap to External Storage, with file name "test.jpg".


layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:orientation="vertical"
    tools:context="com.blogspot.android_er.androiddrawbitmap.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/loadimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Image" />

    <Button
        android:id="@+id/saveimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save Image" />

    <ImageView
        android:id="@+id/result"
        android:scaleType="centerInside"
        android:adjustViewBounds="true"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/background_dark" />
</LinearLayout>


MainActivity.java
package com.blogspot.android_er.androiddrawbitmap;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    Button btnLoadImage, btnSaveImage;
    ImageView imageResult;

    final int RQS_IMAGE1 = 1;

    Uri source;
    Bitmap bitmapMaster;
    Canvas canvasMaster;

    int prvX, prvY;

    Paint paintDraw;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnLoadImage = (Button)findViewById(R.id.loadimage);
        btnSaveImage = (Button)findViewById(R.id.saveimage);
        imageResult = (ImageView)findViewById(R.id.result);

        paintDraw = new Paint();
        paintDraw.setStyle(Paint.Style.FILL);
        paintDraw.setColor(Color.WHITE);
        paintDraw.setStrokeWidth(10);

        btnLoadImage.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                Intent intent = new Intent(Intent.ACTION_PICK,
                        android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                startActivityForResult(intent, RQS_IMAGE1);
            }
        });

        btnSaveImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(bitmapMaster != null){
                    saveBitmap(bitmapMaster);
                }
            }
        });

        imageResult.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                int action = event.getAction();
                int x = (int) event.getX();
                int y = (int) event.getY();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        prvX = x;
                        prvY = y;
                        drawOnProjectedBitMap((ImageView) v, bitmapMaster, prvX, prvY, x, y);
                        break;
                    case MotionEvent.ACTION_MOVE:
                        drawOnProjectedBitMap((ImageView) v, bitmapMaster, prvX, prvY, x, y);
                        prvX = x;
                        prvY = y;
                        break;
                    case MotionEvent.ACTION_UP:
                        drawOnProjectedBitMap((ImageView) v, bitmapMaster, prvX, prvY, x, y);
                        break;
                }
    /*
     * Return 'true' to indicate that the event have been consumed.
     * If auto-generated 'false', your code can detect ACTION_DOWN only,
     * cannot detect ACTION_MOVE and ACTION_UP.
     */
                return true;
            }
        });
    }

    /*
    Project position on ImageView to position on Bitmap draw on it
     */

    private void drawOnProjectedBitMap(ImageView iv, Bitmap bm,
                                       float x0, float y0, float x, float y){
        if(x<0 || y<0 || x > iv.getWidth() || y > iv.getHeight()){
            //outside ImageView
            return;
        }else{

            float ratioWidth = (float)bm.getWidth()/(float)iv.getWidth();
            float ratioHeight = (float)bm.getHeight()/(float)iv.getHeight();

            canvasMaster.drawLine(
                    x0 * ratioWidth,
                    y0 * ratioHeight,
                    x * ratioWidth,
                    y * ratioHeight,
                    paintDraw);
            imageResult.invalidate();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Bitmap tempBitmap;

        if(resultCode == RESULT_OK){
            switch (requestCode){
                case RQS_IMAGE1:
                    source = data.getData();

                    try {
                        //tempBitmap is Immutable bitmap,
                        //cannot be passed to Canvas constructor
                        tempBitmap = BitmapFactory.decodeStream(
                                getContentResolver().openInputStream(source));

                        Bitmap.Config config;
                        if(tempBitmap.getConfig() != null){
                            config = tempBitmap.getConfig();
                        }else{
                            config = Bitmap.Config.ARGB_8888;
                        }

                        //bitmapMaster is Mutable bitmap
                        bitmapMaster = Bitmap.createBitmap(
                                tempBitmap.getWidth(),
                                tempBitmap.getHeight(),
                                config);

                        canvasMaster = new Canvas(bitmapMaster);
                        canvasMaster.drawBitmap(tempBitmap, 0, 0, null);

                        imageResult.setImageBitmap(bitmapMaster);
                    } catch (FileNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    break;
            }
        }
    }

    private void saveBitmap(Bitmap bm){
        File file = Environment.getExternalStorageDirectory();
        File newFile = new File(file, "test.jpg");

        try {
            FileOutputStream fileOutputStream = new FileOutputStream(newFile);
            bm.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
            fileOutputStream.flush();
            fileOutputStream.close();
            Toast.makeText(MainActivity.this,
                    "Save Bitmap: " + fileOutputStream.toString(),
                    Toast.LENGTH_LONG).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(MainActivity.this,
                    "Something wrong: " + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(MainActivity.this,
                    "Something wrong: " + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }
    }
}


"android.permission.WRITE_EXTERNAL_STORAGE" is needed in AndroidManifest.xml. Also I add android:largeHeap="true" to require more heap for the app. But if you have a really big image, it still will closed, caused by Out Of Memory!
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.android_er.androiddrawbitmap">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:largeHeap="true"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


download filesDownload the files (Android Studio Format) .

more: Something about processing images in Android

Wednesday, May 27, 2015

Android custom touch view, with callback interface.

This example, implement custom view with touch function, and also callback interface. And also implement listener at activity side. Such that the view can pass touched information to activity.


com.example.androidtouchview.TouchView.java, custom view.
package com.example.androidtouchview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class TouchView extends View {

    private Paint paint, touchPaint;
    private boolean touched;
    private float touchX, touchY;
    private float touchMajor, touchMinor;

    public TouchView(Context context) {
        super(context);
        init(null, 0);
    }

    public TouchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public TouchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr);
    }

    private void init(AttributeSet attrs, int defStyle) {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);

        touchPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        touchPaint.setColor(Color.RED);
        touchPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        touchPaint.setStrokeWidth(1);

        touched = false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();

        canvas.drawRect(
                0,
                0,
                getWidth(),
                getHeight(),
                paint);
        canvas.drawRect(
                paddingLeft,
                paddingTop,
                getWidth()- paddingRight,
                getHeight()- paddingBottom,
                paint);

        if(touched){
            canvas.drawCircle(touchX, touchY, touchMinor, touchPaint);
            canvas.drawCircle(touchX, touchY, touchMajor, paint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch(event.getAction()){
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_DOWN:
                touchX = event.getX();
                touchY = event.getY();
                touchMajor = event.getTouchMajor();
                touchMinor = event.getTouchMinor();
                touched = true;
                break;
            default:
                touched = false;
        }

        onViewTouchedListener.OnViewTouched(touchX, touchY, touched);

        invalidate();
        return true;
    }

    /*
    Set up callback function
     */
    private OnViewTouchedListener onViewTouchedListener;
    public interface OnViewTouchedListener {
        public void OnViewTouched(float x, float y, boolean touched);
    }

    public void setOnViewTouchedListener(OnViewTouchedListener listener) {
        onViewTouchedListener = listener;
    }
}


layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/touchedInfo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#E0E0E0">
        <com.example.androidtouchview.TouchView
            android:id="@+id/myTouchView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="50dp"
            android:paddingRight="50dp"
            android:paddingTop="50dp"
            android:paddingBottom="50dp" />
    </LinearLayout>


</LinearLayout>

com.example.androidtouchview.MainActivity
package com.example.androidtouchview;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity {

    TextView touchedInfo;
    TouchView touchView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        touchedInfo = (TextView)findViewById(R.id.touchedInfo);
        touchView = (TouchView)findViewById(R.id.myTouchView);
        touchView.setOnViewTouchedListener(new TouchView.OnViewTouchedListener() {
            @Override
            public void OnViewTouched(float x, float y, boolean touched) {
                touchedInfo.setText(
                        "Touched: " + touched + "\n" +
                        "x: " + x + "\n" + "y: " + y);
            }
        });

    }

}



download filesDownload the files (Android Studio Format).

Create custom view with touch detection

Here we implement a custom view with touch detection, to show touched area.


com.example.androidtouchview.TouchView.java, our custom view.
package com.example.androidtouchview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class TouchView extends View {

    private Paint paint, touchPaint;
    private boolean touched;
    private float touchX, touchY;
    private float touchMajor, touchMinor;

    public TouchView(Context context) {
        super(context);
        init(null, 0);
    }

    public TouchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public TouchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr);
    }

    private void init(AttributeSet attrs, int defStyle) {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);

        touchPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        touchPaint.setColor(Color.RED);
        touchPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        touchPaint.setStrokeWidth(1);

        touched = false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();

        canvas.drawRect(
                0,
                0,
                getWidth(),
                getHeight(),
                paint);
        canvas.drawRect(
                paddingLeft,
                paddingTop,
                getWidth()- paddingRight,
                getHeight()- paddingBottom,
                paint);

        if(touched){
            canvas.drawCircle(touchX, touchY, touchMinor, touchPaint);
            canvas.drawCircle(touchX, touchY, touchMajor, paint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch(event.getAction()){
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_DOWN:
                touchX = event.getX();
                touchY = event.getY();
                touchMajor = event.getTouchMajor();
                touchMinor = event.getTouchMinor();
                touched = true;
                break;
            default:
                touched = false;
        }
        invalidate();
        return true;
    }
}


layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#E0E0E0">
        <com.example.androidtouchview.TouchView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="50dp"
            android:paddingRight="50dp"
            android:paddingTop="50dp"
            android:paddingBottom="50dp" />
    </LinearLayout>


</LinearLayout>


download filesDownload the files (Android Studio Format).

Android custom touch view, with callback interface.

Thursday, September 4, 2014

Insert view to GridLayout dynamically, with even width and height

This example insert our custom view to a GridLayout (Added in API level 14) dynamically in Java code, and set the width of height evenly.


Modify layout, activity_main.xml, to have a GridLayout without child.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androidtouchview.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <GridLayout
        android:id="@+id/mygrid"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:columnCount="8"
        android:rowCount="5"
        android:background="@android:color/background_light" >
    </GridLayout>

</LinearLayout>

MyView.java
package com.example.androidtouchview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {
 
 public interface OnToggledListener {
  void OnToggled(MyView v, boolean touchOn);
 }

 boolean touchOn;
 boolean mDownTouch = false;
 private OnToggledListener toggledListener;
 int idX = 0; //default
 int idY = 0; //default

 public MyView(Context context, int x, int y) {
  super(context);
  idX = x;
  idY = y;
  init();
 }
 
 public MyView(Context context) {
  super(context);
  init();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 private void init() {
  touchOn = false;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
    MeasureSpec.getSize(heightMeasureSpec));
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if (touchOn) {
   canvas.drawColor(Color.RED);
  } else {
   canvas.drawColor(Color.GRAY);
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  super.onTouchEvent(event);

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
             
             touchOn = !touchOn;
       invalidate();
       
       if(toggledListener != null){
        toggledListener.OnToggled(this, touchOn);
       }
             
                mDownTouch = true;
                return true;

            case MotionEvent.ACTION_UP:
                if (mDownTouch) {
                    mDownTouch = false;
                    performClick();
                    return true;
                }
        }
        return false;
 }

 @Override
 public boolean performClick() {
        super.performClick();
        return true;
 }
 
 public void setOnToggledListener(OnToggledListener listener){
  toggledListener = listener;
 }
 
 public int getIdX(){
  return idX;
 }
 
 public int getIdY(){
  return idY;
 }

}

MainActivity.java
Our custom views, MyView, are created dynamically and add to the GridLayout in onCreate(). Then re-align their LayoutParams in OnGlobalLayoutListener(), will be called when the global layout state or the visibility of views within the view tree changes.
package com.example.androidtouchview;

import com.example.androidtouchview.MyView.OnToggledListener;

import android.support.v7.app.ActionBarActivity;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.GridLayout;
import android.widget.Toast;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity 
 implements OnToggledListener{
 
 MyView[] myViews;
 
 GridLayout myGridLayout;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  myGridLayout = (GridLayout)findViewById(R.id.mygrid);
  
  int numOfCol = myGridLayout.getColumnCount();
  int numOfRow = myGridLayout.getRowCount();
  myViews = new MyView[numOfCol*numOfRow];
  for(int yPos=0; yPos<numOfRow; yPos++){
   for(int xPos=0; xPos<numOfCol; xPos++){
    MyView tView = new MyView(this, xPos, yPos);
    tView.setOnToggledListener(this);
    myViews[yPos*numOfCol + xPos] = tView;
    myGridLayout.addView(tView);
   }
  }
  
  myGridLayout.getViewTreeObserver().addOnGlobalLayoutListener(
   new OnGlobalLayoutListener(){

   @Override
   public void onGlobalLayout() {
    
    final int MARGIN = 5;
    
    int pWidth = myGridLayout.getWidth();
    int pHeight = myGridLayout.getHeight();
    int numOfCol = myGridLayout.getColumnCount();
    int numOfRow = myGridLayout.getRowCount();
    int w = pWidth/numOfCol;
    int h = pHeight/numOfRow;
    
    for(int yPos=0; yPos<numOfRow; yPos++){
     for(int xPos=0; xPos<numOfCol; xPos++){
      GridLayout.LayoutParams params = 
       (GridLayout.LayoutParams)myViews[yPos*numOfCol + xPos].getLayoutParams();
      params.width = w - 2*MARGIN;
      params.height = h - 2*MARGIN;
      params.setMargins(MARGIN, MARGIN, MARGIN, MARGIN);
      myViews[yPos*numOfCol + xPos].setLayoutParams(params);
     }
    }

   }});
 }

 @Override
 public void OnToggled(MyView v, boolean touchOn) {

  //get the id string
  String idString = v.getIdX() + ":" + v.getIdY();

  Toast.makeText(MainActivity.this, 
   "Toogled:\n" +
   idString + "\n" +
   touchOn, 
   Toast.LENGTH_SHORT).show();
 }

}

download filesDownload the files.

Notice:
- OnGlobalLayoutListener will be called repeatly this way. To remove it, call removeGlobalOnLayoutListener() or removeOnGlobalLayoutListener(), read next post.



Implement callback function by implementing interface for custom view

Last post "Custom View, detect touch to toggle color" and also "warning: custom view overrides onTouchEvent but not performClick and #onTouchEvent should call #performClick when a click is detected", we have a self-running custom view to detect user touch and toggle color. But the MainActivity don't know what happen on it. To make MainActivity informed when color toggled, we can implement interface for our custom view.


MyView.java
package com.example.androidtouchview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {
 
 public interface OnToggledListener {
  void OnToggled(MyView v, boolean touchOn);
 }

 boolean touchOn;
 boolean mDownTouch = false;
 private OnToggledListener toggledListener;

 public MyView(Context context) {
  super(context);
  init();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 private void init() {
  touchOn = false;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
    MeasureSpec.getSize(heightMeasureSpec));
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if (touchOn) {
   canvas.drawColor(Color.RED);
  } else {
   canvas.drawColor(Color.GRAY);
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  super.onTouchEvent(event);

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
             
             touchOn = !touchOn;
       invalidate();
       
       if(toggledListener != null){
        toggledListener.OnToggled(this, touchOn);
       }
             
                mDownTouch = true;
                return true;

            case MotionEvent.ACTION_UP:
                if (mDownTouch) {
                    mDownTouch = false;
                    performClick();
                    return true;
                }
        }
        return false;
 }

 @Override
 public boolean performClick() {
        super.performClick();
        return true;
 }
 
 public void setOnToggledListener(OnToggledListener listener){
  toggledListener = listener;
 }

}

MainActivity.java
package com.example.androidtouchview;

import com.example.androidtouchview.MyView.OnToggledListener;

import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity 
 implements OnToggledListener{
 
 MyView myView00, myView01;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  myView00 = (MyView)findViewById(R.id.myview00);
  myView01 = (MyView)findViewById(R.id.myview01);

  myView00.setOnToggledListener(this);
  myView01.setOnToggledListener(this);
 }

 @Override
 public void OnToggled(MyView v, boolean touchOn) {

  //get the id string
  String idString = getResources().getResourceName(v.getId());

  Toast.makeText(MainActivity.this, 
   "Toogled:\n" +
   idString + "\n" +
   touchOn, 
   Toast.LENGTH_SHORT).show();
 }

}

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.androidtouchview.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        
        <com.example.androidtouchview.MyView 
            android:id="@+id/myview00"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="10dp"/>
        <com.example.androidtouchview.MyView 
            android:id="@+id/myview01"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:layout_margin="10dp"/>
        
    </LinearLayout>
    
</LinearLayout>


Wednesday, September 3, 2014

warning: custom view overrides onTouchEvent but not performClick and #onTouchEvent should call #performClick when a click is detected

If you copy the last example of "Custom View, detect touch to toggle color", you will be warned on onTouchEvent() with message of "MyView overrides onTouchEvent but not performClick"!



Then you override performClick(), you will be warned again with "MyView#onTouchEvent should call .../MyView#performClick when a click is detected"!!!


Android document have mention about "Handling custom touch events":

Custom view controls may require non-standard touch event behavior. For example, a custom control may use the onTouchEvent(MotionEvent) listener method to detect the ACTION_DOWN and ACTION_UP events and trigger a special click event. In order to maintain compatibility with accessibility services, the code that handles this custom click event must do the following:
  1. Generate an appropriate AccessibilityEvent for the interpreted click action.
  2. Enable accessibility services to perform the custom click action for users who are not able to use a touch screen.
To handle these requirements in an efficient way, your code should override the performClick() method, which must call the super implementation of this method and then execute whatever actions are required by the click event. When the custom click action is detected, that code should then call your performClick() method. The following code example demonstrates this pattern.



This video show how to modify MyView.java in last post to remove the warnings.


After changed, MyView.java become:
package com.example.androidtouchview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {

 boolean touchOn;
 boolean mDownTouch = false;

 public MyView(Context context) {
  super(context);
  init();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 private void init() {
  touchOn = false;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
    MeasureSpec.getSize(heightMeasureSpec));
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if (touchOn) {
   canvas.drawColor(Color.RED);
  } else {
   canvas.drawColor(Color.GRAY);
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  super.onTouchEvent(event);

  /*
  int action = event.getAction();

  if (action == MotionEvent.ACTION_DOWN) {
   touchOn = !touchOn;
   invalidate();
   return true;
  }

  return false;
  */
  // Listening for the down and up touch events
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
             
             touchOn = !touchOn;
       invalidate();
             
                mDownTouch = true;
                return true;

            case MotionEvent.ACTION_UP:
                if (mDownTouch) {
                    mDownTouch = false;
                    performClick(); // Call this method to handle the response, and
                                    // thereby enable accessibility services to
                                    // perform this action for a user who cannot
                                    // click the touchscreen.
                    return true;
                }
        }
        return false; // Return false for other touch events
 }

 @Override
 public boolean performClick() {
  // Calls the super implementation, which generates an AccessibilityEvent
        // and calls the onClick() listener on the view, if any
        super.performClick();

        // Handle the action for the custom click here

        return true;
 }

}

Custom View, detect touch to toggle color

This example implement a custom view, it have only one function, toggle color if user touched.


Create MyView.java
package com.example.androidtouchview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {

 boolean touchOn;

 public MyView(Context context) {
  super(context);
  init();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 private void init() {
  touchOn = false;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
    MeasureSpec.getSize(heightMeasureSpec));
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if (touchOn) {
   canvas.drawColor(Color.RED);
  } else {
   canvas.drawColor(Color.GRAY);
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  super.onTouchEvent(event);
  
  int action = event.getAction();
  
  if(action == MotionEvent.ACTION_DOWN){
   touchOn = !touchOn;
   invalidate();
   return true;
  }
  
  return false;
 }

}

Modify /res/layout/activity_main.xml to include the custom view.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.androidtouchview.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        
        <com.example.androidtouchview.MyView 
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="10dp"/>
        <com.example.androidtouchview.MyView 
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:layout_margin="10dp"/>
        
    </LinearLayout>
    
</LinearLayout>

Keep using the auto generated MainActivity.java

You will be warned "MyView overrides onTouchEvent but not performClick" on onTouchEvent() method, read next post to fix it.


Friday, June 6, 2014

Change speed of Animation follow touch path

This exercise change the speed of the former example of "Animation follow touch path".


AnimationView.java
package com.example.androidanimationalongpath;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class AnimationView extends View {
 
 Paint paint;
 
 Bitmap bm;
 int bm_offsetX, bm_offsetY;
 
 Path animPath;
 PathMeasure pathMeasure;
 float pathLength;
 
 float step;   //distance each step
 float distance;  //distance moved
 float curX, curY;
  
 float curAngle;  //current angle
 float targetAngle; //target angle
 float stepAngle; //angle each step

 float[] pos;
 float[] tan;
 
 Matrix matrix;
 
 Path touchPath;

 public AnimationView(Context context) {
  super(context);
  initMyView();
 }

 public AnimationView(Context context, AttributeSet attrs) {
  super(context, attrs);
  initMyView();
 }

 public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  initMyView();
 }
 
 public void initMyView(){
  paint = new Paint();
  paint.setColor(Color.BLUE);
  paint.setStrokeWidth(1);
  paint.setStyle(Paint.Style.STROKE);
    
  bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
  bm_offsetX = bm.getWidth()/2;
  bm_offsetY = bm.getHeight()/2;
  
  animPath = new Path();
  
  pos = new float[2];
  tan = new float[2];
  
  matrix = new Matrix();
  
  touchPath = new Path();
  
  step = 1;  //default
  stepAngle = 1; //default
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if(animPath.isEmpty()){
   return;
  }
  
  canvas.drawPath(animPath, paint);
  
  matrix.reset();
  
  if((targetAngle-curAngle)>stepAngle){
   curAngle += stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else if((curAngle-targetAngle)>stepAngle){
   curAngle -= stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else{
   curAngle=targetAngle;
   if(distance < pathLength){
    pathMeasure.getPosTan(distance, pos, tan);

    targetAngle = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    
    curX = pos[0]-bm_offsetX;
    curY = pos[1]-bm_offsetY;
    matrix.postTranslate(curX, curY);
    
    canvas.drawBitmap(bm, matrix, null);
    
    distance += step;
    
    invalidate();
   }else{
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    matrix.postTranslate(curX, curY);
    canvas.drawBitmap(bm, matrix, null);
   }
  }

 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  
  int action = event.getAction();
  
  switch(action){
  case MotionEvent.ACTION_DOWN:
   touchPath.reset();
   touchPath.moveTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_MOVE:
   touchPath.lineTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_UP:
   touchPath.lineTo(event.getX(), event.getY());
   animPath = new Path(touchPath);
   
   pathMeasure = new PathMeasure(animPath, false);
   pathLength = pathMeasure.getLength();
   
   //step = 1;
   distance = 0;
   curX = 0;
   curY = 0;
   
   //stepAngle = 1; 
   curAngle = 0;
   targetAngle = 0;
   
   invalidate();
   
   break;
    
  }
  
  return true;
 }
 
 public void setSpeed(int sp){
  step = sp;
  stepAngle = sp;
 }

}

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androidanimationalongpath.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <SeekBar
        android:id="@+id/speedbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="10"
        android:progress="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >
        
        <com.example.androidanimationalongpath.AnimationView
            android:id="@+id/animationview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:background="@android:color/darker_gray" />

    </LinearLayout>

</LinearLayout>

MainActivity.java
package com.example.androidanimationalongpath;

import android.support.v7.app.ActionBarActivity;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity {
 
 AnimationView animationView;
 SeekBar speedBar;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  animationView = (AnimationView)findViewById(R.id.animationview);
  speedBar = (SeekBar)findViewById(R.id.speedbar);
  speedBar.setOnSeekBarChangeListener(speedBarOnSeekBarChangeListener);
  animationView.setSpeed(speedBar.getProgress()); //set default speed
 }
 
 OnSeekBarChangeListener speedBarOnSeekBarChangeListener = 
  new OnSeekBarChangeListener(){

   @Override
   public void onProgressChanged(SeekBar seekBar, int progress,
     boolean fromUser) {
    //offset from SeekBar 0~19 to step 1~10
    animationView.setSpeed(progress);
   }

   @Override
   public void onStartTrackingTouch(SeekBar seekBar) {}

   @Override
   public void onStopTrackingTouch(SeekBar seekBar) {}};
   

}


download filesDownload the files.

More example of Drawing Path on canvas of custom View.

Tuesday, May 27, 2014

Animation follow touch path

This example show how to implement animation of moving bitmap follow the touch moving path.


AnimationView.java
package com.example.androidanimationalongpath;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class AnimationView extends View {
 
 Paint paint;
 
 Bitmap bm;
 int bm_offsetX, bm_offsetY;
 
 Path animPath;
 PathMeasure pathMeasure;
 float pathLength;
 
 float step;   //distance each step
 float distance;  //distance moved
 float curX, curY;
  
 float curAngle;  //current angle
 float targetAngle; //target angle
 float stepAngle; //angle each step

 float[] pos;
 float[] tan;
 
 Matrix matrix;
 
 Path touchPath;

 public AnimationView(Context context) {
  super(context);
  initMyView();
 }

 public AnimationView(Context context, AttributeSet attrs) {
  super(context, attrs);
  initMyView();
 }

 public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  initMyView();
 }
 
 public void initMyView(){
  paint = new Paint();
  paint.setColor(Color.BLUE);
  paint.setStrokeWidth(1);
  paint.setStyle(Paint.Style.STROKE);
    
  bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
  bm_offsetX = bm.getWidth()/2;
  bm_offsetY = bm.getHeight()/2;
  
  animPath = new Path();
  
  pos = new float[2];
  tan = new float[2];
  
  matrix = new Matrix();
  
  touchPath = new Path();
 }

 @Override
 protected void onDraw(Canvas canvas) {
  
  if(animPath.isEmpty()){
   return;
  }
  
  canvas.drawPath(animPath, paint);
  
  matrix.reset();
  
  if((targetAngle-curAngle)>stepAngle){
   curAngle += stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else if((curAngle-targetAngle)>stepAngle){
   curAngle -= stepAngle;
   matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
   matrix.postTranslate(curX, curY);
   canvas.drawBitmap(bm, matrix, null);
   
   invalidate();
  }else{
   curAngle=targetAngle;
   if(distance < pathLength){
    pathMeasure.getPosTan(distance, pos, tan);

    targetAngle = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    
    curX = pos[0]-bm_offsetX;
    curY = pos[1]-bm_offsetY;
    matrix.postTranslate(curX, curY);
    
    canvas.drawBitmap(bm, matrix, null);
    
    distance += step;
    
    invalidate();
   }else{
    matrix.postRotate(curAngle, bm_offsetX, bm_offsetY);
    matrix.postTranslate(curX, curY);
    canvas.drawBitmap(bm, matrix, null);
   }
  }

 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  
  int action = event.getAction();
  
  switch(action){
  case MotionEvent.ACTION_DOWN:
   touchPath.reset();
   touchPath.moveTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_MOVE:
   touchPath.lineTo(event.getX(), event.getY());
   break;
  case MotionEvent.ACTION_UP:
   touchPath.lineTo(event.getX(), event.getY());
   animPath = new Path(touchPath);
   
   pathMeasure = new PathMeasure(animPath, false);
   pathLength = pathMeasure.getLength();
   
   step = 1;
   distance = 0;
   curX = 0;
   curY = 0;
   
   stepAngle = 1; 
   curAngle = 0;
   targetAngle = 0;
   
   invalidate();
   
   break;
    
  }
  
  return true;
 }

}

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androidanimationalongpath.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <com.example.androidanimationalongpath.AnimationView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:background="@android:color/darker_gray" />

        <com.example.androidanimationalongpath.AnimationView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:background="@android:color/darker_gray" />
    </LinearLayout>

</LinearLayout>

No change on MainActivity.java, refer HERE.


download filesDownload the files.

More example of Drawing Path on canvas of custom View.


Monday, May 19, 2014

Detect touch and draw circle on Android WebView with Javascript

Last exercise show a simple WebView with touch detection on Android WebView, it's modify to draw circle when user touch and move on HTML canvas.


Modify /assets/mypage.html from last exercise.
<!DOCTYPE HTML>
<HTML>
<HEAD>
<script>

var p1;
var p2;
var canvas;
var context;
var cx;
var cy;
var x;
var y;
var canvasOffsetX;
var canvasOffsetY;

function init(){
 p1 = document.getElementById('p1');
 p2 = document.getElementById('p2');
 p1.innerHTML=navigator.userAgent;
 
 canvas = document.getElementById('myCanvas');
 context = canvas.getContext('2d');
 canvasOffsetX = canvas.getBoundingClientRect().left;
 canvasOffsetY = canvas.getBoundingClientRect().top;
 
 canvas.width = window.innerWidth - (2 * canvasOffsetX);
 canvas.height = window.innerHeight - canvasOffsetY - 100;
 canvas.fillStyle="#a0a0a0";

 canvas.addEventListener('touchstart', touchstartListener, false);
 canvas.addEventListener('touchmove', touchmoveListener, false);
 canvas.addEventListener('touchend', touchendListener, false);
 canvas.addEventListener('touchenter', touchenterListener, false);
 canvas.addEventListener('touchleave', touchleaveListener, false);
 canvas.addEventListener('touchcancel', touchcancelListener, false);
 
}

function touchstartListener(event){
 cx = event.changedTouches[0].pageX - canvasOffsetX;
 cy = event.changedTouches[0].pageY - canvasOffsetY;
 p2.innerHTML= "touchstart - <br/>" 
  + cx + ":" + cy;
 event.preventDefault();
 
 context.rect(0, 0, canvas.width, canvas.height);
 context.fillStyle="white";
 context.fill();
}

function touchmoveListener(event){

 x = event.changedTouches[0].pageX - canvasOffsetX;
 y = event.changedTouches[0].pageY - canvasOffsetY;
 var deltax = x-cx;
 var deltay = y-cy;
 var radius = Math.sqrt(deltax*deltax + deltay*deltay);
 context.beginPath();
 context.arc(cx, cy, radius, 0, 2 * Math.PI, false);
 context.fillStyle = 'green';
 context.fill();
 context.lineWidth = 1;
 context.strokeStyle = '#003300';
 context.stroke();

 p2.innerHTML= "touchmove - <br/>" 
  + x + ":" + y;
 event.preventDefault();
}

function touchendListener(event){
 p2.innerHTML= "touchend - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchenterListener(event){
 p2.innerHTML= "touchenter - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchleaveListener(event){
 p2.innerHTML= "touchleave - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchcancelListener(event){
 p2.innerHTML= "touchcancel - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

</script>
</HEAD>
<BODY onload="init()" style="border:5px solid #000000;">

<p id='p1'>un-init</p>

<canvas id='myCanvas' style="border:1px solid #FF0000;">
Canvas not support!
</canvas>

<p id='p2'></p>

</BODY>
</HTML>

Sunday, May 18, 2014

Android WebView, detect touch events with Javascript.

This example show how to create hybrid web-app, using WebView. And detect the touch events with Javascript.


/assets/mypage.html, it will be loaded in our WebView, to perform the main function.
<!DOCTYPE HTML>
<HTML>
<HEAD>
<script>

var p1;
var p2;

function init(){
 p1 = document.getElementById('p1');
 p2 = document.getElementById('p2');
 p1.innerHTML=navigator.userAgent;
 var canvas = document.getElementById('myCanvas');
 var canvasOffsetX = canvas.getBoundingClientRect().left;
 var canvasOffsetY = canvas.getBoundingClientRect().top;
 
 canvas.width = window.innerWidth - (2 * canvasOffsetX);
 canvas.height = window.innerHeight - canvasOffsetY - 100;
 canvas.fillStyle="#a0a0a0";

 canvas.addEventListener('touchstart', touchstartListener, false);
 canvas.addEventListener('touchmove', touchmoveListener, false);
 canvas.addEventListener('touchend', touchendListener, false);
 canvas.addEventListener('touchenter', touchenterListener, false);
 canvas.addEventListener('touchleave', touchleaveListener, false);
 canvas.addEventListener('touchcancel', touchcancelListener, false);
 
}

function touchstartListener(event){
 p2.innerHTML= "touchstart - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchmoveListener(event){
 p2.innerHTML= "touchmove - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchendListener(event){
 p2.innerHTML= "touchend - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchenterListener(event){
 p2.innerHTML= "touchenter - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchleaveListener(event){
 p2.innerHTML= "touchleave - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

function touchcancelListener(event){
 p2.innerHTML= "touchcancel - <br/>" 
  + event.changedTouches[0].pageX + ":" + event.changedTouches[0].pageY;
 event.preventDefault();
}

</script>
</HEAD>
<BODY onload="init()" style="border:5px solid #000000;">

<p id='p1'>un-init</p>

<canvas id='myCanvas' style="border:1px solid #FF0000;">
Canvas not support!
</canvas>

<p id='p2'></p>

</BODY>
</HTML>

/res/layout/activity_main.xml, with <WebView>.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    android:background="@android:color/background_dark"
    tools:context="com.example.androidhybridwebview.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <WebView
        android:id="@+id/mybrowser"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>

</LinearLayout>

package com.example.androidhybridwebview;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;

public class MainActivity extends Activity {

 WebView myBrowser;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  myBrowser = (WebView) findViewById(R.id.mybrowser);
  myBrowser.getSettings().setJavaScriptEnabled(true);
  myBrowser.loadUrl("file:///android_asset/mypage.html");
 }

}

Next:
Detect touch and draw circle on Android WebView with Javascript

Friday, May 16, 2014

Get touch pressure

MotionEvent provide getPressure() and getPressure(int pointerIndex) to get the touch pressure.

Example:

MyView.java
package com.example.androiddrawinview;

import java.util.ArrayList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {
 
 private ArrayList<xy> drawList;
 private boolean touching = false;
 
 Paint paintTouchPointer_ONE;
 Paint paintTouchPointer;

 public MyView(Context context) {
  super(context);
  init();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(
   MeasureSpec.getSize(widthMeasureSpec),
   MeasureSpec.getSize(heightMeasureSpec));
 }
 
 private void init(){
  paintTouchPointer = new Paint();
  paintTouchPointer.setColor(Color.RED);
  paintTouchPointer.setStyle(Paint.Style.FILL);
  
  paintTouchPointer_ONE = new Paint();
  paintTouchPointer_ONE.setColor(Color.BLUE);
  paintTouchPointer_ONE.setStyle(Paint.Style.STROKE);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  canvas.drawColor(Color.GRAY);
  if(touching){
   for(xy pt : drawList){
    
    float PRESSURE_ONE_RADIUS = 50;
    float radius = pt.getPressure() * PRESSURE_ONE_RADIUS;
    
    canvas.drawCircle(
      pt.getX(), 
      pt.getY(), 
      radius, 
      paintTouchPointer);
    
    canvas.drawCircle(
      pt.getX(), 
      pt.getY(), 
      PRESSURE_ONE_RADIUS, 
      paintTouchPointer_ONE);
   }
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  
  int action = event.getAction() & MotionEvent.ACTION_MASK;
  switch(action){
  case MotionEvent.ACTION_MOVE:
  case MotionEvent.ACTION_DOWN:
   
   ArrayList<xy> touchList = new ArrayList<xy>();
   int pointerCount = event.getPointerCount();
   
   for(int i=0; i < pointerCount; i++){
    touchList.add(
     new xy(
      event.getX(i), 
      event.getY(i),
      event.getPressure(i)));
   }
   drawList = touchList;
   touching = true;
   break;
  default:
   touching = false;
  }
  
  invalidate();
  return true;
 }
 
 class xy{
  float x;
  float y;
  float pressure;
  
  public xy(float x, float y, float pressure){
   this.x = x;
   this.y = y;
   this.pressure = pressure;
  }
  
  public float getX(){
   return x;
  }
  
  public float getY(){
   return y;
  }
  
  public float getPressure(){
   return pressure;
  }
 }
 
}

/res/layout/fragment_main.xml, to include MyView in layout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androiddrawinview.MainActivity$PlaceholderFragment" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <com.example.androiddrawinview.MyView
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:layout_margin="10dp" />
    </LinearLayout>

</LinearLayout>

MainActivity.java, actually it is the default generated by Eclipse.
package com.example.androiddrawinview;

import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

public class MainActivity extends ActionBarActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  if (savedInstanceState == null) {
   getSupportFragmentManager().beginTransaction()
     .add(R.id.container, new PlaceholderFragment()).commit();
  }
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {

  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  // Handle action bar item clicks here. The action bar will
  // automatically handle clicks on the Home/Up button, so long
  // as you specify a parent activity in AndroidManifest.xml.
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }

 /**
  * A placeholder fragment containing a simple view.
  */
 public static class PlaceholderFragment extends Fragment {

  public PlaceholderFragment() {
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
   View rootView = inflater.inflate(R.layout.fragment_main, container,
     false);
   return rootView;
  }
 }

}

/res/layout/activity_main.xml, auto generated by Eclipse.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.androiddrawinview.MainActivity"
    tools:ignore="MergeRootFrame" />

download filesDownload the files.

Tuesday, May 13, 2014

Cannot detect MotionEvent.ACTION_MOVE and ACTION_UP

Refer to former exercises, "Detect single touch on custom View to draw icon on canvas" and "Detect multi touch on custom View to draw icons on canvas"; true is return from onTouchEvent() method of the custom view, indicate that the event was handled.

If false is returned from onTouchEvent() method, indicate that your code will not handle the events, and the system will not pass you the subsequent events such as MotionEvent.ACTION_MOVE and MotionEvent.ACTION_UP.

This example show two custome View, MyView on left side and MyView2 on right side. MyView handle user touch and display icon in onTouchEvent() method and return true. MyView2 do the same job by extending MyView, but return false in onTouchEvent() method. It can be noticed that MyView2 on right side can detect MotionEvent.ACTION_DOWN only, not MotionEvent.ACTION_MOVE and MotionEvent.ACTION_UP.


Modify from last the exercise "Detect multi touch on custom View to draw icons on canvas", create MyView2.java extends MyView, override onTouchEvent() to return false.

package com.example.androiddrawinview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

public class MyView2 extends MyView {

 public MyView2(Context context) {
  super(context);
  // TODO Auto-generated constructor stub
 }

 public MyView2(Context context, AttributeSet attrs) {
  super(context, attrs);
  // TODO Auto-generated constructor stub
 }

 public MyView2(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  // TODO Auto-generated constructor stub
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  super.onTouchEvent(event);
  return false;
 }

}

Modify /res/layout/fragment_main.xml to replace the MyView on right hand side to MyView2.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androiddrawinview.MainActivity$PlaceholderFragment" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <com.example.androiddrawinview.MyView
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:layout_margin="10dp" />
        <com.example.androiddrawinview.MyView2
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:layout_margin="10dp" />
    </LinearLayout>

</LinearLayout>


download filesDownload the files.

Friday, May 9, 2014

Detect multi touch on custom View to draw icons on canvas

Last exercise show how to "Detect single touch on custom View to draw icon on canvas". It is now modified to detect multi touch, to display multi icons.


MyView.java
package com.example.androiddrawinview;

import java.util.ArrayList;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {
 
 private ArrayList<xy> drawList;
 private float offsetX, offsetY;
 private boolean touching = false;
 private Bitmap bm;

 public MyView(Context context) {
  super(context);
  init();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(
   MeasureSpec.getSize(widthMeasureSpec),
   MeasureSpec.getSize(heightMeasureSpec));
 }
 
 private void init(){
  bm = BitmapFactory.decodeResource(
    getResources(),
    R.drawable.ic_launcher);
  offsetX = bm.getWidth()/2;
  offsetY = bm.getHeight()/2;
 }

 @Override
 protected void onDraw(Canvas canvas) {
  canvas.drawColor(Color.GRAY);
  if(touching){
   for(xy pt : drawList){
    canvas.drawBitmap(bm, 
     pt.getX()-offsetX, 
     pt.getY()-offsetY, 
     null);
   }
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  
  int action = event.getAction() & MotionEvent.ACTION_MASK;
  switch(action){
  case MotionEvent.ACTION_MOVE:
  case MotionEvent.ACTION_DOWN:
   
   ArrayList<xy> touchList = new ArrayList<xy>();
   int pointerCount = event.getPointerCount();
   
   for(int i=0; i < pointerCount; i++){
    touchList.add(
     new xy(
      event.getX(i), 
      event.getY(i)));
   }
   drawList = touchList;
   touching = true;
   break;
  default:
   touching = false;
  }
  
  invalidate();
  return true;
 }
 
 class xy{
  float x;
  float y;
  
  public xy(float x, float y){
   this.x = x;
   this.y = y;
  }
  
  public float getX(){
   return x;
  }
  
  public float getY(){
   return y;
  }
 }
 
}

/res/layout/fragment_main.xml, MainActivity.java and /res/layout/activity_main.xml, refer to last exercise.

download filesDownload the files.

Related:
Cannot detect MotionEvent.ACTION_MOVE and ACTION_UP

Thursday, May 8, 2014

Detect single touch on custom View to draw icon on canvas

This example implement custom View, override onTouchEvent() and onDraw() to draw bitmap on canvas when user touch. In this implementation, only one touch point is detected.


Our custom View, MyView.java
package com.example.androiddrawinview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {
 
 private float x, y;
 private float offsetX, offsetY;
 private boolean touching = false;
 private Bitmap bm;

 public MyView(Context context) {
  super(context);
  init();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }
 
 private void init(){
  bm = BitmapFactory.decodeResource(
    getResources(),
    R.drawable.ic_launcher);
  offsetX = bm.getWidth()/2;
  offsetY = bm.getHeight()/2;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(
   MeasureSpec.getSize(widthMeasureSpec),
   MeasureSpec.getSize(heightMeasureSpec));
 }
 

 @Override
 protected void onDraw(Canvas canvas) {
  canvas.drawColor(Color.GRAY);
  if(touching){
   canvas.drawBitmap(bm, x-offsetX, y-offsetY, null);
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  int action = event.getAction();
  
  switch(action){
  case MotionEvent.ACTION_MOVE:
  case MotionEvent.ACTION_DOWN:
   x = event.getX();
   y = event.getY();
   touching = true;
   break;
  default:
   touching = false; 
  }
  invalidate();
    
  return true;
 }
 
}

Modify /res/layout/fragment_main.xml to add <com.example.androiddrawinview.MyView>.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androiddrawinview.MainActivity$PlaceholderFragment" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <com.example.androiddrawinview.MyView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

Keep using the auto generated MainActivity.java and /res/layout/activity_main.xml.

MainActivity.java
package com.example.androiddrawinview;

import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;

public class MainActivity extends ActionBarActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  if (savedInstanceState == null) {
   getSupportFragmentManager().beginTransaction()
     .add(R.id.container, new PlaceholderFragment()).commit();
  }
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {

  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  // Handle action bar item clicks here. The action bar will
  // automatically handle clicks on the Home/Up button, so long
  // as you specify a parent activity in AndroidManifest.xml.
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }

 /**
  * A placeholder fragment containing a simple view.
  */
 public static class PlaceholderFragment extends Fragment {

  public PlaceholderFragment() {
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
   View rootView = inflater.inflate(R.layout.fragment_main, container,
     false);
   return rootView;
  }
 }

}

/res/layout/activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.androiddrawinview.MainActivity"
    tools:ignore="MergeRootFrame" />


download filesDownload the files.

Multi single touch View

Modify /res/layout/fragment_main.xml to have two <com.example.androiddrawinview.MyView>.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androiddrawinview.MainActivity$PlaceholderFragment" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <com.example.androiddrawinview.MyView
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:layout_margin="10dp" />
        <com.example.androiddrawinview.MyView
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:layout_margin="10dp" />
    </LinearLayout>

</LinearLayout>

Now we have two single touch view together.


Next:
Detect multi touch on custom View to draw icons on canvas
Cannot detect MotionEvent.ACTION_MOVE and ACTION_UP

Monday, September 2, 2013

Detect touch and draw rect on bitmap

In last example "Detect touch and free draw on Bitmap", the points are drawn on the canvas (also the bitmap) directly when user touch.

Detect touch and draw rect on bitmap


In case of drawing square, the user touch on the screen to mark the start position, and move, and release on the end position. We have to keep displaying the updated square. But if we draw the rect on the canvasMaster, it will full of rect when user touch and move.

In this example, we create two overlay ImageViews, have same dimension. Also additional bitmap and canvas for the extra ImagewView. When ACTION_DOWN detected, we mark the starting position. When ACTION_MOVE, we draw rect on the extra canvasDrawingPane, not the canvasMaster. Such that we can clear and re-draw the canvas everytime. And finally when ACTION_UP, simple draw the extra bitmap on canvasMaster.



package com.example.androiddrawbitmap;

import java.io.FileNotFoundException;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity {
 
 Button btnLoadImage;
 TextView textSource;
 ImageView imageResult, imageDrawingPane;
 
 final int RQS_IMAGE1 = 1;

 Uri source;
 Bitmap bitmapMaster;
 Canvas canvasMaster;
 Bitmap bitmapDrawingPane;
 Canvas canvasDrawingPane;
 projectPt startPt;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  btnLoadImage = (Button)findViewById(R.id.loadimage);
  textSource = (TextView)findViewById(R.id.sourceuri);
  imageResult = (ImageView)findViewById(R.id.result);
  imageDrawingPane = (ImageView)findViewById(R.id.drawingpane);
  
  btnLoadImage.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    Intent intent = new Intent(Intent.ACTION_PICK,
      android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, RQS_IMAGE1);
   }});
  
  imageResult.setOnTouchListener(new OnTouchListener(){

   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    int action = event.getAction();
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch(action){
    case MotionEvent.ACTION_DOWN:
     textSource.setText("ACTION_DOWN- " + x + " : " + y);
     startPt = projectXY((ImageView)v, bitmapMaster, x, y);
     break;
    case MotionEvent.ACTION_MOVE:
     textSource.setText("ACTION_MOVE- " + x + " : " + y);
     drawOnRectProjectedBitMap((ImageView)v, bitmapMaster, x, y);
     break;
    case MotionEvent.ACTION_UP:
     textSource.setText("ACTION_UP- " + x + " : " + y);
     drawOnRectProjectedBitMap((ImageView)v, bitmapMaster, x, y);
     finalizeDrawing();
     break;
    }
    /*
     * Return 'true' to indicate that the event have been consumed.
     * If auto-generated 'false', your code can detect ACTION_DOWN only,
     * cannot detect ACTION_MOVE and ACTION_UP.
     */
    return true;
   }});
  
 }
 
 class projectPt{
  int x;
  int y;
  
  projectPt(int tx, int ty){
   x = tx;
   y = ty;
  }
 }
 
 private projectPt projectXY(ImageView iv, Bitmap bm, int x, int y){
  if(x<0 || y<0 || x > iv.getWidth() || y > iv.getHeight()){
   //outside ImageView
   return null;
  }else{
   int projectedX = (int)((double)x * ((double)bm.getWidth()/(double)iv.getWidth()));
   int projectedY = (int)((double)y * ((double)bm.getHeight()/(double)iv.getHeight()));

   return new projectPt(projectedX, projectedY);
  }
 }
 
 private void drawOnRectProjectedBitMap(ImageView iv, Bitmap bm, int x, int y){
  if(x<0 || y<0 || x > iv.getWidth() || y > iv.getHeight()){
   //outside ImageView
   return;
  }else{
   int projectedX = (int)((double)x * ((double)bm.getWidth()/(double)iv.getWidth()));
   int projectedY = (int)((double)y * ((double)bm.getHeight()/(double)iv.getHeight()));

   //clear canvasDrawingPane
   canvasDrawingPane.drawColor(Color.TRANSPARENT, Mode.CLEAR);
   
   Paint paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(Color.WHITE);
            paint.setStrokeWidth(3);
   canvasDrawingPane.drawRect(startPt.x, startPt.y, projectedX, projectedY, paint);
   imageDrawingPane.invalidate();
   
   
   textSource.setText(x + ":" + y + "/" + iv.getWidth() + " : " + iv.getHeight() + "\n" +
     projectedX + " : " + projectedY + "/" + bm.getWidth() + " : " + bm.getHeight()
     );
  }
 }
 
 private void finalizeDrawing(){
  canvasMaster.drawBitmap(bitmapDrawingPane, 0, 0, null);
 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  
  Bitmap tempBitmap;
  
  if(resultCode == RESULT_OK){
   switch (requestCode){
   case RQS_IMAGE1:
    source = data.getData();
    textSource.setText(source.toString());
    
    try {
     //tempBitmap is Immutable bitmap,
     //cannot be passed to Canvas constructor
     tempBitmap = BitmapFactory.decodeStream(
       getContentResolver().openInputStream(source));
     
     Config config;
     if(tempBitmap.getConfig() != null){
      config = tempBitmap.getConfig();
     }else{
      config = Config.ARGB_8888;
     }
     
     //bitmapMaster is Mutable bitmap
     bitmapMaster = Bitmap.createBitmap(
       tempBitmap.getWidth(),
       tempBitmap.getHeight(),
       config);
     
     canvasMaster = new Canvas(bitmapMaster);
     canvasMaster.drawBitmap(tempBitmap, 0, 0, null);
     
     imageResult.setImageBitmap(bitmapMaster);
     
     //Create bitmap of same size for drawing
     bitmapDrawingPane = Bitmap.createBitmap(
       tempBitmap.getWidth(),
       tempBitmap.getHeight(),
       config);
     canvasDrawingPane = new Canvas(bitmapDrawingPane);
     imageDrawingPane.setImageBitmap(bitmapDrawingPane);
     
     
    } catch (FileNotFoundException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
    
    break;
   }
  }
 }

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/loadimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Image 1" />

    <TextView
        android:id="@+id/sourceuri"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <ImageView
            android:id="@+id/result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:background="@android:color/background_dark"
            android:scaleType="centerInside" />
        <ImageView 
            android:id="@+id/drawingpane"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:scaleType="centerInside"
            android:layout_alignLeft="@id/result"
            android:layout_alignTop="@id/result"
            android:layout_alignRight="@id/result"
            android:layout_alignBottom="@id/result"/>
    </RelativeLayout>

</LinearLayout>


download filesDownload the files.

Correction: To make sure bitmapDrawingPane have alpha channel, create it with Config.ARGB_8888.

     //Create bitmap of same size for drawing
     bitmapDrawingPane = Bitmap.createBitmap(
       tempBitmap.getWidth(),
       tempBitmap.getHeight(),
       Config.ARGB_8888);



more: Something about processing images in Android