图解Fiddler如何抓手机APP数据包【超详细】

 更新时间:2016年9月20日 19:56  点击:2144
Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据。 Fiddler 要比其他的网络调试器要更加简单,因为它不仅仅暴露http通讯还提供了一个用户友好的格式。

1、PC端安装Fiddler

下载地址:Fiddler.exe,http://www.telerik.com/download/fiddler


2、 配置PC端Fiddler和手机

(1) 配置Fiddler允许监听https

打开Fiddler菜单项Tools->Fiddler Options,选中decrypt https traffic和ignore server certificate errors两项,如下图:


fiddler https options

第一次会提示是否信任fiddler证书及安全提醒,选择yes,之后也可以在系统的证书管理中进行管理。

(2) 配置Fiddler允许远程连接

如上图的菜单中点击connections,选中allow remote computers to connect,默认监听端口为8888,若被占用也可以设置,配置好后需要重启Fiddler,如下图:


fiddler remote connect

(3) 配置手机端

Pc端命令行ipconfig查看Fiddler所在机器ip,本机ip为10.0.4.37,如下图


ipconfig

打开手机连接到同一局域网的wifi,并修改该wifi网络详情(长按wifi选择->修改网络)->显示高级选项,选择手动代理设置,主机名填写Fiddler所在机器ip,端口填写Fiddler端口,默认8888,如下图:


android network proxy

这时,手机上的网络访问在Fiddler就可以查看了,如下图微博和微信的网络请求:


微信抓数据包

可以双击上图某一行网络请求,右侧会显示具体请求内容(Request Header)和返回内容(Response Header and Content),如下图:


微博网络拦截

可以发现Fiddler可以以各种格式查看网络请求返回的数据,包括Header, TextView(文字), ImageView(图片), HexView(十六进制),WebView(网页形式), Auth(Proxy-Authenticate Header), Caching(Header cache), Cookies, Raw(原数据格式), JSON(json格式), XML(xml格式)很是方便。

停止网络监控的话去掉wifi的代理设置即可,否则Fiddler退出后手机就上不网了哦。

如果需要恢复手机无密码状态,Android端之后可以通过系统设置-安全-受信任的凭据-用户,点击证书进行删除或清除凭据删除所有用户证书,再设置密码为无。

如果只需要监控一个软件,可结合系统流量监控,关闭其他应用网络访问的权限。


利用fiddler抓取Android app数据包

做Android开发的朋友经常需要做网络数据的获取和提交表单数据等操作,然而对于调试程序而言,很难知道我们的数据到底是以怎样的形式发送的,是否发送成功,如果发送失败有是什么原因引起的。fiddler工具为我们提供了很方便的抓包操作,可以轻松抓取浏览器的发出的数据,不管是手机APP,还是web浏览器,都是可以的。

fiddler的工作原理

fiddler是基于代理来实现抓取网络数据包的工作的,当我们开启fiddler以后,fiddler会将我们的浏览器的代理默认进行更改为 127.0.0.1 端口是8888,这时fiddler的默认端口,也就是说我们发送的每一个请求和收到的每一个响应都会先经过fiddler,这样就实现了抓取数据包的工作。

路径:选项?>高级设置?>更改代理服务器设置?>局域网设置?>高级


技术分享

9.回话面板说明:


技术分享

session会话的分析

这里我随便选择一个会话来进行简单的分析。


技术分享

替换服务器端返回的数据

利用”autoresponser”可以替换服务器端返回的文件,当调试的时候需要替换服务器端返回的数据的时候,比如一个已经上线的项目,不可能真正的替换器某一个文件,我们可以这样来操作


技术分享

从图片当中,可以很清晰的看出,当我再次加载该会话的时候,会显示之前设置好的404代理。

如果需要设置不同的文件代理,也是可以的。比如对于该会话,原本服务器端返回的内容如下图:


技术分享

由于该session返回的是一个图片类型的,所以我选择ImageView这个选项卡,可以看到此时返回的图片的样子,那么如果需要用本地的文件代理该返回的内容,和之前的操作步骤都是一样的,只是在选择代理的时候选择本地文件即可,如下图:


技术分享

这次,我选择了一个本地的文件作为代理,此时当我再次重新请求该会话的时候,会返回本地的文件:


技术分享

可以看出这个时候该会话返回的内容已经是我本地的代理了。

fiddler网络限速

fiddler还为我们提供了一个很方便的网络限速的功能,通过网络限速的功能,可以来模拟用户的一些真实环境。fiddler提供了网络限速的插件,我们可以在他的官网下载:http://www.telerik.com/fiddler/add-ons


技术分享

点击”download”,下载完成之后,点击安装,需要重新启动fiddler,在重新启动fiddler之后,可以看到fiddler的工具栏选项卡,多出了一个FiddlerScript选项。


技术分享

比如我需要在请求之前延迟一段时间,可以这样做:


技术分享

在onBeforeRequest方法中加入这样一段代码”oSession[“request-trickle-delay”] = “3000”;”,那么如果需要在服务端响应之间做延迟只需要将”oSession[“request-trickle-delay”] = “3000”;”中的request替换成response即可。

利用fiddler抓取Android app数据包

终于到了今天的主题了,如何利用fiddler抓取Android app数据包,其实也是很简单的,只需要稍微配置一下就可以了。由于fiddler默认是抓取http协议的数据包,我们需要其能够抓取https这样的加密数据包,抓取Android app数据包,需要做如下配置:

1.配置fiddler

点击工具栏选项”tools?>FiddlerOptions”

配置https:


技术分享

配置远程连接:


技术分享

这些配置完成之后,一定要重新启动fiddler。

可以看到fiddler的默认端口是8888,我们可以现在浏览器上输入”http://127.0.0.1:8888”


技术分享

到这里为止我们的fiddler就配置完成了,接下来需要配置手机上的无线网络。

2.手机无线网络配置

注意:如果需要fiddler抓取Android app上的数据包,那么两者必须在同一个无线网络中。(同时,必要时请关闭电脑的防火墙)

在手机的无线网络配置之前,必须要首先知道fiddler所在主机的ip地址:


技术分享

可以看到我的fiddler所在主机,也就是我的电脑在无线网中的ip地址是192.168.1.109

打开手机设置中的无线网络界面,进行如下四步操作:

选中连接的网络,点击修改网络


技术分享

点击高级选项


技术分享

代理—>手动


技术分享

输入代理服务器的ip,也就是我们fiddler所在主机的ip地址,和端口,fiddler默认的端口是8888,IP选项设置为”DHCP”


技术分享

点击保存,此时手机端就配置成功了,打开fiddler,使用打开网易新闻客户端。


技术分享

此时可以看到fiddler抓取的网易app发送和接收的相关数据包。


技术分享

ok,左侧是我们的所有会话,我随机的选中一个会话,该会话是image类型的,查看该会话的内容,是我们网易新闻的头条上的图片。

注意:

1.关闭电脑的防火墙

2.如果需要抓取手机app的数据包,需要手机和电脑在都连接同一个无线网络

3.抓完包以后将fiddler关闭(提高访问网络的速度)同时将手机上的代理关闭 (如果不关闭代理,当fiddler关闭,或者是两者连接的不是同一无线网络,手机会不能正常的访问网络)


在Android应用程序开发中,不可避免的会出现异常,我们应该如何快速的在开发阶段捕获异常进行处理呢?在Java线程类中,可以在线程中捕捉未处理异常,本文我们详细介绍。

APP开发出现异常在所难名,甚至会导致应用程序崩溃。如果在debug模式下开发的时候,是可以通过查看logcat日志来查看异常消息,从而进行处理。但是,如果我们在发布版本之后,用户在使用的时候crash掉了,就无法查看异常信息,也就很难找出bug来解决问题。

