noTice501

没有失败,只有放弃。

导航

统计

公告

置顶随笔 #

[置顶]android应用开发全程实录出版

摘要: 这本书经历了大半年的时间,终于可以和读者见面了。书籍由我和微度网http://www.microdu.com/其他两位资深开发者共同完成。最近经历了很多,应该是人生的一个转折点吧。总会有一些选择,将会影响一生。放弃了一些机会,进入了在杭州的算是国内第一梯队的互联网公司,书籍如愿面市。。。不禁有些感慨。介绍下书籍吧。书籍主要还是讲android应用开发基础。全篇都是通过实例来讲解。可以参看我写过的几篇博客,都会以模仿一些应用的某些功能来写demo,会有很强的实战性。后面会陆续放出书籍章节和样章。书籍购买链接:当当网京东卓越亚马逊书籍目录:第1章 步入Android的世界 1.1 Android平阅读全文

posted @ 2011-11-18 16:02 noTice501 阅读(325) 评论(6) 编辑

2012年4月12日 #

android UI进阶之用ViewPager实现欢迎引导页面

ViewPager需要android-support-v4.jar这个包的支持,来自google提供的一个附加包。大家搜下即可。

ViewPager主要用来组织一组数据,并且通过左右滑动的方式来展示。

现在的大多数应用都会有一个欢迎引导页面,如图所示,通过左右滑动来告知用户一些功能特性。


这个引导图效果用ViewPager可以很轻松的实现。

正如前面所说,ViewPager是用来展示一组数据的,所以肯定需要Adapter来绑定数据和view。先写一个Adapter:

 

package com.notice.viewpagerd;

import java.util.List;

import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;

public class ViewPagerAdapter extends PagerAdapter{
    
    //界面列表
    private List<View> views;
    
    public ViewPagerAdapter (List<View> views){
        this.views = views;
    }

    //销毁arg1位置的界面
    @Override
    public void destroyItem(View arg0, int arg1, Object arg2) {
        ((ViewPager) arg0).removeView(views.get(arg1));        
    }

    @Override
    public void finishUpdate(View arg0) {
        // TODO Auto-generated method stub
        
    }

    //获得当前界面数
    @Override
    public int getCount() {
        if (views != null)
        {
            return views.size();
        }
        
        return 0;
    }
    

    //初始化arg1位置的界面
    @Override
    public Object instantiateItem(View arg0, int arg1) {
        
        ((ViewPager) arg0).addView(views.get(arg1), 0);
        
        return views.get(arg1);
    }

    //判断是否由对象生成界面
    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return (arg0 == arg1);
    }

    @Override
    public void restoreState(Parcelable arg0, ClassLoader arg1) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public Parcelable saveState() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void startUpdate(View arg0) {
        // TODO Auto-generated method stub
        
    }

}

 

这里我们要绑定的每一个item就是一个引导界面,我们用一个list来保存。

通过继承PagerAdapter,并实现几个我写注释的方法即可。

布局界面比较简单,加入ViewPager组件,以及底部的引导小点:

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
        />    
    
    
    <LinearLayout 
        android:id="@+id/ll" 
        android:orientation="horizontal" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_marginBottom="24.0dip" 
        android:layout_alignParentBottom="true" 
        android:layout_centerHorizontal="true">
        
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:clickable="true"
            android:padding="15.0dip"
            android:src="@drawable/dot" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:clickable="true"
            android:padding="15.0dip"
            android:src="@drawable/dot" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:clickable="true"
            android:padding="15.0dip"
            android:src="@drawable/dot" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:clickable="true"
            android:padding="15.0dip"
            android:src="@drawable/dot" />

    </LinearLayout>

</RelativeLayout>

其中小点的图片用一个selector来控制颜色(设置item的enable为true或者false)

dot.xml: 

<?xml version="1.0" encoding="UTF-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="true" android:drawable="@drawable/dark_dot" />
    <item android:state_enabled="false" android:drawable="@drawable/white_dot" />
</selector>

 

下面就是写Activity了。

 

