Android多线程编程AsyncTask详解教程

 更新时间:2016年9月20日 19:55  点击:1452
AsyncTask是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.

Android中多线程编程中AsyncTask类的详细解释
1.Android单线程模型
2.耗时操作放在非主线程中执行

Android主线程和子线程之间的通信封装类:AsyncTask类
1.子线程中更新UI
2.封装、简化异步操作。
3.AsyncTask机制:底层是通过线程池来工作的,当一个线程没有执行完毕,后边的线程是无法执行的。必须等前边的线程执行完毕后,后边的线程才能执行。

AsyncTask类使用注意事项:

1.在UI线程中创建AsyncTask的实例
2.必须在UI线程中调用AsyncTask的execute()方法
3.重写的四个方法是系统自动调用的,不应手动调用。
4.每个AsyncTask只能被执行一次。多次调用将会引发异常。

AsyncTask类和Handler类在异步任务中的比较:

(1).AsyncTask:

优点:简单快捷。过程可控。
缺点:在多个异步任务处理进行UI更新的时候,就显得复杂起来。

(2).Handler类:
优点:结构清晰,功能定义明确。对于多个后台任务时,简单,清晰。
缺点:在单个后台异步处理时,代码相对来说过于多。

以下是从网络中获取图片并展示的例子:

(1).MainActivity.java类:

package com.chengdong.su.asynctaskdemo;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.chengdong.su.asynctaskdemo.interfaces.AsyncT;
import com.chengdong.su.asynctaskdemo1.R;
public class MainActivity extends Activity {
    private ImageView mView = null;
    private ProgressBar mProgressBar = null;
    private String URL = "http://pic.nipic.com/2007-11-09/2007119122519868_2.jpg";
    private AsyncT mT = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        mT = new AsyncT(this);
        mT.execute(URL);
    }
    /**
     * Activity退出的时候,Task的任务也相应的退出
     */
    @Override
    protected void onPause() {
        super.onPause();
        if (mT != null && mT.getStatus() != AsyncTask.Status.FINISHED) {
            mT.cancel(true);
        }
    }
    private void init() {
        mView = (ImageView) findViewById(R.id.iv_show);
        mProgressBar = (ProgressBar) findViewById(R.id.pb_show);
    }
    public ImageView getView() {
        return mView;
    }
    public ProgressBar getProgressBar() {
        return mProgressBar;
    }
}


(2).AsyncT.java类:

package com.chengdong.su.asynctaskdemo.interfaces;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import com.chengdong.su.asynctaskdemo.MainActivity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
/**
 * 
 * @author scd
 * 
 */
