Android SDK国内镜像

安装过Android Studio的人估计都体验过下载超慢的问题,有时候直接就不能下载,这里记录一下国内的两个镜像,稍微设置一下就可以解决速度慢的问题。

1.腾讯提供的镜像:

http://android-mirror.bugly.qq.com:8080/include/usage.html

2.东软提供的镜像:

http://mirrors.neusoft.edu.cn/more.we#android

安装的时候还是建议到官方下载一个最新版本的Android Studio,然后选择上面的一个国内镜像,按照配置说明把更新地址配置后,使用起来还是很顺畅的。

Andorid中打开Activity获取返回值

在开发App中经常遇到在一个页面上打开另一个页面用户操作之后返回,第一个页面获取第二页面用户的操作和输入内容等。这种场景就会用到Activity的返回值。

第一个Activity中的代码有两个部分要注意

1.打开第二个activity的方法和参数

context.startActivityForResult(intent, 123);

使用的是startActivityForResult方法,这个方法里的第二个参数是自定义的requestCode,这个参数是为了区分打开多个页面返回的时候可以区分开来。

2.接收第二个页面返回值

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
		if(requestCode == 123){
			//...
			if(resultCode == 1){
				//第一种返回
			}else if(resultCode ==2){
				//第二种返回
			}
		}
	}

重写方法onActivityResult ,其中requestCode参数跟打开第二页面传入的参数是一样的。第二个参数resultCode是第二页面返回的状态吗,有可能有多重可能的返回,这样就可以根据不同的状态做不同的操作。

下面看看第二页面返回

1.在按钮的事件里面返回

Button okButton = (Button) findViewById(R.id.ok_button);
okButton.setOnClickListener(new View.OnClickListener() {
	public void onClick(View view){
		Intent intent = new Intent();
		//...
		setResult(1, intent);
		finish();
	}
});

这里的setResult里的第一个参数就是第一个acitivty里面的onActivityResult的第二个参数resultCode,可以根据这个进行判断是哪个事件返回的。

2.在按下手机返回按键的时候返回

    @Override
    public void onBackPressed() {
		Intent intent = new Intent();
		//...
		setResult(2, intent);
		finish();
	}

这里需要重写方法onBackPressed ,注意不能调用父类的方法,否则第一个activity接收不到状态和intent数据,因为父类里面自动返回RESULT_CANCEL这个状态和空的数据。

 

基本上了解了这些,绝大部分的App就已经满足要求了,一个页面有可能打开多个第二页面,某个第二页面可能有多重可能返回,这些都能满足要求。

Android Activity的生命周期

activity类处于android.app包中,继承体系如下:

1.java.lang.Object

2.android.content.Context

3.android.app.ApplicationContext

4.android.app.Activity

activity是单独的,用于处理用户操作。几乎所有的activity都要和用户打交道,所以activity类创建了一个窗口,开发人员可以通过setContentView(View)接口把UI放到activity创建的窗口上,当 activity指向全屏窗口时,也可以用其他方式实现:作为漂浮窗口(通过windowIsFloating的主题集合),或者嵌入到其他的 activity(使用ActivityGroup)。大部分的Activity子类都需要实现以下两个接口:

  • onCreate(Bundle)接口是初始化activity的地方. 在这儿通常可以调用setContentView(int)设置在资源文件中定义的UI, 使用findViewById(int) 可以获得UI中定义的窗口.
  • onPause()接口是使用者准备离开activity的地方,在这儿,任何的修改都应该被提交(通常用于ContentProvider保存数据).

为了能够使用Context.startActivity(),所有的activity类都必须在AndroidManifest.xml文件中定义有相关的“activity”项。

activity类是Android 应用生命周期的重要部分。

Activity生命周期

在系统中的Activity被一个Activity栈所管理。当一个新的Activity启动时,将被放置到栈顶,成为运行中的Activity,前一个Activity保留在栈中,不再放到前台,直到新的Activity退出为止。

Activity有四种本质区别的状态:

  1. 在屏幕的前台(Activity栈顶),叫做活动状态或者运行状态(active or running)
  2. 如果一个Activity失去焦点,但是依然可见(一个新的非全屏的Activity 或者一个透明的Activity 被放置在栈顶),叫做暂停状态(Paused)。一个暂停状态的Activity依然保持活力(保持所有的状态,成员信息,和窗口管理器保持连接),但是在系统内存极端低下的时候将被杀掉。
  3. 如果一个Activity被另外的Activity完全覆盖掉,叫做停止状态(Stopped)。它依然保持所有状态和成员信息,但是它不再可见,所以它的窗口被隐藏,当系统内存需要被用在其他地方的时候,Stopped的Activity将被杀掉。
  4. 如果一个Activity是Paused或者Stopped状态,系统可以将该Activity从内存中删除,Android系统采用两种方式进行删除,要么要求该Activity结束,要么直接杀掉它的进程。当该Activity再次显示给用户时,它必须重新开始和重置前面的状态。

下面的图显示了Activity的重要状态转换,矩形框表明Activity在状态转换之间的回调接口,开发人员可以重载实现以便执行相关代码,带有颜色的椭圆形表明Activity所处的状态。

activity

在上图中,Activity有三个关键的循环:

  1. 整个的生命周期,从onCreate(Bundle)开始到onDestroy()结束。Activity在onCreate()设置所有的“全局”状态,在onDestory()释放所有的资源。例如:某个Activity有一个在后台运行的线程,用于从网络下载数据,则该Activity可以在onCreate()中创建线程,在onDestory()中停止线程。
  2. 可见的生命周期,从onStart()开始到onStop()结束。在这段时间,可以看到Activity在屏幕上,尽管有可能不在前台,不能和用户交互。在这两个接口之间,需要保持显示给用户的UI数据和资源等,例如:可以在onStart中注册一个IntentReceiver来监听数据变化导致UI的变动,当不再需要显示时候,可以在onStop()中注销它。onStart(),onStop()都可以被多次调用,因为Activity随时可以在可见和隐藏之间转换。
  3. 前台的生命周期,从onResume()开始到onPause()结束。在这段时间里,该Activity处于所有 Activity的最前面,和用户进行交互。Activity可以经常性地在resumed和paused状态之间切换,例如:当设备准备休眠时,当一个 Activity处理结果被分发时,当一个新的Intent被分发时。所以在这些接口方法中的代码应该属于非常轻量级的。

Activity的整个生命周期都定义在下面的接口方法中,所有方法都可以被重载。所有的Activity都需要实现 onCreate(Bundle)去初始化设置,大部分Activity需要实现onPause()去提交更改过的数据,当前大部分的Activity也需要实现onFreeze()接口,以便恢复在onCreate(Bundle)里面设置的状态。

public class Activity extends ApplicationContext {
	protected void onCreate(Bundle icicle);
	protected void onStart();
	protected void onRestart();
	protected void onResume();
	protected void onFreeze(Bundle outIcicle);
	protected void onPause();
	protected void onStop();
	protected void onDestroy();

}

android实现拨打电话

调用系统自带的打电话功能非常简单,在应用中只需要调用对应的接口,并传入电话号码就可以了,界面会自动跳转到打电话界面,并且电话已经开始拨打,等待接通就可以了,下面就是示例的代码:

String number = "110";  
//用intent启动拨打电话  
Intent intent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:"+number));  
startActivity(intent);

当然打电话是调用系统的activity,所以需要分配权限才能使用

AndroidManifest.xml中加入下面的权限即可

<!--添加可以向外拨打电话的权限  -->  
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>

这就是拨打电话的全部,超级简单。

android中播放视频的几种方法

播放视频的方法有很多种,使用andorid自带的也可以播放大部分的视频,有些格式的视频需要专门的解码才能播放,大部分的视频网站都有自己的视频格式,同时自己提供播放软件,这就是最简单的防止盗版,同时也是根据需要压缩自己的视频文件可以减少存储空间,当然最重要的还是节省带宽;

在Android中,我们有三种方式来实现视频的播放:
1、使用其自带的播放器。指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型。
2、使用VideoView来播放。在布局文件中使用VideoView结合MediaController来实现对其控制。
3、使用MediaPlayer类和SurfaceView来实现,这种方式很灵活。

1.调用其自带的播放器:

Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/Test_Movie.m4v");     
//调用系统自带的播放器    
Intent intent = new Intent(Intent.ACTION_VIEW);    
Log.v("URI:::::::::", uri.toString());    
intent.setDataAndType(uri, "video/mp4");    
startActivity(intent);

2.使用VideoView来实现:

Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/Test_Movie.m4v");    
VideoView videoView = (VideoView)this.findViewById(R.id.video_view);    
videoView.setMediaController(new MediaController(this));    
videoView.setVideoURI(uri);    
videoView.start();    
videoView.requestFocus();