package com.notice.viewpagerd;

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

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class ViewPagerDemoActivity extends Activity implements OnClickListener, OnPageChangeListener{
    
    private ViewPager vp;
    private ViewPagerAdapter vpAdapter;
    private List<View> views;
    
    //引导图片资源
    private static final int[] pics = { R.drawable.whatsnew_00,
            R.drawable.whatsnew_01, R.drawable.whatsnew_02,
            R.drawable.whatsnew_03 };
    
    //底部小店图片
    private ImageView[] dots ;
    
    //记录当前选中位置
    private int currentIndex;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        views = new ArrayList<View>();
       
        LinearLayout.LayoutParams mParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
        
        //初始化引导图片列表
        for(int i=0; i<pics.length; i++) {
            ImageView iv = new ImageView(this);
            iv.setLayoutParams(mParams);
            iv.setImageResource(pics[i]);
            views.add(iv);
        }
        vp = (ViewPager) findViewById(R.id.viewpager);
        //初始化Adapter
        vpAdapter = new ViewPagerAdapter(views);
        vp.setAdapter(vpAdapter);
        //绑定回调
        vp.setOnPageChangeListener(this);
        
        //初始化底部小点
        initDots();
        
    }
    
    private void initDots() {
        LinearLayout ll = (LinearLayout) findViewById(R.id.ll);

        dots = new ImageView[pics.length];

        //循环取得小点图片
        for (int i = 0; i < pics.length; i++) {
            dots[i] = (ImageView) ll.getChildAt(i);
            dots[i].setEnabled(true);//都设为灰色
            dots[i].setOnClickListener(this);
            dots[i].setTag(i);//设置位置tag,方便取出与当前位置对应
        }

        currentIndex = 0;
        dots[currentIndex].setEnabled(false);//设置为白色,即选中状态
    }
    
    /**
     *设置当前的引导页 
     */
    private void setCurView(int position)
    {
        if (position < 0 || position >= pics.length) {
            return;
        }

        vp.setCurrentItem(position);
    }

    /**
     *这只当前引导小点的选中 
     */
    private void setCurDot(int positon)
    {
        if (positon < 0 || positon > pics.length - 1 || currentIndex == positon) {
            return;
        }

        dots[positon].setEnabled(false);
        dots[currentIndex].setEnabled(true);

        currentIndex = positon;
    }

    //当滑动状态改变时调用
    @Override
    public void onPageScrollStateChanged(int arg0) {
        // TODO Auto-generated method stub
        
    }

    //当当前页面被滑动时调用
    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {
        // TODO Auto-generated method stub
        
    }

    //当新的页面被选中时调用
    @Override
    public void onPageSelected(int arg0) {
        //设置底部小点选中状态
        setCurDot(arg0);
    }

    @Override
    public void onClick(View v) {
        int position = (Integer)v.getTag();
        setCurView(position);
        setCurDot(position);
    }
}

 

 

注意实现OnClickListener, OnPageChangeListener接口,监听小点的点击事件以及viewPager的滑动,在相应的回调方法中设置小点的enable状态,我相信这个部分代码比我讲的清楚,就是判断当前选中的位置对相应的小点进行设置~

 

可以看到ViewPager还是一个非常简单,也非常实用的一个控件。

有问题欢迎留言交流。

posted @ 2012-04-12 17:20 noTice501 阅读(1892) 评论(4) 编辑

2012年2月17日 #

android UI进阶之实现listview中checkbox的多选与记录

今天继续和大家分享涉及到listview的内容。在很多时候,我们会用到listview和checkbox配合来提供给用户一些选择操作。比如在一个清单页面,我们需要记录用户勾选了哪些条目。这个的实现并不太难,但是有很多朋友来问我如何实现,他们有遇到各种各样的问题,这里就一并写出来和大家一起分享。

ListView的操作就一定会涉及到item和Adapter,我们还是先来实现这部分内容。

首先,写个item的xml布局,里面放置一个TextView和一个CheckBox。要注意的时候,这里我设置了CheckBox没有焦点,这样的话,无法单独点击checkbox,而是在点击listview的条目后,Checkbox会响应操作。

<?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
="horizontal" >

<TextView
android:id="@+id/item_tv"
android:layout_width
="0dp"
android:layout_height
="wrap_content"
android:layout_weight
="1"
android:gravity
="center_vertical"
/>

<CheckBox
android:id="@+id/item_cb"
android:layout_width
="wrap_content"
android:layout_height
="wrap_content"
android:clickable
="false"
android:focusable
="false"
android:focusableInTouchMode
="false"
android:gravity
="center_vertical"
/>

</LinearLayout>

下面就写一个Adapter类,我们依然继承BaseAdapter类。这里我们使用一个HashMap<Integer,boolean>的键值来记录checkbox在对应位置的选中状况,这是本例的实现的基础。

package com.notice.listcheck;

import java.util.ArrayList;
import java.util.HashMap;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.TextView;

public class MyAdapter extends BaseAdapter{
// 填充数据的list
private ArrayList<String> list;
// 用来控制CheckBox的选中状况
private static HashMap<Integer,Boolean> isSelected;
// 上下文
private Context context;
// 用来导入布局
private LayoutInflater inflater = null;

// 构造器
public MyAdapter(ArrayList<String> list, Context context) {
this.context = context;
this.list = list;
inflater = LayoutInflater.from(context);
isSelected = new HashMap<Integer, Boolean>();
// 初始化数据
initDate();
}

// 初始化isSelected的数据
private void initDate(){
for(int i=0; i<list.size();i++) {
getIsSelected().put(i,false);
}
}

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

@Override
public Object getItem(int position) {
return list.get(position);
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
// 获得ViewHolder对象
holder = new ViewHolder();
// 导入布局并赋值给convertview
convertView = inflater.inflate(R.layout.listviewitem, null);
holder.tv = (TextView) convertView.findViewById(R.id.item_tv);
holder.cb = (CheckBox) convertView.findViewById(R.id.item_cb);
// 为view设置标签
convertView.setTag(holder);
} else {
// 取出holder
holder = (ViewHolder) convertView.getTag();
}


// 设置list中TextView的显示
holder.tv.setText(list.get(position));
// 根据isSelected来设置checkbox的选中状况
holder.cb.setChecked(getIsSelected().get(position));
return convertView;
}

public static HashMap<Integer,Boolean> getIsSelected() {
return isSelected;
}