public class AsyncT extends AsyncTask<String, Void, Bitmap> {
    private String TAG = "AsyncT";
    private MainActivity mContext;
    /**
     * 构造方法
     * 
     * @param mContext
     */
    public AsyncT(MainActivity mContext) {
        super();
        this.mContext = mContext;
    }
    /**
     * 在主线程中运行
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // 显示
        mContext.getProgressBar().setVisibility(View.VISIBLE);
    }
    /**
     * 在子线程中运行
     */
    @Override
    protected Bitmap doInBackground(String... params) {
    /**
    ** 若要实现页面退出时,异步任务也要停止,那么此处就需要根据业务来        进行处理进行判断
       if(isCancelled()){
        }
        此处省略没实现。
    **/
        String url = params[0];
        HttpURLConnection connection = null;
        InputStream in = null;
        Bitmap bitmap = null;
        // 从网络中获取图片
        try {
            // 进行网络连接
            connection = (HttpURLConnection) new URL(url).openConnection();
            // 获得输入流
            in = connection.getInputStream();
            // 存放到缓存中
            BufferedInputStream bin = new BufferedInputStream(in);
            // 休眠
            Thread.sleep(3000);
            // 解析缓存中的输入流
            bitmap = BitmapFactory.decodeStream(bin);
            // 关闭数据流
            in.close();
            bin.close();
            return bitmap;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 在UI线程中运行
     */
    @Override
    protected void onPostExecute(Bitmap result) {
        super.onPostExecute(result);
        mContext.getProgressBar().setVisibility(View.GONE);
        mContext.getView().setImageBitmap(result);
    }
    /**
     * 在UI线程中运行
     */
    @Override
    protected void onProgressUpdate(Void... values) {
        super.onProgressUpdate(values);
    }
}




另外一篇关于 Android 多线程AsyncTask详解


本篇随笔将讲解一下Android的多线程的知识,以及如何通过AsyncTask机制来实现线程之间的通信。

一、Android当中的多线程

在Android当中,当一个应用程序的组件启动的时候,并且没有其他的应用程序组件在运行时,Android系统就会为该应用程序组件开辟一个新的线程来执行。默认的情况下,在一个相同Android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为Main线程。当我们通过某个组件来启动另一个组件的时候,这个时候默认都是在同一个线程当中完成的。当然,我们可以自己来管理我们的Android应用的线程,我们可以根据我们自己的需要来给应用程序创建额外的线程。

二、Main Thread 和 Worker Thread

在Android当中,通常将线程分为两种,一种叫做Main Thread,除了Main Thread之外的线程都可称为Worker Thread。

当一个应用程序运行的时候,Android操作系统就会给该应用程序启动一个线程,这个线程就是我们的Main Thread,这个线程非常的重要,它主要用来加载我们的UI界面,完成系统和我们用户之间的交互,并将交互后的结果又展示给我们用户,所以Main Thread又被称为UI Thread。

Android系统默认不会给我们的应用程序组件创建一个额外的线程,所有的这些组件默认都是在同一个线程中运行。然而,某些时候当我们的应用程序需要完成一个耗时的操作的时候,例如访问网络或者是对数据库进行查询时,此时我们的UI Thread就会被阻塞。例如,当我们点击一个Button,然后希望其从网络中获取一些数据,如果此操作在UI Thread当中完成的话,当我们点击Button的时候,UI线程就会处于阻塞的状态,此时,我们的系统不会调度任何其它的事件,更糟糕的是,当我们的整个现场如果阻塞时间超过5秒钟(官方是这样说的),这个时候就会出现 ANR (Application Not Responding)的现象,此时,应用程序会弹出一个框,让用户选择是否退出该程序。对于Android开发来说,出现ANR的现象是绝对不能被允许的。

另外,由于我们的Android UI控件是线程不安全的,所以我们不能在UI Thread之外的线程当中对我们的UI控件进行操作。因此在Android的多线程编程当中,我们有两条非常重要的原则必须要遵守:

    绝对不能在UI Thread当中进行耗时的操作,不能阻塞我们的UI Thread
    不能在UI Thread之外的线程当中操纵我们的UI元素

 三、如何处理UI Thread 和 Worker Thread之间的通信

既然在Android当中有两条重要的原则要遵守,那么我们可能就有疑问了?我们既不能在主线程当中处理耗时的操作,又不能在工作线程中来访问我们的UI控件,那么我们比如从网络中要下载一张图片,又怎么能将其更新到UI控件上呢?这就关系到了我们的主线程和工作线程之间的通信问题了。在Android当中,提供了两种方式来解决线程直接的通信问题,一种是通过Handler的机制(这种方式在后面的随笔中将详细介绍),还有一种就是今天要详细讲解的 AsyncTask 机制。

四、AsyncTask

AsyncTask:异步任务,从字面上来说,就是在我们的UI主线程运行的时候,异步的完成一些操作。AsyncTask允许我们的执行一个异步的任务在后台。我们可以将耗时的操作放在异步任务当中来执行,并随时将任务执行的结果返回给我们的UI线程来更新我们的UI控件。通过AsyncTask我们可以轻松的解决多线程之间的通信问题。

怎么来理解AsyncTask呢?通俗一点来说,AsyncTask就相当于Android给我们提供了一个多线程编程的一个框架,其介于Thread和Handler之间,我们如果要定义一个AsyncTask,就需要定义一个类来继承AsyncTask这个抽象类,并实现其唯一的一个 doInBackgroud 抽象方法。要掌握AsyncTask,我们就必须要一个概念,总结起来就是: 3个泛型,4个步骤。

3个泛型指的是什么呢?我们来看看AsyncTask这个抽象类的定义,当我们定义一个类来继承AsyncTask这个类的时候,我们需要为其指定3个泛型参数:

AsyncTask <Params, Progress, Result>

    Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型
    Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型
    Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型

 我们在定义一个类继承AsyncTask类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成Void,例如:

AsyncTask <Void, Void, Void>

4个步骤:当我们执行一个异步任务的时候,其需要按照下面的4个步骤分别执行

    onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在UI Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出要给ProgressDialog
    doInBackground(Params... params): 在onPreExecute()方法执行完之后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker thread来执行我们的这个方法,所以这个方法是在worker thread当中执行的,这个方法执行完之后就可以将我们的执行结果发送给我们的最后一个 onPostExecute 方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作
    onProgressUpdate(Progess... values): 这个方法也是在UI Thread当中执行的,我们在异步任务执行的时候,有时候需要将执行的进度返回给我们的UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新我们的进度。这个方法在调用之前,我们需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将我们的进度时时刻刻传递给 onProgressUpdate 方法来更新
    onPostExecute(Result... result): 当我们的异步任务执行完之后,就会将结果返回给这个方法,这个方法也是在UI Thread当中调用的,我们可以将返回的结果显示在UI控件上

 为什么我们的AsyncTask抽象类只有一个 doInBackground 的抽象方法呢??原因是,我们如果要做一个异步任务,我们必须要为其开辟一个新的Thread,让其完成一些操作,而在完成这个异步任务时,我可能并不需要弹出要给ProgressDialog,我并不需要随时更新我的ProgressDialog的进度条,我也并不需要将结果更新给我们的UI界面,所以除了 doInBackground 方法之外的三个方法,都不是必须有的,因此我们必须要实现的方法是 doInBackground 方法。

五、通过AsyncTask来从网络上下载一张图片

下面我们就通过两个代码示例,来看看如何通过AsyncTask来从网络上下载一张图片,并更新到我们的ImageView控件上。

①下载图片时,弹出一个ProgressDialog,但是不显示实时进度

我们来看看布局文件:

<RelativeLayout 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" >
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="200dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:scaleType="fitCenter"/>
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/imageView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="41dp"
        android:text="从网络上下载一张图片" />
</RelativeLayout>


就是很简单的一个ImageView控件和一个Button控件,当点击Button控件时,弹出一个ProgressDialog,然后开启一个异步任务,从网络中下载一张图片,并更新到我们的ImageView上。这里还要注意一点,如果我们要使用手机访问网络,必须还要给其授权才行,在后续的学习当中,将会详细讲解Android当中的授权的知识。我们来看看

AndroidManifest.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xiaoluo.android_asynctast"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />
    
    <!-- 授权手机能够访问网络 -->
    <uses-permission android:name="android.permission.INTERNET"/>
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.xiaoluo.android_asynctast.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>




接下来我们来看看我们的Activity代码:

public class MainActivity extends Activity
{
    private Button button;
    private ImageView imageView;
    private ProgressDialog progressDialog;
    private final String IMAGE_PATH = "http://developer.android.com/images/home/kk-hero.jpg";
//    private final String IMAGE_PATH2 = "http://ww2.sinaimg.cn/mw690/69c7e018jw1e6hd0vm3pej20fa0a674c.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        button = (Button)findViewById(R.id.button);
        imageView = (ImageView)findViewById(R.id.imageView);
        //    弹出要给ProgressDialog
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("提示信息");
        progressDialog.setMessage("正在下载中,请稍后......");
        //    设置setCancelable(false); 表示我们不能取消这个弹出框,等下载完成之后再让弹出框消失
        progressDialog.setCancelable(false);
        //    设置ProgressDialog样式为圆圈的形式
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
         // 在UI Thread当中实例化AsyncTask对象,并调用execute方法
                new MyAsyncTask().execute(IMAGE_PATH);
            }
        });
    }
    
    /**
     * 定义一个类,让其继承AsyncTask这个类
     * Params: String类型,表示传递给异步任务的参数类型是String,通常指定的是URL路径
     * Progress: Integer类型,进度条的单位通常都是Integer类型
     * Result:byte[]类型,表示我们下载好的图片以字节数组返回
     * @author xiaoluo
     *
     */
    public class MyAsyncTask extends AsyncTask<String, Integer, byte[]>
    {
        @Override
        protected void onPreExecute()
        {
            super.onPreExecute();
            //    在onPreExecute()中我们让ProgressDialog显示出来
            progressDialog.show();
        }
        @Override
        protected byte[] doInBackground(String... params)
        {
            //    通过Apache的HttpClient来访问请求网络中的一张图片
            HttpClient httpClient = new DefaultHttpClient();
            HttpGet httpGet = new HttpGet(params[0]);
            byte[] image = new byte[]{};
            try
            {
                HttpResponse httpResponse = httpClient.execute(httpGet);
                HttpEntity httpEntity = httpResponse.getEntity();
                if(httpEntity != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
                {
                    image = EntityUtils.toByteArray(httpEntity);
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            finally
            {
                httpClient.getConnectionManager().shutdown();
            }
            return image;
        }
        @Override
        protected void onProgressUpdate(Integer... values)
        {
            super.onProgressUpdate(values);
        }
        @Override
        protected void onPostExecute(byte[] result)
        {
            super.onPostExecute(result);
            //    将doInBackground方法返回的byte[]解码成要给Bitmap
            Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);
            //    更新我们的ImageView控件
            imageView.setImageBitmap(bitmap);
            //    使ProgressDialog框消失
            progressDialog.dismiss();
        }
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}


我们来看看效果图:

 

 
②带有进度条更新的下载一张网络图片

下面这个代码示例,将会在下载图片的时候,显示进度条的更新,配置文件都不变,我们来看看Activity代码:

public class MainActivity extends Activity
{
    private Button button;
    private ImageView imageView;
    private ProgressDialog progressDialog;
    private final String IMAGE_PATH = "http://developer.android.com/images/home/kk-hero.jpg";
//    private final String IMAGE_PATH2 = "http://ww2.sinaimg.cn/mw690/69c7e018jw1e6hd0vm3pej20fa0a674c.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        button = (Button)findViewById(R.id.button);
        imageView = (ImageView)findViewById(R.id.imageView);
        //    弹出要给ProgressDialog
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("提示信息");
        progressDialog.setMessage("正在下载中,请稍后......");
        //    设置setCancelable(false); 表示我们不能取消这个弹出框,等下载完成之后再让弹出框消失
        progressDialog.setCancelable(false);
        //    设置ProgressDialog样式为水平的样式
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                new MyAsyncTask().execute(IMAGE_PATH);
            }
        });
    }
    
    /**
     * 定义一个类,让其继承AsyncTask这个类
     * Params: String类型,表示传递给异步任务的参数类型是String,通常指定的是URL路径
     * Progress: Integer类型,进度条的单位通常都是Integer类型
     * Result:byte[]类型,表示我们下载好的图片以字节数组返回
     * @author xiaoluo
     *
     */
    public class MyAsyncTask extends AsyncTask<String, Integer, byte[]>
    {
        @Override
        protected void onPreExecute()
        {
            super.onPreExecute();
            //    在onPreExecute()中我们让ProgressDialog显示出来
            progressDialog.show();
        }
        @Override
        protected byte[] doInBackground(String... params)
        {
            //    通过Apache的HttpClient来访问请求网络中的一张图片
            HttpClient httpClient = new DefaultHttpClient();
            HttpGet httpGet = new HttpGet(params[0]);
            byte[] image = new byte[]{};
            try
            {
                HttpResponse httpResponse = httpClient.execute(httpGet);
                HttpEntity httpEntity = httpResponse.getEntity();
                InputStream inputStream = null;
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                if(httpEntity != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
                {
                    //    得到文件的总长度
                    long file_length = httpEntity.getContentLength();
                    //    每次读取后累加的长度
                    long total_length = 0;
                    int length = 0;
                    //    每次读取1024个字节
                    byte[] data = new byte[1024];
                    inputStream = httpEntity.getContent();
                    while(-1 != (length = inputStream.read(data)))
                    {
                        //    每读一次,就将total_length累加起来
                        total_length += length;
                        //    边读边写到ByteArrayOutputStream当中
                        byteArrayOutputStream.write(data, 0, length);
                        //    得到当前图片下载的进度
                        int progress = ((int)(total_length/(float)file_length) * 100);
                        //    时刻将当前进度更新给onProgressUpdate方法
                        publishProgress(progress);
                    }
                }
                image = byteArrayOutputStream.toByteArray();
                inputStream.close();
                byteArrayOutputStream.close();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            finally
            {
                httpClient.getConnectionManager().shutdown();
            }
            return image;
        }
        @Override
        protected void onProgressUpdate(Integer... values)
        {
            super.onProgressUpdate(values);
            //    更新ProgressDialog的进度条
            progressDialog.setProgress(values[0]);
        }
        @Override
        protected void onPostExecute(byte[] result)
        {
            super.onPostExecute(result);
            //    将doInBackground方法返回的byte[]解码成要给Bitmap
            Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);
            //    更新我们的ImageView控件
            imageView.setImageBitmap(bitmap);
            //    使ProgressDialog框消失
            progressDialog.dismiss();
        }
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}




我们来看看效果图:



这样我们就能够通过AsyncTask来实现从网络中下载一张图片,然后将其更新到UI控件中,并时时刻刻的更新当前的进度这个功能了。

六、AsyncTask的重要知识点

在上面两节已经详细讲解了AsyncTask的工作原理了,这里我们还要补充一下AsyncTask的一些其他知识点:

1.Cancelling a Task

我们可以在任何时刻来取消我们的异步任务的执行,通过调用 cancel(boolean)方法,调用完这个方法后系统会随后调用 isCancelled() 方法并且返回true。如果调用了这个方法,那么在 doInBackgroud() 方法执行完之后,就不会调用 onPostExecute() 方法了,取而代之的是调用 onCancelled() 方法。为了确保Task已经被取消了,我们需要经常调用 isCancelled() 方法来判断,如果有必要的话。

2.在使用AsyncTask做异步任务的时候必须要遵循的原则:

    AsyncTask类必须在UI Thread当中加载,在Android Jelly_Bean版本后这些都是自动完成的
    AsyncTask的对象必须在UI Thread当中实例化
    execute方法必须在UI Thread当中调用
    不要手动的去调用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,这些都是由Android系统自动调用的
    AsyncTask任务只能被执行一次


到此,有关AsyncTask的总结就到此为止了,本篇随笔主要讲解了Android中的多线程知识,并且详细地讲解了 AsyncTask 异步任务的概念和实现机制,并通过实例来了解 AsyncTask 的执行过程,最后还补充了 AsyncTask 的一些重要知识点,包括如何取消一个 AsyncTask 以及,我们在使用 AsyncTask 时所必须遵循的规则。

Android中属性动画比较流畅,应用广,本文我们来详细的解析一下Android属性动画源代码,及Android属性动画小结。

本文假定你已经对属性动画有了一定的了解,至少使用过属性动画。下面我们就从属性动画最简单的使用开始。

ObjectAnimator
  .ofInt(target,propName,values[])
  .setInterpolator(LinearInterpolator)
  .setEvaluator(IntEvaluator)
  .setDuration(500)
  .start();


相信这段代码对你一定不陌生,代码中有几个地方是本文中将要重点关注的,setInterpolator(...)、setEvaluator(...)、setDuration(...)在源代码中是如何被使用的。另外,我们也将重点关注Android中属性动画是如何一步步地实现动画效果的(精确到每一帧(frame))。最后??录妇洌?疚闹惺褂玫拇?胧?ndroid 4.2.2。

上面代码的作用就是生成一个属性动画,根据ofInt()我们知道只是一个属性值类型为Int的View的动画。先放过其他的函数,从ObjectAnimator的start()函数开始。

public void  start() {
    //...省略不必要代码
    super.start();
}

从代码中我们知道,它调用了父类的start()方法,也就是ValueAnimator.start()。这个方法内部又调用了自身类内部的start(boolean playBackwards)方法。

/**
 * 方法简要介绍:
 * 这个方法开始播放动画。这个start()方法使用一个boolean值playBackwards来判断是否需
 * 要回放动画。这个值通常为false,但当它从reverse()方法被调用的时候也
 * 可能为true。有一点需要注意的是,这个方法必须从UI主线程调用。
 */
private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mPlayingBackwards = playBackwards;
    mCurrentIteration = 0;
    mPlayingState = STOPPED;
    mStarted = true;
    mStartedDelay = false;
    AnimationHandler animationHandler = getOrCreateAnimationHandler();
    animationHandler.mPendingAnimations.add(this);
    if (mStartDelay == 0) {
        // 在动画实际运行前,设置动画的初始值
        setCurrentPlayTime(0);
        mPlayingState = STOPPED;
        mRunning = true;
        notifyStartListeners();
    }
    animationHandler.start();
}