还好在java线程类中,有一个针对上述问题的解决办法:在线程中捕捉未处理的异常。因为crash时,抛出的异常就是因为没有在app中catch处理,就会抛给系统,如果我们在这个时候对这个能够对这个异常进行处理,就最好不过了,这样就能打印异常信息,就能发布给服务器,供开发人员查看。

一,写一个CrashHanler类

package com.raise.wind.utils;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * Created by yu on 2015/7/15.
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    public static CrashHandler instance;
    private CrashHandler() {
    }
    public static CrashHandler get_instance() {
        if (instance == null)
            new CrashHandler();
        return instance;
    }
    public void init() {
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        saveFile(ex.getMessage(), "crash.txt");
        //退出程序
        //这里由于是我们自己处理的异常,必须手动退出程序,不然系统出一只处于crash等待状态
        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(1);
    }
    public static void saveFile(String data, String file_name) {
        File sdPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                + File.separator + "aacrash" + File.separator + "cache");
        if (!sdPath.exists()) {
            sdPath.mkdirs();
        }
        File file = new File(sdPath, file_name);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            fos.write(data.getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null)
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}


二,在Application中声明

记得在Androidanifest.xml中配置

package com.raise.wind.app;
import android.app.Application;
import com.raise.wind.utils.CrashHandler;
/**
 * Created by yu on 2015/7/15.
 */
public class APP extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler.get_instance().init();
    }
}


三,测试

这样就声明了我们线程中出现的为捕捉异常交给CrashHandler类来处理。
现在我们写一个空指针异常来测试:

package com.raise.wind.crashproject;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView.setText("text");
    }
}


打开app,发现程序立即crash,打开文件管理能找到在程序中保存的文件,里面有异常消息

Unable to start activity ComponentInfo{com.raise.wind.crashproject/com.raise.wind.crashproject.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

当然,这只是异常消息的开头的提示,如果要想全部打印出来,使用下面代码:

Could not execute method of the activity
android.view.View$1.onClick(View.java:4010)
android.view.View.performClick(View.java:4759)
android.view.View$PerformClick.run(View.java:19770)
android.os.Handler.handleCallback(Handler.java:739)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:135)
android.app.ActivityThread.main(ActivityThread.java:5235)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)
Caused by null
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
android.view.View$1.onClick(View.java:4005)
android.view.View.performClick(View.java:4759)
android.view.View$PerformClick.run(View.java:19770)
android.os.Handler.handleCallback(Handler.java:739)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:135)
android.app.ActivityThread.main(ActivityThread.java:5235)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)
Caused by Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
com.raise.wind.crashproject.MainActivity.click_1(MainActivity.java:21)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
android.view.View$1.onClick(View.java:4005)
android.view.View.performClick(View.java:4759)
android.view.View$PerformClick.run(View.java:19770)
android.os.Handler.handleCallback(Handler.java:739)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:135)
android.app.ActivityThread.main(ActivityThread.java:5235)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)


这个类一般结合log4j来记录日志,并且在发生crash时,将log文件发送到服务器。
这样程序就可以查看用户手机端的crash消息了,方便我们处理在debug模式开发时未发现的异常。


Android APP级异常捕获实现方式

描述:App级异常捕获,并记录下CrashLog到文件。

以下,代码。

在Application的,onCreate中,初始化自定义的CrashHandler

import android.app.Application;
import com.tjd.appexceptioncatch.exception.CrashHandler;
public class MyApplication extends Application {
    private static MyApplication instance;
    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler.getInstance().init(getApplicationContext());
    }
    public static MyApplication getInstance() {
        if (instance == null) {
            instance = new MyApplication();
        }
        return instance;
    }
}


自定义CrashHandler如下

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.tjd.appexceptioncatch.application.MyApplication;
/**
 * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
 * 需要在Application中注册,为了要在程序启动器就监控整个程序。
 */
public class CrashHandler implements UncaughtExceptionHandler {
    public static final String TAG = "CrashHandler";
    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //CrashHandler实例
    private static CrashHandler instance;
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private Map<String, String> infos = new HashMap<String, String>();
    //用于格式化日期,作为日志文件名的一部分
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    /** 保证只有一个CrashHandler实例 */
    private CrashHandler() {
    }
    /** 获取CrashHandler实例 ,单例模式 */
    public static CrashHandler getInstance() {
        if (instance == null)
            instance = new CrashHandler();
        return instance;
    }
    /**
     * 初始化
     */
    public void init(Context context) {
        mContext = context;
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序的默认处理器    
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                Log.e(TAG, "error : ", e);
            }
            //退出程序    
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }
    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        //收集设备参数信息
        collectDeviceInfo(mContext);
        //使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        //保存日志文件     
        saveCatchInfo2File(ex);
        return true;
    }
    /**
     * 收集设备参数信息
     * @param ctx
     */
    public void collectDeviceInfo(Context ctx) {
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionName = pi.versionName == null ? "null" : pi.versionName;
                String versionCode = pi.versionCode + "";
                infos.put("versionName", versionName);
                infos.put("versionCode", versionCode);
            }
        } catch (NameNotFoundException e) {
            Log.e(TAG, "an error occured when collect package info", e);
        }
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
                Log.d(TAG, field.getName() + " : " + field.get(null));
            } catch (Exception e) {
                Log.e(TAG, "an error occured when collect crash info", e);
            }
        }
    }
    private String getFilePath() {
        String file_dir = "";
        // SD卡是否存在
        boolean isSDCardExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
        // Environment.getExternalStorageDirectory()相当于File file=new File("/sdcard")
        boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
        if (isSDCardExist && isRootDirExist) {
            file_dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/crashlog/";
        } else {
            // MyApplication.getInstance().getFilesDir()返回的路劲为/data/data/PACKAGE_NAME/files,其中的包就是我们建立的主Activity所在的包
            file_dir = MyApplication.getInstance().getFilesDir().getAbsolutePath() + "/crashlog/";
        }
        return file_dir;
    }
    /**
     * 保存错误信息到文件中
     * @param ex
     * @return 返回文件名称,便于将文件传送到服务器
     */
    private String saveCatchInfo2File(Throwable ex) {
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String result = writer.toString();
        sb.append(result);
        try {
            long timestamp = System.currentTimeMillis();
            String time = formatter.format(new Date());
            String fileName = "crash-" + time + "-" + timestamp + ".log";
            String file_dir = getFilePath();
            //            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            File dir = new File(file_dir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            File file = new File(file_dir + fileName);
            if (!file.exists()) {
                file.createNewFile();
            }
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(sb.toString().getBytes());
            //发送给开发人员
            sendCrashLog2PM(file_dir + fileName);
            fos.close();
            //            }
            return fileName;
        } catch (Exception e) {
            Log.e(TAG, "an error occured while writing file...", e);
        }
        return null;
    }
    /**
     * 将捕获的导致崩溃的错误信息发送给开发人员
     * 目前只将log日志保存在sdcard 和输出到LogCat中,并未发送给后台。
     */
    private void sendCrashLog2PM(String fileName) {
        //        if (!new File(fileName).exists()) {
        //            Toast.makeText(mContext, "日志文件不存在!", Toast.LENGTH_SHORT).show();
        //            return;
        //        }
        //        FileInputStream fis = null;
        //        BufferedReader reader = null;
        //        String s = null;
        //        try {
        //            fis = new FileInputStream(fileName);
        //            reader = new BufferedReader(new InputStreamReader(fis, "GBK"));
        //            while (true) {
        //                s = reader.readLine();
        //                if (s == null)
        //                    break;
        //                //由于目前尚未确定以何种方式发送,所以先打出log日志。
        //                Log.i("info", s.toString());
        //            }
        //        } catch (FileNotFoundException e) {
        //            e.printStackTrace();
        //        } catch (IOException e) {
        //            e.printStackTrace();
        //        } finally { // 关闭流
        //            try {
        //                reader.close();
        //                fis.close();
        //            } catch (IOException e) {
        //                e.printStackTrace();
        //            }
        //        }
    }
}