public static void setIsSelected(HashMap<Integer,Boolean> isSelected) {
MyAdapter.isSelected = isSelected;
}

}

注释已经写的非常详尽了,通过

holder.cb.setChecked(getIsSelected().get(position));

这行代码我们实现了设置CheckBox的选中状况。

那么我们只需要在点击事件中,控制isSelected的键值即可控制对应位置checkbox的选中了。

在Activity中我们除了放置一个ListView外,还放置了三个按钮,分别实现全选,取消和反选。

看下Activity类的代码:

package com.notice.listcheck;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

public class Ex_checkboxActivity extends Activity {

private ListView lv;
private MyAdapter mAdapter;
private ArrayList<String> list;
private Button bt_selectall;
private Button bt_cancel;
private Button bt_deselectall;
private int checkNum; // 记录选中的条目数量
private TextView tv_show;// 用于显示选中的条目数量

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/* 实例化各个控件 */
lv = (ListView) findViewById(R.id.lv);
bt_selectall = (Button) findViewById(R.id.bt_selectall);
bt_cancel = (Button) findViewById(R.id.bt_cancelselectall);
bt_deselectall = (Button) findViewById(R.id.bt_deselectall);
tv_show = (TextView) findViewById(R.id.tv);
list = new ArrayList<String>();
// 为Adapter准备数据
initDate();
// 实例化自定义的MyAdapter
mAdapter = new MyAdapter(list, this);
// 绑定Adapter
lv.setAdapter(mAdapter);

// 全选按钮的回调接口
bt_selectall.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// 遍历list的长度,将MyAdapter中的map值全部设为true
for (int i = 0; i < list.size(); i++) {
MyAdapter.getIsSelected().put(i, true);
}
// 数量设为list的长度
checkNum = list.size();
// 刷新listview和TextView的显示
dataChanged();

}
});
// 取消按钮的回调接口
bt_cancel.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// 遍历list的长度,将已选的按钮设为未选
for (int i = 0; i < list.size(); i++) {
if (MyAdapter.getIsSelected().get(i)) {
MyAdapter.getIsSelected().put(i, false);
checkNum--;// 数量减1
}
}
// 刷新listview和TextView的显示
dataChanged();

}
});

// 反选按钮的回调接口
bt_deselectall.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// 遍历list的长度,将已选的设为未选,未选的设为已选
for (int i = 0; i < list.size(); i++) {
if (MyAdapter.getIsSelected().get(i)) {
MyAdapter.getIsSelected().put(i, false);
checkNum--;
} else {
MyAdapter.getIsSelected().put(i, true);
checkNum++;
}

}
// 刷新listview和TextView的显示
dataChanged();
}
});

// 绑定listView的监听器
lv.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {

// 取得ViewHolder对象,这样就省去了通过层层的findViewById去实例化我们需要的cb实例的步骤
          ViewHolder holder = (ViewHolder) arg1.getTag();
                // 改变CheckBox的状态
holder.cb.toggle();
// 将CheckBox的选中状况记录下来
MyAdapter.getIsSelected().put(arg2, holder.cb.isChecked());
// 调整选定条目
if (holder.cb.isChecked() == true) {
checkNum++;
} else {
checkNum--;
}
// 用TextView显示
tv_show.setText("已选中"+checkNum+"项");

}
});
}

// 初始化数据
private void initDate() {
for (int i = 0; i < 15; i++) {
list.add("data" + " " + i);
}
}

// 刷新listview和TextView的显示
private void dataChanged() {
// 通知listView刷新
mAdapter.notifyDataSetChanged();
// TextView显示最新的选中数目
tv_show.setText("已选中" + checkNum + "项");
}


}


代码中在item的点击事件中,直接调用

 holder.cb.toggle();

先改变CheckBox的状态,然后将值存进map记录下来

 MyAdapter.getIsSelected().put(arg2, holder.cb.isChecked());

