今天繼續進行QQ界面的開發工作。前一段時間講過ExpandableListView的使用並且設置了一個比較簡單的具有子菜單效果的聯系人列表,本節添加進ScrollView控件,對QQ2013版的聯系人界面進行仿真。當然本質上是做一些美化工作,但是對於掌握Android界面的開發是很重要的,一個好看的界面對於手機應用至關重要。
首先上圖:
這是官方聯系人效果圖:
這是我自己做的效果圖:
官方版的最外面是一個TabView控件,本節先不考慮,可見和原版的效果還是比較相似的。(圖標是我通過畫圖軟件截過來的圖,鑒於這只是交流學習,算不上侵權~)
談一下本節所涉及的一些知識點。
一、layer-list的使用方法
layer-list的作用是疊放多個圖層,這從名字上就可以看出,因為本節想做出一個一邊沒有邊框,其它邊界上沒有邊框的背景效果,使用<shape > 中的<stroke/>標簽就沒有辦法滿足了,解決的辦法就是在drawable 文件夾中創建一個layer-list文件,然后創建兩個圖層<item> 第一個item放的是邊框的顏色,第二個圖層放的是內部的顏色,並且通過設置第二個item的根標簽屬性,設置第二個item比第一個item窄的程度,這樣第一個item的顏色就會在邊框處顯示出來,從而做出邊框的效果。
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item > <shape > <solid android:color="#cccccc"/> </shape> <!--上面是第一個圖層--> </item> <item android:left="1dip" android:top="1dip" android:right="1dip" android:bottom="0dip">
<!-- 上面一行是設置哪個邊框的關鍵步驟,即第二個圖層比第一個圖層閃出來的邊框! --> <shape > <solid android:color="#f0f0f0"/> </shape> </item> </layer-list>
做這個有什么用呢?系列(十一)中兩個Edittext重合的部分顏色很深,影響美觀,因此可以通過創建layer-list文件,使得第一個EditText的下邊框寬度為0,從而避免顏色加深的情況:
這里僅給出修改后的效果圖:
二、如何理解ExpandableListView的生命周期?
對於ExpandableListView,當剛剛創建該對象並且設置好適配器之后,EListView的每一個group都是沒有展開的,當點擊某一個group時,系統會把所有的group 和展開的childItem 重新繪制一遍,其中每一個的group 的最后一個childItem放的isLastChild 為true.
當有group展開或者關閉時會調用onGroupExpanded()和onGroupCollapsed() 方法。
三、如何動態獲得ExpandableListView的高度?
創建一個一個int 變量 height, 在ExpandableListView剛剛創建時,設置其高度為所有group的寬度之和,然后當打開或者關閉group時,在onGroupExpanded()和onGroupCollapsed() 方法中對height 的值進行增加或者減少,並且通過LayoutParams對象即使更新空間的高度,從而實現動態更新ExpandableListView的高度。
具體代碼如下:
1、獲得ListItem的高度,這里比如獲得group的高度,需要調用適配器的getGroupView的方法
View listItem= listView.getExpandableAdapter().getGroupView(參數);
listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
//這一個必須有,因為listItem 在通過inflate()方法獲得時並未指定父控件,因此導致onMeasure()方法中無法對寬度和高度進行解析,所以這里將布局類型強制設定成wrapContent 類型,然后再調用measure()方法
listItem.measure(0,0);
//0,0是兩個參數,用於比較,measure()方法,用於計算控件的尺寸,關於該方法的具體細節請參考:http://blog.csdn.net/lilybaobei/article/details/8021868
int HEIGHT=listItem.getMeasuredHeight();
2、設置ExpandableListView的高度,通過LayoutParams對象
ViewGroup.LayoutParams params=listView.getLayoutParams();
//獲得尺寸參數
params.height=HEIGHT*3;
listView.setLayoutParams(params);
以上兩點問題的提出主要是因把ListView、ExpandableListView放在一個 ScrollView中時往往會出現顯示不全的問題,因此解決的辦法就是動態更新ListView/ExListView的高度,以使列表能夠顯示完整。
四、ListView的邊界比較模糊,顏色變淡,解決這個問題的方法是在ListView的屬性設置中添加上 android:fadingEdge="none",這樣邊界就不會模糊了
基本上就是以上問題,下面上代碼:
這是MainActivity的布局文件:
<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"> <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> </RelativeLayout>
然后就是第二個MainActivity.java ,就是在系列(九)的基礎上做了一定修改,添加了一個ListView的適配器和ExpandableListView的適配器:
因為控件數量比較多,所以代碼比較多:
package com.dragon.android_qq; import android.os.Bundle; import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; import android.view.Menu; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.widget.BaseAdapter; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.util.*; import com.dragon.persondata.ContactsInfo; public class ContactsListExpandable extends Activity { private int height=0; private int GROUP_HEIGHT=0; private int CHILD_HEIGHT=52; int[] photoRes={R.drawable.contact_0,R.drawable.contact_1,R.drawable.contact_2,R.drawable.contact_3}; String[] groupFrom={"groupImage","groupName","childCount"}; int[] groupTo={R.id.groupImage,R.id.groupName,R.id.childCount}; String[] childFrom={"userImage","userName","userSign"}; int[] childTo={R.id.ct_photo,R.id.ct_name,R.id.ct_sign}; ArrayList<HashMap<String,Object>> groupData=null; ArrayList<ArrayList<HashMap<String,Object>>> childData=null; int[] groupIndicator={R.drawable.toright,R.drawable.todown}; ExpandableListView exListView=null; ListView listViewTop=null,listViewDown=null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_qqlist_expandable); groupData=new ArrayList<HashMap<String,Object>>(); childData=new ArrayList<ArrayList<HashMap<String,Object>>> (); ContactsInfo user1=new ContactsInfo("暗夜之殤","總有一天會尋找到自己的幸福",R.drawable.contact_0,"我的好友"); ContactsInfo user2=new ContactsInfo("街角的幸福","有些事終於想開了",R.drawable.contact_1,"我的好友"); ContactsInfo user3=new ContactsInfo("憤怒的小胖","誰再叫我小胖我跟誰急!",R.drawable.contact_3,"朋友"); ContactsInfo user4=new ContactsInfo("放羊的星星","What ever",R.drawable.contact_2,"陌生人"); ContactsInfo user5=new ContactsInfo("都市麗人","我要我的完美",R.drawable.contact_4,"我的好友"); addUser(user1); addUser(user2); addUser(user3); addUser(user4); addUser(user5); //不能用HashMap的實參賦給Map形參,只能new一個HashMap對象賦給Map的引用! exListView=(ExpandableListView)findViewById(R.id.qq_list); MyExpandableListViewAdapter adapter=new MyExpandableListViewAdapter(this,groupData,R.layout.layout_qqlist_group,groupFrom,groupTo,childData,R.layout.layout_qqlist_child,childFrom,childTo ); exListView.setAdapter(adapter); exListView.setGroupIndicator(null); View exGroupListItem= exListView.getExpandableListAdapter().getGroupView(0,false, null, exListView ); exGroupListItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); exGroupListItem.measure(0, 0); GROUP_HEIGHT=exGroupListItem.getMeasuredHeight(); View exChildListItem=exListView.getExpandableListAdapter().getChildView(0, 0, false, null, exListView); exChildListItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT)); exChildListItem.measure(0, 0); CHILD_HEIGHT=exChildListItem.getMeasuredHeight(); ViewGroup.LayoutParams params= exListView.getLayoutParams(); height=groupData.size()*GROUP_HEIGHT-2; params.height=height; exListView.setLayoutParams(params); listViewTop=(ListView)findViewById(R.id.qqlist_classify); ArrayList<HashMap<String,Object>> listTop=new ArrayList<HashMap<String,Object>>(); HashMap<String,Object> mapTop1=new HashMap<String,Object>(); HashMap<String,Object> mapTop2=new HashMap<String,Object>(); HashMap<String,Object> mapTop3=new HashMap<String,Object>(); mapTop1.put("listTopImage",R.drawable.address_list); mapTop1.put("listTopText", "通訊錄"); mapTop2.put("listTopImage",R.drawable.chat_flock); mapTop2.put("listTopText", "群"); mapTop3.put("listTopImage",R.drawable.forum); mapTop3.put("listTopText", "討論組"); listTop.add(mapTop1); listTop.add(mapTop2); listTop.add(mapTop3); MyListViewAdapter adapterTop=new MyListViewAdapter(this, listTop, R.layout.qqlist_classify_item, new String[]{"listTopImage","listTopText"}, new int[]{R.id.qqlist_classify_image,R.id.qqlist_classify_name}); listViewTop.setAdapter(adapterTop); ViewGroup.LayoutParams paramsTop=listViewTop.getLayoutParams(); View listItem=listViewTop.getAdapter().getView(0, null, listViewTop); listItem.measure(0, 0); paramsTop.height=3*listItem.getMeasuredHeight()-2; //ListView屬於一種ViewGroup類型 listViewTop.setLayoutParams(paramsTop); /** * 創建一個String數組對象不能直接寫String[]{} 必須在前面加上new,即寫成 new String[]{"listTopImage","listTopText"} * */ listViewDown=(ListView)findViewById(R.id.qqlist_classify_down); ArrayList<HashMap<String,Object>> listDown=new ArrayList<HashMap<String,Object>>(); HashMap<String,Object> mapDown1=new HashMap<String,Object>(); mapDown1.put("listTopImage",R.drawable.life_service); mapDown1.put("listTopText", "生活服務"); listDown.add(mapDown1); listTop.add(mapTop3); MyListViewAdapter adapterDown=new MyListViewAdapter(this, listDown, R.layout.qqlist_classify_item, new String[]{"listTopImage","listTopText"}, new int[]{R.id.qqlist_classify_image,R.id.qqlist_classify_name}); listViewDown.setAdapter(adapterDown); } protected void addUser(ContactsInfo user) { int i; for(i=0; i< groupData.size(); i++){ if(groupData.get(i).get("groupName").toString().equals(user.groupInfo)){ break; } } if(i>=groupData.size()){ HashMap<String,Object> map=new HashMap<String,Object>(); map.put("groupImage", groupIndicator); map.put("groupName",user.groupInfo ); map.put("childCount", 0); groupData.add(map); ArrayList<HashMap<String,Object>> list=new ArrayList<HashMap<String,Object>>(); childData.add(list); } HashMap<String,Object> userData=new HashMap<String,Object>(); userData.put("userImage",user.userImage ); userData.put("userName", user.userName); userData.put("userSign", user.userSign); childData.get(i).add(userData); Integer count=(Integer)groupData.get(i).get("childCount")+1; groupData.get(i).put("childCount", count); } /** * ExpandableListView對應的適配器 * @author DragonGN * */ public class MyExpandableListViewAdapter extends BaseExpandableListAdapter{ private Context context=null; private ArrayList<HashMap<String,Object>> groupData=null; int groupLayout=0; private String[] groupFrom=null; private int[] groupTo=null; private ArrayList<ArrayList<HashMap<String,Object>>> childData=null; int childLayout=0; private String[] childFrom=null; private int[] childTo=null; public MyExpandableListViewAdapter(Context context, ArrayList<HashMap<String, Object>> groupData, int groupLayout, String[] groupFrom, int[] groupTo, ArrayList<ArrayList<HashMap<String, Object>>> childData, int childLayout, String[] childFrom, int[] childTo) { super(); this.context = context; this.groupData = groupData; this.groupLayout = groupLayout; this.groupFrom = groupFrom; this.groupTo = groupTo; this.childData = childData; this.childLayout = childLayout; this.childFrom = childFrom; this.childTo = childTo; } @Override public Object getChild(int arg0, int arg1) { // TODO Auto-generated method stub return null; } /** * position與id一樣,都是從0開始計數的, * 這里返回的id也是從0開始計數的 */ @Override public long getChildId(int groupPosition, int childPosition) { // TODO Auto-generated method stub long id=0; for(int i=0;i<groupPosition; i++){ id+=childData.size(); } id+=childPosition; return id; } /**ChildViewHolder內部類**/ class ChildViewHolder{ ImageButton userImage=null; TextView userName=null; TextView userSign=null; } /**頭像點擊事件監聽類**/ class ImageClickListener implements OnClickListener{ ChildViewHolder holder=null; public ImageClickListener(ChildViewHolder holder){ this.holder=holder; } @Override public void onClick(View v) { // TODO Auto-generated method stub Toast.makeText(context, holder.userName.getText()+" is clicked", Toast.LENGTH_SHORT).show(); } } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { // TODO Auto-generated method stub /** * 這里isLastChild目前沒用到,如果出現異常再說 */ ChildViewHolder holder=null; if(convertView==null){ convertView= LayoutInflater.from(context).inflate(childLayout,null); //感覺這里需要把root設置成ViewGroup 對象 /** * ERROR!!這里不能把null換成parent,否則會出現異常退出,原因不太確定,可能是inflate方法獲得的這個item的View * 並不屬於某個控件組,所以使用默認值null即可 */ holder=new ChildViewHolder(); holder.userImage=(ImageButton)convertView.findViewById(childTo[0]); holder.userName=(TextView)convertView.findViewById(childTo[1]); holder.userSign=(TextView)convertView.findViewById(childTo[2]); convertView.setTag(holder); } else{ holder=(ChildViewHolder)convertView.getTag(); } holder.userImage.setBackgroundResource((Integer)(childData.get(groupPosition).get(childPosition).get(childFrom[0]))); holder.userName.setText(childData.get(groupPosition).get(childPosition).get(childFrom[1]).toString()); holder.userSign.setText(childData.get(groupPosition).get(childPosition).get(childFrom[2]).toString()); holder.userImage.setOnClickListener(new ImageClickListener(holder)); return convertView; } @Override public int getChildrenCount(int groupPosition) { // TODO Auto-generated method stub return childData.get(groupPosition).size(); } @Override public Object getGroup(int groupPosition) { // TODO Auto-generated method stub return null; } @Override public int getGroupCount() { // TODO Auto-generated method stub return groupData.size(); } @Override public long getGroupId(int groupPosition) { // TODO Auto-generated method stub return groupPosition; } class GroupViewHolder{ ImageView image=null; TextView groupName=null; TextView childCount=null; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { // TODO Auto-generated method stub GroupViewHolder holder=null; if(convertView==null){ convertView=LayoutInflater.from(context).inflate(groupLayout, null); holder=new GroupViewHolder(); holder.image=(ImageView)convertView.findViewById(groupTo[0]); holder.groupName=(TextView)convertView.findViewById(groupTo[1]); holder.childCount=(TextView)convertView.findViewById(groupTo[2]); convertView.setTag(holder); } else{ holder=(GroupViewHolder)convertView.getTag(); } int[] groupIndicator=(int[])groupData.get(groupPosition).get(groupFrom[0]); holder.image.setBackgroundResource(groupIndicator[isExpanded?1:0]); holder.groupName.setText(groupData.get(groupPosition).get(groupFrom[1]).toString()); holder.childCount.setText(groupData.get(groupPosition).get(groupFrom[2]).toString()); if(groupPosition==groupData.size()-1){ convertView.setBackgroundResource(R.drawable.list_lastitem_border); } else{ convertView.setBackgroundResource(R.drawable.list_item_border); } //else的情況也要考慮,否則在繪制時出現錯位現象 return convertView; /** * 不要在適配器中調用適配器的內部方法,不然會出現奇怪的異常 * */ } @Override public boolean hasStableIds() { // TODO Auto-generated method stub return false; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { // TODO Auto-generated method stub return true; } /** * 在設置ExpandableListView的寬度的時候,要注意每次點擊展開或者關閉時,各個Group和所要顯示的Item都會重繪 * 因此在每次繪制完畢之后都需要對height進行更新 */ @Override public void onGroupExpanded(int groupPosition) { // TODO Auto-generated method stub super.onGroupExpanded(groupPosition); System.out.println("已經展開了"); height+=childData.get(groupPosition).size()*CHILD_HEIGHT; ViewGroup.LayoutParams params= exListView.getLayoutParams(); params.height=height-2; System.out.println("expand current height is "+height); 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(); System.out.println("collapse current height is "+height); params.height=height-2; exListView.setLayoutParams(params); } private int floatToInt(double f){ if(f>0){ return (int)(f+0.5); } else{ return (int)(f-0.5); } } } /** * 這個是ListView的適配器 * @author DragonGN * */ public class MyListViewAdapter extends BaseAdapter{ Context context=null; ArrayList<HashMap<String,Object>> list=null; int layout; String[] from=null; int[] to; public MyListViewAdapter(Context context, ArrayList<HashMap<String, Object>> list, int layout, String[] from, int[] to) { super(); this.context = context; this.list = list; this.layout = layout; this.from = from; this.to = to; } @Override public int getCount() { // TODO Auto-generated method stub return list.size(); } @Override public Object getItem(int arg0) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int arg0) { // TODO Auto-generated method stub return arg0; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub View listItemView= LayoutInflater.from(context).inflate(layout, null); ImageView image=(ImageView)listItemView.findViewById(to[0]); TextView text=(TextView)listItemView.findViewById(to[1]); image.setImageResource((Integer)list.get(position).get(from[0])); text.setText((String)list.get(position).get(from[1])); return listItemView; } } }
drawable文件中的圖片和背景資源就不傳了,若有需要,可以上傳整個Demo。
另外還有一個效果沒有實現,休息兩天繼續更新。