Showing posts with label Android code sample: thread. Show all posts
Showing posts with label Android code sample: thread. Show all posts

Wednesday, August 10, 2016

Custom view to draw bitmap along path, calculate in background thread

Last post show a example of "Custom view to draw bitmap along path", with calculation run inside onDraw(). It's modified version to pre-calculate in back thread.


(remark: in last post, canvas.drawPath() is called inside onDraw(). It seem run very slow, removed in this example.)

Modify AnimationView.java
package com.blogspot.android_er.androidmovingbitmapalongpath;

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

import java.util.ArrayList;
import java.util.List;

public class AnimationView extends View {

    List<AnimationThing> animationThingsList;
    public AnimationView(Context context) {
        super(context);
        initAnimationView();
    }

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

    public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAnimationView();
    }

    private void initAnimationView(){
        animationThingsList = new ArrayList<>();
    }

    public void insertThing(AnimationThing thing){
        animationThingsList.add(thing);
    }

    //prepare calculation in background thread
    public void preDraw(){
        for (AnimationThing thing : animationThingsList){
            if(thing.distance < thing.pathLength){
                thing.pathMeasure.getPosTan(thing.distance, thing.pos, thing.tan);

                thing.matrix.reset();
                float degrees = (float)(Math.atan2(thing.tan[1], 
                        thing.tan[0])*180.0/Math.PI);
                thing.matrix.postRotate(degrees, thing.bm_offsetX, thing.bm_offsetY);
                thing.matrix.postTranslate(thing.pos[0]-thing.bm_offsetX, 
                        thing.pos[1]-thing.bm_offsetY);

                thing.distance += thing.step;
            }else{
                thing.distance = 0;
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (AnimationThing thing : animationThingsList){
            canvas.drawBitmap(thing.bm, thing.matrix, null);
        }

        invalidate();
    }

    /*
    @Override
    protected void onDraw(Canvas canvas) {
        for (AnimationThing thing : animationThingsList){

            //This code run slow!!!
            //canvas.drawPath(thing.animPath, thing.paint);

            if(thing.distance < thing.pathLength){
                thing.pathMeasure.getPosTan(thing.distance, thing.pos, thing.tan);

                thing.matrix.reset();
                float degrees = (float)(Math.atan2(thing.tan[1], 
                    thing.tan[0])*180.0/Math.PI);
                thing.matrix.postRotate(degrees, thing.bm_offsetX, thing.bm_offsetY);
                thing.matrix.postTranslate(thing.pos[0]-thing.bm_offsetX, 
                    thing.pos[1]-thing.bm_offsetY);

                canvas.drawBitmap(thing.bm, thing.matrix, null);

                thing.distance += thing.step;
            }else{
                thing.distance = 0;
            }
        }

        invalidate();
    }
    */


}


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

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Path;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    AnimationView myAnimationView;
    AniThread myAniThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myAnimationView = (AnimationView)findViewById(R.id.MyAnimationView);

        prepareThings();
        myAniThread = new AniThread(myAnimationView);
        myAniThread.start();
    }

    private void prepareThings() {
        Path animPath;
        float step;
        Bitmap bm;

        bm = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);

        animPath = new Path();
        animPath.moveTo(100, 100);
        animPath.lineTo(200, 100);
        animPath.lineTo(300, 50);
        animPath.lineTo(400, 150);
        animPath.lineTo(100, 300);
        animPath.lineTo(600, 300);
        animPath.lineTo(100, 100);
        animPath.close();

        step = 1;

        AnimationThing thing = new AnimationThing(animPath, bm, step);
        myAnimationView.insertThing(thing);

        //The second thing
        bm = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_add);

        animPath.reset();
        animPath.addCircle(400, 400, 300, Path.Direction.CW);
        step = 3;
        thing = new AnimationThing(animPath, bm, step);
        myAnimationView.insertThing(thing);
    }

    class AniThread extends Thread{

        AnimationView targetView;

        public AniThread(AnimationView target) {
            super();
            targetView = target;
        }

        @Override
        public void run() {
            while (true){
                targetView.preDraw();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

}


The other files, AnimationThing.java and activity_main.xml, refer last post.


download filesDownload the files .

Saturday, May 28, 2016

Android example of using Thread and Handler


This example show how to run code in background using Thread, and also show passing data from activity to thread by calling method of thread, and passing data from thread to activity with Handler.


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

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    Button btnStart, btnStop, btnSend;
    EditText editTextMsgToSend;
    TextView textViewCntReceived, textViewMsgReceived;

    MyThread myThread;
    MyHandler myHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnStart = (Button)findViewById(R.id.startthread);
        btnStop = (Button)findViewById(R.id.stopthread);
        btnSend = (Button)findViewById(R.id.send);
        editTextMsgToSend = (EditText)findViewById(R.id.msgtosend);
        textViewCntReceived = (TextView)findViewById(R.id.cntreceived);
        textViewMsgReceived = (TextView)findViewById(R.id.msgreceived);

        myHandler = new MyHandler(this);

        btnStart.setOnClickListener(btnStartOnClickListener);
        btnStop.setOnClickListener(btnStopOnClickListener);
        btnSend.setOnClickListener(btnSendOnClickListener);
    }

    View.OnClickListener btnStartOnClickListener =
            new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(myThread != null){
                myThread.setRunning(false);
            }
            myThread = new MyThread(myHandler);
            myThread.start();
        }
    };

    View.OnClickListener btnStopOnClickListener =
            new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(myThread != null){
                myThread.setRunning(false);
                myThread = null;
            }
        }
    };

    View.OnClickListener btnSendOnClickListener =
            new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(myThread != null){
                myThread.msgToThread(editTextMsgToSend.getText().toString());
            }
        }
    };

    private void updateCnt(int cnt){
        textViewCntReceived.setText(String.valueOf(cnt));
    }

    private void updateMsg(String msg){
        textViewMsgReceived.setText(msg);
    }


    public static class MyHandler extends Handler{

        public static final int UPDATE_CNT = 0;
        public static final int UPDATE_MSG = 1;
        private MainActivity parent;

        public MyHandler(MainActivity parent) {
            super();
            this.parent = parent;
        }

        @Override
        public void handleMessage(Message msg) {

            switch (msg.what){
                case UPDATE_CNT:
                    int c = (int)msg.obj;
                    parent.updateCnt(c);
                    break;
                case UPDATE_MSG:
                    String m = (String)msg.obj;
                    parent.updateMsg(m);
                    break;
                default:
                    super.handleMessage(msg);
            }

        }
    }
}