而其他几个Button的点击事件,都是通过遍历list的长度来设置isSelected的值,进而通知listview根据已经变化的adapter刷新,来实现Checkbox的对应选中状态。因为对listview的处理中我们仍然使用了ViewHolder来优化ListView的效率(通过findViewById层层查找是比较耗时的,这里不了解的朋友可以看我另一篇博客http://www.cnblogs.com/noTice520/archive/2011/12/05/2276379.html,全面解析listview的)。

最后,来看下运行效果:

 

    

好了,就写到这里。相信大家都能明白了。这里要说下一个问题,有很多朋友留言或者发邮件要博客中的一些源码。我在这里声明下,我不会去发任何我觉得已经在博客里介绍的非常清楚的实例的源码,有些实例我已经把所有代码都贴出来了,还是有人要源码。。。我希望看我博客的朋友都能真正理解这个实例,能学到更多的知识,最好能有自己的改进然后再和大家一起分享。很多朋友现在已经习惯了拿别人的源码,功能类似的就直接搬到自己项目里,这是非常不好的习惯。动动手,多写写,你会学到更多。

posted @ 2012-02-17 10:42 noTice501 阅读(1955) 评论(15) 编辑

2012年2月10日 #

android UI进阶之实现listview的分页加载

上篇博文和大家分享了下拉刷新,这是一个用户体验非常好的操作方式。新浪微薄就是使用这种方式的典型。

还有个问题,当用户从网络上读取微薄的时候,如果一下子全部加载用户未读的微薄这将耗费比较长的时间,造成不好的用户体验,同时一屏的内容也不足以显示如此多的内容。这时候,我们就需要用到另一个功能,那就是listview的分页了。通过分页分次加载数据,用户看多少就去加载多少。

通常这也分为两种方式,一种是设置一个按钮,用户点击即加载。另一种是当用户滑动到底部时自动加载。今天我就和大家分享一下这个功能的实现。

首先,写一个xml文件,moredata.xml,该文件即定义了放在listview底部的视图:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width
="match_parent"
android:layout_height
="match_parent"
android:orientation
="vertical" >
<Button
android:id="@+id/bt_load"
android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:text
="加载更多数据" />
<ProgressBar
android:id="@+id/pg"
android:layout_width
="wrap_content"
android:layout_height
="wrap_content"
android:layout_gravity
="center_horizontal"
android:visibility
="gone"
/>
</LinearLayout>

可以看到是一个按钮和一个进度条。因为只做一个演示,这里简单处理,通过设置控件的visibility,未加载时显示按钮,加载时就显示进度条。

写一个item.xml,大家应该很熟悉了。用来定义listview的每个item的视图。

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

<TextView
android:id="@+id/tv_title"
android:textSize
="20sp"
android:layout_width
="wrap_content"
android:layout_height
="wrap_content"
android:layout_marginTop
="5dp"
/>
<TextView
android:textSize="12sp"
android:id
="@+id/tv_content"
android:layout_width
="wrap_content"
android:layout_height
="wrap_content"
android:layout_marginTop
="5dp"
/>

</LinearLayout>

main.xml就不贴了,整个主界面就一个listview。

直接先看下Activity的代码,在里面实现分页效果。

package com.notice.moredate;

import java.util.ArrayList;
import java.util.HashMap;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SimpleAdapter;
import android.widget.Toast;

public class MoreDateListActivity extends Activity implements OnScrollListener {

// ListView的Adapter
private SimpleAdapter mSimpleAdapter;
private ListView lv;
private Button bt;
private ProgressBar pg;
private ArrayList<HashMap<String,String>> list;
// ListView底部View
private View moreView;
private Handler handler;
// 设置一个最大的数据条数,超过即不再加载
private int MaxDateNum;
// 最后可见条目的索引
private int lastVisibleIndex;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);


MaxDateNum = 22; // 设置最大数据条数

lv = (ListView) findViewById(R.id.lv);

// 实例化底部布局
moreView = getLayoutInflater().inflate(R.layout.moredate, null);

bt = (Button) moreView.findViewById(R.id.bt_load);
pg = (ProgressBar) moreView.findViewById(R.id.pg);
handler = new Handler();

// 用map来装载数据,初始化10条数据
list = new ArrayList<HashMap<String,String>>();
for (int i = 0; i < 10; i++) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("ItemTitle", "第" + i + "行标题");
map.put("ItemText", "第" + i + "行内容");
list.add(map);
}
// 实例化SimpleAdapter
mSimpleAdapter = new SimpleAdapter(this, list, R.layout.item,
new String[] { "ItemTitle", "ItemText" },
new int[] { R.id.tv_title, R.id.tv_content });
// 加上底部View,注意要放在setAdapter方法前
lv.addFooterView(moreView);
lv.setAdapter(mSimpleAdapter);
// 绑定监听器
lv.setOnScrollListener(this);

bt.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
pg.setVisibility(View.VISIBLE);// 将进度条可见
bt.setVisibility(View.GONE);// 按钮不可见

handler.postDelayed(new Runnable() {

@Override
public void run() {
loadMoreDate();// 加载更多数据
bt.setVisibility(View.VISIBLE);
pg.setVisibility(View.GONE);
mSimpleAdapter.notifyDataSetChanged();// 通知listView刷新数据
}

}, 2000);
}
});

}

private void loadMoreDate() {
int count = mSimpleAdapter.getCount();
if (count + 5 < MaxDateNum) {
// 每次加载5条
for (int i = count; i < count + 5; i++) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("ItemTitle", "新增第" + i + "行标题");
map.put("ItemText", "新增第" + i + "行内容");
list.add(map);
}
} else {
// 数据已经不足5条
for (int i = count; i < MaxDateNum; i++) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("ItemTitle", "新增第" + i + "行标题");
map.put("ItemText", "新增第" + i + "行内容");
list.add(map);
}
}

}

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// 计算最后可见条目的索引
lastVisibleIndex = firstVisibleItem + visibleItemCount - 1;

// 所有的条目已经和最大条数相等,则移除底部的View
if (totalItemCount == MaxDateNum + 1) {
lv.removeFooterView(moreView);
Toast.makeText(this, "数据全部加载完成,没有更多数据!", Toast.LENGTH_LONG).show();
}

}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 滑到底部后自动加载,判断listview已经停止滚动并且最后可视的条目等于adapter的条目
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE
&& lastVisibleIndex == mSimpleAdapter.getCount()) {
// 当滑到底部时自动加载
// pg.setVisibility(View.VISIBLE);
// bt.setVisibility(View.GONE);
// handler.postDelayed(new Runnable() {
//
// @Override
// public void run() {
// loadMoreDate();
// bt.setVisibility(View.VISIBLE);
// pg.setVisibility(View.GONE);
// mSimpleAdapter.notifyDataSetChanged();
// }
//
// }, 2000);

}

}

}