3.使用MediaPlayer:

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:weightSum="1">
    <SurfaceView android:layout_height="220dip" android:layout_gravity="center" android:id="@+id/surface" android:layout_weight="0.25" android:layout_width="320dip"></SurfaceView>
    <LinearLayout android:id="@+id/linearLayout1" android:layout_height="wrap_content" android:layout_width="fill_parent">
        <Button android:text="播放" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
        <Button android:text="暂停" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
        <Button android:text="停止" android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
    </LinearLayout>
</LinearLayout>

主程序:

public class SurfaceActivity extends Activity implements SurfaceHolder.Callback {
    /** Called when the activity is first created. */
    MediaPlayer player;
    SurfaceView surface;
    SurfaceHolder surfaceHolder;
    Button play,pause,stop;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        play=(Button)findViewById(R.id.button1);
        pause=(Button)findViewById(R.id.button2);
        stop=(Button)findViewById(R.id.button3);
        surface=(SurfaceView)findViewById(R.id.surface);
 
        surfaceHolder=surface.getHolder();  //SurfaceHolder是SurfaceView的控制接口
        surfaceHolder.addCallback(this);   //因为这个类实现了SurfaceHolder.Callback接口,所以回调参数直接this
        surfaceHolder.setFixedSize(320, 220);  //显示的分辨率,不设置为视频默认
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);  //Surface类型
 
        play.setOnClickListener(new OnClickListener(){
             @Override
            public void onClick(View v) {
                player.start();
            }});
        pause.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                player.pause();
            }});
        stop.setOnClickListener(new OnClickListener(){
             @Override
            public void onClick(View v) {
                player.stop();
            }});
    }
 
    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    }
 
    @Override
    public void surfaceCreated(SurfaceHolder arg0) {
//必须在surface创建后才能初始化MediaPlayer,否则不会显示图像
        player=new MediaPlayer();
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        player.setDisplay(surfaceHolder);
        //设置显示视频显示在SurfaceView上
            try {
                player.setDataSource("/sdcard/3.mp4");
                player.prepare();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder arg0) {
        // TODO Auto-generated method stub
 
    }
 
    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        if(player.isPlaying()){
        player.stop();
        }
        player.release();
        //Activity销毁时停止播放,释放资源。不做这个操作,即使退出还是能听到视频播放的声音
    }
}

总结

第三种算是比较常用的播放方式,比较灵活可以对视频进行控制,当然这毕竟是自带的播放器,支持视频格式根据系统安装的情况而定,那么可以使用一些第三方播放器来实现,比如说vlc开源播放器http://www.videolan.org/,或者其他的开源播放器都可以做的很好。

android使用ACTION_SEND分享内容

几乎是所有的app现在都有分享的功能,分享一些文本,链接和图片之类的到各大社交平台,那么这么一个简单的功能是如何实现的能,android里面有一个默认的接口,我们来看看怎么使用。

android默认的intent ACTION_SEND可以实现这个分享的功能,看看下面的示例

Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("text/html");
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, Html.fromHtml("<p>This is the text that will be shared.</p>"));
startActivity(Intent.createChooser(sharingIntent,"Share using"));

以上是分享一段html,也是最常用的分享方式,也可以分享一些图片如下

Intent sharingIntent = new Intent(Intent.ACTION_SEND);
Uri screenshotUri = Uri.parse(path);

sharingIntent.setType("image/png");
sharingIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
startActivity(Intent.createChooser(sharingIntent, "Share image using"));

分享多个图片

ArrayList<Uri> imageUris = new ArrayList<Uri>();
imageUris.add(imageUri1); // Add your image URIs here
imageUris.add(imageUri2);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
shareIntent.setType("image/*");
startActivity(Intent.createChooser(shareIntent, "Share images to.."));

上面的分享当然是在手机上有可以分享的软件才能接受到分享的内容,以上的分享事件都会打开一个选择分享软件的列表,比如微信或者微博之类的,用户选择一个之后发送分享内容,那么下面这段xml可以让你的app可以出现在分享列表中,以实现分享的效果:

<activity android:name=".ui.MyActivity" >
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="image/*" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SEND_MULTIPLE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="image/*" />
    </intent-filter>
</activity>

下面这段代码是处理接收到消息之后的基本过程

void onCreate (Bundle savedInstanceState) {
    ...
    // Get intent, action and MIME type
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();

    if (Intent.ACTION_SEND.equals(action) && type != null) {
        if ("text/plain".equals(type)) {
            handleSendText(intent); // Handle text being sent
        } else if (type.startsWith("image/")) {
            handleSendImage(intent); // Handle single image being sent
        }
    } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
        if (type.startsWith("image/")) {
            handleSendMultipleImages(intent); // Handle multiple images being sent
        }
    } else {
        // Handle other intents, such as being started from the home screen
    }
    ...
}

void handleSendText(Intent intent) {
    String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    if (sharedText != null) {
        // Update UI to reflect text being shared
    }
}

void handleSendImage(Intent intent) {
    Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
    if (imageUri != null) {
        // Update UI to reflect image being shared
    }
}

void handleSendMultipleImages(Intent intent) {
    ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
    if (imageUris != null) {
        // Update UI to reflect multiple images being shared
    }
}

总结

android自带的分享只是列出接收分享内容的app列表,由用户选择启动对应的app进行分享,当系统中没有安装目标app的时候是没办法分享的,那么对一般的应用来说这就已经足够了,系统自带的功能一般都是比较基本的东西,另一个办法是使用第三方分享sdk可以实现更加复杂的分享功能。

Android中简单使用Sqlite

当数据比较复杂的时候需要保存数据到数据库中,那么Android中默认集成了sqlite数据库,这是一个被广泛使用的轻量数据库,在很多场合都使用到,Android中提供了很多便利的api来使用,下面来看看入门级别的几个api.

1.打开和关闭数据库

//打开或创建test.db数据库  
SQLiteDatabase db = openOrCreateDatabase("test.db", Context.MODE_PRIVATE, null); 

//...对数据的操作

//关闭当前数据库  
db.close();  
//删除test.db数据库  
deleteDatabase("test.db");

2.使用sql语句进行操作数据,使用db.execSQL(…)这个方法执行写好的sql

//删除表
db.execSQL("DROP TABLE IF EXISTS person");  
//创建person表  
db.execSQL("CREATE TABLE person (id INTEGER, name VARCHAR, age SMALLINT)");  
//插入数据  
db.execSQL("INSERT INTO person VALUES (?, ?, ?)", new Object[]{1,"fullstacks.cn",28}); 
//查询数据
Cursor c = db.rawQuery("SELECT * FROM person WHERE age >= ?", new String[]{"20"});  
while (c.moveToNext()) {  
	int id = c.getInt(c.getColumnIndex("id"));  
	String name = c.getString(c.getColumnIndex("name"));  
	int age = c.getInt(c.getColumnIndex("age"));  
	Log.i("db", "id=>" + id + ", name=>" + name + ", age=>" + age);  
}
//删除数据
db.execSQL("DELETE FROM person WHERE id = ?", new String[]{1});
//更新数据
db.execSQL("UPDATE person set age = ? WHERE id = ?", new String[]{29,1});

3.使用android提供的api进行增删改

//新增
//ContentValues以键值对的形式存放数据  
ContentValues cv = new ContentValues();  
cv.put("name", "www.fullstacks.cn");  
cv.put("age", 30);  
cv.put("id", 2); 
//插入ContentValues中的数据  
db.insert("person", null, cv); 

//删除
db.delete("person", "age < ?", new String[]{"30"}); 

//更新数据
cv = new ContentValues();  
cv.put("age", 35);  //这是更新的内容
db.update("person", cv, "name = ?", new String[]{"john"}); //后面的两个参数是更新的条件

//查询数据还是使用sql查询 db.rawQuery

数据库创建之后文件存放在 /data/data/[PACKAGE_NAME]/databases目录下,可以使用文件浏览器查看

以上的几个简单api对基本的数据操作就已经满足了,更多的操作比如对数据库的升级维护之类的,更多的游标操作等可以查看api.

总结

本质上sqlite数据库在移动应用中的使用和服务端的使用没什么区别,也可以做一些实体和数据之间的映射,在移动应用中这些复杂的数据访问层并不是很需要,所以很多数据访问层的服务端框架并没有在Android上实现,使用这些基本的api都已经全部搞定,最多就是做一些简单的封装。

Android给ListView添加搜索功能

当列表的数据比较多的时候,给列表添加搜索功能是非常有必要的,根据搜索框输入的内容过滤列表的信息,很多应用都这样的功能,只是各种实现方式不一样而已,下面来看看最简单的一个实现方式。

1.创建主界面

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
     
    <!-- Editext for Search -->
    <EditText android:id="@+id/inputSearch"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="Search products.."
        android:inputType="textVisiblePassword"/>
  
    <!-- List View -->
    <ListView
        android:id="@+id/list_view"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
  
</LinearLayout>

2.列表行布局文件

list_item.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" >
     
    <!-- Single ListItem -->
     
    <!-- Product Name -->
    <TextView android:id="@+id/product_name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10dip"
        android:textSize="16dip"
        android:textStyle="bold"/>    
 