MyThread.java
package com.blogspot.android_er.androidthread;

import android.os.Looper;
import android.os.Message;

public class MyThread extends Thread{

    private int cnt;
    private boolean running;
    MainActivity.MyHandler mainHandler;

    public MyThread(MainActivity.MyHandler mainHandler) {
        super();
        this.mainHandler = mainHandler;
    }

    public void setRunning(boolean running){
        this.running = running;
    }

    public void msgToThread(String msgin){
        String msgout = new StringBuilder(msgin).reverse().toString();
        mainHandler.sendMessage(
                Message.obtain(mainHandler,
                        MainActivity.MyHandler.UPDATE_MSG, msgout));
    }

    @Override
    public void run() {
        cnt = 0;
        running = true;

        String prompt;
        if(Looper.myLooper() == Looper.getMainLooper()){
            prompt = "myThread run in UI Thread";
        }else{
            prompt = "myThread run in NOT UI Thread";
        }
        mainHandler.sendMessage(
                Message.obtain(mainHandler,
                        MainActivity.MyHandler.UPDATE_MSG, prompt));

        while (running){
            try {
                Thread.sleep(1000);
                mainHandler.sendMessage(
                        Message.obtain(mainHandler,
                                MainActivity.MyHandler.UPDATE_CNT, cnt));

                cnt++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


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.androidthread.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/startthread"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Thread"/>
    <Button
        android:id="@+id/stopthread"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop Thread"/>
    <EditText
        android:id="@+id/msgtosend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="msg to send..." />
    <Button
        android:id="@+id/send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send msg to thread"/>
    <TextView
        android:id="@+id/cntreceived"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:textSize="26dp"/>
    <TextView
        android:id="@+id/msgreceived"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textStyle="bold"
        android:textSize="20dp"/>
</LinearLayout>



download filesDownload the files .

Related:
Android example of using Service and BroadcastReceiver
Example of IntentService and BroadcastReceiver

Thursday, May 19, 2016

SwipeRefreshLayout, refresh in background thread


Last post show a basic example of SwipeRefreshLayout. But, normally refreshing involve some longtime task; such as loading data from Internet. This example show how to refresh with longtime task in background thread.


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

import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Toast;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    SwipeRefreshLayout swipeRefreshLayout;
    ListView swipeList;

    List<String> myList;
    ListAdapter adapter;

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

        swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swiperefreshlayout);
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                refresh();
            }
        });

        swipeList = (ListView)findViewById(R.id.swipelist);

        myList = new ArrayList<>();
        adapter = new ArrayAdapter<String>(
                this, android.R.layout.simple_list_item_1, myList);
        swipeList.setAdapter(adapter);

        swipeList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                String item = (String) parent.getItemAtPosition(position);
                Toast.makeText(MainActivity.this, item, Toast.LENGTH_LONG).show();
            }
        });
    }

    private void refresh(){

        final int pos = myList.size();
        myList.add(pos, "Refreshing...");
        swipeList.invalidateViews();
        swipeRefreshLayout.setRefreshing(true);

        //refresh long-time task in background thread
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //dummy delay for 2 second
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //update ui on UI thread
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        String currentDateTime =
                                DateFormat.getDateTimeInstance().format(new Date());
                        myList.set(pos, pos + " - " + currentDateTime);
                        swipeList.invalidateViews();
                        swipeRefreshLayout.setRefreshing(false);
                    }
                });

            }
        }).start();
    }
}


Keep using the layout in last post.

more:
SwipeRefreshLayout, work with RecyclerView

Wednesday, March 30, 2016

Load something from Internet using URLConnection and ReadableByteChannel, in Thread

Last example show "Load something from Internet using URLConnection and BufferedReader, in Thread". Here is another example using java.nio.channels.ReadableByteChannel.

Read operations are synchronous on a ReadableByteChannel, that is, if a read is already in progress on the channel then subsequent reads will block until the first read completes. It is undefined whether non-read operations will block.


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

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;

public class MainActivity extends AppCompatActivity {

    TextView textResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textResult = (TextView)findViewById(R.id.tresult);

        MyThread myThread = new MyThread("http://android-er.blogspot.com", textResult);
        myThread.start();
    }

    class MyThread extends Thread{
        String target;
        TextView textviewResult;

        private Handler handler = new Handler();

        public MyThread(String target, TextView textviewResult) {
            super();
            this.target = target;
            this.textviewResult = textviewResult;
        }

        @Override
        public void run() {
            String result = "";

            URL url = null;
            try {
                url = new URL(target);
                URLConnection urlConnection = url.openConnection();
                InputStream inputStream = urlConnection.getInputStream();

                ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream);
                ByteBuffer buffer = ByteBuffer.allocate(1024);

                while (readableByteChannel.read(buffer) > 0)
                {
                    result += new String(buffer.array());
                    buffer.clear();
                }

            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            final String finalResult = result;
            handler.post(new Runnable() {
                @Override
                public void run() {
                    textviewResult.setText(finalResult);
                    Toast.makeText(MainActivity.this,
                            "finished", Toast.LENGTH_LONG).show();
                }
            });
        }
    }
}


For layout and AndroidManifest.xml, refer to the previous post "Load something from Internet using URLConnection and BufferedReader, in AsyncTask".


Tuesday, March 29, 2016

Load something from Internet using URLConnection and BufferedReader, in Thread