在MainActivity中触发异常

import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
    final String str = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        str.equals("exception");
    }
}


我们知道,Activity是Android组件中最基本也是最为常见用的四大组件之一,Activity提供一个屏幕,本教程我们来详细学习一下Activity组件。

Activity是Android组件中最基本也是最为常见用的四大组件(Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器)之一。它间接继承自android.content.Context,因此,有些时候都直接把Activity实例当做Context的实例来使用。

如前面所提到的要在应用程序中使用Activity,必须在Android Manifest.xml中配置它。

新建一个Android工程,新建过程中勾选create activity,让系统自动帮我们创建一个Activity并在Android Manifest.xml中配置它。

AndroidManifest.xml代码如下:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.csc.activity"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="14" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".FirstActivity"
            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节点比较重要的属性有:

android:name属性:指明该Activity节点对应的Activity定义所在的类名,这里默认简写为.FisrtActivity,完整的类名需要与Manifest节点的package属性进行拼接,也可以直接写完整的类名,这里即为cn.csc.activity.FirstActivity。注意,name属性是必须配置的,否则报错,毕竟不配置name属性本身就没有任何意义了。

android:label属性:指明该Activity的标题栏显示的内容。

此外,比较重要的还有一个:

android:launchMode属性:指明该Activity的加载模式。取值可以是standard、singleTop、singleTask和singleInstance。这个属性在提到Activity生命周期时会用到。

回到FirstActivity.java的代码来看:

public class FirstActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
    }
}


Activity中常用的方法:

void  onCreate(Bundle savedInstanceState)  :这个方法在该Activity被创建时回调,进行相关的初始化工作。如,我们最常做的setContentView();设置一个UI界面。

void  setContentView(int layoutResID)  

void  setContentView(View view)  :这两个方法用于给Activity设置UI界面,只是传入的参数类型不同,一个是传入一个layout资源id,一个是直接在代码中编写UI界面。

View  findViewById(int id)  :根据控件的id找到该id,返回值是一个View实例,通常需要进行向下转型到具体类型,如Button、TextView等,以便对控件进行操作,如设置控件属性值,进行事件绑定等。

在first_layout.xml中添加一个按钮,需要设置按钮的id属性,在FirstActivity中的onCreate()方法中根据id获取该按钮,然后设置它的单击响应,弹出一个Toast信息。

代码如下:

first_layout.xml:

<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" >
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/btnText"
        />
</RelativeLayout>


FirstActivity.java:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button btn =  (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
                      Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show();
                 }
           });
}


void  startActivity(Intent intent)  :用于启动一个新的Activity,参数intent中指定了要启动Activity的相关信息。

void  finish()  :用于结束,并销毁当前Activity。

新建一个Activity,名为SecondActivity;新建一个layout文件,名为second_layout.xml

修改first_layout中按钮的点击事件,使它启动SecondActivity。

代码如下:

second_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <Button android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/close"/>
</LinearLayout>


SecondActivity.java:

protected void onCreate(Bundle savedInstanceState) {
       // TODO Auto-generated method stub
       super.onCreate(savedInstanceState);
       setContentView(R.layout.second_layout);
       Button btn2 =  (Button) findViewById(R.id.btn2);
       btn2.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
                  // TODO Auto-generated method stub
                  finish();
             }
       });
  }


FirstActivity.java:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button btn =  (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
//                    Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show();
                      Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                            startActivity(intent);
                 }
           });
}


注意:使用SecondActivity一定要在Manifest.xml中配置

<activity android:name=".SecondActivity" android:label="@string/second">

</activity>

否则会出现如下错误:


点击FirstActivity中的按钮,会启动SecondActivity,点击SecondActivity中的按钮会销毁SecondActivity,然后又回到FirstActivity中。跟点击模拟器上的返回键效果一样。

 

点击I am a button

 


点击Close或者返回键:


关于Intent的使用,将在之后详细说明。

void  startActivityForResult(Intent intent, int requestCode)  :用于启动一个新的Activity,并期望在这个新的Activity结束时返回数据。

void  onActivityResult(int requestCode, int resultCode, Intent data)  :用于接收处理启动的新的Activity结束时返回的数据。

这两个函数在Intent在Android之间传递数据时会用到。

Intent  getIntent()  :获取启动该Activity的意图实例,该方法可以实现获取该Activity的启动者所要传递给自己的存放在Intent中的数据。

关于管理Activity的任务栈:

Activity中有一个int  getTaskId()方法 :用于获取当前Activity所处的栈的id。

修改FirstActivity中的onCreate():

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button btn =  (Button) findViewById(R.id.btn);
        Log.i("TaskId","First:"+getTaskId());
        btn.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
//                    Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show();
                      Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                      startActivity(intent);
                 }
           });
}

及SecondActivity中的onCreate():

protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        Button btn2 =  (Button) findViewById(R.id.btn2);
        Log.i("TaskId","Second:"+getTaskId());
        btn2.setOnClickListener(new OnClickListener() {
            
             @Override
             public void onClick(View v) {
                   // TODO Auto-generated method stub
                   finish();
             }
        });
  }


注意:Log.i("TaskId","First:"+getTaskId());用于在LogCat中输出程序运行信息,第一个为Tag参数,第二个要输出的字符串信息。

打开LogCat界面:window ----> show view ----> other ---->即可找到LogCat。


由于显示的运行信息比较多,可以添加一个信息过滤器,只显示我们所关心的信息。

点击绿色的加号

 

由于我们在之前的Log.i()中设定了Tag参数为TaskId,这时选择by Log Tag,然后填入我们所设置的TaskId即可,随便给过滤器取个名字,然后OK。

启动应用程序,若发现仍有很多运行信息:

 


此时,单击下TaskId会发现只剩下一条了

 


此时运行的是FirstActivity,显示的是FirstActivity所在的任务栈的id为15。然后点击程序中的按钮,启动SecondActivity,发现又多出一条信息:

发现SecondActivity所在的任务栈id同样为15。

一个android应用中,不可能只有一个Activity,如上面启动了两个Activity。Android中使用任务栈来管理多个Activity。

当一个Activity被创建启动时,就会把它放入一个任务栈的栈顶。当该Activity结束被销毁时,就会从所在任务栈弹出,其下的Activity变为栈顶,切换到活动状态。当有新的Activity被启动时,它会入栈称为新的栈顶,之前活动的Activity被强制切换到暂停或者停止状态。

任意时刻,只有栈顶的Activity处于活动状态,可以与用户进行交互。栈中其他Activity若是仍然可见或部分可见,即没有被当前活动Activity完全遮盖时,则处于暂停状态。若完全不可见,则处于停止状态。

一般来说,一个android应用中所有的Activity都会被放到同一个任务栈中进行统一管理,但是也有例外,如上面提到的在Manifest.xml中配置launchMode时,配置不同的值就会有所差别。

关于Activity的状态:

任意时刻,一个Activity都处于下面四个状态之一:

运行状态:位于任务栈的栈顶,此时能够与用户进行交互。

暂停状态:不再处于任务栈的栈顶,不能与用户交互,但是仍然有部分可见。

停止状态:不再处于任务栈的栈顶,不能与用户交互,而且完全不可见。

销毁状态:已从任务栈中弹出。

当系统内存不足时,会优先回收处于销毁状态的Activity所占用的资源;仍然不足时,会回收处于停止状态的Activity所占用的资源;仍然不足时,会回收处于暂停状态的Activity的资源;最不愿意回收的是运行状态的Activity资源。

关于Activity的加载模式与任务栈的关联:

前面提到android:launchMode属性:指明该Activity的启动模式。取值可以是standard、singleTop、singleTask和singleInstance。

standard模式:是默认的启动模式,若没有明确指定启动模式,则为standard模式。如上面的FirstActivity和SecondActivity都没有设置launchMode属性,即为standard模式。

在该模式下,每当新启动一个Activity时,都会为之创建一个新的实例,然后放入栈顶,而不在乎任务栈中是否已然存在该Activity的实例。

修改FirstActivity代码:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button btn =  (Button) findViewById(R.id.btn);
        Log.i("TaskId","First:"+this+" "+getTaskId());
        btn.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
//                    Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show();
                      Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                      startActivity(intent);
                 }
           });
    }


Log.i("TaskId","First:"+this+" "+getTaskId());此处,加上this,打印出当前实例引用this的值。

按钮的点击响应改为启动FirstActivity自身。

运行信息:

 


发现每次this的值都不同,可见每次都新建了一个FirstActivity实例,即便当前FirstActivity实例已然位于任务栈中,且位于任务栈的栈顶。

singleTop模式:standard模式很多时候明显不太合理,当前Activity已然有实例位于任务栈的栈顶,直接使用不就得了,干嘛非要再创建一个实例浪费资源呢。singleTop模式就是当要启动的Activity已然有实例位于任务栈的栈顶就直接使用当前栈顶,而不重新创建实例。

修改Manifest.xml:

<activity
            android:name=".FirstActivity"
            android:label="@string/first"
            android:launchMode="singleTop" >


修改FirstActivity代码:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button btn =  (Button) findViewById(R.id.btn);
        Log.i("TaskId","Create:"+this+" "+getTaskId());
        btn.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     
                      Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                      Log.i("TaskId","Click:"+FirstActivity.this+" "+getTaskId());
                      startActivity(intent);
                 }
           });
}


Log.i("TaskId","Create:"+this+" "+getTaskId());表示是onCreate()中调用

在button的onclick中Log.i("TaskId","Click:"+FirstActivity.this+" "+getTaskId());表明是点击按钮调用。

运行信息:

 


发现只有一个Create,即onCreate()方法只调用了一次,只创建了一个FirstActivity实例。

若FirstActivity实例在任务栈中,但是不是在栈顶,又会如何呢?

修改代码,FirstActivity中启动SecondActivity,而SecondActivity又启动FirstActivity,两者均配置为singleTop。

修改FirstActivity代码:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button btn =  (Button) findViewById(R.id.btn);
        Log.i("TaskId","Create First:"+this+" "+getTaskId());
        btn.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     
                      Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                      startActivity(intent);
                 }
           });
}


修改SecondActivity代码:

protected void onCreate(Bundle savedInstanceState) {
           // TODO Auto-generated method stub
           super.onCreate(savedInstanceState);
           setContentView(R.layout.second_layout);
           Button btn2 =  (Button) findViewById(R.id.btn2);
           Log.i("TaskId","Create Second:"+this + " "+getTaskId());
           btn2.setOnClickListener(new OnClickListener() {
                
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
                      Intent intent = new Intent(SecondActivity.this, FirstActivity.class);
                      startActivity(intent);
                 }
           });
      }


运行信息:

 


发现每次都新建了一个实例,即使当前Activity在任务栈中已然存在,但由于并没与处于栈顶,就要再次创建新的实例。

singleTask模式:可能会觉得singleTop模式还是浪费资源,明明栈中已然存在实例,只因为它不在栈顶便要重新创建。若是想重用不在栈顶的Activity实例,则需要使用singleTask模式,该模式下,新启动一个Activity时,检查当前任务栈中是否存在实例,若存在,但是不在栈顶也没关系,系统会把所有在这个实例之上的Activity实例统统出栈,这样这个实例就处于栈顶,然后就可以直接重用了。

修改Manifest.xml将两个Activity的launchMode都改为singleTask,源代码不需要改动,此时观察运行信息:

 


启动FirstActivity时,输出一条信息,点击FirstActivity中的按钮,由于SecondActivity在栈中不存在,创建了一个实例,然后点击SecondActivity中的按钮,发现FirstActivity实例已然存在,但是栈顶是SecondActiviy,则会将SecondActivity实例出栈,直接重用已存在的FirstActivity实例,所以,此时没有回调onCreate(),故而没有输出信息。

singleInstance模式:用于共享Activity时使用。设置为该模式的Activity不会与当前应用中的其他Activity公用一个任务栈,而是出于自己单独的任务栈中。而几个公用这个Actiivty的应用,共享这个单独的任务栈,就实现了该Activity实例的共享。不然的话,每个应用都有自己的任务栈,启动的Activity肯定在自己的任务栈中管理,根本做不到Activity实例的共享。

修改程序,添加一个ThirdActivity,程序运行的效果FirstActivity为入口,在其中点击按钮可以启动SecondActivity,SecondActivity的launchMode设置为singleInstance,点击SecondActivity中的按钮,可以启动ThirdActivity。ThirdActivity中的按钮可以启动SecondActivity。

FirstActivity和ThirdActivity的launchMode均设置为singleTask。

运行信息:

 


FirstActivity和ThirdActivity都在同一个栈中,id为28

而SecondActivity在id为29的栈中。

注意,此时,点击ThirdActivity中的按钮启动SecondActivity时,不会输出任何信息,因为直接重用了id为29的任务栈中的SecondActivity实例。若在ThirdActivity时,按下模拟器的返回按钮,销毁ThirdActivity,则会直接回到FirstActivity中,因为它俩是处在同一个栈中的。

关于Activity的生命周期:

下面是Activity整个生命周期中,状态发生变化时所回调的方法,它们对应着Activity完整的生命过程。



void  onCreate(Bundle savedInstanceState):Activity被创建时回调

void  onStart()  :在onCreate()或者onRestart()之后被调用,即Activity第一次创建或者从不可见变为可见状态时调用。

void  onResume()  :恢复到活动状态时回到,在onStart()之后一定会调用该方法。之后该活动就处于活动状态了,处于任务栈的栈顶。

void  onPause()  :失去焦点,但是仍然部分可见时回调。

void  onStop()  :Activity变为完全不可见时回调

void  onRestart()  :Activity重新启动时回调

void  onDestroy()  :Activity被销毁前回调

上面的7个方法,除了onRestart()之外,在生命周期的图中都是成对出现的。分为三对,也就出现了三种生存期。

从onCreate()到onDestroy(),一个Activity实例经历了创建到销毁的所有过程,被称之为完整生存期。

从onStart()到onStop(),一个Activity实例从可见状态变为不可见状态,被称之为可见生存期。注意,可见并不一定处于栈顶,因而并一定能与用户交互。

从onResume()到onPause(),一个Activity实例经历了从活动状态到暂停状态,这两个方法之间的过程,该Activity实例都处于活动状态,被称之为前台生存期,或者活动状态生存期。

完整生命周期程序演示,参考《第一行代码》

程序有三个Activity:MainActivity是入口,放置两个按钮,分别用于启动另外两个Activity,实现7个生命周期回调方法,分别输出一条运行信息;NormalActivity就是一个普通的Activity;DialogActivity在Manifest.xml中配置了theme属性,使其成为一个对话框样式的Activity,<activity android:name=".DialogActivity" android:theme="@android:style/Theme.Dialog"></activity>。

具体代码:

main_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/normal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/start_normal_activity"/>
    <Button
        android:id="@+id/dialog"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/start_dialog_activity"/>
</LinearLayout>


MainActivity.java:

public class MainActivity extends ActionBarActivity implements OnClickListener {
      @Override
      public void onClick(View view) {
           // TODO Auto-generated method stub
           switch (view.getId()) {
           case R.id.normal:
                 Intent intent1 = new Intent(this, NormalActivity.class);
                 startActivity(intent1);
                 break;
           case R.id.dialog:
                 Intent intent2 = new Intent(this, DialogActivity.class);
                 startActivity(intent2);
           default:
                 break;
           }
      }
      @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);
        Log.i("LIFECYCLE","onCreate");
        Button btnNormal = (Button) findViewById(R.id.normal);
        Button btnDialog = (Button) findViewById(R.id.dialog);
        btnNormal.setOnClickListener(this);
        btnDialog.setOnClickListener(this);
    }
      @Override
      protected void onStop() {
           // TODO Auto-generated method stub
           super.onStop();
           Log.i("LIFECYCLE","onStop");
      }
      @Override
      protected void onDestroy() {
           // TODO Auto-generated method stub
           super.onDestroy();
           Log.i("LIFECYCLE","onDestroy");
      }
      @Override
      protected void onPause() {
           // TODO Auto-generated method stub
           super.onPause();
           Log.i("LIFECYCLE","onPause");
      }
      @Override
      protected void onStart() {
           // TODO Auto-generated method stub
           super.onStart();
           Log.i("LIFECYCLE","onStart");
      }
      @Override
      protected void onRestart() {
           // TODO Auto-generated method stub
           super.onRestart();
           Log.i("LIFECYCLE","onRestart");
      }
      @Override
      protected void onResume() {
           // TODO Auto-generated method stub
           super.onResume();
           Log.i("LIFECYCLE","onResume");
      }
}


运行信息:

首先启动该应用程序,依次输出:

 


可见,正如生命周期图中所示,依次调用了onCreate()、onStart()、onResume()。

然后点击第一个按钮,启动那个普通的Activity,依次输出:

 


可见,当NormalActivity启动时,MainActivity调用onPause()进入暂停状态,由于NormalActivity启动后,MainActivity被NormalActivity完全遮住时,又要调用onStop()进入停止状态。

然后,点击模拟器的返回按钮,依次输出:

 

可见,由于按下返回键后,NormalActivity被销毁,MainActivity由不可见状态变为可见状态,则依次调用onRestart()、onStart(),又由于MainActivity当前处于任务栈栈顶,所以又调用onResume()进入活动状态。

然后,点击第二个按钮,启动DialogActivity,依次输出:

 

由于MainActivity仍然有部分可见,只是当前不再处于任务栈栈顶而已,所以调用了onPause()进入暂停状态。

然后,按下模拟器上的返回按钮,依次输出:

 

DialogActivity被销毁,MainActivity重新回到栈顶,调用onResume()进入活动状态。

然后,再按下模拟器上的返回按钮,依次输出:

 


MainActivity要被销毁,从活动状态到销毁状态,依次调用了onPause()、onStop()和onDestroy()。

以上就是一个完整的Activity生命周期演示。

此外,由于停止状态和暂停状态的Activity有可能被系统回收资源,当一个Activity从暂停或者停止状态重新回到活动状态时,由于可能已经被回收依次,之前的操作、数据等,如填写了好大一张表单,全都要重新开始,用户体验极差。这时,就要用到涉及Activity实例状态保存的回调函数:

onSaveInstanceState(Bundle bundle):用于在被系统回收之前,将需要保存的一些Activity实例状态信息,重要数据等保存到bundle对象中。当该Activity实例下次被创建时,调用onCreate(Bundle bundle)方法时,这个bundle对象会传递给onCreate()方法,则可以在onCreate方法中,获取到上次保存的数据,进行相应的初始化,恢复工作。

本文我们来学习一下android开发入门教程,UI布局从浅入深,android布局分 1.LinearLayout,线性布局方式,2.Relative Layout,相对布局,3.AbsoluteLayout,绝对位置布局等。

Android的UI组件都是继承View类,View表示一个空白的矩形区域。TextView、Button、EditText这些常用的组件等都是直接或间接继承自View。

此外,View还有一个重要的子类ViewGroup,该类可以用来包含多个View组件,本身也可以当做一个View组件被其他的ViewGroup所包含,由此,可以构建出非常复杂的UI界面。

常用的布局管理器如FrameLayout、LinearLayout、RelativeLayout等都直接继承自ViewGroup。

在Android应用中,Activity就相当于传统桌面开发中的Form,刚创建出来就是一个空白的屏幕,因此,要显示UI界面时,就需要调用setContentView()方法传入要显示的视图实例或者布局资源。

如:

传入一个布局资源:

setContentView(R.layout.main);

传入一个View实例:

TextView myTv = new TextView(this);

setContentView(myTv);

myTv.setText(“hello, world”);

因为setContentView()只能接受一个View实例,要显示复杂的UI界面,就需要用到ViewGroup来包含多个多个View实例,然后将ViewGroup实例传给setContentView。ViewGroup是个抽象类,一般直接使用的都是它的子类,被称之为布局管理器。

Android有两种方式编写UI界面,一种是在xml布局资源文件中,另一种是直接在代码中编写,如上面的传入一个View实例的做法就是直接在代码中编写,这是传统的Form编程的做法。现在比较推荐的是在xml布局资源文件中编写UI界面,这样一来就可以将应用表示层与逻辑层相分离,无需修改代码就可以修改表示层。

要编写复杂的UI界面,需要掌握android中常用的布局管理器。主要有:

AbsoluteLayout:绝对布局

FrameLayout:帧布局

LinearLayout:线性布局

RelativeLayout:相对布局

TableLayout:表格布局

GridLayou:网格布局(Android 4.0添加的新的布局管理器)

1.LinearLayout 线性布局

线性布局就是放在其中的View组件将进行线性对齐排列,可以设置是垂直排列还是水平排列。

新建一个布局资源文件的方法:

右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建LinearLayout布局文件,就选择LinearLayout作为其根节点即可。

linear_layout.xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="aaaaaa"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="bbbbbb"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="cccccc"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="dddddd"
        />
</LinearLayout>


activity中代码如下:

protected void onCreate(Bundle savedInstanceState) {
           // TODO Auto-generated method stub
           super.onCreate(savedInstanceState);
           setContentView(R.layout.linear_layout);
}


显示效果:

 

常用的几个属性:

1)orientation属性:设置LinearLayout中组件的排列方式,可以取值vertical或者horizontal表示垂直排成一列或者水平排成一行。

上面代码中,如果把orientation设置为horizontal。显示则变为:

 


因为只显示一行,而第一个Button的宽度就是充满父元素,所以只显示出来了第一个Button。

2)layout_width属性:设置在父元素中该组件的宽度,可以取值wrap_content、match_parent或者fill_parent。其中wrap_content表示宽度能够包裹该组件中的内容即可,fill_parent和match_parent含义相同表示宽度充满父元素,现在,更常使用match_parent,而很少用fill_parent。

如上面代码中把所有的Button的layout_width都设置为wrap_content,则显示效果如下:

3)layout_height属性:设置在父元素中该组件的宽度,取值同layout_width。

4)grativity属性:设置该容器内组件的对齐方式。

如在LinearLayout节点中添加属性:android:gravity="center_vertical"

则显示效果如下:

 

该属性的取值可以是:top、bottom、left、right、center、center_vertical、center_horizontal等值,或者这些值相或(即位或运算 | )

如:android:gravity="bottom|right" 显示效果

5)layout_gravity属性:当前控件在父元素的位置。

如将aaaaaa那个Button中layout_gravity设置为”center”,其效果将会与其所处容器即LinearLayout中的gravity属性效果进行叠加,显示如下:

垂直上进行了居中,水平上还是排在bbbbbb的左边

6)layout_weight属性:在子控件中设置父元素中多出来的额外空间的分配权重。

此时,如果只在aaaaaa这个button中设置layout_weight属性,可以设置为任意值,习惯设置为1。则aaaaaa这个button会拉伸占据剩下的空间,显示如下:

 