</LinearLayout>

3.MainActivity.java的代码实现

public class MainActivity extends Activity {
     
    // List view
    private ListView lv;
     
    // Listview Adapter
    ArrayAdapter<String> adapter;
     
    // Search EditText
    EditText inputSearch;
     
     
    // ArrayList for Listview
    ArrayList<HashMap<String, String>> productList;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         
        // Listview Data
        String products[] = {"Dell Inspiron", "HTC One X", "HTC Wildfire S", "HTC Sense", "HTC Sensation XE",
                                "iPhone 4S", "Samsung Galaxy Note 800",
                                "Samsung Galaxy S3", "MacBook Air", "Mac Mini", "MacBook Pro"};
         
        lv = (ListView) findViewById(R.id.list_view);
        inputSearch = (EditText) findViewById(R.id.inputSearch);
         
        // Adding items to listview
        adapter = new ArrayAdapter<String>(this, R.layout.list_item, R.id.product_name, products);
        lv.setAdapter(adapter);       
         
    }
     
}

运行之后的效果图

image

4.给搜索框添加事件

MainActivity.java
inputSearch.addTextChangedListener(new TextWatcher() {
     
    @Override
    public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
        // When user changed the Text
        MainActivity.this.adapter.getFilter().filter(cs);   
    }
     
    @Override
    public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
            int arg3) {
        // TODO Auto-generated method stub
         
    }
     
    @Override
    public void afterTextChanged(Editable arg0) {
        // TODO Auto-generated method stub                          
    }
});

这样所有的事情就做完了

总结

这个例子只是实现了简单的对当前显示的列表进行过滤,而且是实时的根据输入字母进行过滤,那么很多时候真正的应用是需去后台进行搜索,由于网速和查询速度的限制,对输入进行实时搜索可能不太现实,那么可以加一个搜索按钮,用户输入完毕之后再点击搜索按钮,然后再从后台获取数据进行刷新列表。

Android中的XML动画

在App中加入一些动画效果会让界面更加丰富多彩,动画可以通过XML定义来实现,当然也可以通过代码来实现, 动画效果一般使用在界面切换之间的效果,或者加载一些界面的效果,比如淡入淡出之类的。

通过XML创建一个动画是很简单的,只需要简单的xml和简单一些代码就可以完成

1.创建XML定义文件,位于res->anim->animation.xml文件中,如果不存在文件夹anim可以创建一个

image

2.fade_in.xml淡入效果

fade_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true" >
 
    <alpha
        android:duration="1000"
        android:fromAlpha="0.0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toAlpha="1.0" />
 
</set>

3.加载动画

FadeInActivity.java
public class FadeInActivity extends Activity{
 
    TextView txtMessage;
 
    // Animation
    Animation animFadein;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fadein);
 
        txtMessage = (TextView) findViewById(R.id.txtMessage);
 
        // load the animation
        animFadein = AnimationUtils.loadAnimation(getApplicationContext(),
                R.anim.fade_in);        
    }
}

4.可以监听动画的事件

public class FadeInActivity extends Activity implements AnimationListener {
.
.
.
// set animation listener
animFadein.setAnimationListener(this);
.
.
.
// animation listeners
    @Override
    public void onAnimationEnd(Animation animation) {
        // Take any action after completing the animation
        // check for fade in animation
        if (animation == animFadein) {
            Toast.makeText(getApplicationContext(), "Animation Stopped",
                    Toast.LENGTH_SHORT).show();
        }
 
    }
 
    @Override
    public void onAnimationRepeat(Animation animation) {
        // Animation is repeating
    }
 
    @Override
    public void onAnimationStart(Animation animation) {
        // Animation started
    }

5.最后在某个界面元素上启动动画

// start the animation
txtMessage.startAnimation(animFadein);

以下是完整的代码

public class FadeInActivity extends Activity implements AnimationListener {
 
    TextView txtMessage;
    Button btnStart;
 
    // Animation
    Animation animFadein;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fadein);
 
        txtMessage = (TextView) findViewById(R.id.txtMessage);
        btnStart = (Button) findViewById(R.id.btnStart);
 
        // load the animation
        animFadein = AnimationUtils.loadAnimation(getApplicationContext(),
                R.anim.fade_in);
         
        // set animation listener
        animFadein.setAnimationListener(this);
 
        // button click event
        btnStart.setOnClickListener(new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                txtMessage.setVisibility(View.VISIBLE);
                 
                // start the animation
                txtMessage.startAnimation(animFadein);
            }
        });
 
    }
 
    @Override
    public void onAnimationEnd(Animation animation) {
        // Take any action after completing the animation
 
        // check for fade in animation
        if (animation == animFadein) {
            Toast.makeText(getApplicationContext(), "Animation Stopped",
                    Toast.LENGTH_SHORT).show();
        }
 
    }
 
    @Override
    public void onAnimationRepeat(Animation animation) {
        // TODO Auto-generated method stub
 
    }
 
    @Override
    public void onAnimationStart(Animation animation) {
        // TODO Auto-generated method stub
 
    }
 
}

以下是常用的一些动画

1. Fade In
2. Fade Out
3. Cross Fading
4. Blink
5. Zoom In
6. Zoom Out
7. Rotate
8. Move
9. Slide Up
10. Slide Down
11. Bounce
12. Sequential Animation
13. Together Animation

fade_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true" >
 
    <alpha
        android:duration="1000"
        android:fromAlpha="0.0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toAlpha="1.0" />
 
</set>
fade_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true" >
 
    <alpha
        android:duration="1000"
        android:fromAlpha="1.0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toAlpha="0.0" />
 
</set>
//Cross Fading
TextView txtView1, txtView2;
Animation animFadeIn, animFadeOut;
.
.
// load animations
animFadeIn = AnimationUtils.loadAnimation(getApplicationContext(),
                R.anim.fade_in);
animFadeOut = AnimationUtils.loadAnimation(getApplicationContext(),
                R.anim.fade_out);
.
.
// set animation listeners
animFadeIn.setAnimationListener(this);
animFadeOut.setAnimationListener(this);
 
.
.
// Make fade in elements Visible first
txtMessage2.setVisibility(View.VISIBLE);
 
// start fade in animation
txtMessage2.startAnimation(animFadeIn);
                 
// start fade out animation
txtMessage1.startAnimation(animFadeOut);
blink.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:duration="600"
        android:repeatMode="reverse"
        android:repeatCount="infinite"/>
</set>
zoom_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true" >
 
    <scale
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:fromXScale="1"
        android:fromYScale="1"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="3"
        android:toYScale="3" >
    </scale>
 
</set>
zoom_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true" >
 
    <scale
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="0.5"
        android:toYScale="0.5" >
    </scale>
 
</set>
rotate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="600"
        android:repeatMode="restart"
        android:repeatCount="infinite"
        android:interpolator="@android:anim/cycle_interpolator"/>
 
</set>
move.xml
<?xml version="1.0" encoding="utf-8"?>
<set
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator"
    android:fillAfter="true">
 
   <translate
        android:fromXDelta="0%p"
        android:toXDelta="75%p"
        android:duration="800" />
</set>
slide_up.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true" >
 
    <scale
        android:duration="500"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toXScale="1.0"
        android:toYScale="0.0" />
 
</set>
slide_down.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
 
    <scale
        android:duration="500"
        android:fromXScale="1.0"
        android:fromYScale="0.0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toXScale="1.0"
        android:toYScale="1.0" />
 
</set>
bounce.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:interpolator="@android:anim/bounce_interpolator">
 
    <scale
        android:duration="500"
        android:fromXScale="1.0"
        android:fromYScale="0.0"
        android:toXScale="1.0"
        android:toYScale="1.0" />
 
</set>
sequential.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:interpolator="@android:anim/linear_interpolator" >
 
    <!-- Use startOffset to give delay between animations -->
 
 
    <!-- Move -->
    <translate
        android:duration="800"
        android:fillAfter="true"
        android:fromXDelta="0%p"
        android:startOffset="300"
        android:toXDelta="75%p" />
    <translate
        android:duration="800"
        android:fillAfter="true"
        android:fromYDelta="0%p"
        android:startOffset="1100"
        android:toYDelta="70%p" />
    <translate
        android:duration="800"
        android:fillAfter="true"
        android:fromXDelta="0%p"
        android:startOffset="1900"
        android:toXDelta="-75%p" />
    <translate
        android:duration="800"
        android:fillAfter="true"
        android:fromYDelta="0%p"
        android:startOffset="2700"
        android:toYDelta="-70%p" />
 
    <!-- Rotate 360 degrees -->
    <rotate
        android:duration="1000"
        android:fromDegrees="0"
        android:interpolator="@android:anim/cycle_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:startOffset="3800"
        android:repeatCount="infinite"
        android:repeatMode="restart"
        android:toDegrees="360" />
 