Previous post show how to Load something from Internet using URLConnection and BufferedReader, in AsyncTask. This post show how to do it in Thread, and update UI using Handler.



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

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class MainActivity extends AppCompatActivity {

    TextView textResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textResult = (TextView)findViewById(R.id.tresult);

        MyThread myThread = new MyThread("http://android-er.blogspot.com", textResult);
        myThread.start();
    }

    class MyThread extends Thread{
        String target;
        TextView textviewResult;

        private Handler handler = new Handler();

        public MyThread(String target, TextView textviewResult) {
            super();
            this.target = target;
            this.textviewResult = textviewResult;
        }

        @Override
        public void run() {
            String result = "";

            try {
                URL url = new URL(target);
                URLConnection urlConnection = url.openConnection();
                InputStream inputStream = urlConnection.getInputStream();
                InputStreamReader inputStreamReader =
                        new InputStreamReader(inputStream);
                BufferedReader buffReader = new BufferedReader(inputStreamReader);

                String line;
                while ((line = buffReader.readLine()) != null) {
                    result += line;
                }

            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            final String finalResult = result;
            handler.post(new Runnable() {
                @Override
                public void run() {
                    textviewResult.setText(finalResult);
                    Toast.makeText(MainActivity.this,
                            "finished", Toast.LENGTH_LONG).show();
                }
            });
        }
    }
}


For layout and AndroidManifest.xml, refer to the previous post "Load something from Internet using URLConnection and BufferedReader, in AsyncTask".

Related:
Load something from Internet using URLConnection and ReadableByteChannel, in Thread

Thursday, March 24, 2016

AsyncTask vs Thread + Handler


This post show how to implement using AsyncTask and Thread/Handler to perform the same function: doing something in background and update UI elements (ProgressBar and TextView).

This video show how it run on Android Emulator running Android N, in Multi-Window.


AsyncTask

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

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    Button btnStart;
    ProgressBar progressBar;
    TextView textMsg;

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

        progressBar = (ProgressBar)findViewById(R.id.progress);
        textMsg = (TextView)findViewById(R.id.msg);

        btnStart = (Button)findViewById(R.id.start);
        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyAsyncTask myAsyncTask = new MyAsyncTask(progressBar, textMsg);
                myAsyncTask.execute();
            }
        });
    }

    class MyAsyncTask extends AsyncTask<Void, Integer, Void>{

        ProgressBar pBar;
        TextView tMsg;

        public MyAsyncTask(ProgressBar pBar, TextView tMsg) {
            super();
            this.pBar = pBar;
            this.tMsg = tMsg;
        }

        @Override
        protected Void doInBackground(Void... params) {
            for(int i=0; i<=10; i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            pBar.setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            tMsg.setText("finished");
        }
    }
}


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.androidasynctask.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/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start"/>

    <ProgressBar
        android:id="@+id/progress"
        style="?android:attr/progressBarStyleHorizontal"
        android:indeterminate="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="10"
        android:progress="0"/>
    <TextView
        android:id="@+id/msg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>



Thread + Handler

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

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {

    Button btnStart;
    ProgressBar progressBar;
    TextView textMsg;

    private Handler handler = new Handler();

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

        progressBar = (ProgressBar)findViewById(R.id.progress);
        textMsg = (TextView)findViewById(R.id.msg);

        btnStart = (Button)findViewById(R.id.start);
        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyThread myThread = new MyThread(progressBar, textMsg);
                myThread.start();
            }
        });
    }

    class MyThread extends Thread{

        ProgressBar pBar;
        TextView tMsg;

        public MyThread(ProgressBar pBar, TextView tMsg) {
            super();
            this.pBar = pBar;
            this.tMsg = tMsg;
        }

        @Override
        public void run() {

            for (int i = 0; i <= 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //is accessed from within inner class, needs to be declared final
                final int finalI = i;
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        pBar.setProgress(finalI);
                    }
                });
            }

            handler.post(new Runnable() {
                @Override
                public void run() {
                    tMsg.setText("finished");
                }
            });
        }
    }
}


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.androidthreadhandler.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/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start"/>

    <ProgressBar
        android:id="@+id/progress"
        style="?android:attr/progressBarStyleHorizontal"
        android:indeterminate="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="10"
        android:progress="0"/>
    <TextView
        android:id="@+id/msg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>



next:
- HandlerThread example

related:
Load something from Internet using URLConnection and BufferedReader, in AsyncTask
Load something from Internet using URLConnection and BufferedReader, in Thread


Monday, September 21, 2015

Load animated GIF from Internet, example 2 to load new animated Google logo


Actually it is similar to the example of "Load animated GIF from Internet", but load the new animated Google logo.



com.blogspot.android_er.androidgif.GifView.java
package com.blogspot.android_er.androidgif;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class GifView extends View{

    //Set true to use decodeStream
    //Set false to use decodeByteArray
    private static final boolean DECODE_STREAM = true;

    private InputStream gifInputStream;
    private Movie gifMovie;
    private int movieWidth, movieHeight;
    private long movieDuration;
    private long mMovieStart;

    //original source
    //final static String gifAddr = "https://lh4.googleusercontent.com/-HAx9Y7EqDgI/VendP007MKI/AAAAAAAAEpE/3pplCHO0MAA/w1044-h522-no/GD_article_sharegraphic.gif";
    //my copy of animated Google Logo
    final static String gifAddr = "http://goo.gl/ZHn9K7";
    public GifView(Context context) {
        super(context);
        init(context);
    }

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

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

    private void init(final Context context){
        setFocusable(true);

        gifMovie = null;
        movieWidth = 0;
        movieHeight = 0;
        movieDuration = 0;

        Thread threadLoadGif = new Thread(new Runnable(){

            @Override
            public void run() {
                try {
                    URL gifURL = new URL(gifAddr);

                    HttpURLConnection connection = (HttpURLConnection)gifURL.openConnection();
                    gifInputStream = connection.getInputStream();

                    if(DECODE_STREAM){
                        gifMovie = Movie.decodeStream(gifInputStream);
                    }else{
                        byte[] array = streamToBytes(gifInputStream);
                        gifMovie = Movie.decodeByteArray(array, 0, array.length);
                    }

                    movieWidth = gifMovie.width();
                    movieHeight = gifMovie.height();
                    movieDuration = gifMovie.duration();

                    ((MainActivity)context).runOnUiThread(new Runnable(){

                        @Override
                        public void run() {
                            //request re-draw layout
                            invalidate();
                            requestLayout();
                            Toast.makeText(context,
                                    movieWidth + " x " + movieHeight + "\n"
                                            + movieDuration,
                                    Toast.LENGTH_LONG).show();
                        }});

                } catch (MalformedURLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }});

        threadLoadGif.start();

    }

    private static byte[] streamToBytes(InputStream is) {
        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = is.read(buffer)) >= 0) {
                os.write(buffer, 0, len);
            }
        } catch (java.io.IOException e) {
        }
        return os.toByteArray();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec,
                             int heightMeasureSpec) {
        setMeasuredDimension(movieWidth, movieHeight);
    }

    public int getMovieWidth(){
        return movieWidth;
    }

    public int getMovieHeight(){
        return movieHeight;
    }

    public long getMovieDuration(){
        return movieDuration;
    }

    @Override
    protected void onDraw(Canvas canvas) {

        long now = android.os.SystemClock.uptimeMillis();
        if (mMovieStart == 0) {   // first time
            mMovieStart = now;
        }

        if (gifMovie != null) {

            int dur = gifMovie.duration();
            if (dur == 0) {
                dur = 1000;
            }

            int relTime = (int)((now - mMovieStart) % dur);

            gifMovie.setTime(relTime);

            gifMovie.draw(canvas, 0, 0);
            invalidate();

        }

    }
}