对代码中几个值的解释:

    mPlayingStated代表当前动画的状态。用于找出什么时候开始动画(if state == STOPPED)。当然也用于在animator被调用了cancel()或 end()在动画的最后一帧停止它。可能的值为STOPPED, RUNNING, SEEKED.
    mStarted是Animator中一个额外用于标识播放状态的值,用来指示这个动画是否需要延时执行。
    mStartedDelay指示这个动画是否已经从startDelay中开始执行。
    AnimationHandler animationHandler 是一个实现了Runnable接口的ValueAnimator内部类,暂时先放过,后面我们会具体谈到。

从上面这段代码中,我们了解到一个ValueAnimator有它自己的状态(STOPPED, RUNNING, SEEKED),另外是否延时也影响ValueAnimator的执行。代码的最后调用了animationHandler.start(),看来动画就是从这里启动的。别急,我们还没初始化ValueAnimator呢,跟进setCurrentPlayTime(0)看看。

public void setCurrentPlayTime(long playTime) {
    initAnimation();
    long currentTime = AnimationUtils.currentAnimationTimeMillis();
    if (mPlayingState != RUNNING) {
        mSeekTime = playTime;
        mPlayingState = SEEKED;
    }
    mStartTime = currentTime - playTime;
    doAnimationFrame(currentTime);
}


这个函数在animation开始前,设置它的初始值。这个函数用于设置animation进度为指定时间点。playTime应该介于0到animation的总时间之间,包括animation重复执行的时候。如果animation还没有开始,那么它会等到被设置这个时间后才开始。如果animation已经运行,那么setCurrentTime()会将当前的进度设置为这个值,然后从这个点继续播放。

接下来让我们看看initAnimation()

void initAnimation() {
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].init();
        }
        mInitialized = true;
    }
}