通过注释,大家应该很容易理解了。这里做下简单的解析。首先要注意的是,addFootView方法一定要在setAdapter方法之前,否则会无效。addFootView方法为listview底部加入一个视图,在本例中就是那个Button加progressbar的视图。当用户点击按钮时,调用loadmoreDate方法,为listview绑定更多的数据,通过adapter的notifyDataSetChanged方法通知listview刷新,显示刚加入的数据。

这里用handler异步延迟2秒操作,模仿加载过程。同时listview绑定了onScrollListener监听器,并且实现了onScroll和onScrollStateChanged方法。在后者方法中,我们通过判断listview已经停止滚动并且最后可视的条目等于adapter的条目,可以知道用户已经滑动到底部并且自动加载,代码中将这部分代码注释掉了,大家可以自己试下。

代码中还加入了一个MaxDateNum变量,用来记录最大的数据数量。也就是说网络或者其他地方一共的数据。通过onScroll方法判断用户加载完这些数据后,移除listview底部视图,不让继续加载。同时在loadmoreDate方法中也对最大数据量做相应的操作来判断加载数量。(默认加载5条,不足5条时加载剩余的)。

看下效果图:

   

   

 

 

就写这么多了,总的来说还是很简单的,但是确实非常有用的一个效果。欢迎留言交流。





posted @ 2012-02-10 11:26 noTice501 阅读(2295) 评论(2) 编辑

2012年1月20日 #

android UI进阶之实现listview的下拉加载

关于listview的操作五花八门,有下拉刷新,分级显示,分页列表,逐页加载等,以后会陆续和大家分享这些技术,今天讲下下拉加载这个功能的实现。

最初的下拉加载应该是ios上的效果,现在很多应用如新浪微博等都加入了这个操作。即下拉listview刷新列表,这无疑是一个非常友好的操作。今天就和大家分享下这个操作的实现。

先看下运行效果:

   

 

   

代码参考国外朋友Johan Nilsson的实现,http://johannilsson.com/2011/03/13/android-pull-to-refresh-update.html

主要原理为监听触摸和滑动操作,在listview头部加载一个视图。那要做的其实很简单:1.写好加载到listview头部的view 2.重写listview,实现onTouchEvent方法和onScroll方法,监听滑动状态。计算headview全部显示出来即可实行加载动作,加载完成即刷新列表。重新隐藏headview。

首先写下headview的xml代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width
="fill_parent"
android:layout_height
="fill_parent"
android:paddingTop
="10dip"
android:paddingBottom
="15dip"
android:gravity
="center"
android:id
="@+id/pull_to_refresh_header"
>
<ProgressBar
android:id="@+id/pull_to_refresh_progress"
android:indeterminate
="true"
android:layout_width
="wrap_content"
android:layout_height
="wrap_content"
android:layout_marginLeft
="30dip"
android:layout_marginRight
="20dip"
android:layout_marginTop
="10dip"
android:visibility
="gone"
android:layout_centerVertical
="true"
style
="?android:attr/progressBarStyleSmall"
/>
<ImageView
android:id="@+id/pull_to_refresh_image"
android:layout_width
="wrap_content"
android:layout_height
="wrap_content"
android:layout_marginLeft
="30dip"
android:layout_marginRight
="20dip"
android:visibility
="gone"
android:layout_gravity
="center"
android:gravity
="center"
android:src
="@drawable/ic_pulltorefresh_arrow"
/>
<TextView
android:id="@+id/pull_to_refresh_text"
android:textAppearance
="?android:attr/textAppearanceMedium"
android:textStyle
="bold"
android:paddingTop
="5dip"
android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:layout_gravity
="center"
android:gravity
="center"
/>
<TextView
android:id="@+id/pull_to_refresh_updated_at"
android:layout_below
="@+id/pull_to_refresh_text"
android:visibility
="gone"
android:textAppearance
="?android:attr/textAppearanceSmall"
android:layout_width
="fill_parent"
android:layout_height
="wrap_content"
android:layout_gravity
="center"
android:gravity
="center"
/>
</RelativeLayout>

代码比较简单,即headview包括一个进度条一个箭头和两段文字(一个显示加载状态,另一个显示最后刷新时间,本例就不设置了)。

而后重写listview,代码如下:

package com.notice.pullrefresh;


import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;



public class PullToRefreshListView extends ListView implements OnScrollListener {

// 状态
private static final int TAP_TO_REFRESH = 1;
private static final int PULL_TO_REFRESH = 2;
private static final int RELEASE_TO_REFRESH = 3;
private static final int REFRESHING = 4;


private OnRefreshListener mOnRefreshListener;


// 监听对listview的滑动动作
private OnScrollListener mOnScrollListener;
private LayoutInflater mInflater;

//顶部刷新时出现的控件
private RelativeLayout mRefreshView;
private TextView mRefreshViewText;
private ImageView mRefreshViewImage;
private ProgressBar mRefreshViewProgress;
private TextView mRefreshViewLastUpdated;

// 当前滑动状态
private int mCurrentScrollState;
// 当前刷新状态
private int mRefreshState;

// 箭头动画效果
private RotateAnimation mFlipAnimation;
private RotateAnimation mReverseFlipAnimation;

private int mRefreshViewHeight;
private int mRefreshOriginalTopPadding;
private int mLastMotionY;

private boolean mBounceHack;

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

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

public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}