Add <com.blogspot.android_er.androidgif.GifView> in layout file, 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:padding="10dp"
    android:orientation="vertical"
    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" />

    <com.blogspot.android_er.androidgif.GifView
        android:id="@+id/gifview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

</LinearLayout>


Nothing to do in com.blogspot.android_er.androidgif.MainActivity.java
package com.blogspot.android_er.androidgif;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    GifView gifView;

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

        gifView = (GifView)findViewById(R.id.gifview);
    }

}


Edit src/main/AndroidManifest.xml to add <uses-permission> of "android.permission.INTERNET", and disable hardware ccceleration by setting android:hardwareAccelerated="false".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.android_er.androidgif" >

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:hardwareAccelerated="false" >
            <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) .


My copy of animated Google logo (http://goo.gl/ZHn9K7), original in https://lh4.googleusercontent.com/-HAx9Y7EqDgI/VendP007MKI/AAAAAAAAEpE/3pplCHO0MAA/w1044-h522-no/GD_article_sharegraphic.gif.

Saturday, June 6, 2015

Android example: pass string between threads, in Bundler

This example show how to pass string between Threads, in Bundle, via Handler/Message.


com.example.androidthreadlooperhandler.MainActivity.java
package com.example.androidthreadlooperhandler;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity {

    static final String KEY_STRING_1 = "KEY_STRING_TO_THREAD";
    static final String KEY_STRING_2 = "KEY_STRING_TO_UI";

    public UiHandler uiHandler;

    EditText editText;
    Button btn;
    TextView textInfo;

    MyThread myThread;

    private class UiHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            // ...Run in UI Thread
            String strUi = msg.getData().getString(KEY_STRING_2, "no string received from THREAD");
            textInfo.setText(strUi);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editText = (EditText)findViewById(R.id.edittext);
        btn = (Button)findViewById(R.id.button);
        textInfo = (TextView)findViewById(R.id.info);

        btn.setOnClickListener(btnOnClickListener);

        uiHandler = new UiHandler();
        myThread = new MyThread(uiHandler);
        myThread.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //stop and quit the background Thread
        myThread.handler.getLooper().quit();
    }

    View.OnClickListener btnOnClickListener =
        new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                if(myThread.handler != null){
                    String str = editText.getText().toString();

                    Bundle bundle = new Bundle();
                    bundle.putString(KEY_STRING_1, str);
                    Message message = myThread.handler.obtainMessage();
                    message.setData(bundle);
                    myThread.handler.sendMessage(message);
                }
            }
        };

    private class MyThread extends Thread{

        public Handler handler;
        private UiHandler callbackHandler;

        MyThread(UiHandler handler){
            callbackHandler = handler;
        }

        public void run(){
            Looper.prepare();
            handler = new MyHandler();
            Looper.loop();
        }

        private class MyHandler extends Handler{
            @Override
            public void handleMessage(Message msg) {
                // ...Run in background

                String str = msg.getData().getString(KEY_STRING_1, "no string received from UI");
                String strRev = new StringBuilder(str).reverse().toString();

                Bundle bundleThread = new Bundle();
                bundleThread.putString(KEY_STRING_2, strRev);
                Message messageThread = callbackHandler.obtainMessage();
                messageThread.setData(bundleThread);
                callbackHandler.sendMessage(messageThread);

            }
        }
    }
}


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="10dp"
    android:paddingRight="10dp"
    android:paddingTop="10dp"
    android:paddingBottom="10dp"
    android:orientation="vertical"
    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" />

    <EditText
        android:id="@+id/edittext"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="android-er.blogspot.com"/>

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

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

</LinearLayout>


Friday, June 5, 2015

Android example: bi-direction passing data between UI and background thread using Handler/Message.

Last example show how to use Thread, Handler and Looper in Android. In that example, UI thread pass data to background thread using Handler/Message, background thread update UI (TextView) via runOnUiThread(). It's modified in this example, background thread update UI via Handler/Message also.



In main activity, implement a custom Handler (UiHandler) and instantiate a object, then pass it to background thread via constructor.