</set>
together.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:interpolator="@android:anim/linear_interpolator" >
 
    <scale
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="4000"
        android:fromXScale="1"
        android:fromYScale="1"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="4"
        android:toYScale="4" >
    </scale>
 
    <!-- Rotate 180 degrees -->
    <rotate
        android:duration="500"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="infinite"
        android:repeatMode="restart"
        android:toDegrees="360" />
 
</set>

总结

在App中适当的加入一下动画确实是可以达到画龙点睛的效果,但是胡乱的使用也是很容易搞的乱七八糟,追根揭底还是设计层面上的问题,好的创意往往都是可遇不可求。

Android Expandable List View

可展开的列表主要用于分组或者分类的列表,在列表中一组就是一行,展开一行可以显示这一组的子列表,也可以收起来节省空间,每一行即每一组可以看做两部分组成,就是头部和子列表部分,点击头部即可展开或者收起,这种组件的应用也很普遍。

1.创建主布局文件

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="#f4f4f4" >
 
            <ExpandableListView
                android:id="@+id/lvExp"
                android:layout_height="match_parent"
                android:layout_width="match_parent"/>   
 
</LinearLayout>

2.创建组的布局文件即组的头部

list_group.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp"
    android:background="#000000">
 
 
    <TextView
        android:id="@+id/lblListHeader"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
        android:textSize="17dp"
        android:textColor="#f9f93d" />
 
</LinearLayout>

3.创建子列表行布局文件

list_item.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="55dip"
    android:orientation="vertical" >
 
    <TextView
        android:id="@+id/lblListItem"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="17dip"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft" />
 
</LinearLayout>

4.创建Adapter继承自BaseExpandableAdapter

public class ExpandableListAdapter extends BaseExpandableListAdapter {
 
    private Context _context;
    private List<String> _listDataHeader; // header titles
    // child data in format of header title, child title
    private HashMap<String, List<String>> _listDataChild;
 
    public ExpandableListAdapter(Context context, List<String> listDataHeader,
            HashMap<String, List<String>> listChildData) {
        this._context = context;
        this._listDataHeader = listDataHeader;
        this._listDataChild = listChildData;
    }
 
    @Override
    public Object getChild(int groupPosition, int childPosititon) {
        return this._listDataChild.get(this._listDataHeader.get(groupPosition))
                .get(childPosititon);
    }
 
    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }
 
    @Override
    public View getChildView(int groupPosition, final int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {
 
        final String childText = (String) getChild(groupPosition, childPosition);
 
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this._context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.list_item, null);
        }
 
        TextView txtListChild = (TextView) convertView
                .findViewById(R.id.lblListItem);
 
        txtListChild.setText(childText);
        return convertView;
    }
 
    @Override
    public int getChildrenCount(int groupPosition) {
        return this._listDataChild.get(this._listDataHeader.get(groupPosition))
                .size();
    }
 
    @Override
    public Object getGroup(int groupPosition) {
        return this._listDataHeader.get(groupPosition);
    }
 
    @Override
    public int getGroupCount() {
        return this._listDataHeader.size();
    }
 
    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }
 
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
            View convertView, ViewGroup parent) {
        String headerTitle = (String) getGroup(groupPosition);
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this._context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.list_group, null);
        }
 
        TextView lblListHeader = (TextView) convertView
                .findViewById(R.id.lblListHeader);
        lblListHeader.setTypeface(null, Typeface.BOLD);
        lblListHeader.setText(headerTitle);
 
        return convertView;
    }
 
    @Override
    public boolean hasStableIds() {
        return false;
    }
 
    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }
}

5.在Activity中使用

public class MainActivity extends Activity {
 
    ExpandableListAdapter listAdapter;
    ExpandableListView expListView;
    List<String> listDataHeader;
    HashMap<String, List<String>> listDataChild;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // get the listview
        expListView = (ExpandableListView) findViewById(R.id.lvExp);
 
        // preparing list data
        prepareListData();
 
        listAdapter = new ExpandableListAdapter(this, listDataHeader, listDataChild);
 
        // setting list adapter
        expListView.setAdapter(listAdapter);
    }
 
    /*
     * Preparing the list data
     */
    private void prepareListData() {
        listDataHeader = new ArrayList<String>();
        listDataChild = new HashMap<String, List<String>>();
 
        // Adding child data
        listDataHeader.add("Top 250");
        listDataHeader.add("Now Showing");
        listDataHeader.add("Coming Soon..");
 
        // Adding child data
        List<String> top250 = new ArrayList<String>();
        top250.add("The Shawshank Redemption");
        top250.add("The Godfather");
        top250.add("The Godfather: Part II");
        top250.add("Pulp Fiction");
        top250.add("The Good, the Bad and the Ugly");
        top250.add("The Dark Knight");
        top250.add("12 Angry Men");
 
        List<String> nowShowing = new ArrayList<String>();
        nowShowing.add("The Conjuring");
        nowShowing.add("Despicable Me 2");
        nowShowing.add("Turbo");
        nowShowing.add("Grown Ups 2");
        nowShowing.add("Red 2");
        nowShowing.add("The Wolverine");
 
        List<String> comingSoon = new ArrayList<String>();
        comingSoon.add("2 Guns");
        comingSoon.add("The Smurfs 2");
        comingSoon.add("The Spectacular Now");
        comingSoon.add("The Canyons");
        comingSoon.add("Europa Report");
 
        listDataChild.put(listDataHeader.get(0), top250); // Header, Child data
        listDataChild.put(listDataHeader.get(1), nowShowing);
        listDataChild.put(listDataHeader.get(2), comingSoon);
    }
}

代码就这么多了,运行之后就看到效果了,如下图所示

image

监听列表展开和收起的事件和子节点的点击事件

// Listview Group expanded listener
expListView.setOnGroupExpandListener(new OnGroupExpandListener() {
 
    @Override
    public void onGroupExpand(int groupPosition) {
        Toast.makeText(getApplicationContext(),
                listDataHeader.get(groupPosition) + " Expanded",
                Toast.LENGTH_SHORT).show();
    }
});
// Listview Group collasped listener
expListView.setOnGroupCollapseListener(new OnGroupCollapseListener() {
 
    @Override
    public void onGroupCollapse(int groupPosition) {
        Toast.makeText(getApplicationContext(),
                listDataHeader.get(groupPosition) + " Collapsed",
                Toast.LENGTH_SHORT).show();
 
    }
});
// Listview on child click listener
expListView.setOnChildClickListener(new OnChildClickListener() {

	@Override
	public boolean onChildClick(ExpandableListView parent, View v,
			int groupPosition, int childPosition, long id) {
		Toast.makeText(
				getApplicationContext(),
				listDataHeader.get(groupPosition)
						+ " : "
						+ listDataChild.get(
								listDataHeader.get(groupPosition)).get(
								childPosition), Toast.LENGTH_SHORT)
				.show();
		return false;
	}
});

Android中使用ListView

数据列表是使用很普遍的组件,Android中的列表用法很灵活,行的布局是可以自定义的,因此可以显示任何形式的数据列表,本示例显示一个数据列表并且响应行的点击事件。

1.创建一个静态的数据xml用于列表数据展示

list_data.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="adobe_products">
        <item>Adobe After Effects</item>
        <item>Adobe Bridge</item>
        <item>Adobe Dreamweaver</item>
        <item>Adobe Edge</item>
        <item>Adobe Fireworks</item>
        <item>Adobe Flash</item>
        <item>Adobe Photoshop</item>
        <item>Adobe Premiere</item>
        <item>Adobe Reader</item>
        <item>Adobe Illustrator</item>
    </string-array>
</resources>

2.创建行的布局list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<!--  Single List Item Design -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/label"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:padding="10dip"
        android:textSize="16dip"
        android:textStyle="bold" >
</TextView>

3.创建acitvity继承自ListActivity

public class AndroidListViewActivity extends ListActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         
        // storing string resources into Array
        String[] adobe_products = getResources().getStringArray(R.array.adobe_products);
         
        // Binding resources Array to ListAdapter
        this.setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, R.id.label, adobe_products));
         
    }
}

这样一个listview就大功告成了,运行起来就看到效果了

 image

下面我们来添加行点击事件,并且打开另外一个页面

4.给listview添加事件

public class AndroidListViewActivity extends ListActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         
        // storing string resources into Array
        String[] adobe_products = getResources().getStringArray(R.array.adobe_products);
         
        // Binding resources Array to ListAdapter
        this.setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, R.id.label, adobe_products));
         
        ListView lv = getListView();
 
        // listening to single list item on click
        lv.setOnItemClickListener(new OnItemClickListener() {
          public void onItemClick(AdapterView<?> parent, View view,
              int position, long id) {
               
              // selected item 
              String product = ((TextView) view).getText().toString();
               
              // Launching new Activity on selecting single List Item
              Intent i = new Intent(getApplicationContext(), SingleListItem.class);
              // sending data to new activity
              i.putExtra("product", product);
              startActivity(i);
             
          }
        });
    }
}