这个函数一看就觉得跟初始化动画有关。这个函数在处理动画的第一帧前就会被调用。如果startDelay不为0,这个函数就会在就会在延时结束后调用。它完成animation最终的初始化。

那么mValues是什么呢?还记得我们在文章的开头介绍ObjectAnimator的使用吧?还有一个ofInt(T target, Property property, int... values)方法没有介绍。官方文档中对这个方法的解释是:构造并返回一个在int类型的values数值之间ObjectAnimator对象。当values只有一个值的时候,这个值就作为animator的终点值。如果有两个值的话,那么这两个值就作为开始值和结束值。如果有超过两个以上的值的话,那么这些值就作为开始值,作为animator运行的中间值,以及结束值。这些值将均匀地分配到animator的持续时间。
先中断ObjectAnimator.start()流程的分析,回到开头ObjectAnimator.ofInt(...)

接下来让我们深入ofInt(...)的内部看看。

public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setIntValues(values);
    return anim;
}


对这个函数的解释:

    target 就是将要进行动画的对象
    propertyName 就是这个对象属性将要进行动画的属性名
    values 一组值。随着时间的推移,动画将根据这组值进行变化。

再看看anim.setIntValues这个函数

public void setIntValues(int... values) {
    if (mValues == null || mValues.length == 0) {
        // No values yet - this animator is being constructed piecemeal. Init the values with
        // whatever the current propertyName is
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofInt(mProperty, values));
        } else {
            setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
        }
    } else {
        super.setIntValues(values);
    }
}


一开始的时候,mProperty肯定还没有初始化,我们进去setValues(PropertyValuesHolder.ofInt(mPropertyName, values))看看。这里涉及到PropertyValuesHolder这个类。PropertyValuesHolder这个类拥有关于属性的信息和动画期间需要使用的值。PropertyValuesHolder对象可以用来和ObjectAnimator或ValueAnimator一起创建可以并行操作不PropertyValuesHolder同属性的animator。

那么PropertyValuesHolder.ofInt()是干嘛用的呢?它通过传入的属性和values来构造并返回一个特定类型的PropertyValuesHolder对象(在这里是IntPropertyValuesHolder类型)。

public static PropertyValuesHolder ofInt(String propertyName, int... values) {
    return new IntPropertyValuesHolder(propertyName, values);
}


在IntPropertyValuesHolder内部

public IntPropertyValuesHolder(String propertyName, int... values) {
    super(propertyName);
    setIntValues(values);
}
@Override
public void setIntValues(int... values) {
    super.setIntValues(values);
    mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
}


跳转到父类(PropertyValuesHolder)的setIntValues

public void setIntValues(int... values) {
    mValueType = int.class;
    mKeyframeSet = KeyframeSet.ofInt(values);
}


这个函数其实跟我们前面介绍到的 PropertyValueHolder的构造函数相似,它就是设置动画过程中需要的值。如果只有一个值,那么这个值就假定为animator的终点值,动画的初始值会自动被推断出来,通过对象的getter方法得到。当然,如果所有值都为空,那么同样的这些值也会在动画开始的时候也会自动被填上。这套自动推断填值的机制只在PropertyValuesHolder对象跟ObjectAnimator一起使用的时候才有效,并且有一个能从propertyName自动推断出的getter方法这些条件都成立的时候才能用,不然PropertyValuesHolder没有办法决定这些值是什么。
接下来我们看到KeyframeSet.ofInt(values)方法。KeyframeSet这个类持有Keyframe的集合,在一组给定的animator的关键帧(keyframe)中会被ValueAnimator用来计算值。这个类的访问权限为包可见,因为这个类实现Keyframe怎么被存储和使用的具体细节,外部不需要知道。

接下来我们看看KeyframeSet.ofInt(values)方法。

public static KeyframeSet ofInt(int... values) {
    int numKeyframes = values.length;
    IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
    if (numKeyframes == 1) {
        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
        keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
    } else {
        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
        }
    }
    return new IntKeyframeSet(keyframes);
}


在这个方法里面,我们看见从最开始的ObjectAnimator.ofInt(target,propName,values[]),也就是我们在文章的开头使用系统提供的动画初始化函数中传入的int数组,在这里得到了具体的使用。先不关心具体的使用Keyframe.ofInt(...)。从这里我们就可以知道原来Android SDK通过传入的int[]的长度来决定animator中每个帧(frame)的值。具体的对传入的int[]的使用可以参考文章里面对ObjectAnimator.ofInt(...)的介绍。在KeyframeSet.ofInt这个函数的最后一句话使用了IntKeyframeSet的构造函数来初始化这些Keyframe。

public  IntKeyframeSet(IntKeyframe... keyframes) {
     super(keyframes);
}


在IntKeyframeSet的构造函数中又调用父类KeyframeSet的构造函数来实现。

public KeyframeSet(Keyframe... keyframes) {
    mNumKeyframes = keyframes.length;
    mKeyframes = new ArrayList<Keyframe>();
    mKeyframes.addAll(Arrays.asList(keyframes));
    mFirstKeyframe = mKeyframes.get(0);
    mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
    mInterpolator = mLastKeyframe.getInterpolator();
}


从这个构造函数中我们又可以了解到刚刚初始化后的Keyframe数组的第一项和最后一项(也就是第一帧和最后一帧)得到了优先的待遇,作为在KeyframeSet中的字段,估计是为了后面计算动画开始和结束的时候方便。

小结ObjectValue、PropertyValueHolder、KeyframeSet的关系

我们绕了很久,不知道是否把你弄晕了,这里稍稍总结一下。我们就不从调用的顺序一步步分析下来了,太长了。我直接说说ObjectValue、PropertyValueHolder、KeyframeSet之间的关系。这三个类比较有特点的地方,ObjectAnimator无疑是对的API接口,ObjectAnimator持有PropertyValuesHolder作为存储关于将要进行动画的具体对象(通常是View类型的控件)的属性和动画期间需要的值。而PropertyValueHolder又使用KeyframeSet来保存animator从开始到结束期间关键帧的值。这下子我们就了解animator在执行期间用来存储和使用的数据结构。废话一下,从PropertyValueHolder、KeyframeSet这个两个类的源代码来看,这三个类的API的设计挺有技巧的,他们都是通过将具有特定类型的实现作为一个大的概况性的类的内部实现,通过这个大的抽象类提供对外的API(例如,PropertyValuesHolder.ofInt(...)的实现)。
回到ObjectAnimator.start()流程的分析

不知道是否把你上面你是否能清楚,反正不太影响下面对ObjectAnimator.start()流程的分析。从上面一段的分析我们了解到ValueAnimator.initAnimation()中的mValue是 PropertyValuesHolder类型的东西。在initAnimation()里mValues[i].init()初始化它们的估值器Evaluator

void init() {
    if (mEvaluator == null) {
        // We already handle int and float automatically, but not their Object
        // equivalents
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator :
                null;
    }
    if (mEvaluator != null) {
        // KeyframeSet knows how to evaluate the common types - only give it a custom
        // evaluator if one has been set on this class
        mKeyframeSet.setEvaluator(mEvaluator);
    }
}


mEvaluator当然也可以使用ObjectAnimator.setEvaluator(...)传入;为空时,SDK根据mValueType为我们初始化特定类型的Evaluator。这样我们的初始化就完成了。接下来,跳出initAnimation()回到
setCurrentPlayTime(...)

public void setCurrentPlayTime(long playTime) {
    initAnimation();
    long currentTime = AnimationUtils.currentAnimationTimeMillis();
    if (mPlayingState != RUNNING) {
        mSeekTime = playTime;
        mPlayingState = SEEKED;
    }
    mStartTime = currentTime - playTime;
    doAnimationFrame(currentTime);
}