如果同时在aaaaaa和dddddd两个button中都设置layout_weight属性,且第一个设置为1,第二个设置为2,则之前多出来的剩余空间会分给aaaaaa 1/3,分给dddddd 2/3,即各自的权重值/总的权重值,即为各自所分得的剩余空间的比例,显示如下:
http://images0.cnblogs.com/blog2015/693030/201506/271935226428211.png

7)weightSum属性:设置容器中剩余空间的总的权重值,这个属性是LinearLayout中的属性,而layout_weight是各个子控件中的属性,若不设置,则默认为各个子控件layout_weight属性值的总和。

若如上面aaaaaa的layout_weight值为1,dddddd的layout_weight的值为2,同时在LinearLayout中设置weightSum值为6,则仍会有一半的剩余空间,aaaaaa只分得原来剩余空间的1/6,dddddd分得2/6,显示如下:

8)visibility属性:控制是否显示,取值可以是invisible、visible、gone。visible表示显示出来,invisible和gone不显示出来,其中invisible不显示,但控件仍然存在,占用着空间,而gone表示控件不存在了,也就不占用空间了。

如:cccccc设置visibility属性为gone,显示如下:

若改为invisible:

LinearLayout设置invisible:


2.RelativeLayout:相对布局

顾名思义,即根据各控件的相对位置进行布局,相对位置,可以是子控件A相对父控件的位置,也可以是子控件A相对于子控件B的位置。

右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建RelativeLayout布局文件,就选择RelativeLayout作为其根节点即可。文件名为relative_layout.xml。

代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <Button
        android:id="@+id/aa"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="aaaaaa"
       
        />
    <Button
        android:id="@+id/bb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/aa"
        android:layout_alignTop="@id/aa"
        android:text="bbbbbb"
        />
    <Button
        android:id="@+id/cc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@id/aa"
        android:layout_alignBottom="@id/aa"
        android:text="cccccc"
        />
    <Button
        android:id="@+id/dd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/aa"
        android:layout_alignLeft="@id/aa"
        android:text="dddddd"
        />
    <Button
        android:id="@+id/ee"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/aa"
        android:layout_alignLeft="@id/aa"
        android:text="eeeeee"
        />
</RelativeLayout>


修改FirstActivity中setContentView(R.layout.relative_layout);

显示效果:


aaaaaa在父容器中居中显示

bbbbbb在aaaaaa的右边显示,并且与aaaaaa顶部对齐

ccccccc在aaaaaa的左边显示,并且与aaaaaa顶部对齐

dddddd在aaaaaa的上面显示,并且与aaaaaa左对齐

eeeeee在aaaaaa的下面显示,并且与aaaaaa左对齐

主要属性:均为设置父子相对位置,或者子控件与子控件的相对位置

android:layout_toRightOf 在指定控件的右边

android:layout_toLeftOf  在指定控件的左边

android:layout_above          在指定控件的上边

android:layout_below           在指定控件的下边

android:layout_alignBaseline  跟指定控件水平对齐

android:layout_alignLeft  跟指定控件左对齐

android:layout_alignRight 跟指定控件右对齐

android:layout_alignTop  跟指定控件顶部对齐

android:layout_alignBottom   跟指定控件底部对齐

android:layout_alignParentLeft     是否跟父布局左对齐

android:layout_alignParentTop    是否跟父布局顶部对齐

android:layout_alignParentRight   是否跟父布局右对齐

android:layout_alignParentBottom     是否跟父布局底部对齐

android:layout_centerVertical       在父布局中垂直居中

android:layout_centerHorizontal   在父布局中水平居中

android:layout_centerInParent     在父布局中居中


3.FrameLayout:帧布局

如同Flash或者photoshop中图层的概念,在上面的图层遮盖下面的图层,没被遮到的地方仍然显示出来。

右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建FrameLayout布局文件,就选择FrameLayout作为其根节点即可。文件名为frame_layout.xml。

代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <ImageView
        android:src="@drawable/bg"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <ImageView
        android:src="@drawable/hero"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:layout_height="wrap_content"/>
</FrameLayout>

依次放置两个ImageView用于显示两张图片,第一张为背景图片,第二张为一个人物图片。

修改FirstActivity中setContentView(R.layout.frame_layout);

显示效果如下:

 


先添加的控件位于下面,后添加的控件位于上面。

4.AbsoluteLayout:绝对布局

根据绝对坐标位置进行布局,不灵活,故而很少使用。

eclipse中也提示:AbsoluteLayout is deprecated,即不建议使用绝对布局。

新建一个layout文件,名为absolute_layout.xml,代码入下:

<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_x="100dp"
        android:layout_y="100dp"
        android:text="aaaaaa"
        />
</AbsoluteLayout>


修改FirstActivity中代码:setContentView(R.layout.absolute_layout);

显示如下:

 

属性:

android:layout_x 指定控件在父布局的x轴坐标

android:layout_y      指定控件在父布局的y轴坐标

5.TableLayout:表格布局

需要配合TableRow进行使用,也不是太常用。

在TableLayout中每加入一个TableRow子节点,就表示在表格中加入了一行,之后在TableRow中每加入一个控件,就表示加入了一列。注意TableRow单元行里的单元格的宽度小于默认的宽度时就不起作用,其默认是fill_parent,高度可以自定义大小。

如果直接在TableLayout中添加控件,那么该控件将会占用一行。

新建一个layout布局文件,名为table_layout.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="aaaaaa"
        />
    <TableRow >
        <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="bbbbbb"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="cccccc"
        />
     <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="bbbbbb"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="cccccc"
        />
     <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="bbbbbb"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="cccccc"
        />
    </TableRow>
   <TableRow >
        <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="dddddd"
        />
    </TableRow>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="eeeeee"
        />
</TableLayout>


修改setContentView(R.layout.table_layout);

显示效果如下:

 

第0行一个按钮aaaaaa

第1行6个按钮,但是父控件宽度有限,只显示了5个

第2行一个TableRow,里面加了一个按钮dddddd

第3行也是一个按钮eeeeee

主要属性:

android:shrinkColumns         设置收缩的列,其值为要收缩的列的索引,从0开始,多个时用逗号分隔。

如:android:shrinkColumns="0,2",表示第0和第2列收缩,显示效果如下:

 

可以看出没有放在TableRow中的行没有被收缩。

android:stretchColumns        设置拉伸的列,其值为要收缩的列的索引,从0开始,多个时用逗号分隔。如上显示中第1行有6个按钮,父容器宽度不够用,此时拉伸任何一列都不会有效果。若第1行只有两个按钮,此时,设置android:stretchColumns="1",则会把第1列拉伸,充满父容器剩下的空间。显示效果如下:

android:collapseColumns       设置要隐藏的列,这里的隐藏于visibility设置为gone效果相同的。隐藏之后不占用父容器的空间。

如:android:collapseColumns="1,3,5",则第一行6个按钮,只剩下3个bbbbbb

6.GridLayout:网格布局

Android4.0中新增的布局管理器。因此,在android4.0之后的版本才可以直接使用。

新建项目设置最小SDK为14,其他也要高于14。

新建一个layout文件,名为grid_layout.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnCount="5"
    android:rowCount="4">
    <Button
        android:layout_row="0"
        android:text="aaaaaa"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:layout_row="1"
        android:layout_column="0"
        android:text="bbbbbb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:layout_row="2"
        android:layout_column="2"
        android:text="cccccc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:layout_row="3"
        android:layout_column="1"
        android:text="dddddd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</GridLayout>


显示效果如下:

 


整个布局被分为4行5列。

主要属性:

android:columnCount 设置GridLayout的列数

android:rowCount设置GridLayou的行数

每个添加到GridLayout中的子控件都可以设置如下属性:

android:layout_row  设置该元素所在行,从0开始

android:layout_column  设置该元素所在列,从0开始

android:layout_rowSpan  设置该元素所跨的行数

android:layout_columnSpan  设置该元素所跨的列数。

通过本实例我们将主要学习:Android利用ViewPager和PagerAdapter实现图片轮播,使用反射机制获取Android的资源信息。文件后面我们还举出另外两个实例图片轮播的实例。

在手机app应用中我们经常会看到图片轮播动画效果,Android中想要实现图片轮播,主要用到ViewPager这个控件来实现,这个控件的主要功能是实现图片的滑动效果。

那么有了滑动,在滑动的基础上附上图片也就实现了图片轮播的效果...这个控件类似于ListView,需要使用到适配器这个东西,适配器在这里的作用是为轮播时设置一些效果...这里需要使用到PagerAdapter适配器...下面来一个例子,这个例子的效果是在图片轮播的同时显示播放的是第几张图片的信息...并且下面的点也是会随之进行变化的...

先上一下布局文件的代码...这个布局文件其实还是有点说道的...这句话必须要引进...否则会出现错误...意思就是我设置了一个滑动的效果,这个效果填充整个FrameLayout...每一个View表示一个控件,这个控件的显示方式在另外的xml文件当中...下面是两个xml文件...

            
        


上面通过配置xml文件来完成View的显示方式,因为这五个点的形状,大小,甚至是显示方式基本都是相同的,如果再去找5个点图片或者是一个点图片,然后通过Drawable资源的调用完成图片的显示...通过加载5次的方式...这样显然是没有必要的,会浪费不必要的资源..因此我们可以使用xml提供的自定义图形来完成这个过程...xml为我们提供了shape属性,自定义控件..这里我定义了一个实心圆...这个实心圆来完成随着图像的滑动,这个点也随之进行相应的变化...看起来并不是什么难理解的东西...

                                                                                                                                                          


重要的部分还是如何去实现这个过程...这个过程的实现就再下面的代码中,详细解释也在代码当中...

package com.example.picture_change;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
/*
 * HashMap存储的键是不允许重复的...但是值是可以重复的...
 * 
 * */
public class MainActivity extends Activity {
    ArrayList imageSource=null; //存放图像控件...
    ArrayList dots=null;             //存放5个点...
    int []images=null;                     //存放图像的资源...
    String []titles=null;                  //伴随着图像的变动,标题也会随之变动...
    TextView tv=null;                      //TextView来来显示title的变化...
    ViewPager viewpager;                   //ViewPager来完成滑动效果...
    MyPagerAdapter adapter;                //适配器...
    Mapmap=new HashMap(); 
    @SuppressLint("UseSparseArrays")
    MapmapValues=new HashMap();
    private int curr=0;
    private int old=0;
    int o=0;
    int mapsize;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(MainActivity.this, "a", Toast.LENGTH_LONG).show();
        /* 下面利用反射来完成Drawable的资源获取...
         * 在这里我获取了5张图片的资源数据..这5张图片分别为a.jpg b.jpg c.jpg d.jpg e.jpg
         * 这里使用了一个length<=1来完成数据的获取...其实这个方式并不好,是我自己想出来的...
         * 暂时没有更好的方法...我这里使用反射的目的在下面会进行介绍...
         * */
        Field [] field=R.drawable.class.getFields();
        for(Field f:field){
            if(f.getName().length()<=1){
                try {
                    o++;
                    String str="image"+"_"+o;
                    map.put(str, f.getInt(R.drawable.class));//使用map以键值对的形式来保存图片的数据资源...
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        mapsize=map.size()-1;
        /* 这里我再次使用了一个map以键值对的形式只保存上一个map的Value值...
         * 这么做的目的在下面进行说明...
         * 
         * */
        for(Entry entry:map.entrySet()){
            mapValues.put(mapsize, entry.getValue());
            mapsize--;
        }
        init();
    }
    public void init(){
        //数据信息的初始化...
        
        images=new int[]{R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d,R.drawable.e};
        titles=new String[]{"this is the one picture","this is two picture","this is three picture","this is four picture","this is five picture"};
        imageSource=new ArrayList();
        //这里初始化imageSource...
        for(int i=0;i<images.length;i++){
            ImageView iamgeview =new ImageView(this);
            iamgeview.setBackgroundResource(images[i]);
            imageSource.add(iamgeview);
        }
        //这里使用了一个方法...我们没有必要一次一次的findViewById()...使用下面的方法很有效的解决了多次findViewById()函数的引用...
        dots=new ArrayList();
        for(int j=0;j<5;j++){
            String dotid="dot"+"_"+j;
            int resId=getResources().getIdentifier(dotid, "id", "com.example.picture_change");
            dots.add(findViewById(resId));
        }
        tv=(TextView) findViewById(R.id.tv);
        tv.setText(titles[0]);
        viewpager=(ViewPager) findViewById(R.id.vp);
        
        adapter=new MyPagerAdapter();   //这里定义了一个适配器对象...
        viewpager.setAdapter(adapter);  //传递对象,绑定适配器...
        viewpager.setOnPageChangeListener(new onpagelistener());  //这里设置了一个当图片发生滑动后的一个监听效果...
        
        ScheduledExecutorService scheduled =  Executors.newSingleThreadScheduledExecutor();//这里我们开启一个线程...
        scheduled.scheduleAtFixedRate(new Runnable() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                curr=(curr+1)%images.length;
                handler.sendEmptyMessage(0);//将信息发送给Handler,让Handler处理数据,完成一些操作...
            }
        }, 2, 2, TimeUnit.SECONDS);  //实现内部方法,设置播放时间...
        
    }
    private class MyPagerAdapter extends PagerAdapter{
        @Override
        public int getCount() {
            // TODO Auto-generated method stub
            return images.length;
        }
        @Override
        public boolean isViewFromObject(View arg0, Object arg1) {
            // TODO Auto-generated method stub
            //判断前后两张的显示图片是否相同...
            return arg0==arg1;
        }
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            //销毁...释放内存...
            container.removeView(imageSource.get(position));
        }
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            /* 这个方法表示的是滑动到了第几张图片的定位...通过传递一个ViewGroup来完成数据的传递...
             * 我们上面使用到了一个Map来保存上一个Map的Value值,这个的真正目的就在这里..目的是为了
             * 获取当前显示图片的资源信息..说白了就是要获取(R.drawable.属性),为什么要实现这个目的
             * 因为我们要实现,当这个显示的图片被点击的时候,我们应该进行哪些操作...
             * */
            ImageView v=imageSource.get(position);//获取当前图片...
            //position是从0-4的值...因此可以获取到Map中的值了...
            v.setClickable(true);  //设置图片是可以点击的...
            final int values=(Integer)mapValues.get(position); //这里我们获取map中保存的Values值...
            System.out.println(values);
            //下面就是实现触发图片时的监听...
            v.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    switch(values){
                    case R.drawable.a:
                        Toast.makeText(MainActivity.this, "a", Toast.LENGTH_LONG).show();
                        break;
                    case R.drawable.b:
                        Toast.makeText(MainActivity.this, "b", Toast.LENGTH_LONG).show();
                        break;
                    case R.drawable.c:
                        Toast.makeText(MainActivity.this, "c", Toast.LENGTH_LONG).show();
                        break;
                    case R.drawable.d:
                        Toast.makeText(MainActivity.this, "d", Toast.LENGTH_LONG).show();
                        break;
                    case R.drawable.e:
                        Toast.makeText(MainActivity.this, "e", Toast.LENGTH_LONG).show();
                        break;
                    }
                }
            });
            container.addView(imageSource.get(position));  //将所有的图片都加载到了container中...  
            return imageSource.get(position);
            
        }
    }
    //定义一个内部类实现图片在变化的时候的监听...
    class onpagelistener implements OnPageChangeListener{
        @Override
        public void onPageScrollStateChanged(int arg0) {
            // TODO Auto-generated method stub
            
        }
        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
            // TODO Auto-generated method stub
            
        }
        @Override
        public void onPageSelected(int arg0) {
            // TODO Auto-generated method stub
            //当发生滑动后,要完成的一些相应操作...
            tv.setText(titles[arg0]);
            dots.get(arg0).setBackgroundResource(R.drawable.dot);
            dots.get(old).setBackgroundResource(R.drawable.dot_1);
            old=arg0;
            curr=arg0;
        }
        
    }
    
    @SuppressLint("HandlerLeak")
    private Handler handler=new Handler(){
        public void handleMessage(Message msg) {
            //接收到消息后,更新页面
            viewpager.setCurrentItem(curr);
        };
    };
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}