5.创建打开页面的布局文件

single_list_item_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <TextView android:id="@+id/product_label"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textSize="25dip"
            android:textStyle="bold"
            android:padding="10dip"
            android:textColor="#ffffff"/>    
</LinearLayout>

6.创建打开页面的acitvity

SingleListItem.java
package com.androidhive.androidlistview;
 
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
 
public class SingleListItem extends Activity{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.single_list_item_view);
         
        TextView txtProduct = (TextView) findViewById(R.id.product_label);
         
        Intent i = getIntent();
        // getting attached intent data
        String product = i.getStringExtra("product");
        // displaying selected product name
        txtProduct.setText(product);
         
    }
}

7.别忘了在AnroidManifest.xml中加入activity的声明

<application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".AndroidListViewActivity"
                  android:label="Android List View">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
         
        <activity android:name=".SingleListItem"
                    android:label="Single Item Selected"></activity>
    </application>

8.最后运行的时候,列表点击就有效果了,如下所示

image

总结

ListView的用途非常广泛,可以放置在任何地方显示数据,小的方面就是一个菜单列表,或者简单的数据展示,比较大的方面行的布局比较复杂,比如显示新闻列表,行的布局比较复杂有图片和文字或者一些其他一些图标等等,可以对ListView进行扩展得到更复杂的应用效果,不如树形结构的列表等等。


	

Android录制并播放视频

录制视频已经是一些社交软件的必备功能,使用内置的录制视频软件其实跟拍个照片一样简单,如果说要自定义录制视频的界面,需要做的事情稍微复杂一些,只有一些比较专业的软件才会做,那么我们来看看内置的视频如何使用。

在布局文件中使用VideoView来播放视频

<!-- To preview video recorded -->
<VideoView
	android:id="@+id/videoPreview"
	android:layout_width="wrap_content"
	android:layout_height="400dp"
/>

调用录制视频的方法

/**
判断SD卡是否就绪
*/
private boolean isSDCardReady(){
	String state = Environment.getExternalStorageState();  
    if (state.equals(Environment.MEDIA_MOUNTED)) {
		return true;
	else {  
        Toast.makeText(getApplicationContext(), "请确认已经插入SD卡", Toast.LENGTH_LONG).show();  
    }
	return false;
}

/**
* 判断设备是否支持相机功能
* */
private boolean isDeviceSupportCamera() {
	if (getApplicationContext().getPackageManager().hasSystemFeature(
			PackageManager.FEATURE_CAMERA)) {
		// this device has a camera
		return true;
	} else {
		// no camera on this device
		Toast.makeText(getApplicationContext(), "设备不支持相机功能", Toast.LENGTH_LONG).show();  
		return false;
	}
}
/**
视频存放路径
*/
private String videoFile;
/*
 * 录制视频
 */
private void recordVideo() {
	if(!(isSDCardReady() && isDeviceSupportCamera()))return;
        
	//存放路径
	String out_file_path = Environment.getExternalStorageDirectory() + "/myapp/";  
	File dir = new File(out_file_path);  
	if (!dir.exists()) {  
		dir.mkdirs();  
	}
	videoFile = out_file_path + System.currentTimeMillis() + ".mp4";  
 
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
  
    // set video quality
    // 1- for high quality video
    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
 
    intent.putExtra(MediaStore.EXTRA_OUTPUT, new Uri(videoFile));
 
    // start the video capture Intent
    startActivityForResult(intent, CAMERA_CAPTURE_VIDEO_REQUEST_CODE);
}

/**
播放视频
*/
private void previewVideo() {
	try {
		videoPreview = (VideoView) findViewById(R.id.videoPreview);
		videoPreview.setVisibility(View.VISIBLE);
		videoPreview.setVideoPath(new Uri(videoFile));
		// start playing
		videoPreview.start();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

 /**
 * 视频录制完成回调方法
 * */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	if (requestCode == CAMERA_CAPTURE_VIDEO_REQUEST_CODE) {
		if (resultCode == RESULT_OK) {
			// video successfully recorded
			// preview the recorded video
			previewVideo();
		}
	}
}

视频录制调用的过程和拍照的过程完全一样,只是给的参数稍微不同,其中视频的画面质量参数可以根据实际情况而定,清晰度越高生成的视频文件越大,当然最大的清晰度最终由摄像头而定了。

总结

视频录制除了社交工具可以使用,在很多应用场合也很有意义,比如实时在线教育,设备管理的现场拍摄,还有一些巡查等等的应用里面都可以加入视频录制的功能。

Android中显示提示框

显示提示框很容易,一般提示框中可以显示的按钮有一个确定按钮,一个取消按钮,有时候可能需要三个按钮比如是、否和取消。

最简单的是至少显示一个按钮,然后给按钮添加事件以便监听用户的行为

显示一个提示框中主要的内容包括标题,内容和按钮

一个按钮的示例

AlertDialog alertDialog = new AlertDialog.Builder(
                        AlertDialogActivity.this).create();
// Setting Dialog Title
alertDialog.setTitle("Alert Dialog");

// Setting Dialog Message
alertDialog.setMessage("Welcome to fullstacks.cn");

// Setting Icon to Dialog
alertDialog.setIcon(R.drawable.tick);

// Setting OK Button
alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
		public void onClick(DialogInterface dialog, int which) {
		// Write your code here to execute after dialog closed
		Toast.makeText(getApplicationContext(), "You clicked on OK", Toast.LENGTH_SHORT).show();
		}
});

// Showing Alert Message
alertDialog.show();

两个按钮的示例

AlertDialog.Builder alertDialog = new AlertDialog.Builder(AlertDialogActivity.this);
 
// Setting Dialog Title
alertDialog.setTitle("Confirm Delete...");

// Setting Dialog Message
alertDialog.setMessage("Are you sure you want delete this?");

// Setting Icon to Dialog
alertDialog.setIcon(R.drawable.delete);

// Setting Positive "Yes" Button
alertDialog.setPositiveButton("YES", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog,int which) {

	// Write your code here to invoke YES event
	Toast.makeText(getApplicationContext(), "You clicked on YES", Toast.LENGTH_SHORT).show();
	}
});

运行的效果图

image

三个按钮的示例

AlertDialog.Builder alertDialog = new AlertDialog.Builder(AlertDialogActivity.this);
 
// Setting Dialog Title
alertDialog.setTitle("Save File...");

// Setting Dialog Message
alertDialog.setMessage("Do you want to save this file?");

// Setting Icon to Dialog
alertDialog.setIcon(R.drawable.save);

// Setting Positive "Yes" Button
alertDialog.setPositiveButton("YES", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {
	// User pressed YES button. Write Logic Here
	Toast.makeText(getApplicationContext(), "You clicked on YES",
						Toast.LENGTH_SHORT).show();
	}
});

// Setting Negative "NO" Button
alertDialog.setNegativeButton("NO", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {
	// User pressed No button. Write Logic Here
	Toast.makeText(getApplicationContext(), "You clicked on NO", Toast.LENGTH_SHORT).show();
	}
});

// Setting Netural "Cancel" Button
alertDialog.setNeutralButton("Cancel", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {
	// User pressed Cancel button. Write Logic Here
	Toast.makeText(getApplicationContext(), "You clicked on Cancel",
						Toast.LENGTH_SHORT).show();
	}
});

// Showing Alert Message
alertDialog.show();

运行之后的效果

image

总结

系统默认的提示框是简单的显示文本信息再加上一些图标,已提示用户系统中正要做的一些重要事件,由用户做最终决定是否执行,这种提示框是比较简单,如果需要复杂的提示框,可以采用对话框的模式,可以通过自定页面布局达到复杂的业务要求。

ANDROID中获取照片和拍照

拍照的功能一般的应用都不会自己去实现,那也算是比较专业的领域,只有一些做系统UI的厂商才会涉及这些领域,尝试去完善或者定制与硬件相关的优化,那么对一般的开发来说,系统中提供了通用的组件去获取图片或者拍照。

不管是获取照片还是拍照其实都是启动第三方应用,然后获取返回值,那么需要获取返回值的的时候启动方法不一样,下面来看如何来获取相片

/**
启动获取相片的activity
*/
protected void getImageFromAlbum() {  
   Intent intent = new Intent(Intent.ACTION_PICK);  
   intent.setType("image/*");//相片类型  
   startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);  
}
/**
打开的activity返回时调用这个方法
*/
@Override  
protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
	if (requestCode == REQUEST_CODE_PICK_IMAGE) {             
		Uri uri = data.getData();  
		//uri 为图片的路径 
		//...这处理获取到的图片
	} 
}

下面来看看如何启动拍照并获取到相片