/**
* 初始化控件和箭头动画(这里直接在代码中初始化动画而不是通过xml)
*/
private void init(Context context) {
mFlipAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mFlipAnimation.setInterpolator(new LinearInterpolator());
mFlipAnimation.setDuration(250);
mFlipAnimation.setFillAfter(true);
mReverseFlipAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
mReverseFlipAnimation.setDuration(250);
mReverseFlipAnimation.setFillAfter(true);

mInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);

mRefreshView = (RelativeLayout) mInflater.inflate(
R.layout.pull_to_refresh_header, this, false);
mRefreshViewText =
(TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);
mRefreshViewImage =
(ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);
mRefreshViewProgress =
(ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);
mRefreshViewLastUpdated =
(TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);

mRefreshViewImage.setMinimumHeight(50);
mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();

mRefreshState = TAP_TO_REFRESH;

//为listview头部增加一个view
addHeaderView(mRefreshView);

super.setOnScrollListener(this);

measureView(mRefreshView);
mRefreshViewHeight = mRefreshView.getMeasuredHeight();
}

@Override
protected void onAttachedToWindow() {
setSelection(1);
}

@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);

setSelection(1);
}

/**
* 设置滑动监听器
*
*/
@Override
public void setOnScrollListener(AbsListView.OnScrollListener l) {
mOnScrollListener = l;
}

/**
* 注册一个list需要刷新时的回调接口
*
*/
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
mOnRefreshListener = onRefreshListener;
}

/**
* 设置标签显示何时最后被刷新
*
*
@param lastUpdated
* Last updated at.
*/
public void setLastUpdated(CharSequence lastUpdated) {
if (lastUpdated != null) {
mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
mRefreshViewLastUpdated.setText(lastUpdated);
} else {
mRefreshViewLastUpdated.setVisibility(View.GONE);
}
}

// 实现该方法处理触摸
@Override
public boolean onTouchEvent(MotionEvent event) {
final int y = (int) event.getY();
mBounceHack = false;

switch (event.getAction()) {

case MotionEvent.ACTION_UP:
if (!isVerticalScrollBarEnabled()) {
setVerticalScrollBarEnabled(true);
}
if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
// 拖动距离达到刷新需要
if ((mRefreshView.getBottom() >= mRefreshViewHeight
|| mRefreshView.getTop() >= 0)
&& mRefreshState == RELEASE_TO_REFRESH) {
// 把状态设置为正在刷新
mRefreshState = REFRESHING;
// 准备刷新
prepareForRefresh();
// 刷新
onRefresh();
} else if (mRefreshView.getBottom() < mRefreshViewHeight
|| mRefreshView.getTop() <= 0) {
// 中止刷新
resetHeader();
setSelection(1);
}
}
break;
case MotionEvent.ACTION_DOWN:
// 获得按下y轴位置
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算边距
applyHeaderPadding(event);
break;
}
return super.onTouchEvent(event);
}

// 获得header的边距
private void applyHeaderPadding(MotionEvent ev) {

int pointerCount = ev.getHistorySize();

for (int p = 0; p < pointerCount; p++) {
if (mRefreshState == RELEASE_TO_REFRESH) {
if (isVerticalFadingEdgeEnabled()) {
setVerticalScrollBarEnabled(false);
}

int historicalY = (int) ev.getHistoricalY(p);

// 计算申请的边距,除以1.7使得拉动效果更好
int topPadding = (int) (((historicalY - mLastMotionY)
- mRefreshViewHeight) / 1.7);

mRefreshView.setPadding(
mRefreshView.getPaddingLeft(),
topPadding,
mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
}
}

/**
* 将head的边距重置为初始的数值
*/
private void resetHeaderPadding() {
mRefreshView.setPadding(
mRefreshView.getPaddingLeft(),
mRefreshOriginalTopPadding,
mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}

/**
* 重置header为之前的状态
*/
private void resetHeader() {
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshState = TAP_TO_REFRESH;

resetHeaderPadding();

// 将刷新图标换成箭头
mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);
// 清除动画
mRefreshViewImage.clearAnimation();
// 隐藏图标和进度条
mRefreshViewImage.setVisibility(View.GONE);
mRefreshViewProgress.setVisibility(View.GONE);
}
}

// 估算headview的width和height
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}