com.example.androidthreadlooperhandler.MainActivity.java
package com.example.androidthreadlooperhandler;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity {

    static final int UIMSG3 = 3;
    static final int UIMSG4 = 4;

    public UiHandler uiHandler;

    Button btn1, btn2;
    TextView textInfo;

    MyThread myThread;

    private class UiHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            // ...Run in UI Thread
            int what = msg.what;
            switch (what){
                case UIMSG3:
                    textInfo.setText("UI Message 3");
                    break;
                case UIMSG4:
                    textInfo.setText("UI Message 4");
                    break;
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1 = (Button)findViewById(R.id.button1);
        btn2 = (Button)findViewById(R.id.button2);
        textInfo = (TextView)findViewById(R.id.info);

        btn1.setOnClickListener(btnOnClickListener);
        btn2.setOnClickListener(btnOnClickListener);

        uiHandler = new UiHandler();
        myThread = new MyThread(uiHandler);
        myThread.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //stop and quit the background Thread
        myThread.handler.getLooper().quit();
    }

    View.OnClickListener btnOnClickListener =
        new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                if(myThread.handler != null){
                    Message message;
                    if(v==btn1){
                        message = myThread.handler.obtainMessage(MyThread.MSG1);
                    }else{
                        message = myThread.handler.obtainMessage(MyThread.MSG2);
                    }
                    myThread.handler.sendMessage(message);
                }
            }
        };

    private class MyThread extends Thread{

        static final int MSG1 = 1;
        static final int MSG2 = 2;

        public Handler handler;
        private UiHandler callbackHandler;

        MyThread(UiHandler handler){
            callbackHandler = handler;
        }

        public void run(){
            Looper.prepare();
            handler = new MyHandler();
            Looper.loop();
        }

        private class MyHandler extends Handler{
            @Override
            public void handleMessage(Message msg) {
                // ...Run in background

                int what = msg.what;
                switch (what){
                    case MSG1:

                        //doing something...
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        Message callbackMessage3;
                        callbackMessage3 = callbackHandler.obtainMessage(UIMSG3);
                        callbackHandler.sendMessage(callbackMessage3);

                        /*
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                textInfo.setText("Message 1");
                            }
                        });
                        */

                        break;
                    case MSG2:

                        //doing something...
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        Message callbackMessage4;
                        callbackMessage4 = callbackHandler.obtainMessage(UIMSG4);
                        callbackHandler.sendMessage(callbackMessage4);

                        /*
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                textInfo.setText("Message 2");
                            }
                        });
                        */

                        break;

                }
            }
        }
    }
}


The layout file refer to last example Thread, Handler and Looper in Android.

Next:
Android example: pass string between threads, in Bundler

Friday, May 29, 2015

Android example: Thread, Handler and Looper

Example to run task on background thread using Thread, Handler and Looper:


com.example.androidthreadlooperhandler.MainActivity
package com.example.androidthreadlooperhandler;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity {

    Button btn1, btn2;
    TextView textInfo;

    MyThread myThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1 = (Button)findViewById(R.id.button1);
        btn2 = (Button)findViewById(R.id.button2);
        textInfo = (TextView)findViewById(R.id.info);

        btn1.setOnClickListener(btnOnClickListener);
        btn2.setOnClickListener(btnOnClickListener);

        myThread = new MyThread();
        myThread.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //stop and quit the background Thread
        myThread.handler.getLooper().quit();
    }

    View.OnClickListener btnOnClickListener =
        new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                if(myThread.handler != null){
                    Message message;
                    if(v==btn1){
                        message = myThread.handler.obtainMessage(MyThread.MSG1);
                    }else{
                        message = myThread.handler.obtainMessage(MyThread.MSG2);
                    }
                    myThread.handler.sendMessage(message);
                }
            }
        };

    private class MyThread extends Thread{

        static final int MSG1 = 1;
        static final int MSG2 = 2;

        public Handler handler;

        public void run(){
            Looper.prepare();
            handler = new MyHandler();
            Looper.loop();
        }

        private class MyHandler extends Handler{
            @Override
            public void handleMessage(Message msg) {
                // ...Run in background

                int what = msg.what;
                switch (what){
                    case MSG1:

                        //doing something...
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                textInfo.setText("Message 1");
                            }
                        });

                        break;
                    case MSG2:

                        //doing something...
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                textInfo.setText("Message 2");
                            }
                        });

                        break;

                }
            }
        }
    }
}


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="10dp"
    android:paddingRight="10dp"
    android:paddingTop="10dp"
    android:paddingBottom="10dp"
    android:orientation="vertical"
    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/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 1"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 2"/>

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

</LinearLayout>



In this example, background thread update UI (TextView) via runOnUiThread(). It's modified in next example to implement Handlers in both UI and background thread, to pass data in both direction via Handler/Message.


Wednesday, September 17, 2014

getView() to load images in AsyncTask

This post show getView() loading images in AsyncTask, to make UI responsive. Re-call my old "GridView example: load images to GridView from SD Card in background", it load the files in backgroud, to make the GridView start-up faster; but not in loading images in getView. Such that if the GridView going to load large images, it will block the UI in getView() and make the UI unresponsive.


The Android tutorial "Making ListView Scrolling Smooth" show how to improve getView() by loading images in AsyncTask.

The page advise to check (v.position == position) to varify if this item hasn't been recycled already. But it not work in this case. (actually, I don't understand how is the logic) In my implementation, roll-out images will be shown on incorrect cell, but the final images will replace and correct it finally.

To load images in AsyncTask will not speed up your image loading, but improve UI response.