/**
拍照相片的文件路径
*/
private String captureImgPath = null;  
/**
启动拍照的 activity
*/
protected void getImageFromCamera() {  
	String state = Environment.getExternalStorageState();  
	if (state.equals(Environment.MEDIA_MOUNTED)) {  
		Intent cameraIntent = new Intent("android.media.action.IMAGE_CAPTURE");  
		//存放相片的路径
		String out_file_path = Environment.getExternalStorageDirectory() + "/myapp/";  
		File dir = new File(out_file_path);  
		if (!dir.exists()) {  
			dir.mkdirs();  
		}
		captureImgPath = out_file_path + System.currentTimeMillis() + ".jpg";  
		cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(captureImgPath)));  
		cameraIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);  
		startActivityForResult(cameraIntent, Constant.REQUEST_CODE_CAPTURE_CAMEIA);  
	}  
	else {  
		Toast.makeText(getApplicationContext(), "请确认已经插入SD卡", Toast.LENGTH_LONG).show();  
	}  
}
/**
打开的activity返回时调用这个方法
/*
@Override  
protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
	if (requestCode == REQUEST_CODE_CAPTURE_CAMEIA) {             
		Uri uri = data.getData();  
		// 这个uri为空
		Bitmap  photo = (Bitmap) bundle.get("data"); //get bitmap 
		//这样获取的 photo是压缩过的
		
		//获取原始相片就直接使用路径获取
		File photo_file = new File(captureImgPath);
	} 
}

总结

单纯的读取相片较为简单,可以理解文打开个对话框然后选择一个图片,对话框的结果就是返回图片的路径,那么打开照相的过程也类似,只不过需要传入一些参数,比如相片保存的路径,照相的参数比如说相片的质量啊等等,这些参数用于控制照相的一些特性,照相完成之后可以直接从给定的相片路径取到相片了。

ANDROID开机启动

当操作系统启动完毕之后会广播一个消息叫ACTION_BOOT_COMPLETED,应用程序可以通过实现BroadcastReceiver接口来监听这个消息,接到消息之后就可以启动程序,一般启动一个后台运行的Service,当然也可以直接启动某个activity.

我们来写个简单的service,以便在接收到系统启动完成的消息之后来启动这个后台执行的service

public class BootStartService extends IntentService {

    public UpdateService(){
        this("app start service");
    }
    public UpdateService(String name) {
        super(name);
    }
 
    @Override
    protected void onHandleIntent(Intent workIntent) {
        //启动
	String arg = workIntent.getStringExtra("arg");
        Log.d("BootStartService", "BootStartService start");
	//...做一些默默无闻的事情
    }
}

实现接口BroadcastReceiver来监听消息

public class BootStartReceiver extends BroadcastReceiver {
    static final String action_boot="android.intent.action.BOOT_COMPLETED";
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(action_boot)){
            Intent mServiceIntent = new Intent(context, BootStartService.class);
	    mServiceIntent.putExtra("arg", "app start args");
	    context.startService(mServiceIntent);
        }
    }
}

那么代码就已经完成了,记得在AndroidManifest.xml中加入下面的声明

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
<receiver android:name="cn.fullstacks.blogapp.BootStartReceiver">
	<intent-filter>
	<action android:name="android.intent.action.BOOT_COMPLETED" />
	<category android:name="android.intent.category.HOME" />
	</intent-filter>
</receiver>
<service android:name="cn.fullstacks.blogapp.BootStartService" android:exported="false"/>

重启之后就会看到你要的效果了

总结

开机启动一般是会启动一个后台的程序而不会启动个界面,这样就太霸道了,在系统管理或者一些安全软件里面会有控制开机启动的地方,那么那里面就会看到你的开机启动项了,如果被禁止掉了,你的这个启动消息就不会受到了,这也就是系统级别的消息过滤,如果说必须要开机启动而不想让其他软件阻止的话,那就是把程序写成安全软件了,这就是流氓的开端。

在一些即时通的软件里或者一些需要接受消息的软件,大部分都会做成开机启动,然后启动一个后台服务监听消息,当服务器推送消息过来之后在通知栏里面显示一条消息,这是最常用的场景了。

ANDROID中下载并打开文档

应用系统中往往会需要打开world文档进行阅读,或者pdf文档等等,而阅读文档这个过程那是非常复杂,一般的人是很难实现的,这时候就需要引入第三方工具进行阅读,比如使用wps打开各种office文档,使用pdf reader打开pdf文档,或者使用一些付费的阅读工具打开,不管是采用哪一种方式,基本上需要执行几个基本的步骤:

1.判断系统中是否已经安装了阅读工具

2.如果没有安装则下载安装

3.下载要打开的文档

4.调用阅读工具打开文档

下面使用wps来做一个示例

1.判断系统中是否已经安装了wps

    /**
     * 查找安装的wps包
     * @param context
     * @return
     */
    public static List getWPSInstalledPackage(Context context)
    {
        if(wpspackage != null && wpspackage.size() >0){
            return wpspackage;
        }
        ArrayList arraylist = new ArrayList();
        ArrayList arraylist1 = (ArrayList)context.getPackageManager().getInstalledPackages(0);
        int i = 0;
        do
        {
            if (i >= arraylist1.size())
            {
                wpspackage = arraylist;
                return arraylist;
            }
            PackageInfo packageinfo = (PackageInfo)arraylist1.get(i);
            if (packageinfo.packageName.matches("cn.wps.moffice.*"))
            {
                arraylist.add(packageinfo.packageName);
            }
            i++;
        } while (true);
    }

这里是通过查找系统里面所有的安装包,判断包名是否符合wps的包名,得到一个列表从而判断是否已经安装了。

2.下载安装包

if(getWPSInstalledPackage(this).size() == 0){
	String url = "localhost:8080/wps.apk";//安装包的地址
	Intent intent = new Intent(context,UpdateService.class);
	intent.putExtra("url",url);
	context.startService(intent);
}

如何下载并安装请参阅前篇文章《ANDROID Service下载APK并安装》

3.下载并打开文档

这里使用异步方法下载文档,当然也可以使用service方式下载,可参阅文章《ANDROID异步调用》

/**
通用打开下载文档任务
*/
public class DownloadDocTask extends AsyncTask<Void, Void, Object> {
    private Context context;
    private String fileUrl;
    private String fileName;
    private String localFile;
    private View progress;
    /**
     * 传入需要的参数
     * @param contextf
     * @param fileUrlf 稳当地址
     * @param fileNamef 文件名
     * @param progressf 进度条
     */
    public void setContext(Context contextf,String fileUrlf,String fileNamef, View progressf) {
        context = contextf;
        fileUrl = fileUrlf;
        fileName = fileNamef;
        progress = progressf;
        progress.setVisibility(View.VISIBLE);
    }
    @Override
    protected Object doInBackground(Void... params) {
        doDownload();
        return null;
    }
    /**
     * 下载完成之后打开文档,并取消进度条
     * @param result
     */
    @Override
    protected void onPostExecute(Object result) {
        if(progress!= null)
            progress.setVisibility(View.GONE);
        if(localFile != null)
        openFile(context,localFile);
    }
    /**
     * 下载
     */
    private void doDownload() {
        try {
            //String PATH = "/mnt/sdcard/Download/";
            File file = new File(Environment.getExternalStorageDirectory() + "/myapp/");
            file.mkdirs();
            File outputFile = new File(file, fileName);
            if(outputFile.exists()){
                outputFile.delete();
            }
            String cookies = "sessionid=abc";//这里可以传入cookie以保证和登陆连接一致
            URL url = new URL(fileUrl);
            HttpURLConnection c = (HttpURLConnection) url.openConnection();
            c.setRequestMethod("GET");
            c.setDoOutput(true);
            c.addRequestProperty("Cookie",cookies);
            c.connect();

            FileOutputStream fos = new FileOutputStream(outputFile);
            InputStream is = c.getInputStream();
            byte[] buffer = new byte[c.getContentLength()];
            int len1 = 0;
            while ((len1 = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len1);
            }
            fos.close();
            is.close();
            localFile = outputFile.getPath();
        } catch (Exception e) {
            Log.e("DownloadDocTask", "DownloadDocTask error! " + e.getMessage(), e);
        }
    }
	 /**
     * 打开office文档
     * @param context
     * @param path
     * @return
     */
    public static boolean openFile(Context context, String path)
    {
        String packageName ="cn.wps.moffice_i18n";// "cn.wps.moffice_eng";
        String className = "cn.wps.moffice.documentmanager.PreStartActivity2";
        Intent intent = new Intent();
        Bundle bundle = new Bundle();
        //bundle.putString("OpenMode", "ReadMode");
        bundle.putString("OpenMode", "Normal");
        bundle.putBoolean("SendCloseBroad", false);
        bundle.putString("ThirdPackage", "");
        bundle.putBoolean("ClearBuffer", true);
        bundle.putBoolean("ClearTrace", true);
        bundle.putFloat("ViewProgress", 0.0F);
        bundle.putFloat("ViewScale", 1.0F);
        bundle.putInt("ViewScrollX", 0);
        bundle.putInt("ViewScrollY", 0);
        bundle.putBoolean("AutoJump", false);
        //bundle.putBoolean(CLEAR_FILE, true);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(android.content.Intent.ACTION_VIEW);

        intent.setClassName(packageName, className);

        File file = new File(path);
        if (file == null || !file.exists())
        {
            Toast.makeText(context,"文件不存在",Toast.LENGTH_LONG).show();
            return false;
        }

        Uri uri = Uri.fromFile(file);
        intent.setData(uri);
        intent.putExtras(bundle);

        try
        {
            context.startActivity(intent);
        }
        catch (ActivityNotFoundException e)
        {
            Log.e("Utils",e.getMessage(),e);
            Toast.makeText(context, "WPS打开文档是发生异常", Toast.LENGTH_LONG).show();
            return false;
        }

        return true;
    }
}

