前兩天電腦壞掉了,導致這一兩天沒有更新。發現之前寫的幾篇博客浏覽量已經接近1000,有種受寵若驚的感覺,寫這個系列的博客主要是假期想干一些平時沒有時間做的興趣愛好,並把自己的一點一滴記錄下來。這一段時間由於各種原因以致於更新速度很慢,之前的打算是先把QQ2013的Activity的布局搭建出來,然後再設計其中的核心代碼,如網絡編程,搭建一個小型服務器,進而實現QQ基本的功能,從現在的進度來看這個計劃可能完不成了。不過我仍然會繼續做這個項目,相信就算不會徹底完成也能夠搭出漂亮的QQ框架來。
進入正題。上一節結束時提到QQ聯系人列表界面還有一個效果沒有實現,這個效果是當聯系人一級條目滑動到屏幕的頂端時,該一級條目就會懸停在屏幕的頂端,當往下拉時又會隨著ExpandableListView的Item正常移動,如官方效果圖圖所示:
注意觀察“我的好友”那一欄,本節實現的即上述效果。
首先講一下思路:
1、不能使用onScroll()方法,這一方法會在ExpandableListView展開的時候調用,至於其調用的周期並不是太明確,但可以確定的是onScroll並不能直接實現上述效果。
2、這裡應該獲得ScrollView的實例(findViewById方法),然後調用setOnTouch()方法,在setOnTouch()方法中進一步進行設置。
3、這裡的基本思路是:當拖動屏幕時,setOnTouch方法被調用,在該方法中開辟一個子線程,這個子線程用於檢測屏幕中的ScrollView對象是否仍在運動,直到不再拖動屏幕或者屏幕上的控件在慣性的作用下運動至停止時,該線程才會自動終結。在該線程中判斷一級條目(這裡用groupItem表示)是否向上滾出了屏幕。
4、在該Activity的主布局文件中ScrollView的後面添加一個LinearLayout控件,該控件的布局和GroupItem的布局文件的內容是一樣的,將其位置設置為至於屏幕(父控件)的頂端,平時設置其Visibility為View.GONE 不可見不可操作狀態,當某一個已經展開(expanded)的groupItem滾出了屏幕時,就將該隱藏的控件設置為可見,並將其內容設置為和剛剛滾出屏幕的groupItem的內容一摸一樣。若所有的groupItem都為滑出屏幕,那麼將該控件重新設置為不可見(GONE);
5、本節中遇見的最大的障礙就是我沒有找到獲得每一個groupItem在屏幕上的實時位置的方法,所以這裡對原來的groupData對象做了修改,在HashMap對象中新添加了一個鍵值對 key="location" value= 該groupItem在ExpandableListView中的相對縱坐標。在後在上面創建的坐標檢測子線程中獲得groupData中存放的groupItem的相對坐標再加上ExpandableListView的絕對縱坐標,然後判斷groupItem是否滑出了屏幕,進而對“隱藏控件”執行相應的操作。注意這裡應該先判斷groupPosition靠後的groupItem的坐標,懸停的控件顯示的是最後滑出屏幕的groupItem的信息。
6、本節將Activity的標題去掉了,去掉的方法是在onCreate()方法中 setContentView之前(必須是之前)調用requestWindowFeature(Window.FEATURE_NO_TITLE);
7、本節中需要在子線程中修改MainActivity 安卓UI,而直接修改是不允許的,所以需要使用一個自定義的Handler對象,調用父類的構造方法
public MyHandler(Looper looper){
super(looper);
}
並復寫handleMessage()方法修改MainActivity的UI。
關於Android消息處理機制我目前不想再多講,做開發的話夠用就行,沒必要掌握的太徹底,如果想對Handler有一個比較好的了解,可以參考:
http://blog.csdn.net/zkdemon/article/details/6914161
8、ExpandableListView可以調用collapseGroup(int groupPosition)或者ExpandGroup(int groupPosition)強制的關閉或者展開一個Group。這對於實現懸浮控件的點擊事件具有重要作用!
鑒於代碼量越來越多了,這裡先把修改的地方列出來:
主布局文件:
復制代碼
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f1f1f1"
tools:context=".MainActivity"
android:scrollbars="vertical"
android:scrollbarAlwaysDrawVerticalTrack="true" >
<ScrollView
android:id="@+id/qqlist_scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadingEdge="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ListView
android:id="@+id/qqlist_classify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#000000"
android:dividerHeight="0px"
android:fadingEdge="none"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/qqlist_classify_text"
android:text="好友分組"
android:textSize="6pt"
android:textColor="#666666"
android:padding="4dip"/>
<ExpandableListView
android:id="@+id/qq_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#888888"
android:dividerHeight="0px"
android:fadingEdge="none"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/qqlist_classify_text"
android:text="生活服務"
android:textSize="6pt"
android:textColor="#666666"
android:padding="4dip"/>
<ListView
android:id="@+id/qqlist_classify_down"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#000000"
android:dividerHeight="0px"/>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dip"
android:id="@+id/titleGroupView"
android:orientation="horizontal"
android:visibility="gone"
android:background="@drawable/list_item_border">
<ImageView
android:id="@+id/title_groupImage"
android:layout_width="match_parent"
android:layout_height="16dip"
android:layout_weight="1.8"
android:layout_gravity="center"
android:background="@drawable/todown" />
<TextView
android:id="@+id/title_groupName"
android:layout_width="match_parent"
android:layout_height="42dip"
android:layout_weight="1"
android:paddingLeft="15dip"
android:paddingTop="11dip"
android:textSize="7pt"
android:text="fdg" />
<TextView
android:id="@+id/title_childCount"
android:layout_width="match_parent"
android:layout_height="42dip"
android:layout_weight="1"
android:gravity="right"
android:paddingRight="10dip"
android:paddingTop="11dip"
android:text="ewsf"/>
</LinearLayout>
</RelativeLayout>
復制代碼
展開或者關閉group時都會對每一個groupItem的縱坐標進行更新。
復制代碼
@Override
public void onGroupExpanded(int groupPosition) {
// TODO Auto-generated method stub
super.onGroupExpanded(groupPosition);
/**
* 更新groupItem的相對坐標
*/
groupData.get(groupPosition).put("expanded", true);
for(int i=groupPosition+1; i<groupData.size(); i++){
groupData.get(i).put("location",(Integer)groupData.get(i).get("location")+childData.get(groupPosition).size()*CHILD_HEIGHT );
}
float deviationFix=0; //對ExpandableListView高度的誤差修正
for(int i=0;i<groupData.size() ;i++){
deviationFix +=(Integer)groupData.get(i).get("location")/CHILD_HEIGHT*0.5;
}
height+=childData.get(groupPosition).size()*CHILD_HEIGHT;
ViewGroup.LayoutParams params= exListView.getLayoutParams();
params.height=height-floatToInt(deviationFix);
exListView.setLayoutParams(params);
}
@Override
public void onGroupCollapsed(int groupPosition) {
// TODO Auto-generated method stub
super.onGroupCollapsed(groupPosition);
height=height-childData.get(groupPosition).size()*CHILD_HEIGHT;
ViewGroup.LayoutParams params= exListView.getLayoutParams();
params.height=height;
exListView.setLayoutParams(params);
/**
* 更新groupItem的相對坐標
*/
groupData.get(groupPosition).put("expanded", false);
for(int i=groupPosition+1; i<groupData.size(); i++){
groupData.get(i).put("location",(Integer)groupData.get(i).get("location")-childData.get(groupPosition).size()*CHILD_HEIGHT );
}
}
復制代碼
對ScrollView設置監聽器:
復制代碼
qqScroll.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==MotionEvent.ACTION_MOVE && isChecking==false){
new LocationCheckThread().start();
}
return false;
}
});
復制代碼
坐標檢測線程:
復制代碼
/**
* 實現這個Activity的最後一個效果,即滑動到某一個Group那麼改GroupItem就在屏幕頂端懸停的效果
* 方法:需要使用onTouchEvent方法,這個比用onScrollView方法靠譜
*/
private boolean isChecking=false;
private class LocationCheckThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
isChecking=true;
int[] location=new int[2];
int beforeY=0;
int i;
ExpandableHandler mHandler=new ExpandableHandler(Looper.getMainLooper());
while(true){
//exListView.getLocationOnScreen(location);
exListView.getLocationOnScreen(location);
if(beforeY==location[1]){ //控件停止運動,線程關閉
isChecking=false;
return;
}
else{
beforeY=location[1];
for(i=groupData.size()-1; i>=0; i--){
if((Boolean)groupData.get(i).get("expanded")&&(Integer)groupData.get(i).get("location")+location[1]<=24){
Message msg=new Message();
msg.arg1=childData.get(i).size();
msg.arg2=i;
msg.obj=groupData.get(i).get("groupName");
msg.what=VISIBILITY_VISIBLE;
mHandler.sendMessage(msg);
break;
}
}
if(i<0){
Message msg=new Message();
msg.what=VISIBILITY_GONE;
msg.obj=""; //必須加這一步,否則會有空指針異常
mHandler.sendMessage(msg);
}
}
try {
this.sleep(80); //sleep的時間不能過短!!
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
復制代碼
在線程中判斷GroupItem是否有滑動出屏幕的,創建Message對象,綁定響應的groupItem的信息(position,Name,childCount,是否顯示Visibility),然後由Handler對象發出[sendMessage();] 注意sleep的時間不能過短,因為當手指不再脫離屏幕而ListView 自由滑動時,由於滑動的速度較慢,導致兩次檢測坐標是結果一樣,從而意外的終止線程。
isChecking是boolean型數據,標記線程工作的狀態,避免在滑動的過程中同時開辟多個子線程產生異常。
然後是MyHandler類:
復制代碼
public class ExpandableHandler extends Handler{
public ExpandableHandler(Looper looper){
super(looper);
}
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
int listNum=msg.arg1;
int visibility=msg.what;
int groupPos=msg.arg2;
String groupName=msg.obj.toString();
if(visibility==VISIBILITY_GONE){
titleGroupView.setVisibility(View.GONE);
return;
}
else{
titleGroupView.setVisibility(View.VISIBLE);
titleGroupName.setText(groupName);
titleChildCount.setText(""+listNum);
titleGroupView.setTag(groupPos); //給這個View控件設置一個標簽屬性,用於存放groupPosition
/**
* setText()中的參數不能使int型!!
*/
}
}
}
復制代碼
創建MyHandler對象,在其中復寫handleMessage();方法,在該方法中設置懸浮控件的顯示效果(更新系統UI)
最後還需要實現一個效果,當懸浮控件出現時,點擊懸浮控件,會把該控件對應的groupItem關掉(collapseGroup),因此需要設置一個點擊監聽器:
復制代碼
titleGroupView.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
int groupPosition=(Integer)titleGroupView.getTag();
exListView.collapseGroup(groupPosition);
new LocationCheckThread().start();
}
});
復制代碼
注意這裡又一次啟動了坐標檢測線程,是因為需要在調用collapseGroup之後對系統的UI做一次刷新,避免產生顯示異常。