文档章节

第二章:根据oschina开源的app代码快速构建自己站点的ANDROID APP

大猪
 大猪
发布于 2012/12/10 01:06
字数 2268
阅读 1531
收藏 23

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,我们先去看看WEB SERVICE,创建交互数据使用的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);
					//当前pageIndex
					int 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数据








© 著作权归作者所有

共有 人打赏支持
大猪
粉丝 48
博文 20
码字总数 27656
作品 0
广州
产品经理
私信 提问
加载中

评论(4)

多多de棉花糖
多多de棉花糖
怎么减少模块呢,我现在减少一个,就启动不起来了
多多de棉花糖
多多de棉花糖
讲解的很清晰
华中湖北人才培训
华中湖北人才培训
顶起
FoxHu
FoxHu
不错,学习了!
通过Gradle自动实现Android组件化模块构建

原文博客地址:Tangpj 不管全世界所有人怎么说,我都认为自己的感受才是正确的。无论别人怎么看,我绝不打乱自己的节奏。喜欢的事自然可以坚持,不喜欢的怎么也长久不了。 —— 村上春树 背景...

Tangpj
2018/07/24
0
0
越写越快乐之如何快速开始开发一款Android应用

今天的越写越快乐系列大家带来工作上的一些事,关于如何快速开发一款Android应用,也就是如何利用框架快速搭建你的App应用架构,可以快速地入手,只关注你的业务逻辑,其他事项交由现有的框架...

韬声依旧在路上
01/21
0
0
OSChina 客户端第二弹 - iPhone 版本

上周我们发布了 OSChina Android 客户端,其实 iPhone 版本的也做好了,无奈水果店审批比较慢,不懂何时 App Store 上才能审核通过。 但是年轻的心灵寂寞难耐,按耐不住,于是决定发布 ipa ...

虫虫
2012/05/07
9.9K
92
使用calabash测试开源中国Android客户端

Calabash-android是支持android的UI自动化测试框架,前面已经介绍过《中文Win7下成功安装calabash-android步骤》,这篇博文尝试测试一个真实应用:开源中国客户端。目的是和大家一起学习cal...

超爱fitnesse
2014/09/01
0
3
30多个Android 开发者工具 带你开发带你飞

FlowUp 这是一个帮助你跟踪app整体性能的工具,深入分析关键的性能数据如FPS, 内存, CPU, 磁盘, 等等。FlowUp根据用户数量收费。 Stetho 由Facebook开发的一个强大的开源Android debug平台,...

猴亮屏
2017/10/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

深入 理解char * ,char ** ,char a[ ] ,char *a[] 的区别

C语言中由于指针的灵活性,导致指针能代替数组使用,或者混合使用,这些导致了许多指针和数组的迷惑,因此,刻意再次深入探究了指针和数组这玩意儿,其他类型的数组比较简单,容易混淆的是字...

天王盖地虎626
20分钟前
1
0
关于我这三年的架构历程(待完成)

从16年7月实习至今,快三年的开发经历中,经手了好几个项目。目前有幸作为一个项目的负责人,完成了一个项目的完全架构设计。因此想记录下这份架构设计中的点点面面。 总架构: 基于DNS的负载...

赵熠熠
21分钟前
0
0
springboot 使用 flyway 进行数据库版本管理

要在启动时自动运行Flyway数据库迁移,请将其添加 org.flywaydb:flyway-core到类路径中。 迁移是表单中的脚本V<VERSION>__<NAME>.sql(使用<VERSION>下划线分隔的版本,例如“1”或“2_1”)...

NotFound403
40分钟前
4
0
spring 5.1.5版本(二)

spring 5.1.5版本(一) spring 5.1.5版本(二) spring 5.1.5版本(三) 对象创建方式 方式一 applicationContext.xml <?xml version='1.0' encoding='UTF-8'?><beans xmlns="http://ww......

gwl_
42分钟前
0
0
CMake生成Mingw用的Make文件

CMake 在win下 默认会生成vc++的nmake用的make 当没安装时 就会报 -- Building for: NMake Makefiles -- The C compiler identification is unknown -- The CXX compiler identification is......

shzwork
59分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部