对animator三种状态STOPPED、RUNNING、SEEKED的解释

    static final int STOPPED = 0; // 还没开始播放
    static final int RUNNING = 1; // 正常播放中
    static final int SEEKED = 2; // 定位到一些时间值(Seeked to some time value)

对mSeekedTime、mStartTime的解释

mSeekedTime 当setCurrentPlayTime()被调用的时候设置。如果为负数,animator还没能定位到一个值。
mStartTime 第一次在animation.animateFrame()方法调用时使用。这个时间在第二次调用animateFrame()时用来确定运行时间(以及运行的分数值)

setCurrentPlayTime(...)中doAnimationFrame(currentTime) 之前的代码其实都是对Animator的初始化。看来doAnimator(...)就是真正处理动画帧的函数了。这个函数主要主要用来处理animator中的一帧,并在有必要的时候调整animator的开始时间。

final boolean doAnimationFrame(long frameTime) {
    //对animator的开始时间和状态进行调整
    if (mPlayingState == STOPPED) {
        mPlayingState = RUNNING;
        if (mSeekTime < 0) {
            mStartTime = frameTime;
        } else {
            mStartTime = frameTime - mSeekTime;
            // Now that we're playing, reset the seek time
            mSeekTime = -1;
        }
    }
    // The frame time might be before the start time during the first frame of
    // an animation.  The "current time" must always be on or after the start
    // time to avoid animating frames at negative time intervals.  In practice, this
    // is very rare and only happens when seeking backwards.
    final long currentTime = Math.max(frameTime, mStartTime);
    return animationFrame(currentTime);
}


看来这个函数就是调整了一些参数,真正的处理函数还在animationFrame(...)中。我们跟进去看看。

boolean animationFrame(long currentTime) {
    boolean done = false;
    switch (mPlayingState) {
    case RUNNING:
    case SEEKED:
        float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
        if (fraction >= 1f) {
            if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                // Time to repeat
                if (mListeners != null) {
                    int numListeners = mListeners.size();
                    for (int i = 0; i < numListeners; ++i) {
                        mListeners.get(i).onAnimationRepeat(this);
                    }
                }
                if (mRepeatMode == REVERSE) {
                    mPlayingBackwards = mPlayingBackwards ? false : true;
                }
                mCurrentIteration += (int)fraction;
                fraction = fraction % 1f;
                mStartTime += mDuration;
            } else {
                done = true;
                fraction = Math.min(fraction, 1.0f);
            }
        }
        if (mPlayingBackwards) {
            fraction = 1f - fraction;
        }
        animateValue(fraction);
        break;
    }
    return done;
}


这个内部函数对给定的animation的一个简单的动画帧进行处理。currentTime这个参数是由定时脉冲(先不要了解这个定时脉冲是什么,后面我们会涉及)通过handler发送过来的(当然也可能是初始化的时候,被程序调用的,就像我们的分析过程一样),它用于计算animation已运行的时间,以及已经运行分数值。这个函数的返回值标识这个animation是否应该停止(在运行时间超过animation应该运行的总时长的时候,包括重复次数超过的情况)。

我们可以把这个函数里面的fraction简单地理解成animator的进度条的当前的位置。if (fraction >= 1f) 注意到函数里面的这句话,当animator开始需要重复执行的时候,那么就需要执行这个if判断里面的东西,这里面主要就是记录和改变重复执行animator的一些状态和变量。为了不让这篇文章太复杂,我们这里就不进行分析了。通过最简单的animator只执行一次的情况来分析。那么接下来就应该执行animateValue(fraction)了。

void animateValue(float fraction) {
    fraction = mInterpolator.getInterpolation(fraction);
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].calculateValue(fraction);
    }
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}


在每一个animator的帧,这个函数都会被调用,结合传入的参数:已运行时间分数(fraction)。这个函数将已经运行的分数转为interpolaterd分数,然后转化成一个可用于动画的值(通过evaluator、这个函数通常在animation update的时候调用,但是它也可能在end()函数调用的时候被调用,用来设置property的最终值)。

在这里我们需要理清一下Interpolaterd和evaluator之间的关系。

Interpolator:用来定义animator变化的速率。它让基础的动画效果(渐变、拉伸、平移、旋转)有加速、减速、重复等效果。在源代码中,其实interpolation的作用就是根据某一个时间点来计算它的播放时间分数,具体见官方文档。
evaluator: evaluator全部继承至TypeEvaluator接口,它只有一个evaluate()方法。它用来返回你要进行动画的那个属性在当前时间点所需要的属性值。

我们可以把动画的过程想象成是一部电影的播放,电影的播放中有进度条,Interpolator就是用来控制电影播放频率,也就是快进快退要多少倍速。然后Evaluator根据Interpolator提供的值计算当前播放电影中的哪一个画面,也就是进度条要处于什么位置。

这个函数分三步:

通过Interpolator计算出动画运行时间的分数
变量ValueAnimator中的mValues[i].calculateValue(fraction)(也就是 PropertyValuesHolder对象数组)计算当前动画的值
调用animation的onAnimationUpdate(...)通知animation更新的消息

//PropertyValuesHolder.calculateValue(...)
void calculateValue(float fraction) {
    mAnimatedValue = mKeyframeSet.getValue(fraction);
}
//mKeyframeSet.getValue
public Object getValue(float fraction) {
// Special-case optimization for the common case of only two keyframes
if (mNumKeyframes == 2) {
    if (mInterpolator != null) {
        fraction = mInterpolator.getInterpolation(fraction);
    }
    return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
            mLastKeyframe.getValue());
}
if (fraction <= 0f) {
    final Keyframe nextKeyframe = mKeyframes.get(1);
    final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
    if (interpolator != null) {
        fraction = interpolator.getInterpolation(fraction);
    }
    final float prevFraction = mFirstKeyframe.getFraction();
    float intervalFraction = (fraction - prevFraction) /
        (nextKeyframe.getFraction() - prevFraction);
    return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
            nextKeyframe.getValue());
} else if (fraction >= 1f) {
    final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
    final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
    if (interpolator != null) {
        fraction = interpolator.getInterpolation(fraction);
    }
    final float prevFraction = prevKeyframe.getFraction();
    float intervalFraction = (fraction - prevFraction) /
        (mLastKeyframe.getFraction() - prevFraction);
    return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
            mLastKeyframe.getValue());
}
Keyframe prevKeyframe = mFirstKeyframe;
for (int i = 1; i < mNumKeyframes; ++i) {
    Keyframe nextKeyframe = mKeyframes.get(i);
    if (fraction < nextKeyframe.getFraction()) {
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        final float prevFraction = prevKeyframe.getFraction();
        float intervalFraction = (fraction - prevFraction) /
            (nextKeyframe.getFraction() - prevFraction);
        return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                nextKeyframe.getValue());
    }
    prevKeyframe = nextKeyframe;
}
// shouldn't reach here
return mLastKeyframe.getValue();
}


我们先只关注if (mNumKeyframes == 2)这种情况,因为这种情况最常见。还记的我们使用ObjectAnimator.ofInt(...)传入的int[]数组吗?我们一般就传入动画开始值和结束值,也就是这里的mNumKeyframes ==2 的情况。这里通过mEvaluator来计算,我们看看代码(IntEvaluator.evaluate(...)的代码)。

public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
    int startInt = startValue;
    return (int)(startInt + fraction * (endValue - startInt));
}