这里我使用了反射机制来获取Drawable的图像资源,然后通过switch方法来完成了当图片被点击的时候需要完成的操作...这是笔者我自己想出来的一种方法...因为Android没有提供ImageClickListener()这类的方法,因此我们只能够自己去进行书写图片被点击的方法...至于更好的方法,我是还没有发现...也是确实是能力有限制了,这个方法今天也想了整整一个下午才折腾出来的...

注意:给自己的一个提醒,HashMap的键是绝对不能够重复保存的...但是值是可以保存重复的数据的,如果保存了重复的键,那么在map只会保存第一个数据,不会对后续数据进行保存...这个也是一个很大的注意点,自己就栽这里很久,虽然很低级的错误,但是很有可能在不注意的情况下就犯下了...因此在这里也算是给自己提个醒...下次不会再犯下这样的错误的


Android图片轮播效果的几种实现方法

第一种:使用动画的方法实现:(代码繁琐)

这种发放需要:两个动画效果,一个布局,一个主类来实现,不多说了,来看代码吧:

public class IamgeTrActivity extends Activity {
/** Called when the activity is first created. */
public ImageView imageView;
public ImageView imageView2;
public Animation animation1;
public Animation animation2;
public TextView text;
public boolean juage = true;
public int images[] = new int[] { R.drawable.icon, R.drawable.expriment,
R.drawable.changer, R.drawable.dataline, R.drawable.preffitication };
public int count = 0;
public Handler handler = new Handler();
public Runnable runnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
AnimationSet animationSet1 = new AnimationSet(true);
AnimationSet animationSet2 = new AnimationSet(true);
imageView2.setVisibility(0);
TranslateAnimation ta = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF,
-1f, Animation.RELATIVE_TO_SELF, 0f,
Animation.RELATIVE_TO_SELF, 0f);
ta.setDuration(2000);
animationSet1.addAnimation(ta);
animationSet1.setFillAfter(true);
ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF,
0f, Animation.RELATIVE_TO_SELF, 0f);
ta.setDuration(2000);
animationSet2.addAnimation(ta);
animationSet2.setFillAfter(true);
//iamgeView 出去 imageView2 进来
imageView.startAnimation(animationSet1);
imageView2.startAnimation(animationSet2);
imageView.setBackgroundResource(images[count % 5]);
count++;
imageView2.setBackgroundResource(images[count % 5]);
text.setText(String.valueOf(count));
if (juage)
handler.postDelayed(runnable, 6000);
Log.i(handler, handler);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imageView = (ImageView) findViewById(R.id.imageView);
imageView2 = (ImageView) findViewById(R.id.imageView2);
text=(TextView)findViewById(R.id.text);
text.setText(String.valueOf(count));
//将iamgeView先隐藏,然后显示
imageView2.setVisibility(4);
handler.postDelayed(runnable, 2000);
}
public void onPause() {
juage = false;
super.onPause();
}
}


 布局代码:

android:orientation=vertical
android:layout_width=fill_parent
android:layout_height=fill_parent
android:id=@+id/rl>
android:id=@+id/imageView
android:layout_width=fill_parent
android:background=@drawable/icon
android:layout_below=@+id/rl
android:layout_height=120dp />
android:id=@+id/imageView2
android:layout_width=fill_parent
android:background=@drawable/expriment
android:layout_below=@+id/rl
android:layout_height=120dp />
android:id=@+id/text
android:layout_width=fill_parent
android:layout_height=wrap_content
android:layout_below=@id/imageView/>


第二种:使用ViewFlipper实现图片的轮播

 
Android系统自带的一个多页面管理控件,它可以实现子界面的自动切换:

首先 需要为ViewFlipper加入View

(1) 静态导入:在layout布局文件中直接导入

(2) 动态导入:addView()方法

ViewPlipper常用方法:

setInAnimation:设置View进入屏幕时候使用的动画

setOutAnimation:设置View退出屏幕时候使用的动画

showNext:调用该函数来显示ViewFlipper里面的下一个View

showPrevious:调用该函数来显示ViewFlipper里面的上一个View

setFlipInterval:设置View之间切换的时间间隔

startFlipping使用上面设置的时间间隔来开始切换所有的View,切换会循环进行

stopFlipping:停止View切换

讲了这么多,那么我们今天要实现的是什么呢?

(1) 利用ViewFlipper实现图片的轮播

(2) 支持手势滑动的ViewFlipper

我们需要先准备几张图片:把图片放进drawable中

创建两个动画:在res下面新建一个folder里面新建两个xml:

left_in:

android:duration=5000
android:fromXDelta=100%p
android:toXDelta=0/>

 
left_out:

android:fromXDelta=0
android:toXDelta=-100%p
android:duration=5000/>

 
一个布局文件:

xmlns:tools=http://schemas.android.com/tools
android:layout_width=match_parent
android:layout_height=match_parent
tools:context=.MainActivity >
android:id=@+id/flipper
android:layout_width=fill_parent
android:layout_height=fill_parent/>


一个主类:

public class MainActivity extends Activity {
private ViewFlipper flipper;
private int[] resId = {R.drawable.pc1,R.drawable.pc2,R.drawable.pc3,R.drawable.pc4};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
flipper = (ViewFlipper) findViewById(R.id.flipper);
/*
* 动态导入的方式为ViewFlipper加入子View
* */
for (int i = 0; i < resId.length; i++) {
flipper.addView(getImageView(resId[i]));
}
/*
* 为ViewFlipper去添加动画效果
* */
flipper.setInAnimation(this, R.anim.left_in);
flipper.setOutAnimation(this, R.anim.left_out);
flipper.setFlipInterval(5000);
flipper.startFlipping();
}
private ImageView getImageView(int resId){
ImageView image = new ImageView(this);
image.setBackgroundResource(resId);
return image;
}
}


那么这样就实现了一个图片轮询的功能效果了

我们还可以添加点击,滑动效果:

我们还需要添加两个向右的滑动效果:

right_in:

android:fromXDelta=0
android:toXDelta=-100%p
android:duration=2000/>

 
right_out:

android:fromXDelta=100%p
android:toXDelta=0
android:duration=2000/>

 
然后我们还需要在主类里面添加(如果你不想让图片自动播放,只想通过手势来实现图片播放那么你需要把“为ViewFlipper添加动画效果的代码”删掉):

public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
break;
case MotionEvent.ACTION_MOVE://判断向左滑动还是向右滑动
if (event.getX() - startX > 100) {
flipper.setInAnimation(this, R.anim.left_in);
flipper.setOutAnimation(this, R.anim.left_out);
flipper.showPrevious();
}else if (startX - event.getX() > 100) {
flipper.setInAnimation(this, R.anim.right_in);
flipper.setOutAnimation(this, R.anim.right_out);
flipper.showNext();
}
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}


这样我们利用我们的ViewFlipper完成的图片轮询的功能就做完了。

[!--infotagslink--]

相关文章