MainActivity.java
package com.example.androidgridview;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 AsyncTaskLoadFiles myAsyncTaskLoadFiles;

 public class AsyncTaskLoadFiles extends AsyncTask<Void, String, Void> {
  
  File targetDirector;
  ImageAdapter myTaskAdapter;

  public AsyncTaskLoadFiles(ImageAdapter adapter) {
   myTaskAdapter = adapter;
  }

  @Override
  protected void onPreExecute() {
   String ExternalStorageDirectoryPath = Environment
     .getExternalStorageDirectory().getAbsolutePath();

   String targetPath = ExternalStorageDirectoryPath + "/test/";
   targetDirector = new File(targetPath);
   myTaskAdapter.clear();
   
   super.onPreExecute();
  }

  @Override
  protected Void doInBackground(Void... params) {
   
   File[] files = targetDirector.listFiles();
   Arrays.sort(files);
   for (File file : files) {
    publishProgress(file.getAbsolutePath());
    if (isCancelled()) break;
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(String... values) {
   myTaskAdapter.add(values[0]);
   super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(Void result) {
   myTaskAdapter.notifyDataSetChanged();
   super.onPostExecute(result);
  }

 }

 public class ImageAdapter extends BaseAdapter {

  private Context mContext;
  ArrayList<String> itemList = new ArrayList<String>();

  public ImageAdapter(Context c) {
   mContext = c;
  }

  void add(String path) {
   itemList.add(path);
  }
  
  void clear() {
   itemList.clear();
  }
  
  void remove(int index){
   itemList.remove(index);
  }

  @Override
  public int getCount() {
   return itemList.size();
  }

  @Override
  public Object getItem(int position) {
   // TODO Auto-generated method stub
   return itemList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  //getView load bitmap ui thread
  /*
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
   } else {
    imageView = (ImageView) convertView;
   }

   Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220,
     220);

   imageView.setImageBitmap(bm);
   return imageView;
  }
  */

  //getView load bitmap in AsyncTask
  @Override
  public View getView(final int position, View convertView, ViewGroup parent) {
   ViewHolder holder;

   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
    
    convertView = imageView;
    
    holder = new ViewHolder();
    holder.image = imageView;
    holder.position = position;
    convertView.setTag(holder);
   } else {
    //imageView = (ImageView) convertView;
    holder = (ViewHolder) convertView.getTag();
    ((ImageView)convertView).setImageBitmap(null);
   }
   
   //Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);
   // Using an AsyncTask to load the slow images in a background thread
   new AsyncTask<ViewHolder, Void, Bitmap>() {
       private ViewHolder v;

       @Override
       protected Bitmap doInBackground(ViewHolder... params) {
           v = params[0];
           Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);
           return bm;
       }

       @Override
       protected void onPostExecute(Bitmap result) {
           super.onPostExecute(result);
           //Not work for me!
           /*
           if (v.position == position) {
               // If this item hasn't been recycled already, 
            // show the image
               v.image.setImageBitmap(result);
           }
           */

           v.image.setImageBitmap(result);

       }
   }.execute(holder);

   //imageView.setImageBitmap(bm);
   //return imageView;
   return convertView;
  }

  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth,
    int reqHeight) {

   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);

   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth,
     reqHeight);

   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options);

   return bm;
  }

  public int calculateInSampleSize(

  BitmapFactory.Options options, int reqWidth, int reqHeight) {
   // Raw height and width of image
   final int height = options.outHeight;
   final int width = options.outWidth;
   int inSampleSize = 1;

   if (height > reqHeight || width > reqWidth) {
    if (width > height) {
     inSampleSize = Math.round((float) height
       / (float) reqHeight);
    } else {
     inSampleSize = Math.round((float) width / (float) reqWidth);
    }
   }

   return inSampleSize;
  }
  
  class ViewHolder {
            ImageView image;
            int position;
        }

 }

 ImageAdapter myImageAdapter;

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

  final GridView gridview = (GridView) findViewById(R.id.gridview);
  myImageAdapter = new ImageAdapter(this);
  gridview.setAdapter(myImageAdapter);

  /*
   * Move to asyncTaskLoadFiles String ExternalStorageDirectoryPath =
   * Environment .getExternalStorageDirectory() .getAbsolutePath();
   * 
   * String targetPath = ExternalStorageDirectoryPath + "/test/";
   * 
   * Toast.makeText(getApplicationContext(), targetPath,
   * Toast.LENGTH_LONG).show(); File targetDirector = new
   * File(targetPath);
   * 
   * File[] files = targetDirector.listFiles(); for (File file : files){
   * myImageAdapter.add(file.getAbsolutePath()); }
   */
  myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
  myAsyncTaskLoadFiles.execute();

  gridview.setOnItemClickListener(myOnItemClickListener);
  
  Button buttonReload = (Button)findViewById(R.id.reload);
  buttonReload.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    //Cancel the previous running task, if exist.
    myAsyncTaskLoadFiles.cancel(true);
    
    //new another ImageAdapter, to prevent the adapter have
    //mixed files
    myImageAdapter = new ImageAdapter(MainActivity.this);
    gridview.setAdapter(myImageAdapter);
    myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
    myAsyncTaskLoadFiles.execute();
   }});

 }

 OnItemClickListener myOnItemClickListener = new OnItemClickListener() {

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   String prompt = "remove " + (String) parent.getItemAtPosition(position);
   Toast.makeText(getApplicationContext(), prompt, Toast.LENGTH_SHORT)
     .show();
   
   myImageAdapter.remove(position);
   myImageAdapter.notifyDataSetChanged();

  }
 };

}

/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">

    <Button
        android:id="@+id/reload"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Reload"/>
    <GridView
        android:id="@+id/gridview"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:columnWidth="220dp"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:gravity="center"/>

</LinearLayout>

Permission of "android.permission.READ_EXTERNAL_STORAGE" is needed to read storage.

download filesDownload the files.

Next:
Improve JPG loading with ExifInterface.getThumbnail()


Friday, March 21, 2014

Load animated GIF from Internet

Previous examples show how to diaply animated GIF using decodeStream(InputStream) and decodeByteArray(InputStream) loaded from  /res/drawable/ folder. This example show how to load from Internet.



  • We cannot access Internet in main thread, such that we have to implement a background thread to load the gif from Internet.
  • Once loaded, we have to ask Android system to re-layout with updated graph.
  • In the example code, dummy delay is added to simulate network delay.
  • Both decodeStream(InputStream) and decodeByteArray(InputStream) are implemented, you can choice it by setting DECODE_STREAM true or false.
  • It can be noted that textViewInfo is filled with 0, 0 x 0; because the GIF is not loaded when onCreate() called.
  • uses-permission android:name="android.permission.INTERNET" is needed in AndroidManifest.xml
Modify activity_main.xml to added a LinearLayout to show how the system layout at run-time.
<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"
    tools:context="com.example.androidgif.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="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/android_er" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ff000090"
            android:orientation="vertical"
            android:padding="5dp" >

            <com.example.androidgif.GifView
                android:id="@+id/gifview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </LinearLayout>

    <TextView
        android:id="@+id/textinfo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="info..." />

</LinearLayout>

GifView.java
package com.example.androidgif;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Toast;

public class GifView extends View {
 
 //Set true to use decodeStream
 //Set false to use decodeByteArray
    private static final boolean DECODE_STREAM = true;
 
 private InputStream gifInputStream;
 private Movie gifMovie;
 private int movieWidth, movieHeight;
 private long movieDuration;
 private long mMovieStart;
 
 final static String gifAddr = "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6myuJnVNIFw_fX5kZ3ru5B_xtomsmaUZsq2IWaFIKDcy_88cRvwaX9sn8x2rIBWsrOezV9w3BJHcwG2mSq3VLZB0eXCWb2so3znRvZeF3fpK3h0uAZy9WX5S7afEJYvEPl4AJln99dJI/s1600/android_er.gif";

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

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