mEvaluator.evaluate(...)计算后,我们就返回到ValueAnimator.animateValue(...)中,再回退到ValueAnimator.setCurrentPlayTime(...)。最后回到ValueAnimator.start(boolean playBackwards)。终于解析完了setCurrentPlayTime(...)这个函数,总结一下:这个函数主要用来初始化动画的值,当然这个初始化比我们想象中的复杂多了,它主要通过PropertyValuesHolder、Evaluator、Interpolator来进行值的初始化。PropertyValueHolder又通过KeyframeSet来存储需要的值。
我们回到文章开头介绍的ValueAnimator.start(boolean playBackwards)

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mPlayingBackwards = playBackwards;
    mCurrentIteration = 0;
    mPlayingState = STOPPED;
    mStarted = true;
    mStartedDelay = false;
    AnimationHandler animationHandler = getOrCreateAnimationHandler();
    animationHandler.mPendingAnimations.add(this);
    if (mStartDelay == 0) {
        // This sets the initial value of the animation, prior to actually starting it running
        setCurrentPlayTime(0);
        mPlayingState = STOPPED;
        mRunning = true;
        notifyStartListeners();
    }
    animationHandler.start();
}


在setCurrentPlayTime(0)后,紧接着就通过notifyStartListeners()通知animation启动的消息。最后通过animationHandler.start()去执行。animationHandler是一个AnimationHandler类型的对象,它实现了runable接口。

//AnimationHandler.start()
public void start() {
    scheduleAnimation();
}
//AnimationHandler.scheduleAnimation()
private void scheduleAnimation() {
    if (!mAnimationScheduled) {
        mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
        mAnimationScheduled = true;
    }
}
// Called by the Choreographer.
@Override
public void run() {
    mAnimationScheduled = false;
    doAnimationFrame(mChoreographer.getFrameTime());
}


mHandler.start()最终就是通过mChoreographer.发送给UI系统。这个过程比较复杂,这里不介绍。我们仅仅需要知道,动画中的一帧通过这种方式发送给UI系统后,在UI系统执行完一帧后,又会回调AnimationHandler.run()。那么其实这个过程就相当于,AnimationHandler.start()开始第一次动画的执行→UI系统执行AnimationHandler.run()→UI系统执行完后,回调相关函数→再执行AnimationHandler.run().可以理解为AnimationHandler.run()会一直调用自身多次(当然这是由UI系统驱动的),直至动画结束。


android属性动画小结

一直以来都没有用属性动画,认为可以靠postDelayed()一个任务来不断invalidate这个view从而实现动画效果。

但是今天发现使用属性动画会更流畅

public void rotateyAnimRun(final View view)
{
ObjectAnimator anim = ObjectAnimator//
.ofFloat(view, "zhy", 1.0F,  0.0F)//
.setDuration(500);//
anim.start();
anim.addUpdateListener(new AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
//在这里给我们提供了时间接点刷新view,这里会让效果看起来更流畅,究其原因应该是统计学算好的人眼流畅时间结点
float cVal = (Float) animation.getAnimatedValue();
view.setAlpha(cVal);
view.setScaleX(cVal);
view.setScaleY(cVal);
}
});
}


Android应用中动态添加view是非常普遍的效果,本文我们学习一下在开发中如何动态添加view的两个实例,学习子view的属性怎么设置。

举个例子:比如要在一个LinearLayout中添加一个Button,  子view是Button,父view是LinearLayout。 子view的属性就是通过LayoutParams来设置的,注意是LinearLayout.LayoutParams,因为子view的高度,宽度这些都是针对父view的,要告诉父view自己要占用多大空间,所以是LinearLayout(原来总是会用子view的LayoutParams来设置,错误)

public class MyActivity extends Activity {  
    private Context mContext;  
    private LinearLayout mLinearLayout;  
    private LinearLayout.LayoutParams mLayoutParams;  
  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        mContext = this;  
        mLinearLayout = (LinearLayout)findViewById(R.id.parent_view);  
        mLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);  
        Button button = new Button(mContext);  
        button.setText("添加button");  
        mLinearLayout.addView(button, mLayoutParams);  
    }  
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
              android:orientation="vertical"  
              android:layout_width="fill_parent"  
              android:layout_height="fill_parent"  
        >  
    <LinearLayout  
            android:id="@+id/parent_view"  
            android:orientation="vertical"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"/>  
</LinearLayout>






Android 利用addView 动态给Activity添加View组件

本文主要讲述如何动态给UI界面添加布局和控件,在编程的时候很多时候需要动态显示一些内容,在动态添加View的时候,主要使用addView方法。

1. addView方法简介

在Android 中,可以利用排版View的 addView 函数,将动态产生的View 物件加入到排版View 中。

例子如下:

Activity代码:

public class helloWorld extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView( R.layout.main );
// 取得LinearLayout 物件
LinearLayout ll = (LinearLayout)findViewById(R.id.viewObj);
// 将TextView 加入到LinearLayout 中
TextView tv = new TextView(this);
tv.setText(Hello World);
ll. addView ( tv );
// 将Button 1 加入到LinearLayout 中
Button b1 = new Button(this);
b1.setText(取消);
ll. addView ( b1 );
// 将Button 2 加入到LinearLayout 中
Button b2 = new Button(this);
b2.setText(确定);
ll. addView ( b2 );
// 从LinearLayout 中移除Button 1
ll. removeView ( b1 );
}
}


上述代码的位置,是垂直顺序排列的因为界面代码Linerlayout的orientation设置的是vertical的,但是为了美观,需要设置添加的View的位置和样式。在添加View的时候分为两类来介绍,一种是布局(例如:Linearlayout等),一种是控件(例如:Button,TextView等等。)

2. 动态添加布局(包括样式和位置)

下面的例子将介绍如何动态添加布局,基本内容和上面的代码一致,主要注重如何控制添加的布局的位置。在控制布局的位置的时候使用LayoutParam类来实现。

例子:

界面代码和上面的界面代码类似,就不在重复介绍。

Activity类部分代码:

RelativeLayout rl = new RelativeLayout(this);
//设置RelativeLayout布局的宽高
RelativeLayout.LayoutParams relLayoutParams=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
this.addView(rl, relLayoutParams);


3. 动态添加控件

动态添加控件和添加布局很相似,下述代码主要注重看控制控件的位置,下面的代码和第二项添加布局的补充,在新添加的布局里面再添加控件。

界面代码同样不在重复。

Activity类部分代码:

RelativeLayout rl = new RelativeLayout(this);
//设置RelativeLayout布局的宽高
RelativeLayout.LayoutParams relLayoutParams=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
TextView temp = new TextView(this);
temp .setId(1);
temp.setText(“图片”);
rl.addView(temp);
TextView tv = new TextView(this);
tv.setText(“文字”);
tv.setId(2);
LayoutParams param1 = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
param1.addRule(RelativeLayout.BELOW, 1);//此控件在id为1的控件的下边
rl.addView(tv,param1);
Button update = new Button(this);
update.setText(Button);
LayoutParams param2 = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
param2.addRule(RelativeLayout.RIGHT_OF, 1);//此控件在id为1的控件的右边
rl.addView(update,param2);
this.addView(rl, relLayoutParams);


注意:控制位置和样式的时候,布局和控件使用的方法是一样的。

本文是自学Android开发整理的非常有用的自觉资料,绝对的干货,打算零基础自学Android开发的同学值得参考,如果有java基础的话,就更方便了。

Java 学习

