第二章:根据oschina开源的app代码快速构建自己站点的ANDROID APP
OSCHINA的APP已经开源了,下载地址:http://www.oschina.net/p/oschina-android-app
立即下载回去研究研究吧,很多不错的方法值得学习
研究了几天,发现一些快速上手的方法,跟大家分享一下
第一:启动屏幕分享
AndroidManifest.xml
<activity android:name=".AppStart" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:screenOrientation="portrait">
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 全屏的样式
android:name=".AppStart"对应打开:/src/net/oschina/app/AppStart.java
渐变展示启动屏,然后跳转到main
那么我们来修改启动屏幕
final View view = View.inflate(this, R.layout.start, null);
对应打开:/res/layout/start.xml 可以看到 android:background="@drawable/start_background">
然后对应打开:/res/drawable/start_background.xml
然后就找到 android:src="@drawable/welcome"对应的 /res/drawable/welcome.png
我们可以做个同尺寸的图片,这个不用说了吧,比如我给dlog.cn做了个
替换图片后,run as ,呵呵,是不是很不错,到此为止,大家都应该会做启动屏幕了吧
第二:数据与服务器交互 及 LISTVIEW 展现(穿插讲解顶部下拉刷新数据,及底部分页数据)
OSCHINA APP全部功能已经实现,打开代码后初学者会发现很难上手,因为太多功能太复杂,看着头晕,
我来帮大家理理头绪。。。。。。。
任何网站最主要的数据归根结底都是去做LIST,ANDROID主要是做LISTVIEW,
OSCHINA的APP第一屏就是NEWS的LISTVIEW,那么我们就打开/src/net/oschina/app/ui/Main.java
我们把NEWS更改成我们想要和网站交互的LIST的数据,比如我需要将DLOG.CN的日记列表用NEWS的方式来展现
打开 /src/net/oschina/app/bean/News.java 你会发现,OSCHINA交互数据使用的XML格式的
因为研究过JSON,所以我把DLOG.CN使用JSON来作为和ANDROID的数据交互
1,我们先去看看WEBSERVICE,创建交互数据使用的JSON,
http://www.dlog.cn/api/diary_list?pageSize=30&pageIndex=1 日记列表的JSON
http://www.dlog.cn/api/diary_detail?id=154157907&site=njzj3 日记详情的JSON
pageSize=分页数据列表多少,pageIndex=第几页,
id=日记的ID,site=日记的空间地址
JSON格式如下:
{id:,site:,catalog:,title:,user:,author:,pubTime:,replyCount:,viewCount:}
2,按照这个JSON的KEY,在APP的BEAN目录下创建一个DiaryBean.JAVA
BEAN文件的创建,我就不说了,大家可以打开/src/net/oschina/app/bean/News.java参照一下
我主要讲解一下下面这个方法:
public static Diary parse(InputStream inputStream) throws IOException, AppException, JSONException {
}
inputStream是URL请求后返回的JSON文件,获得文件需要做一些读取转换的工作,然后给DIARY BEAN的KEY赋值,废话不多说,代码如下:
public static Diary parse(InputStream inputStream) throws IOException, AppException, JSONException {Diary diary = null;StringBuilder result = new StringBuilder();InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");BufferedReader reader = new BufferedReader(inputStreamReader);String s;while (((s = reader.readLine()) != null)) {result.append(s);} reader.close(); try { JSONObject jsonObject = new JSONObject(result.toString()); Iterator<String> keyIter = jsonObject.keys(); String key; diary = new Diary(); while (keyIter.hasNext()) { key = (String) keyIter.next(); if (key.equalsIgnoreCase("id")){ diary.id = StringUtils.toInt(jsonObject.get(key)); }else if(key.equalsIgnoreCase("site")){ diary.site = jsonObject.get(key).toString(); }else if(key.equalsIgnoreCase("catalog")){ diary.setCatalog(StringUtils.toInt(jsonObject.get(key))); }else if(key.equalsIgnoreCase("replyCount")){ diary.setReplyCount(StringUtils.toInt(jsonObject.get(key))); }else if(key.equalsIgnoreCase("viewCount")){ diary.setViewCount(StringUtils.toInt(jsonObject.get(key))); }else if(key.equalsIgnoreCase("title")){ diary.setTitle(jsonObject.get(key).toString()); }else if(key.equalsIgnoreCase("content")){ diary.setContent(jsonObject.get(key).toString()); }else if(key.equalsIgnoreCase("user")){ diary.setUser(jsonObject.get(key).toString()); }else if(key.equalsIgnoreCase("author")){ diary.setAuthor(jsonObject.get(key).toString()); }else if(key.equalsIgnoreCase("pubTime")){ diary.setPubTime(StringUtils.friendly_time(jsonObject.get(key).toString())); } } } catch (JSONException e) {e.printStackTrace();} return diary; }
你可以模拟一个JSON文件,然后赋值给BEAN,检查是否正常
3,用同样的方法,做一个DiaryListBean.JAVA
4,ANDROID LISTVIEW通过 ADAPTER将值赋给 ID,然后展现出来,那么我们需要制作一个ADAPTER,和layout里面XML的ID对应起来,
参照/src/net/oschina/app/adapter/ListViewNewsAdapter.java
我们新建立一个 ListViewDiaryAdapter.java,里面id部分不用更改,如果你重新制作XML文件后,重新命名,那么你需要修改对应的id
//获取控件对象listItemView.title = (TextView)convertView.findViewById(R.id.news_listitem_title);listItemView.author = (TextView)convertView.findViewById(R.id.news_listitem_author);listItemView.count= (TextView)convertView.findViewById(R.id.news_listitem_commentCount);listItemView.date= (TextView)convertView.findViewById(R.id.news_listitem_date);listItemView.flag= (ImageView)convertView.findViewById(R.id.news_listitem_flag);5,增加请求数据源的地址:打卡:/src/cn/dlog/app/bean/URLs.java
public final static String DHOST = "www.dlog.cn";private final static String URL_API_DHOST = HTTP + DHOST + URL_SPLITTER;public final static String DIARY_LIST = URL_API_DHOST+"api/diary_list";public final static String DIARY_DETAIL = URL_API_DHOST+"api/diary_detail";6,打开/src/cn/dlog/app/AppContext.java,判断是服务器交互读取还是本机缓存读取的方法
/** * 日记列表 * @param catalog * @param pageIndex * @param pageSize * @return * @throws ApiException */public DiaryList getDiaryList(int catalog, int pageIndex, boolean isRefresh) throws AppException {DiaryList list = null;String key = "diarylist_"+catalog+"_"+pageIndex+"_"+PAGE_SIZE;if(isNetworkConnected() && isRefresh) {try{list = ApiClient.getDiaryList(this, catalog, pageIndex, PAGE_SIZE);if(list != null && pageIndex == 0){Notice notice = list.getNotice();list.setNotice(null);list.setCacheKey(key);saveObject(list, key);list.setNotice(notice);}}catch(AppException e){list = (DiaryList)readObject(key);if(list == null)throw e;}} else {list = (DiaryList)readObject(key);if(list == null)list = new DiaryList();}return list;}/** * 日记详情 * @param diary_id * @param diary_site * @return * @throws ApiException */public Diary getDiary(long diary_id, String diary_site, boolean isRefresh) throws AppException {Diary diary = null;String key = "diary_"+diary_id+"_"+diary_site;if(isNetworkConnected() && (!isReadDataCache(key) || isRefresh)) {try{diary = ApiClient.getDiaryDetail(this, diary_id, diary_site);if(diary != null){Notice notice = diary.getNotice();diary.setNotice(null);diary.setCacheKey(key);saveObject(diary, key);diary.setNotice(notice);}}catch(AppException e){diary = (Diary)readObject(key);if(diary == null)throw e;}} else {diary = (Diary)readObject(key);if(diary == null)diary = new Diary();}return diary;}7,打开: /src/cn/dlog/app/api/ApiClient.java ,增加服务器交互数据读取的方法,即请求URL返回JSON文件的方法。
/** * 获取日记列表 * @param url * @param catalog * @param pageIndex * @param pageSize * @return * @throws AppException */public static DiaryList getDiaryList(AppContext appContext, final int catalog, final int pageIndex, final int pageSize) throws AppException {String diaryUrl = _MakeURL(URLs.DIARY_LIST, new HashMap<String, Object>(){{put("catalog", catalog);put("pageIndex", pageIndex);put("pageSize", pageSize);}});try{return DiaryList.parse(http_get(appContext, diaryUrl));}catch(Exception e){if(e instanceof AppException)throw (AppException)e;throw AppException.network(e);}}/** * 获取日记的详情 * @param url * @param news_id * @return * @throws AppException */public static Diary getDiaryDetail(AppContext appContext, final long diary_id , final String diary_site) throws AppException {String diaryUrl = _MakeURL(URLs.DIARY_DETAIL, new HashMap<String, Object>(){{put("id", diary_id);put("site", diary_site);}});try{return Diary.parse(http_get(appContext, diaryUrl));}catch(Exception e){if(e instanceof AppException)throw (AppException)e;throw AppException.network(e);}}8,开始编写main文件,让前面的准备的代码都运转起来
打开:/src/net/oschina/app/ui/Main.java
PullToRefreshListView控件是用来下拉刷新页面,及达到页面底部自动获取第二页数据的控件方法,效果很不错
ListViewDiaryAdapter 第4步创建的adapter
Handler是当前的线程
private PullToRefreshListView lvDiary;private ListViewDiaryAdapter lvDiaryAdapter;private List<Diary> lvDiaryData = new ArrayList<Diary>();private Handler lvDiaryHandler;
查看onCreate方法,onCreate是当app进入main页面后,这个页面都在处理什么,
所以我们在这里可以看得很清晰
其实是OSCHINA代码注释的很清晰
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //注册广播接收器 tweetReceiver = new TweetReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction("net.oschina.app.action.APP_TWEETPUB"); registerReceiver(tweetReceiver, filter); appContext = (AppContext)getApplication(); //网络连接判断 if(!appContext.isNetworkConnected()) UIHelper.ToastMessage(this, R.string.network_not_connected); //初始化登录 appContext.initLoginInfo();this.initHeadView(); this.initFootBar(); this.initPageScroll(); this.initFrameButton(); this.initBadgeView(); this.initQuickActionGrid(); this.initFrameListView(); //检查新版本 UpdateManager.getUpdateManager().checkAppUpdate(this, false); //启动轮询通知信息 this.foreachUserNotice(); }
this.initHeadView();初始化头部视图
this.initFootBar();初始化底部栏
this.initPageScroll();初始化水平滚动翻页
this.initFrameButton(); 初始化各个主页的按钮(资讯、问答、动弹、动态、留言)
this.initBadgeView(); 初始化通知信息标签控件
this.initQuickActionGrid();初始化快捷栏
this.initFrameListView();初始化所有ListView
那么我们主要是做 LISTVIEW,点击这个方法查看:
/** * 初始化所有ListView */ private void initFrameListView() { //初始化listview控件this.initNewsListView();this.initBlogListView();this.initQuestionListView();this.initTweetListView();this.initActiveListView();this.initMsgListView();//加载listview数据this.initFrameListViewData(); } /** * 初始化所有ListView数据 */ private void initFrameListViewData() { //初始化Handler lvNewsHandler = this.getLvHandler(lvNews, lvNewsAdapter, lvNews_foot_more, lvNews_foot_progress, AppContext.PAGE_SIZE); lvBlogHandler = this.getLvHandler(lvBlog, lvBlogAdapter, lvBlog_foot_more, lvBlog_foot_progress, AppContext.PAGE_SIZE); lvQuestionHandler = this.getLvHandler(lvQuestion, lvQuestionAdapter, lvQuestion_foot_more, lvQuestion_foot_progress, AppContext.PAGE_SIZE); lvTweetHandler = this.getLvHandler(lvTweet, lvTweetAdapter, lvTweet_foot_more, lvTweet_foot_progress, AppContext.PAGE_SIZE); lvActiveHandler = this.getLvHandler(lvActive, lvActiveAdapter, lvActive_foot_more, lvActive_foot_progress, AppContext.PAGE_SIZE); lvMsgHandler = this.getLvHandler(lvMsg, lvMsgAdapter, lvMsg_foot_more, lvMsg_foot_progress, AppContext.PAGE_SIZE); //加载资讯数据 if(lvNewsData.isEmpty()) { loadLvNewsData(curNewsCatalog, 0, lvNewsHandler, UIHelper.LISTVIEW_ACTION_INIT); } }
this.initFrameListViewData(); //加载listview数据
loadLvNewsData(curNewsCatalog, 0, lvNewsHandler, UIHelper.LISTVIEW_ACTION_INIT); //加载资讯数据
这样整个main文件的结构大家应该清晰了,我们只需要增加两个方法,
一个是this.initDiaryListView();
一个是 loadLvDiaryData();
代码如下:
/** * 初始化日记列表 */ private void initDiaryListView() { lvDiaryAdapter = new ListViewDiaryAdapter(this, lvDiaryData, R.layout.diary_listitem); lvDiary_footer = getLayoutInflater().inflate(R.layout.listview_footer, null); lvDiary_foot_more = (TextView)lvDiary_footer.findViewById(R.id.listview_foot_more); lvDiary_foot_progress = (ProgressBar)lvDiary_footer.findViewById(R.id.listview_foot_progress); lvDiary = (PullToRefreshListView)findViewById(R.id.frame_listview_diary); lvDiary.addFooterView(lvDiary_footer);//添加底部视图 必须在setAdapter前 lvDiary.setAdapter(lvDiaryAdapter); lvDiary.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //点击头部、底部栏无效 if(position == 0 || view == lvDiary_footer) return; Diary diary = null; //判断是否是TextView if(view instanceof TextView){ diary = (Diary)view.getTag(); }else{ TextView tv = (TextView)view.findViewById(R.id.diary_listitem_title); diary = (Diary)tv.getTag(); } if(diary == null) return; //跳转到日记详情 UIHelper.showDiaryRedirect(view.getContext(), diary); } }); lvDiary.setOnScrollListener(new AbsListView.OnScrollListener() {public void onScrollStateChanged(AbsListView view, int scrollState) {lvDiary.onScrollStateChanged(view, scrollState);//数据为空--不用继续下面代码了if(lvDiaryData.isEmpty()) return;//判断是否滚动到底部boolean scrollEnd = false;try {if(view.getPositionForView(lvDiary_footer) == view.getLastVisiblePosition())scrollEnd = true;} catch (Exception e) {scrollEnd = false;}int lvDataState = StringUtils.toInt(lvDiary.getTag());if(scrollEnd && lvDataState==UIHelper.LISTVIEW_DATA_MORE){lvDiary.setTag(UIHelper.LISTVIEW_DATA_LOADING);lvDiary_foot_more.setText(R.string.load_ing);lvDiary_foot_progress.setVisibility(View.VISIBLE);//当前pageIndexint pageIndex = lvDiarySumData/AppContext.PAGE_SIZE;loadLvDiaryData(curDiaryCatalog, pageIndex, lvDiaryHandler, UIHelper.LISTVIEW_ACTION_SCROLL);}}public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {lvDiary.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);}}); lvDiary.setOnRefreshListener(new PullToRefreshListView.OnRefreshListener() { public void onRefresh() { loadLvDiaryData(curDiaryCatalog, 0, lvDiaryHandler, UIHelper.LISTVIEW_ACTION_REFRESH); } }); }/** * 线程加载日记数据 * @param catalog 分类 * @param pageIndex 当前页数 * @param handler 处理器 * @param action 动作标识 */ private void loadLvDiaryData(final int catalog,final int pageIndex,final Handler handler,final int action){ mHeadProgress.setVisibility(ProgressBar.VISIBLE); new Thread(){ public void run() { Message msg = new Message(); boolean isRefresh = false; if(action == UIHelper.LISTVIEW_ACTION_REFRESH || action == UIHelper.LISTVIEW_ACTION_SCROLL) isRefresh = true; try { DiaryList list = appContext.getDiaryList(catalog, pageIndex, isRefresh); msg.what = list.getPageSize(); msg.obj = list; } catch (AppException e) { e.printStackTrace(); msg.what = -1; msg.obj = e; } msg.arg1 = action; msg.arg2 = UIHelper.LISTVIEW_DATATYPE_DIARY; if(curDiaryCatalog == catalog) handler.sendMessage(msg); } }.start(); }修改initFrameListView和initFrameListViewData方法
/** * 初始化所有ListView */ private void initFrameListView() {this.initDiaryListView();this.initFrameListViewData(); } /** * 初始化所有ListView数据 */ private void initFrameListViewData() { //初始化Handler lvDiaryHandler = this.getLvHandler(lvDiary, lvDiaryAdapter, lvDiary_foot_more, lvDiary_foot_progress, AppContext.PAGE_SIZE); //加载资讯数据 if(lvDiaryData.isEmpty()) {loadLvDiaryData(curDiaryCatalog, 0, lvDiaryHandler, UIHelper.LISTVIEW_ACTION_INIT);} }
大功告成:RUN AS测试,效果如下:
当原理熟悉了以后,剩余的只是机器人工作了
可以使用同样的方法,制作其他分类板块的LISTVIEW数据
更多相关文章
- 查看Sqlite 数据库
- [对android程序作代码混淆]
- Android 多媒体数据库
- Android SQLiteOpenHelper Sqlite数据库的创建与打开
- Android GSM驱动模块(rild)详细分析(一)基本架构及初始化
- Android代码混淆配置(Proguard文件解析)
- 使用android快速开发框架afinal的FinalDb操作android sqlite数据