int childWidthSpec = ViewGroup.getChildMeasureSpec(0,
0 + 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {

// 在refreshview完全可见时,设置文字为松开刷新,同时翻转箭头
if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
&& mRefreshState != REFRESHING) {
if (firstVisibleItem == 0) {
mRefreshViewImage.setVisibility(View.VISIBLE);
if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20
|| mRefreshView.getTop() >= 0)
&& mRefreshState != RELEASE_TO_REFRESH) {
mRefreshViewText.setText("松开加载...");
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mFlipAnimation);
mRefreshState = RELEASE_TO_REFRESH;
} else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
&& mRefreshState != PULL_TO_REFRESH) {
mRefreshViewText.setText("下拉刷新...");
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mReverseFlipAnimation);
}
mRefreshState = PULL_TO_REFRESH;
}
} else {
mRefreshViewImage.setVisibility(View.GONE);
resetHeader();
}
} else if (mCurrentScrollState == SCROLL_STATE_FLING
&& firstVisibleItem == 0
&& mRefreshState != REFRESHING) {
setSelection(1);
mBounceHack = true;
} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
setSelection(1);
}

if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem,
visibleItemCount, totalItemCount);
}
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;

if (mCurrentScrollState == SCROLL_STATE_IDLE) {
mBounceHack = false;
}

if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
}

public void prepareForRefresh() {
resetHeaderPadding();// 恢复header的边距

mRefreshViewImage.setVisibility(View.GONE);
// 注意加上,否则仍然显示之前的图片
mRefreshViewImage.setImageDrawable(null);
mRefreshViewProgress.setVisibility(View.VISIBLE);

// 设置文字
mRefreshViewText.setText("加载中...");

mRefreshState = REFRESHING;
}

public void onRefresh() {

if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
}

/**
* 重置listview为普通的listview,该方法设置最后更新时间
*
*
@param lastUpdated
* Last updated at.
*/
public void onRefreshComplete(CharSequence lastUpdated) {
setLastUpdated(lastUpdated);
onRefreshComplete();
}

/**
* 重置listview为普通的listview,不设置最后更新时间
*/
public void onRefreshComplete() {

resetHeader();

// 如果refreshview在加载结束后可见,下滑到下一个条目
if (mRefreshView.getBottom() > 0) {
invalidateViews();
setSelection(1);
}
}



/**
* 刷新监听器接口
*/
public interface OnRefreshListener {
/**
* list需要被刷新时调用
*/
public void onRefresh();
}
}

相信我注释已经写的比较详细了,主要注意onTouchEvent和onScroll方法,在这里面计算头部边距,从而通过用户的手势实现“下拉刷新”到“松开加载”以及“加载”三个状态的切换。其中还有一系列和header有关的方法,用来设置header的显示以及取得header的边距。于此同时,代码留出了接口以供调用。

那么现在写一个测试Activity来试验下效果:

package com.notice.pullrefresh;

import java.util.Arrays;
import java.util.LinkedList;

import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ArrayAdapter;

import com.notice.pullrefresh.PullToRefreshListView.OnRefreshListener;


public class PullrefreshActivity extends ListActivity {
private LinkedList<String> mListItems;
ArrayAdapter<String> adapter;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pull_to_refresh);

// list需要刷新时调用
((PullToRefreshListView) getListView())
.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
// 在这执行后台工作
new GetDataTask().execute();
}
});



mListItems = new LinkedList<String>();
mListItems.addAll(Arrays.asList(mStrings));

adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, mListItems);

setListAdapter(adapter);
}


private class GetDataTask extends AsyncTask<Void, Void, String[]> {

@Override
protected String[] doInBackground(Void... params) {
// 在这里可以做一些后台工作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return mStrings;
}

@Override
protected void onPostExecute(String[] result) {
// 下拉后增加的内容
mListItems.addFirst("Added after refresh...");

// 刷新完成调用该方法复位
((PullToRefreshListView) getListView()).onRefreshComplete();

super.onPostExecute(result);
}
}

private String[] mStrings = { "normal data1", "normal data2",
"nomal data3", "normal data4", "norma data5", "normal data6" };
}

代码通过asyncTask实现一个异步操作,并通过设置onRefreshListener监听器调用onRefresh方法实现下拉时刷新,并在刷新完成后调用onRefreshComplete做复位处理。

 

今天就和大家分享这些,有问题欢迎留言交流。




posted @ 2012-01-20 17:06 noTice501 阅读(2748) 评论(2) 编辑

2012年1月10日 #

记录一些莫名奇妙的问题,方便自己帮助大家

从今天起,在这篇文章里不断将平时遇到的一些小麻烦及解决办法记录下来。方便自己查找,也希望能帮助到其他遇到相同问题的朋友。

1.eclipse高亮显示相同变量功能消失

原因:在导包的时候不小心按下了alt + shift + o,从而导致关闭了该功能。

恢复方法:windows-> preferences->java->Editor->Mark Occurences,选中最上方的复选框即可。

2.android工程出现莫名奇妙的错误

试试project->clean

3.linux下svn出现Failed to load JavaHL Library.错误

是因为没有安装javaHL

装一下即可,终端运行sudo apt-get install libsvn-java

再进行一下配置:

修改eclipse.ini文件,添加下面内容: -Djava.library.path=/usr/lib/jni 重启eclipse

如果还出现Incompatible JavaHL library loaded. 1.7.x or later required错误,那么查看一下你的版本是否是1.8.x,这个版本需要对应1.7.x的javaHL,官方源还是1.6.x的,安装1.6.x的svn即可。

4.ubuntu 11.04和11.10将eclipse固定在启动器上