我要强调,一定要有Java 基础,而且Java 基础要牢固,当然我也不是说,Java 学的特别好,才能去学习Android,毕竟学习是循序渐进的,所以在以后的学习中要注意,对于Java 系统的学习,我有一下几个推荐;

        Introduction to Programming in Java: An Interdisciplinary Approach
        http://introcs.cs.princeton.edu/java/home/
        普林斯顿的公开课,也有书籍,我觉得可以直接跟着公开课学习就OK 啦;
        Java Tutorial - TutorialSpoint
        www.tutorialspoint.com/java/
        TutorialSpoint 是一个专门技术教程网站,其他教程也可以去看学习;当然你想离线观看,网站还提供了PDF 版
            Java Tutorial PDF
            www.tutorialspoint.com/java/java_tutorial.pdf
        Javanotes 7.0 -- Title Page
        http://math.hws.edu/eck/cs124/downloads/javanotes7.pdf
        这个是霍巴特威廉史密斯学院的一本书,跟着JDK 版本,已经发行了好多版本;
 

Android 系列教程

这一段时间,发现了好多学习资源,自己收藏了,不敢独享,所以拿出来和大家分享,如果您发现不错的学习资源,欢迎留言;
而学习的过程中,也忽略了一些问题,比如自己学习的不系统,如果您也是自学的话,一定要注意这个问题,我觉得,除了看官方文档和大牛的博客之外,最重要就找一本好书,把自己的知识系统化;

 

入门指南

    How to get started programming Android apps | HalfApp
    http://halfapp.com/blog/get-started-programming-android-apps/
    A step by step guide about how to get started and involved in Android Development - Reddit
    www.reddit.com/r/Android/comments/1w3woc/a_step_by_step_guide_about_how_to_get_started_and/
    
    Beginning Android Resources · codepath/android_guides Wiki
    https://github.com/codepath/android_guides/wiki/Beginning-Android-Resources
    
    我是如何自学Android,资料分享
    http://www.jianshu.com/p/2ee0e74abbdf
    
    我推荐的 Android 基础学习路线
    http://drakeet.me/android_base_road
    
    Android入门杂谈
    http://mmclub.github.io/blog/2014/04/03/start-learning-android/


书籍推荐

首先自己没有很系统地去看一本书,所以我也是搜索,或者实在知乎上别人推荐的,所以,要根据自己的情况,适合自己的书来看;

    The Busy Coder's Guide to Android Development
    http://commonsware.com/Android/
    这是大牛CommonsGuy 开源的一本书,它的更新非常及时,基本上就是跟着SDK 更新的,下载PDF 可以在这里(Four-to-Free Guarantee http://commonsware.com/Android/4-2-free)下载,书籍中源码可以在Github(commonsguy/cw-omnibus https://github.com/commonsguy/cw-omnibus) 中查看;


公开课

    How to Develop Android Apps Online Course - Udacity
    https://www.udacity.com/course/ud853
    
    Programming Mobile Applications for Android (Coursera)
    https://class.coursera.org/android-001/lecture
    
    Android Development For Absolute Beginners - YouTube
    https://www.youtube.com/playlist?list=PLB03EA9545DD188C3


系列教程

    Home · codepath/android_guides Wiki - codepath
    https://github.com/thecodepath/android_guides/wiki
    Android 指南,它不仅介绍怎么创建一个最简单的App Demo,循序渐进教你使用各种流行的框架,对于即将工作的同学来说是很用用的;Demo 很有实战意义;
    
    Android tutorial - TutorialSpoint
    www.tutorialspoint.com/android/index.htm
    是不是特别熟悉呀,对上面提到过,教程特别通俗易懂,代码实例也不错;
    
    Android Development - Vogella
    http://www.vogella.com/tutorials/android.html
    教程挺通俗易懂
    
    AndroidHive | Tutorials, Games, Apps, Tips |
    http://www.androidhive.info/
    博主是印度人,博客主要是以教程为主,质量较高,而且会分享比较新的东西;
    
    Android Tutorial | Interactive and Step by step tutorial to learn Android
    http://www.codelearn.org/android-tutorial/
    
    Android SDK - Tuts+ Code Category
    http://code.tutsplus.com/categories/android-sdk
    
    Android Programming Archives - Learn2Crack
    www.learn2crack.com/category/androidprogramming
    
    Android Learning Path | SlideRule
    https://www.mysliderule.com/learning-paths/android/learn/


大牛博客推荐

    android-cn/android-dev-cn
    https://github.com/android-cn/android-dev-cn
    主要介绍国内Android 开发大牛;
    
    android-cn/android-dev-com
    https://github.com/android-cn/android-dev-com
    主要介绍国外Android 开发大牛;
    
    What are must-read Android developer blogs? - Quora
    http://www.quora.com/What-are-must-read-Android-developer-blogs
    Quora 上的回答
    
    有哪些 Android 大牛的 blog 值得推荐? - 知乎
    http://www.zhihu.com/question/19775981
    知乎上的回答


开源App

    F-Droid | Free and Open Source Android App Repository
    https://f-droid.org/
    F-Droid 是一个Android 开源App 仓库
    
    Android优秀开源项目 - 小猪爬爬
    blog.tisa7.com/android_open_source_projects
    
    The Android Arsenal - A categorized directory of free libraries and tools for Android
    https://android-arsenal.com/
    在这里,找到最新最流行的Android 开源类库
    
    Trinea/android-open-project - Trinea
    https://github.com/Trinea/android-open-project
    大牛Trinea 写的Android 开源项目汇总


开发工具

    Great Code Examples & Snippets | Codota
    http://www.codota.com/
    一个Android 代码搜索引擎;前两天发现了这个工具就爱不释手,写了篇文章——《Android 开发工具之Codota——搜索最好的Android 代码 - 简书》,让你参考入门。
    
    Gradle
    www.gradleware.com
    知乎上我回答的《如何从eclipse转入android studio,感觉Gradle什么的很难理解的。有什么教程吗? - Tikitoo 的回答 - 知乎》,可以参考。
    
    Android Studio
    http://www.jianshu.com/p/874ff12a4c01
    从Google 的态度就可以看出,Android Studio 就是未来,而且在最近发布了正式版,教程在网上也不少。
    
    Android User Interface | User Experience | Inspiration source for Android Designers and Developers
    http://androiduiux.com/
    
    Android App Patterns
    www.android-app-patterns.com
    
    Iconfinder - 400,000+ free and premium icons
    https://www.iconfinder.com/
    一个Icon 搜索引擎
    
    google/material-design-icons - github
    https://github.com/google/material-design-icons
    Google 把官方的Material Design 1000+ 的Icon 开源了,不仅仅只有Android 版,而且还有Web 和iOS,真是业界良心呀。
    
    inferjay/AndroidDevTools
    https://github.com/inferjay/AndroidDevTools
    inferjay 总结的开发工具,并且提供了国内的镜像。
    
    Genymotion - A faster Android emulator
    https://www.genymotion.com/
    Genymotion 是Android 的虚拟机,比官方的快了不知多少啊,它是基于Virtual Box,并且提供了插件。


订阅

    Android 周刊 http://androidweekly.net/
    Android开发技术周报 http://www.androidweekly.cn/
    码农周刊 - Android https://github.com/nemoTyrant/manong#ANDROID

资源

    Best resources for Android development
    http://www.androidauthority.com/best-resources-android-development-372414/
    
    Resources every Android developer must know
    http://www.bongizmo.com/blog/android-resources-each-developer-should-know/
    中文版——《Android开发者必知的开发资源 - ImportNew
    》,译者是ImportNew - 黄小非 http://www.importnew.com/3988.html

参考

    http://www.quora.com/What-are-the-best-resources-to-learn-Android-development
    http://www.quora.com/What-are-the-best-websites-to-learn-Android-development-tools#
    http://www.sitepoint.com/12-android-tutorials-beginners/
    http://nimooli.com/blog/best-android-books-2014/

Activity是Android组件中最基本也是最为常见用的四大组件之一,本文我们来讲讲activity 生命周期以及启动模式。

Activity是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务。

Activity中所有操作都与用户密切相关,是一个负责与用户交互的组件,可以通过setContentView(View)来显示指定控件。

在一个android应用中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。Activity之间通过Intent进行通信。

Activity生命周期

安卓活动由一个返回栈管理

安卓活动有四个状态

1.运行状态

  当一个活动位于栈顶的时候,这个活动就处于运行状态,也就是和用户进行交互的那个界面。

2.暂停状态

  当活动不处于栈顶,但依然可见。意思就是这个活动没有被完全覆盖,上面有一层对话框之类的。

3.停止状态

  活动不处于栈顶,完全不可见。这个好理解吧,就是用户看不到了。

4.销毁状态

  活动从栈中移除了,也就是被用户关闭了。

Activity共有七个回调方法,覆盖了活动整个生命周期

1.onCreate()

  活动创建时调用,一般被用来初始化。

2.onStart()

  由不可见重新可见的时候调用。

3.onResume()

  当活动到达栈顶的时候调用,也就是活动准备和用户交互的时候调用

4.onPause()

  启动或者恢复另一个活动的时候调用。

5.onStop()

  当活动完全不可见的时候会执行,注意是完全不可见,若是是启动一个对话框形式的活动,则不会启动。

6.onDestroy()

  当活动被销毁之前调用

7.onRestart()

  当活动由停止状态变为运行状态之前调用

下图很直观的表示了活动的生命周期


 
你可以用如下示例代码 仔细探索一下 通过logcat打印的内容 你能很容易的弄明白

public class MainActivity extends Activity {
    public static final String TAG="MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"onCreate");
        Log.d(TAG,this.toString());
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        if(savedInstanceState!=null)
        {
            String temp=savedInstanceState.getString("data_key");
            Log.d(TAG,temp);
        }
        Button startNormal=(Button)findViewById(R.id.start_normal_activity);
        Button startDialog=(Button)findViewById(R.id.start_dialog_activity);
        startNormal.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                Intent intent=new Intent(MainActivity.this,NormalActivity.class);
                startActivity(intent);
            }
        });
        startDialog.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                Intent intent=new Intent(MainActivity.this,DialogActivity.class);
                startActivity(intent);
            }
        });
    }
    @Override
    public void onStart(){
        super.onStart();
        Log.d(TAG,"onStart");
    }
    @Override
    public void onResume(){
        super.onResume();
        Log.d(TAG,"onResume");
    }
    @Override
    public void onPause(){
        super.onPause();
        Log.d(TAG,"onPause");
    }
    @Override
    public void onStop(){
        super.onStop();
        Log.d(TAG,"onStop");
    }
    @Override
    public void onDestroy(){
        super.onDestroy();
        Log.d(TAG,"onDestroy");
    }
    @Override
    public void onRestart(){
        super.onRestart();
        Log.d(TAG,"onRestart");
    }


