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实现拨打电话

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

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中简单使用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发展到现在,这些体验的代码实现已经变得简单而不足为道了。

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) ,那么这个方法很简单的赋值而已;

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

ANDROID通讯之Spring Rest Template

移动设备上的应用程序,大部分的内容都是通过通讯模块从服务端下载而来的,Android上用来通讯的大部分都采用http或者socket这两种模式,这些通讯模式都有大量的地方组件来协助以方便使用,当然也可用原生的http请求,不过需要做一些较多代码的辅助,下面我们来看看Spring Rest Template这个组件的使用。

创建项目

引入Spring依赖包和JSON序列化组件Jackson,Jackson不是必须,可以引入其他Spring支持的序列化组件不GSON之类的,不过个人感觉Jackson还是比较靠谱一点。

看看build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.1'
    compile 'org.springframework.android:spring-android-rest-template:2.0.0.M1'
    compile 'com.fasterxml.jackson.core:jackson-databind:2.6.0'
    //compile 'com.google.code.gson:gson:2.3.1'
}

RestTemplate

这个类是核心并且是个泛型实现,这个类提供了几个方法,我们只需要明白一个方法就行,那就是下面这个东西

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
			HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {

她有四五个参数

  1. url ,这个不需要说了吧
  2. method , 这个可以是post或者get的枚举,当然还有其他的方法,根据服务端的需要给
  3. requestEntity,请求的实体,包括发送的实体和http头部消息,这个头部消息有时候很重要,比如需要给定数据格式JSON格式,也可以加入Cookie以便请求和你登录的时候在一个会话里面。
  4. responseType,返回的数据类型
  5. 5.uriVariables,这个是url上要传的参数,一般情况直接写在第一参数里面就可以了,这个参数可以不给

下面给通用的get请求方法

/**
     * get 请求
     * @param url 相对路径
     * @param data 发送数据 可传入对象 或者map
     * @param tClass 返回数据类型
     * @param <T>
     * @return
     */
    public static <T> T getJson(String url,Object data, Class<T> tClass){
        String fullurl = “http://localhost:8080/myapp”+url;//这里根据需要修改
        RestTemplate restTemplate = new RestTemplate();
        //头部信息
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setContentType(new MediaType("application", "json"));
        requestHeaders.set("Cookie",”cookie value”);//这里根据需要给
        //返回的实体
        ResponseEntity<T> responseEntity;
        //JSON反序列化参数
        MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jacksonConverter.setObjectMapper(mapper);

        restTemplate.getMessageConverters().clear();
        restTemplate.getMessageConverters().add(jacksonConverter);

        //请求
        if(data != null){
            responseEntity = restTemplate.exchange(fullurl, HttpMethod.GET, new HttpEntity<>(data,requestHeaders), tClass);
        }else {
            responseEntity = restTemplate.exchange(fullurl, HttpMethod.GET, new HttpEntity<>(requestHeaders),tClass);
        }
        //返回数据
        T datas = responseEntity.getBody();
        return datas;
    }

结合AsyncTask使用

/**
* 请求服务器
*/
private class HttpRequestTask extends AsyncTask<Void, Void, HashMap> {
	//这个方法在后台执行,不要写涉及UI的操作,切记
	@Override
	protected HashMap doInBackground(Void... params) {
		try {
			final String url = "app/getUserInfo";
			HashMap userInfo= Utils.getJson(url, null,HashMap.class);
			return userInfo;
		} catch (Exception e) {
			Log.e("HttpRequestTask", e.getMessage(), e);
		}

		return null;
	}

	//这个方法在UI线程中执行,这里可以写任何UI的操作
	@Override
	protected void onPostExecute(HashMap userInfo) {
		//
		//这里操作界面
	}
}

这样在activity中就可以调用了

 new HttpRequestTask().execute();

 

总结

这个RestTemplate提供了通讯的基本实现包括数据结构的映射,直接返回对象使用这个是非常方便,再加上应用程序里面自己进行封装,请求后台服务变成了一个方法就能搞定,我已经不能想象能够有比这个更加简单的了,简单才是好不是吗?而且这个方法提供头部信息附加,加上Cookie信息就可以所有的请求都在一个会话里面了,返回的实体里面也是可以取到头部信息。

这里只是给出一个getJson的通用例子,当然把请求方法改成post就可以变成post的通用,基本上常用的app这两个方法就够用了。

当然除了restTemplate.exchange这个方法,还有其他一些个简单的方法,可以查看一下api,这里就不再嘚瑟了。