 public GifView(Context context, AttributeSet attrs, 
   int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init(context);
 }
 
 private void init(final Context context){
  setFocusable(true);

  gifMovie = null;
  movieWidth = 0;
  movieHeight = 0;
  movieDuration = 0;
  
  Thread threadLoadGif = new Thread(new Runnable(){

   @Override
   public void run() {
    try {
     URL gifURL = new URL(gifAddr);
     
     HttpURLConnection connection = (HttpURLConnection)gifURL.openConnection();
     gifInputStream = connection.getInputStream();
     
     //Insert dummy sleep
     //to simulate network delay
     try {
      Thread.sleep(3000);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
     
     if(DECODE_STREAM){
      gifMovie = Movie.decodeStream(gifInputStream);
     }else{
      byte[] array = streamToBytes(gifInputStream);
            gifMovie = Movie.decodeByteArray(array, 0, array.length);
     }
     
     movieWidth = gifMovie.width();
     movieHeight = gifMovie.height();
     movieDuration = gifMovie.duration();
     
     ((MainActivity)context).runOnUiThread(new Runnable(){

      @Override
      public void run() {
       //request re-draw layout
       invalidate();
       requestLayout();
       Toast.makeText(context, 
         movieWidth + " x " + movieHeight + "\n"
         + movieDuration, 
         Toast.LENGTH_LONG).show();
      }});
     
    } catch (MalformedURLException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    } catch (IOException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
   }});
  
  threadLoadGif.start();
  
 }
 
 private static byte[] streamToBytes(InputStream is) {
        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = is.read(buffer)) >= 0) {
                os.write(buffer, 0, len);
            }
        } catch (java.io.IOException e) {
        }
        return os.toByteArray();
    }

 @Override
 protected void onMeasure(int widthMeasureSpec, 
   int heightMeasureSpec) {
  setMeasuredDimension(movieWidth, movieHeight);
 }
 
 public int getMovieWidth(){
  return movieWidth;
 }
 
 public int getMovieHeight(){
  return movieHeight;
 }
 
 public long getMovieDuration(){
  return movieDuration;
 }

 @Override
 protected void onDraw(Canvas canvas) {

  long now = android.os.SystemClock.uptimeMillis();
        if (mMovieStart == 0) {   // first time
            mMovieStart = now;
        }
        
        if (gifMovie != null) {

            int dur = gifMovie.duration();
            if (dur == 0) {
                dur = 1000;
            }

            int relTime = (int)((now - mMovieStart) % dur);
            
            gifMovie.setTime(relTime);

            gifMovie.draw(canvas, 0, 0);
            invalidate();
            
        }
        
 }

}

Other files, MainActivity.java and AndroidManifest.xml to turn OFF hardwareAccelerated, refer to the post "Play animated GIF using android.graphics.Movie, with Movie.decodeStream(InputStream)".

(Remark: to load from Internet, uses-permission of "android.permission.INTERNET" is needed in AndroidManifest.xml.

download filesDownload the files.


Another example of playing new Google animated GIF, load from Internet.

Sunday, March 16, 2014

Try Motion aftereffect on Android

The motion aftereffect (MAE) is a visual illusion experienced after viewing a moving visual stimulus for a time (tens of milliseconds to minutes) with stationary eyes, and then fixating a stationary stimulus. The stationary stimulus appears to move in the opposite direction to the original (physically moving) stimulus. The motion aftereffect is believed to be the result of motion adaptation. ~ Wikipedia.

This exercise TRY to simulate the effect on Android. The code for motion aftereffect generation is modified from http://en.wikipedia.org/wiki/File:Illusion_movie.ogg (c source code), with my optimization. But the effect seem not good! I test it on Nexus 7 tablet.

To see the illusions: Run the app on Android Tablet, and stare to the center of the image. After around one minute, look away (for example look to a face or to your hands). For few seconds everything you see will appear to distort. Or touch the motion picture on screen, it will stop and change to another ImageView.

motion aftereffect (MAE)
Motion AfterEffect (MAE)

The main part is in MySurfaceView.java.
package com.example.androidsurfaceview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MySurfaceView extends SurfaceView {
 
    private SurfaceHolder surfaceHolder;
    private MyThread myThread;
    
    MainActivity mainActivity;
    
    int T;
    static final float freq = 80;

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

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

 public MySurfaceView(Context context, 
   AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  init(context);
 }
 
 private void init(Context c){
  mainActivity = (MainActivity)c;
  T = 0;
  myThread = new MyThread(this);
  
  surfaceHolder = getHolder();

  
  surfaceHolder.addCallback(new SurfaceHolder.Callback(){

   @Override
   public void surfaceCreated(SurfaceHolder holder) {
    myThread.setRunning(true);
    myThread.start();
   }

   @Override
   public void surfaceChanged(SurfaceHolder holder, 
     int format, int width, int height) {
    // TODO Auto-generated method stub
    
   }

   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
    boolean retry = true;
                myThread.setRunning(false);
                while (retry) {
                       try {
                             myThread.join();
                             retry = false;
                       } catch (InterruptedException e) {
                       }
                }
   }});
 }

 protected void drawSomething(Canvas canvas) {
  
  int sizex = getWidth();
  int sizey = getHeight();

  float divby_sizex_sq = 1.0f/((float)sizex * (float)sizex);

  int[] data = new int[sizex * sizey];
  int m;
  
  T++;
  if(T >= 1200){
   T = 0;
  }
  float halfT = T * 0.5f;

  for (int j=0;j<sizey;j++){
   
   float y0 = j*2-sizey;
   float y2_2 = y0 * y0 * divby_sizex_sq;
   
   float absY = Math.abs(y0/(float)sizey);
            float halfT_plus_absY = absY + halfT;
            float halfT_neg_plus_absY = absY - halfT;
            
            int j_multi_sizex = j*sizex;
   
         for (int i=0;i<sizex;i++){
             float x=(i*2-sizex)/(float)sizex;
             
             //0.2 instead of 0.1 to have a bigger circle
             if ((x*x + y2_2)<0.2){
              m = (int) (Math.sin((halfT_plus_absY+Math.abs(x))*freq)*127.0+128) & 0xFF;
             }else {
              m = (int) (Math.sin((halfT_neg_plus_absY+Math.abs(x))*freq)*127.0+128) & 0xFF;
             }
             
             data[i+j_multi_sizex] = 0xFF000000
               + (m << 16)
               + (m << 8)
               + m;
         }
     }
     
     Bitmap bm = Bitmap.createBitmap(data, sizex, sizey, Bitmap.Config.ARGB_8888);

        canvas.drawBitmap(bm, 0, 0, null);

 }
}