关于启动模式

activity的启动也共有四种

要修改activity的启动模式,需要在AndroidManifest.xml中修改activity标签下的   android:launchMode

<activity
    android:name=".MainActivity"
    android:launchMode="singleTop"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>


 
1.standard

  这是活动的默认启动模式 ,这种启动模式,每次启动都会在栈中新建一个该活动的实例

  好比在FirstActivity的基础上再启动一个FirstActivity  如果你要返回的话你就要按两次返回键才能返回到桌面

2.singleTop

  这种模式很好的解决了上一种模式的问题

  如果FirstActivity这个活动已经在栈顶了 那么如果你要再启动FirstActivity 是不会再创建新的实例了的

  但是这种方法仅限于FirstActivity在栈顶的情况,如果FirstActivity不在栈顶,也就是不在用户能看到的界面,那么还是会创建新的实例。

3.singleTask

  singleTask完美解决创建重复活动实例的情况。每次启动活动,它会自动查找栈中是否存在该活动的实例,若存在直接使用,不存在才创建

4.singleInstance

  这个比较特殊,它启动活动时,会新建一个栈来存放新启动的活动。这种模式可以解决不同应用程序之间调用活动的问题。

[!--infotagslink--]

相关文章

  • Painter绘制红衣喝酒男水粉画效果教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一说绘制红衣喝酒男水粉画效果的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
  • iPhone6怎么激活?两种苹果iPhone6激活教程图文详解

    iPhone6新机需要激活后才可以正常使用,那么对于小白用户来说,iPhone6如何激活使用呢?针对此问题,本文就为大家分别介绍Wifi无线网络激活以及iPhone6连接电脑激活这两种有效的方法,希望本文能够帮助到大家...2022-09-14
  • Photoshop制作雨中野外孤独行走的一头牛海报教程

    今天小编在这里就来给各位photoshop的这一款软件的使用者们来说下制作雨中野外孤独行走的一头牛海报的教程,各位想知道具体制作方法的使用者们,大家就快来看一看小编给...2016-09-14
  • Painter绘制帅气卡通魔法王子漫画教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制帅气卡通魔法王子漫画的具体教程,各位想知道绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
  • Illustrator鼠绘堆雪人的孩童矢量插画教程

    今天小编在这里就来给各位Illustrator的这一款软件的使用者们来说说鼠绘堆雪人的孩童矢量插画的教程,各位想知道具体绘制方法的使用者们,那么各位就快来跟着小编来看看...2016-09-14
  • 安卓手机app添加支付宝支付开发教程

    支付宝支付在国内算是大家了,我们到处都可以使用支付宝了,下文整理介绍的是在安卓app应用中使用支付宝进行支付的开发例子。 之前讲了一篇博客关与支付宝集成获取...2016-09-20
  • llustrator绘制扁平化风格卡通警察护士空姐肖像教程

    今天小编在这里就来给llustrator的这一款软件的使用者们来说一说绘制扁平化风格卡通警察护士空姐肖像的教程,各位想知道具体绘制步骤的使用者们,那么下面就快来跟着小编...2016-09-14
  • 美图秀秀给照片天空加蓝天白云教程一览

    今天小编在这里就来给美图秀秀的这一款软件的使用者们来说下究竟该怎么给照片天空加蓝天白云的教程,各位想知道具体制作步骤的,那么下面就来跟着小编一起看看吧。 ...2016-09-14
  • Illustrator绘制一个方形的录音机图标教程

    今天小编在这里就来给Illustrator的这一款软件的使用者们来说一下绘制一个方形的录音机图标的教程,各位想知道具体绘制方法的使用者们,那么下面就来看一下小编给大家分...2016-09-14
  • photoshop简单制作一个搞笑的换脸表情包教程

    今天小编在这里就来给photoshop的这一款软件的使用者们来说一说简单制作一个搞笑的换脸表情包的教程,各位想知道具体制作方法的使用者们,那么大家就快来看一看教程吧。...2016-09-14
  • 美图秀秀让你胸丰满起来处理教程

    今天小编在这里就来给美图秀秀的这一款软件的使用者们来说一下让你胸丰满起来的处理教程,各位想知道具体处理步骤的,那么下面就快来跟着小编一起看一下教程吧。 给...2016-09-14
  • photoshop给手绘画调色变换场景后期教程

    今天小编在这里就来给各位photoshop的这一款软件的使用者们来说说给手绘画调色变换场景的后期教程,各位想知道具体后期处理步骤的使用者们,那么大家就快来跟着小编来看...2016-10-02
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Painter绘制雷神传插画教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制雷神传插画的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看看绘制方法吧。 ...2016-09-14
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • MySQL中的联合索引学习教程

    联合索引又叫复合索引。对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进...2015-11-24
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Lua语言新手简单入门教程

    这篇文章主要给大家介绍的是关于Lua语言新手入门的简单教程,文中通过示例代码一步步介绍的非常详细,对各位新手们的入门提供了一个很方便的教程,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。...2020-06-30