這個例子是我去年鑽研Android應用時透過Google搜尋找到的,此文章源始於對岸大陸http://blog.csdn.net/caowenbin,自認學習Android也有些時日及一定功力,當時乍然一看到此範例之前段說明時感覺有點驚奇,因此對大陸有些從事IT程式開發人員改觀且刮目相看,在本島上個人週遭所認識很多的電腦人本位主義相當重,很少像大陸IT如此熱衷及分享研究心得,說真的,已逐漸感受到台灣技術已被對岸超出甚多,對未來想從事程式開發的人期能急起直追趕上進度,否則市場總有一天會被對岸的人所瓜分取代的(此為作者長期觀察大陸最近幾年技術快速猛進所提之肺腑之言,供各位深思之)。
回到正題,此例當時花了幾天時間徹底研讀過,除對源碼中增加注釋外,其餘均不作更動(※尊重原始文章作者之著作權),只是將此篇文章經個人消化後去蕪存菁加以整理為日後作開發之參考,在此提到個人對此範例深究後之觀點:
² 此範例從始至末原作者對概念及程式代碼上均交待得非常清楚,但不適合初等學習者,換言之,須具備一定開發經驗者才能探究其真正意義所在。(很多稍具Android基礎的開發者無法一下子解讀程式之某些特點,經個人詳加說明後才壑然開朗其整體設計思路)
² 強烈建議未來想從事Android程式開發人員學習此範例,此會對日後程式開發上(指的是對解決問題之邏輯分析及程式代碼之構建)絕對有莫大的助益,個人也將此範例作整理為一個教學專題提供給學員作觀摩,藉此給想成為一位專業且出色之程式設計員應訓練至此境界才能於市場立足。
下圖為使用對岸”新浪微博手機用戶端Android版本”察看新浪微博網頁所呈現之介面:
從圖中之主體可觀察出其清單(ListView)方塊部份包含很複雜的部分,既要能顯示頭像、微博內容,又要能在網頁內容中顯示表情、圖片、@某人、URL等元素混雜在一起。下圖為仿照此介面所開發出之運行結果畫面,其內所用的圖片均來自新浪微博。
茲將此程式之整體開發流程分述如下:
Ø 首先分析原網頁介面將其切割找出所包含之資料部份,主要的資料抽象可定義為Site(網站)、Blog(博文)、User(用戶),這些資料模型之相依性及其具體成員定義如下圖所示:
Ø 定義資料模型之代碼
資料模型找出之後,接著為這些資料模型建構其類別代碼使之成為可供後續開發使用之基本元件,各資料模型之類別代碼列述如下:
n User.class
package com.wenbin.test.site;
public class User{
private
String
profileImageUrl="http://tp3.sinaimg.cn/1500460450/50/1289923764/0"; //瀏覽特定網頁
private
String screenName="测试";
private
boolean verified=false;
public
User(){ //建構函式
}
public
String getProfileImageUrl(){ //取得網頁URL
return
profileImageUrl;
}
public
String getScreenName(){ //取得畫面名稱
return
screenName;
}
public void
setProfileImageUrl(String profileImageUrl) {
//設定網頁URL
this.profileImageUrl
= profileImageUrl;
}
public
void setScreenName(String screenName) {
//設定畫面名稱
this.screenName
= screenName;
}
public void
setVerified(boolean verified) { //設定識別條件
this.verified
= verified;
}
public
boolean isVerified(){ //判斷識別條件
return
verified;
}
}
n Blog.class
package com.wenbin.test.site;
import java.util.Date;
public class Blog
implements Comparable<Blog>{
private Date createAt=new
Date(System.currentTimeMillis()); //取得系統目前日期
private Blog
retweetedBlog;
private
String text="就算把我打的遍体鳞伤也见不得会[泪]?http://blog.csdn.net/caowenbin
@移动云_曹文斌 。";
//測試用之字串資料
//測試用之字串資料
private
String smallPic="";
private
String source="IE9";
private User
user;
private Site
site;
public
Blog(){ //建構函式1
}
public
Blog(Site site){ //建構函式2
this.site=site;
}
public
boolean isHaveRetweetedBlog(){ //判斷識別條件
return
retweetedBlog!=null;
}
public Blog
getRetweetedBlog(){ //取得RetweetedBlog資料
return
retweetedBlog;
}
public
String getText(){ //取得測試之字串資料
return
text;
}
public User
getUser(){ //取得用戶資訊
return
user;
}
public
String getSmallPic(){ //取得SmallPic資料
return
smallPic;
}
public void
setRetweetedBlog(Blog retweetedBlog) {
//設定RetweetedBlog資料
//設定RetweetedBlog資料
this.retweetedBlog
= retweetedBlog;
}
public void
setText(String text) { //設定測試之字串資料
this.text
= text;
}
public
String getInReplyUserScreenName(){ //取得畫面名稱
if
(retweetedBlog!=null && retweetedBlog.getUser()!=null)
return
retweetedBlog.getUser().getScreenName();
else
return
"";
}
public
String getInReplyBlogText(){ //取得Blog內容
if
(retweetedBlog!=null)
return
retweetedBlog.getText();
else
return
"";
}
public void
setPic(String smallPic){ //設定SmallPic
this.smallPic=smallPic;
}
public void
setUser(User user) { //設定用戶資訊
this.user
= user;
}
public int
compareTo(Blog another) { //判斷比較Blog建置之先後
int
ret=0;
if
(this.createAt.before(another.createAt)){
ret=-1;
}
else
if (this.createAt.after(another.createAt)){
ret=1;
}
else{
ret=0;
}
return
ret;
}
public void
setSource(String source) { //設定資源
this.source
= source;
}
public
String getSource() { //取得資源
return
source;
}
public void
setSite(Site site) { //設定網站
this.site
= site;
}
public Site
getSite() { //取得網站
return
site;
}
}
n Site.class
package com.wenbin.test.site;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
public abstract
class Site{
protected
Set<Blog> blogs=new TreeSet<Blog>();
protected
String name;
protected
Map<String,String> faceMap=new HashMap<String,String>();
public
Site() { //建構函式
onConstruct(); //呼叫自訂函式
}
protected abstract void onConstruct(); //由繼承之子類別實作之
public
Map<String, String> getFaceMap() {//取得Face圖形資訊集合
return
faceMap;
}
public
Set<Blog> getBlogs(){ // 取得Blog物件集合
return
blogs;
}
public long
getBlogsCount(){ //取得Blog物件之數目
return
blogs.size();
}
public void
addBlog(Blog blog){ //加入Blog物件
blogs.add(blog);
}
public void
removeBlog(Blog blog){ //移除Blog物件
blogs.remove(blog);
}
public
Iterator<Blog> getBlogsIterator(){//取得Blog集合之Iterator
return
blogs.iterator();
}
public void
clearBlogs(){ //清除Blog集合
blogs.clear();
}
public
String getName(){ //取得Site名稱
return
name;
}
}
n SinaSite.class
package com.wenbin.test.site;
public class SinaSite extends
Site {
protected
void onConstruct(){ //實作繼承父類別之抽象函式
name="新浪微博";
initFaceMap(); //自訂方法(初始化Face資訊)
}
private void initFaceMap(){ //建立Face 資訊
faceMap.put("[呵呵]", "hehe");
faceMap.put("[嘻嘻]", "xixi");
faceMap.put("[哈哈]", "haha");
faceMap.put("[爱你]", "aini");
faceMap.put("[晕]", "yun");
faceMap.put("[泪]", "lei");
}
}
上述所建之資料模型(類別)最主要可作為應用之基本元件來使用。
Ø 介面設計:綜觀整個介面,可分成上下兩部份,其中上層為操作條,下層則為主體的清單(ListView)方塊,分別將其特性列述如下:
上層之操作條可分解成三個部分,其中左側為微博按鈕,中間為用戶名稱顯示,右側則為刷新按鈕。其中兩個按鈕採用相同之設計風格,分別帶有常規和按下兩種狀態,欲達此目的之作法可如下:
1.
在drawable資料夾下建立兩個xml文件,分別對應了兩個按鈕;
2.
每個xml檔中使用selector標籤定義常規狀態和按下狀態之兩個圖片資源;
3.
在Activity的主佈局中使用ImageButton控制項,於屬性定義中指定按鈕的background為透明,並指定src為之前定義的兩個xml。
下面是微博按鈕xml檔的內容:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/title_new_selected" />
<item android:drawable="@drawable/title_new_normal" />
</selector>
而下面是刷新按鈕xml檔的內容:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/title_reload_selected" />
<item android:drawable="@drawable/title_reload_normal" />
</selector>
接著在main.xml檔中進行必要元件安排之佈局規劃,對於這三個控制項元素而言,因其彼此間有明確的相對位置關係,故採用RelativeLayout較合適,其xml檔之內容如下所示:
Main.xml
<RelativeLayout
android:layout_width="fill_parent" android:layout_height="44dp"
android:background="@drawable/titlebar_lightgray_bg" android:orientation="horizontal">
<ImageButton android:id="@+id/BtnWrite"
android:layout_width="wrap_content" android:layout_height="fill_parent"
android:layout_alignParentLeft="true" android:background="@android:color/transparent"
android:src="@drawable/write_button">
</ImageButton>
<TextView android:id="@+id/TextViewUsername"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:textColor="@color/black" android:gravity="center" android:textSize="18sp">
</TextView>
<ImageButton android:id="@+id/BtnRefresh"
android:layout_width="wrap_content" android:layout_height="fill_parent"
android:layout_alignParentRight="true" android:background="@android:color/transparent"
android:src="@drawable/refresh_button">
</ImageButton>
</RelativeLayout>
在此須指定RelativeLayout的background為背景圖片,使用到之圖片列述如下:
在main.xml主佈局中以ImageButton及TextView控制項和RelativeLayout進行UI規劃之先期工作,接著要進行主體格局下層之規劃。回顧新浪微博的瀏覽介面可知其應用了ListView控制項,但仔細瞧瞧覺得在ListView中又難以實現這種圖文並茂且具有佈局要求形態之複雜顯示,另注意到此清單方塊的第一item項和最後一item項是固定的,分別放置了“刷新”和“更多”兩個item,無論清單方塊內有多少項,這兩個Item必存在於畫面上。故掌握思路為:保持這兩個Item作為清單方塊的一個不變的組成部分,而其它則用於填充資料部份。
欲實作上述所提而想出之解決方案為:基於上述觀察與分析,可通過為ListView添加HeaderView和FooterView來解決這兩個特殊的item問題,如此便可依實際需求進行這兩個View之佈局規劃,底下分別列述其 xml檔之規劃內容:
n bloglistheader.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:gravity="center"
android:background="@color/white">
<TextView android:id="@+id/textHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:layout_gravity="center_horizontal"
android:text="@string/refresh"
android:textColor="@color/black"
android:textSize="15sp">
</TextView>
</LinearLayout>
n bloglistfooter.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:gravity="center"
android:background="@color/white">
<TextView android:id="@+id/textfooter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:layout_gravity="center_horizontal"
android:text="@string/more"
android:textColor="@color/black"
android:textSize="15sp">
</TextView>
</LinearLayout>
有了此兩個佈局檔,就可以依實際需求彈性設計自訂ListView,故在此新建一個繼承自ListView 之BlogListView.class,並在該控制項完成佈局時加入上述之兩個佈局以達擴建格局之功能,此BlogListView類的代碼如下:
package com.wenbin.test;
import com.wenbin.test.site.Site;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ListView;
public class BlogListView extends
ListView {
private Site
site;
public
BlogListView(Context context) { //建構函式1
super(context);
}
public BlogListView(Context context, AttributeSet attrs) {
//建構函式2
super(context,
attrs);
}
public
BlogListView(Context context, AttributeSet attrs, int defStyle) { //建構函式3
super(context,
attrs, defStyle);
}
@Override
protected void
onFinishInflate() { //覆寫方法
super.onFinishInflate();
LayoutInflater li=LayoutInflater.from(getContext());
View headerView=li.inflate(R.layout.bloglistheader, null);
headerView.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
refresh(); //更新方法
}
});
View footerView=li.inflate(R.layout.bloglistfooter, null);
footerView.setOnClickListener(new
OnClickListener(){
@Override
public
void onClick(View v) {
more(); //再次更新方法
}
});
addHeaderView(headerView);
addFooterView(footerView);
}
public void
init(Site site){ //初始化Site之自訂方法
this.site=site;
if
(site!=null && site.getBlogsCount()>0){
setAdapter(new
BlogAdapter(site.getBlogs(),getContext()));
}
}
public void refresh() {
//TODO:
}
public void more(){
//TODO:
}
public Site
getSite(){ //取得Site 物件
return
site;
}
}
由於是自訂控制項,所以要在main.xml中加入它的話,得把佈局修改成如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent" android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<RelativeLayout
android:layout_width="fill_parent" android:layout_height="44dp"
android:background="@drawable/titlebar_lightgray_bg" android:orientation="horizontal">
<ImageButton android:id="@+id/BtnWrite"
android:layout_width="wrap_content" android:layout_height="fill_parent"
android:layout_alignParentLeft="true" android:background="@android:color/transparent"
android:src="@drawable/write_button">
</ImageButton>
<TextView android:id="@+id/TextViewUsername"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:textColor="@color/black" android:gravity="center" android:textSize="18sp">
</TextView>
<ImageButton android:id="@+id/BtnRefresh"
android:layout_width="wrap_content" android:layout_height="fill_parent"
android:layout_alignParentRight="true" android:background="@android:color/transparent"
android:src="@drawable/refresh_button">
</ImageButton>
</RelativeLayout>
<com.wenbin.test.BlogListView android:id="@+id/sinaList" android:layout_width="fill_parent" //自訂控制項之標籤
android:layout_height="fill_parent" android:clickable="true"
android:divider="@drawable/divider" android:dividerHeight="1dp">
</com.wenbin.test.BlogListView>
</LinearLayout>
其中之divider屬性是定義Item間的分隔條的。此時若模擬運行會發現其結果並沒有顯示出清單方塊,此是因為沒有資料可顯示所造成,等之後添加正式資料便會顯示結果資訊。在此提一個在開發中很重要之概念:當Google提供之標準控制項無法達到應用之設計需求時,可直接繼承該類別後再分別覆寫其內之必要方法以彈性擴增不足之功能,此種作法是開發應用工程師必須瞭解之技術,此是很受用之竅門,要學會它。
<<下回再續>>








0 意見:
張貼留言