MyThread.java
package com.example.androidsurfaceview;

import android.graphics.Canvas;

public class MyThread extends Thread {
 
 MySurfaceView myView;
 private boolean running = false;

 public MyThread(MySurfaceView view) {
  myView = view;
 }
 
 public void setRunning(boolean run) {
        running = run;
 }

 @Override
 public void run() {
  while(running){
   
   Canvas canvas = myView.getHolder().lockCanvas();
   
   if(canvas != null){
    synchronized (myView.getHolder()) {
     myView.drawSomething(canvas);
    }
    myView.getHolder().unlockCanvasAndPost(canvas);
   }
   
   /*
   try {
    sleep(30);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   */
   
  }
 }

}

/res/layout/activity_main.xml, where @drawable/android_er is a 640x480 .png in /res/drawable/ folder.
<LinearLayout 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"
    android:orientation="vertical"
    android:background="@android:color/background_dark"
    tools:context="com.example.androidsurfaceview.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" />

    <FrameLayout 
        android:layout_width="640dp"
        android:layout_height="480dp"
        android:layout_gravity="center">
        <com.example.androidsurfaceview.MySurfaceView
            android:id="@+id/myview"
            android:layout_width="640dp"
            android:layout_height="480dp"
            android:layout_gravity="center" />
        <ImageView 
            android:id="@+id/imageicon"
            android:layout_width="640dp"
            android:layout_height="480dp"
            android:layout_gravity="center"
            android:src="@drawable/android_er"
            android:visibility="invisible"/>
    </FrameLayout>
    
</LinearLayout>

MainActivity.java. The Motion graph will run when the app start. When user touch on it, it will show the ImageView of "@drawable/android_er".
package com.example.androidsurfaceview;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;

public class MainActivity extends Activity {
 
 ImageView imageIcon;
 MySurfaceView mySurfaceView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  mySurfaceView = (MySurfaceView)findViewById(R.id.myview);
  imageIcon = (ImageView)findViewById(R.id.imageicon);
  
  mySurfaceView.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    imageIcon.setVisibility(View.VISIBLE);
    mySurfaceView.setVisibility(View.INVISIBLE);
   }});

 }

}

Modify AndroidManifest.xml to force the app run in landscape mode.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidsurfaceview"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.androidsurfaceview.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="landscape" >
            <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.

Download and try the APK.

Friday, March 14, 2014

Create animation on SurfaceView in background Thread

Last post "Simple SurfaceView example" draw bitmap in surfaceCreated() callback. In this step, a customized Thread, MyThread, is implemented to draw the bitmap running across screen in background thread.


MyThread.java
package com.example.androidsurfaceview;

import android.graphics.Canvas;

public class MyThread extends Thread {
 
 MySurfaceView myView;
 private boolean running = false;

 public MyThread(MySurfaceView view) {
  myView = view;
 }
 
 public void setRunning(boolean run) {
        running = run;    
 }

 @Override
 public void run() {
  while(running){
   
   Canvas canvas = myView.getHolder().lockCanvas();
   
   if(canvas != null){
    synchronized (myView.getHolder()) {
     myView.drawSomething(canvas);
    }
    myView.getHolder().unlockCanvasAndPost(canvas);
   }
   
   try {
    sleep(30);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   
  }
 }

}

Modify MySurfaceView.java in last post.
package com.example.androidsurfaceview;

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.SurfaceHolder;
import android.view.SurfaceView;

public class MySurfaceView extends SurfaceView {
 
    private SurfaceHolder surfaceHolder;
    private Bitmap bmpIcon;
    private MyThread myThread;
    int xPos = 0;
    int yPos = 0;
    int deltaX = 5;
    int deltaY = 5;
    int iconWidth;
    int iconHeight;

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

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

 public MySurfaceView(Context context, 
   AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  init();
 }
 
 private void init(){
  
  myThread = new MyThread(this);
  
  surfaceHolder = getHolder();
  bmpIcon = BitmapFactory.decodeResource(getResources(), 
    R.drawable.ic_launcher);

  iconWidth = bmpIcon.getWidth();
  iconHeight = bmpIcon.getHeight();
  
  surfaceHolder.addCallback(new SurfaceHolder.Callback(){

   @Override
   public void surfaceCreated(SurfaceHolder holder) {
    myThread.setRunning(true);
    myThread.start();
   }

   @Override
   public void surfaceChanged(SurfaceHolder holder, 
     int format, int width, int height) {
    // TODO Auto-generated method stub
    
   }

   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
    boolean retry = true;
                myThread.setRunning(false);
                while (retry) {
                       try {
                             myThread.join();
                             retry = false;
                       } catch (InterruptedException e) {
                       }
                }
   }});
 }

 protected void drawSomething(Canvas canvas) {
  canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(bmpIcon, 
          getWidth()/2, getHeight()/2, null);
        
        xPos += deltaX;
        if(deltaX > 0){
         if(xPos >= getWidth() - iconWidth){
             deltaX *= -1;
            }
        }else{
         if(xPos <= 0){
             deltaX *= -1;
            }
        }
        
        yPos += deltaY;
        if(deltaY > 0){
         if(yPos >= getHeight() - iconHeight){
             deltaY *= -1;
            }
        }else{
         if(yPos <= 0){
             deltaY *= -1;
            }
        }

        canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(bmpIcon, 
          xPos, yPos, null);

 }

}

Other files, /res/layout/activity_main.xml and MainActivity.java, refer to last post.

download filesDownload the files.

Next:
Draw bitmap programmatically for SurfaceView