下载文档之前一般需要启动进度条,完成之后隐藏进度条,这里使用的是传入一个进度条的view应用,显示一个无限时的进度,当然也可以使用一个确定的进度,参考前面的下载apk的文章,里面有具体的根据下载的进度显示进度条。

下载完成之后记得一定要在UI线程里面去打开文档,打开文档wps官方有文档参考提供很多参数进行对文档的控制,这里就简单的打开文档,不再有后续的操作了,也就是我们的应用只需要打开文档,其他的事情就交给wps了。

4.最后的代码调用

private Handler handler = new Handler();
/**
 * 打开文档
 * @param url
 */
public void openDoc(String url,String fileName){

	final String myurl = url;
	final String name = fileName;

	//2.下载并打开
	//在ui thread运行以便打开进度条
	handler.post(new Runnable() {
		@Override
		public void run() {

			//1.判断是否能够打开
			if(getWPSInstalledPackage(WebViewActivity.this).size() == 0){
				downloadWPS(WebViewActivity.this);//当前的acitvity实例
				return;
			}

			View v = WebViewActivity.this.findViewById(R.id.webview_progress);

			DownloadDocTask task = new DownloadDocTask();
			task.setContext(WebViewActivity.this, myurl, name, v);
			task.execute();
		}
	});
}

这里使用Handler避免界面卡顿,那么以上的过程完成之后最终用户的操作可以这样更人性化的一个过程:

1.当用户点击打开文档按钮,判断是否存在wps工具,不存在提示用户是否下载wps进行安装

2.下载并安装wps,这时系统通知栏有下载的进度,下载完成之后,这时候界面转到安装界面,需要用户确认安装之后返回应用界面

3.用户再次点击打开文档按钮

4.打开进度条提示下载文档

5.文档下载完成之后自动打开文档

6.用户阅读文档

7.用户关闭文档返回应用界面

总结

一个打开文档对用户来说操作非常简单,只需要点击一个按钮,但是后台做了很多默默无闻的工作,以上的方法算是较为通用的,可以和webview进行结合,将打开文档的方法暴露给webview里面的页面javascript来调用,这样的话就可以直接在网页上打开文档,具体的javascript和webview的通讯可以参考前面的文章《ANDROID WebView简单使用》

ANDROID Service下载APK并安装

自动判断是否有新的版本然后下载安装是客户端程序的必备功能之一,让用户自己下载新版本的程序那是比较困难的事情,手机上那么多程序谁会一个个去更新呢,只有在用到的时候提示有新的版本需要更新,用户只需要确认一下就可以了。

下面使用Service来实现下载并在通知栏显示进度条,下载完成之后开始安装,当然不一定需要使用service来实现,也可以使用异步方法来下载。

这个下载和安装需要一定的权限包括访问网络,读写文件等,并且service也需要在AndroidManifest.xml中进行声明,如下所示

<!--访问网路-->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

<!-- service声明 -->
<service android:name="cn.fullstacks.blogapp.UpdateService" android:exported="false"/>

创建UpdateService,继承IntentService

public class UpdateService extends IntentService {
    private Context context;

    public UpdateService(){
        this("app update service");
    }
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public UpdateService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(Intent workIntent) {
        // Gets data from the incoming Intent
        String address = workIntent.getStringExtra("url");
        context = getApplicationContext();
        doUpdate(address);
    }


    private void doUpdate(String address) {
        try {

            int id = new Random().nextInt();
            NotificationManager mNotifyManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
            mBuilder.setContentTitle(context.getText(R.string.app_name)+" 更新")
                    .setContentText("下载...")
                    .setSmallIcon(R.drawable.ic_launcher)
                    .setPriority(NotificationCompat.PRIORITY_MAX);

            URL url = new URL(address);
            HttpURLConnection c = (HttpURLConnection) url.openConnection();
            c.setRequestMethod("GET");
            c.setDoOutput(true);
            c.connect();

            //String PATH = "/mnt/sdcard/Download/";
            File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            file.mkdirs();
            File outputFile = new File(file, "myapp.apk");
            if(outputFile.exists()){
                outputFile.delete();
            }
            FileOutputStream fos = new FileOutputStream(outputFile);

            InputStream is = c.getInputStream();
            //int size = is.available();
            int size = c.getContentLength();
            int downsize = 0;

            mBuilder.setTicker(context.getText(R.string.app_name) + " 更新");
            mBuilder.setProgress(size, downsize, false);
            mBuilder.setPriority(NotificationCompat.PRIORITY_MAX);
            mBuilder.setDefaults(NotificationCompat.DEFAULT_SOUND);
            // Displays the progress bar for the first time.
            mNotifyManager.notify(id, mBuilder.build());

            mBuilder.setDefaults(0);

            byte[] buffer = new byte[1024];
            int len1 = 0;
            int index = 0;
            while ((len1 = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len1);
                downsize += len1;
                index ++;
                if(index % 15 == 0) {//进度更新不能太过频发,界面会卡
                    mBuilder.setProgress(size, downsize, false).setContentText("下载..."+downsize+"/"+size);
                    // Displays the progress bar for the first time.
                    mNotifyManager.notify(id, mBuilder.build());
                }
            }

            fos.close();
            is.close();

            // When the loop is finished, updates the notification
            mBuilder.setContentText("下载结束").setProgress(0, 0, false);         // Removes the progress bar
            mNotifyManager.notify(id, mBuilder.build());
            mNotifyManager.cancel(id);

            //启动安装
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
            context.startActivity(intent);


        } catch (Exception e) {
            Log.e("UpdateService", "UpdateService error! " + e.getMessage(), e);
        }
    }

}

在activity里面判断需要更新的时候,就调用这个服务就开始跟新了,调用方法如下

Intent mServiceIntent = new Intent(getApplicationContext(), UpdateService.class);
mServiceIntent.putExtra("url", "http://192.168.1.111:8080/test.apk");
getApplication().startService(mServiceIntent);

总结

这个下载安装不单单是可以用来更新自己的程序,也可以下载其他的一些程序的安装包进行安装,一些自己程序依赖的安装包,在需要的时候进行下载安装,当然这种下载的服务不单单是可以用来下载安装包,很多的资源都是可以下载的,特别是一些图片的加载,通过这种异步多线程进行下载还是很方便的。

ANDROID下滑刷新列表官方实现

在屏幕上用你的拇指按住屏幕下滑刷新列表,这个动作现在基本成了习惯,基本上绝大部分手机的应用都是这么实现的,那么这个过程到底是怎么实现的呢,不了解的话感觉不太好实现,但是现在Android官方已经提实现的组件,用起来非常方便而且简单,简单到你都敢相信这是真的,下面就来看看

如何创建项目什么的这里就不再多说了,可以查阅前面的一些文章

直接贴上SwipeRefreshLayout布局文件,activity_refreshview.xml

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ListView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/listView">
    </ListView>

</android.support.v4.widget.SwipeRefreshLayout>

布局里面放一个listview,那么listview需要一行的布局list_row.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">

    <TextView
        android:id="@+id/title"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:paddingLeft="20dp"
        android:textSize="18dp" />

</LinearLayout>

 

这里只是个实例,一行就显示一个文本,下面开始代码部分,先来定义一行对应的一个实体Movie.java

public class Movie {
    public int id;
    public String title;

    public Movie() {
    }

    public Movie(int id, String title) {
        this.title = title;
        this.id = id;
    }
}

实现listview的adapter ,SwipeListAdapter.java

public class SwipeListAdapter extends BaseAdapter {
    private Activity activity;
    private LayoutInflater inflater;
    private List<Movie> movieList;

    public SwipeListAdapter(Activity activity, List<Movie> movieList) {
        this.activity = activity;
        this.movieList = movieList;
    }

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

    @Override
    public Object getItem(int location) {
        return movieList.get(location);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (inflater == null)
            inflater = (LayoutInflater) activity
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null)
            convertView = inflater.inflate(R.layout.list_row, null);

        TextView title = (TextView) convertView.findViewById(R.id.title);

        title.setText(movieList.get(position).title);

        return convertView;
    }

}

然后再看看activity里面的代码