11.04比较方便,桌面,右击->创建启动器,然后将创建好的启动器拖到边栏上即可。

11.10没了这个功能。可以按如下步骤创建:

输入命令sudo gedit /usr/share/applications/Eclipse.desktop

打开文件按如下修改,保存

[Desktop Entry]

Name=Eclipse   //AP名称

Comment=Eclipse   //注释

Exec=/home/kesenhoo/Program/eclipse-indigo-J2EE/eclipse   //执行文件目录

Icon=/home/kesenhoo/Program/eclipse-indigo-J2EE/icon.xpm   //图标位置

Terminal=false              //

Type=Application         //

Categories=GNOME;Application;Programing    //为这个AP分类

StartupNotify=true     //

4.在eclipse中显示空格和tab

Window->Preferences->General->Editors->Text Editors->Show whitespace characters

5.eclipse中编译出现@Override错误

@Override是JDK5 就已经有了,但有个小小的Bug,就是不支持对接口的实现,认为这不是Override
JDK6 修正了这个Bug,无论是对父类的方法覆盖还是对接口的实现都可以加上@Override

修改你的eclipse指定的编译器版本
在选项里的java compiler中指定版本至少在5.0以上

在myEclipse中改变编译器的方法:Project->Properties->Java Compiler->Configure Workspace Setting,在弹出的页面中可以进行设置,改成1.6即可。

posted @ 2012-01-10 21:44 noTice501 阅读(219) 评论(0) 编辑

2011年12月27日 #

android应用开发全程实录-实现甩动拨打和挂断电话

摘要: 今天继续给大家带来《Android应用开发全程实录》中的章节,这部分是讲传感器中的一个实例。通过上面的例子我们学会了如何获得某种类型的传感器,下面通过一个实例来学习如何使用某一个类型的传感器。我们以加速传感器为例,来实现这样一个功能:摇动手机便自动拨打某亲情号码,并实现再次摇动则挂机的功能。工程目录:EX_12_03第一步,UI布局main.xml的代码如下,其运行效果如图12-10所示。<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="htt阅读全文

posted @ 2011-12-27 14:08 noTice501 阅读(1492) 评论(1) 编辑

2011年12月5日 #

android应用开发全程实录-你有多熟悉listview?

摘要: 今天给大家带来《android应用开发全程实录》中关于listview和adatper中的部分。包括listview的基本使用,listview的优化等。我们经常会在应用程序中使用列表的形式来展现一些内容,所以学好ListView是非常必需的。ListView也是Android中比较难以使用的控件,这节内容就将详细解读ListView的用法。一个ListView通常有两个职责。(1)将数据填充到布局。(2)处理用户的选择点击等操作。第一点很好理解,ListView就是实现这个功能的。第二点也不难做到,在后面的学习中读者会发现,这非常简单。一个ListView的创建需要3个元素。(1)ListV阅读全文

posted @ 2011-12-05 09:35 noTice501 阅读(2709) 评论(3) 编辑

2011年11月24日 #

android应用开发全程实录-用户界面部分章节-你真的会用最简单的TextView么?

摘要: TextView为文本控件,在前面的学习中多次用到,可能读者已经对它的用法有所了解了,这里再单独介绍一下,加深理解,因为TextView是一个非常重要和常用的控件。我们通常在xml布局文件中声明一个TextView,代码如下:<TextView android:id="@+id/tv"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="@string/hello"/>通常在Activity阅读全文

posted @ 2011-11-24 09:22 noTice501 阅读(1640) 评论(9) 编辑

2011年11月23日 #

android应用开发全程实录-关于google map的部分章节-漂亮的气泡地图

摘要: 12.1.2 Geopoint与MapView在开始GoogleMap之前,先来认识下maps包下两个重要的类Geopoint和MapView 。Geopoint类是一个不可变类,表示一组经度和纬度值,以微度的整数形式存储。public构造方法GeoPoint(intlatitudeE6,intlongitudeE6)。用给定的经度和纬度构造一个GeoPoint,单位微度(度*1E6). 参数:latitudeE6- 该点的纬度,为保持Mercator投影精确度,其取值范围是[-80,80]。longitudeE6- 该点的经度,可被规范化到(-180,180)。publicint getLa阅读全文

posted @ 2011-11-23 09:00 noTice501 阅读(2323) 评论(3) 编辑

2011年11月18日 #

android应用开发全程实录出版

摘要: 这本书经历了大半年的时间,终于可以和读者见面了。书籍由我和微度网http://www.microdu.com/其他两位资深开发者共同完成。最近经历了很多,应该是人生的一个转折点吧。总会有一些选择,将会影响一生。放弃了一些机会,进入了在杭州的算是国内第一梯队的互联网公司,书籍如愿面市。。。不禁有些感慨。介绍下书籍吧。书籍主要还是讲android应用开发基础。全篇都是通过实例来讲解。可以参看我写过的几篇博客,都会以模仿一些应用的某些功能来写demo,会有很强的实战性。后面会陆续放出书籍章节和样章。书籍购买链接:当当网京东卓越亚马逊书籍目录:第1章 步入Android的世界 1.1 Android平阅读全文

posted @ 2011-11-18 16:02 noTice501 阅读(325) 评论(6) 编辑

仅列出标题  下一页