public class RefreshActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {

    private SwipeRefreshLayout swipeRefreshLayout;
    private ListView listView;
    private SwipeListAdapter adapter;
    private List<Movie> movieList;

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

        listView = (ListView) findViewById(R.id.listView);
        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);

        movieList = new ArrayList<>();
        adapter = new SwipeListAdapter(this, movieList);
        listView.setAdapter(adapter);

        swipeRefreshLayout.setOnRefreshListener(this);

        fetchMovies();
    }

    @Override
    public void onRefresh() {
        fetchMovies();
    }

    /**
     * Fetching movies json by making http call
     */
    private void fetchMovies() {

        // showing refresh animation before making http call
        swipeRefreshLayout.setRefreshing(true);

        //异步请求数据
        Movie movie = new Movie();
        movie.title = "new item";
        movieList.add(movie);

        //这个方法需要在UI线程中执行
        adapter.notifyDataSetChanged();

        //
        swipeRefreshLayout.setRefreshing(false);

    }
}

这里来简单解释一下

1.实现刷新接口OnRefreshListener的事件onRefresh,这就是拇指下滑产生的事件了

2.onRefresh里面做的事情有三部,第一显示加载的图标swipeRefreshLayout.setRefreshing(true);然后就使用异步方式去后台请求数据,这个参阅之前的文章如何异步调用ANDROID异步调用,获取导数据之后添加的列表里面movieList.add(movie); 最后在UI线程里面通知列表有数据要更新了,最后把加载的图标隐藏swipeRefreshLayout.setRefreshing(false);

3.大功告成

总结

这个下滑刷新列表貌似是那个什么特推首创,具体不可考究,不过现在是所有的应用都可以这么做了,所有使用手机的人都享受着这样的创意所带来的优美用户体验,Android发展到现在,这些体验的代码实现已经变得简单而不足为道了。

ANDROID WebView简单使用

现在的很多应用都使用了原生与网页相结合的模式,同时网页也可以通过javascript借助webview与原生部分进行交互,这种模式在现有的很多应用里面都可以体验到,比如微信之流的这样可以大大减少APP的体量又能快速更新内容而不需要让最终用户跟新APP。

我们来做一个简单的全屏幕的Webview,activity_webview.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">

    <WebView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/webView"
        android:layout_margin="0dp"
        android:padding="0dp"
        android:layout_centerInParent="true"
        />


</RelativeLayout>

这里需要实现一个WebViewClient,可以监听到页面加载的事件

public class MyWebViewClient extends WebViewClient {

    private Context context;

    public MyWebViewClient(Context cxt){
        this.context = cxt;
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        view.loadUrl(url);
        return true;
    }
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        //开始进度条
    }

    @Override
    public void onPageFinished(WebView view, String url)
    {
        super.onPageFinished(view,url);
        //结束进度条
    }


}

这里就看这是三个方法,其他的可以根据需要去重载,shouldOverrideUrlLoading 这个方法默认情况下页面上有连接的时候他是不会自动打开链接指向的页面的,重写他然后加载url,这样页面上的链接就又笑了,onPageStarted ,onPageFinished 这两个方法可以控制页面上的进度条,提示用户页面正在加载。

在Activity里面加载页面

@SuppressLint("SetJavaScriptEnabled")
public class WebViewActivity  extends AppCompatActivity {


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


        String url = "http://www.fullstacks.cn");

        webView = (WebView)this.findViewById(R.id.webView);
        //webView.clearCache(false);

        webView.setWebViewClient(new MyWebViewClient(this));

        webView.getSettings().setLoadsImagesAutomatically(true);//自动加载图片
        webView.getSettings().setJavaScriptEnabled(true);//允许使用javascript
        webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
        webView.getSettings().setDefaultTextEncodingName("utf-8");
        webView.addJavascriptInterface(this, "WebViewJavascriptBridge");//添加javascript与原生交互的接口


        webView.loadUrl(url);
    }
    /**
     * 关闭当前页面
     * @param args 返回参数
     */
    @JavascriptInterface
    public void close(String args){
        this.finish();
    }
}

与javascript的交互接口可以独立实现,这里就直接在activity上实现了,类名上需要加个注解@SuppressLint(“SetJavaScriptEnabled”)  , 方法上加上注解@JavascriptInterface 表示javascritp可以访问到该方法了。

那么到这里App里面的代码就大功告成了,运行起来之后就可以看到打开的网页了

页面上调用Webview里面注册的接口webView.addJavascriptInterface(this, “WebViewJavascriptBridge”);

根据注册的这个名字WebViewJavascriptBridge ,可以在加载的页面上访问到如window.WebViewJavascriptBridge ,直接可以在上面调用注解@JavascriptInterface暴露出来的方法window.WebViewJavascriptBridge(“args…”) ,但是有时候注册的过程可能比较慢,页面出来这个接口还没注册完毕所以需要判断一下如下这个方法

function connectWebViewJavascriptBridge(callback) {
	if (window.WebViewJavascriptBridge) {
		callback(WebViewJavascriptBridge);
	} else {
		document.addEventListener('WebViewJavascriptBridgeReady', function() {
			callback(WebViewJavascriptBridge);
		}, false)
	}
}

function closeWebView(){
	connectWebViewJavascriptBridge(function(bridge){
		bridge.close();
	});
}

connectWebViewJavascriptBridge这样我们可以传入一个回调方法进去,当获取到接口的时候就调用这个回调方法,然后再执行需要的代码,这就就不会出现空指针的错误了,后面的方法closeWebView就是简单的调用了关闭webview的接口。

在html上一个按钮上加上事件就可以了

<button onclick="closeWebView()">关闭</button>

总结

通过javascript与webview的通讯,页面就可以辅以更多的能力,比如说打开一个文档,传入url让app下载然后打开,也可以进行分享,调用分享接口,使用支付功能等等。

Groovy基本语法看懂Android Gradle构建脚本

为什么要学习Groovy基本语法呢,因为他是Gradle构建工具使用的脚本语言,为什么要用Gradle这个东西啊,因为他是Android Studio使用的构建工具,为了使用一个工具我们得学习好多东西,这就是大公司引领潮流,要跟上时代的步伐我们得学习,学习,学习,重要的事情要说三遍。

在Android Studio 里面的菜单 Tools->Groovy Console…打开终端

image

输入groovy脚本,点击左边的执行按钮执行输入的代码,下边就会显示输出的结果

image

使用前面的文章《快速学习一门新语言的基本思路》使用的基本法,我们来看看groovy的一些基本概念,这里不需要深入学习,只需要能够了解基本的东西,然后能够看懂别人的代码。

1.对象

class classA{
	public propa = "abc"
	public funca(a,b){
		return a+b;
	}
}
def myobj = new classA()
println myobj.propa
myobj["propa"] = 123
println myobj["propa"]
println myobj.funca(1,2)

以上这段代码可以直接复制到终端执行,这就是简单的对象声明方式,一切都是键值对,理解了这个概念基本上就了解了,这里每一行结束可以用分号结束当然也可以光嘟嘟的,有时候光嘟嘟的还是有点不太习惯
2.变量

def a = 1;
b = 123
println a;
println b

变量可以用def关键字声明当然也可以不用,类似javascript 里面的var ,也可以不用,不用的时候是全局的,用的时候就在作用域里面有效,比如在一个方法里面,到方法外面就无效了
3.数组

mylist = [1,2,3]
alist = new int[]{1,2,3}
println mylist[0]

数组其实跟其他语言没有太大的区别,都可以通过下标去访问,里面有一些遍历的操作符重载,比如mylist << 4 就是往里面加个元素
4.方法体

//常规方法
public funca(a,b){println(a+b)}
funca(1,2);
//传入键值对
public funcb(map){println(map.a)}
funcb a:1
//匿名方法
def funce = {arg –> println(arg)}
funce(1)

这个跟javascript真的类似,键值对可以传入很多个键值对,如 funcb a:1,b:2 等等
5.表达式

if(a>1){...}

基本上区别不大一看就明白 有时候也可以不需要括号
6.遍历

mylist = [1,2,3];
for(index=0;index<3;index++){
	println mylist[index]
}
mylist.each{item -> println item}
mylist.each{print it}

it是个特殊的变量就是当前遍历到的数组中的元素

build.gradle

apply plugin: 'com.android.application'
//调用方法 apply

//调用 android 方法传入 闭包 {},闭包里面都是调用各种方法
android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "cn.fullstacks.app"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
...
}

grandle大量使用闭包,基本上赋值一个属性都是使用闭包,比如调用android方法传入一个闭包,闭包里面调用 defaultConfig 传入一个闭包,然后闭包里面都是调用各种方法,只是简写了不加括号

比如 compileSdkVersion 22 相当于调用 compileSdkVersion(22) ,那么这个方法很简单的赋值而已;

整体来看会很简洁,没有多余的东西,如果语法不了解看起来很费经,理解了之后就非常简单了