一、表情列表的實現
需要用到的控件:GridView和ViewFlipper用GridView存放表情,因為可能有多個頁面的表情,所以要把GridView放進ViewFlipper中
首先講一下GridView:GridView類似於ListView,也需要自己定義適配器,只不過ListView的每一個條目是一行一行排放的,而GridView則是以格子的形式排放的,因此完全可以按照ListView的方式設置這裡的GridView,GridView有兩個特殊的方法:
setNumColumns() 設置GridView的行數;
setSelector(new ColorDrawable(Color.TRANSPARENT)); 設置選中GridView的某一個格子時,改格子的顏色為透明色,默認是橘黃色
GridView的每一個格子我稱其為GridItem,其對應的布局為:
復制代碼
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent" >
<ImageView
android:id="@+id/gridImage"
android:layout_width="30dip"
android:layout_height="30dip"
android:layout_margin="10dip"/>
</RelativeLayout>
復制代碼
GridView自定義的適配器如下(和ListView其實很像)
復制代碼
class MyGridAdapter extends BaseAdapter{
Context context;
ArrayList<HashMap<String,Object>> list;
int layout;
String[] from;
int[] to;
public MyGridAdapter(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 position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
class ViewHolder{
ImageView image=null;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder=null;
if(convertView==null){
convertView=LayoutInflater.from(context).inflate(layout, null);
holder=new ViewHolder();
holder.image=(ImageView)convertView.findViewById(to[0]);
convertView.setTag(holder);
}
else{
holder=(ViewHolder)convertView.getTag();
}
holder.image.setImageResource((Integer)list.get(position).get(from[0]));
class MyGridImageClickListener implements OnClickListener{
int position;
public MyGridImageClickListener(int position) {
super();
this.position = position;
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
editText.append((String)list.get(position).get("faceName"));
}
}
//這裡創建了一個方法內部類
holder.image.setOnClickListener(new MyGridImageClickListener(position));
return convertView;
}
}
復制代碼
然後在Java代碼中通過findViewById()方法獲得GridView對象,然後設置適配器即可,關於如何配置數據後面會講。
ViewFlipper:ViewFlipper用來實現翻頁效果,即如果有多個頁數的表情,那麼把每一個GridView作為ViewFlipper的一個ChildView添加進去,形成多個頁面,最後通過設置onTouchListener實現翻頁效果,這和我之前做過的一個ImageSwitcher的思路是完全相同的,當時的那個項目完全可以用ViewFlipper實現
ViewFlipper 對象常用的一些方法:
addView() 把ChildView添加進去;
setDisplayedChild(int index) 人為設置要顯示的childView
getDisplayedChild(); 獲得當前顯示的childView的索引
showNext(); 顯示下一個childView;
showPrevious(); 顯示前一個 childView;
注意在ViewFlipper中添加GridView後 ViewFlipper的onTouchListener就失效了,因此這裡為GridView設置的監聽器,根據滑動情況設置翻頁效果。
復制代碼
class MyTouchListener implements OnTouchListener{
ViewFlipper viewFlipper=null;
public MyTouchListener(ViewFlipper viewFlipper) {
super();
this.viewFlipper = viewFlipper;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:startX=event.getX(); moveable=true; break;
case MotionEvent.ACTION_MOVE:
if(moveable){
if(event.getX()-startX>60){
moveable=false;
int childIndex=viewFlipper.getDisplayedChild();
/**
* 這裡的這個if檢測是防止表情列表循環滑動
*/
if(childIndex>0){
viewFlipper.setInAnimation(AnimationUtils.loadAnimation(ChatActivity.this, R.anim.left_in));
viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(ChatActivity.this, R.anim.right_out));
viewFlipper.showPrevious();
setPointEffect(childIndex-1);
}
}
else if(event.getX()-startX<-60){
moveable=false;
int childIndex=viewFlipper.getDisplayedChild();
/**
* 這裡的這個if檢測是防止表情列表循環滑動
*/
if(childIndex<listGrid.size()-1){
viewFlipper.setInAnimation(AnimationUtils.loadAnimation(ChatActivity.this, R.anim.right_in));
viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(ChatActivity.this, R.anim.left_out));
viewFlipper.showNext();
setPointEffect(childIndex+1);
}
}
}
break;
case MotionEvent.ACTION_UP:moveable=true;break;
default:break;
}
return false;
}
}
復制代碼
下面講一下如何配置GridView的數據源和把GridView添加進ViewFlipper,這裡我設計了兩個方法,根據 表情圖片資源數組 faceId[] 和 表情名稱數組 faceName[] 自動生成
GridView的數據源ArrayList<ArrayList<HashMap<String,Object>>> gridList對象,並把多個(根據表情的個數決定)GridView設置onTouchListener 然後添加進 ViewFlipper中,代碼如下:
復制代碼
private void addFaceData(){
ArrayList<HashMap<String,Object>> list=null;
for(int i=0; i<faceId.length; i++){
if(i%14==0){
list=new ArrayList<HashMap<String,Object>>();
listGrid.add(list);
}
HashMap<String,Object> map=new HashMap<String,Object>();
map.put("image", faceId[i]);
map.put("faceName", faceName[i]);
/**
* 這裡把表情對應的名字也添加進數據對象中,便於在點擊時獲得表情對應的名稱
*/
listGrid.get(i/14).add(map);
}
System.out.println("listGrid size is "+listGrid.size());
}
private void addGridView(){
for(int i=0; i< listGrid.size();i++){
View view=LayoutInflater.from(this).inflate(R.layout.view_item, null);
GridView gv=(GridView)view.findViewById(R.id.myGridView);
gv.setNumColumns(5);
gv.setSelector(new ColorDrawable(Color.TRANSPARENT));
MyGridAdapter adapter=new MyGridAdapter(this, listGrid.get(i), R.layout.chat_grid_item, new String[]{"image"}, new int[]{R.id.gridImage});
gv.setAdapter(adapter);
gv.setOnTouchListener(new MyTouchListener(viewFlipper));
viewFlipper.addView(view);
// ImageView image=new ImageView(this);
// ImageView image=(ImageView)LayoutInflater.from(this).inflate(R.layout.image_point_layout, null);
/**
* 這裡不喜歡用Java代碼設置Image的邊框大小等,所以單獨配置了一個Imageview的布局文件
*/
View pointView=LayoutInflater.from(this).inflate(R.layout.point_image_layout, null);
ImageView image=(ImageView)pointView.findViewById(R.id.pointImageView);
image.setBackgroundResource(R.drawable.qian_point);
pagePoint.addView(pointView);
/**
* 這裡驗證了LinearLayout屬於ViewGroup類型,可以采用addView 動態添加view
*/
pointList.add(image);
/**
* 將image放入pointList,便於修改點的顏色
*/
}
}
復制代碼
注意addGridView最後一部分是用來設置效果圖最下方的那兩個圓點,關於實現原理接下來會講。
表情列表的布局分析:
復制代碼
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="44dip"
android:id="@+id/chat_title"
android:layout_alignParentTop="true"
android:background="@drawable/chat_title_layer">
<Button
android:id="@+id/chat_msg_button"
android:layout_width="match_parent"
android:layout_height="36dip"
android:layout_weight="1.9"
android:layout_marginLeft="8dip"
android:layout_marginTop="3dip"
android:text="消息(0)"
android:textColor="@android:color/white"
android:textSize="7pt"
android:background="@drawable/msg_button_back"/>
<TextView
android:id="@+id/chat_contact_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="龍行天下"
android:textSize="8pt"
android:textColor="@android:color/white"
android:gravity="center"
android:layout_gravity="center_vertical"/>
<ImageButton
android:id="@+id/chat_contact_button"
android:layout_width="match_parent"
android:layout_height="36dip"
android:layout_weight="2"
android:layout_marginRight="8dip"
android:layout_marginTop="3dip"
android:background="@drawable/chat_contact_back"/>
</LinearLayout>
<RelativeLayout
android:id="@+id/faceLayout"
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_alignParentBottom="true">
<ViewFlipper
android:id="@+id/faceFlipper"
android:layout_width="match_parent"
android:layout_height="130dip"
android:background="#d0d3d5"
>
</ViewFlipper>
<LinearLayout
android:id="@+id/fill_the_gap"
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="#272b34"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:id="@+id/pagePoint"
android:layout_width="match_parent"
android:layout_height="20dip"
android:layout_below="@id/faceFlipper"
android:background="#d0d3d5"
android:gravity="center"
android:orientation="horizontal">
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/chat_bottom_linear"
android:layout_width="match_parent"
android:layout_height="42dip"
android:background="@drawable/chat_title_layer"
android:orientation="horizontal"
android:layout_above="@id/faceLayout"
android:paddingTop="5dip"
android:paddingBottom="3dip">
<ImageButton
android:id="@+id/chat_bottom_look"
android:layout_width="match_parent"
android:layout_height="26dip"
android:layout_weight="3.5"
android:layout_marginLeft="7dip"
android:layout_marginTop="5dip"
android:background="@drawable/chat_bottom_look"/>
<ImageButton
android:id="@+id/chat_bottom_add"
android:layout_width="match_parent"
android:layout_height="26dip"
android:layout_weight="3.5"
android:layout_marginLeft="7dip"
android:layout_marginTop="5dip"
android:background="@drawable/chat_bottom_add"/>
<EditText
android:id="@+id/chat_bottom_edittext"
android:layout_width="match_parent"
android:layout_height="32dip"
android:layout_marginLeft="5dip"
android:layout_marginRight="7dip"
android:layout_weight="1.5"
android:background="@drawable/edit_fillet_shape"/>
<Button
android:id="@+id/chat_bottom_sendbutton"
android:layout_width="match_parent"
android:layout_height="26dip"
android:layout_marginBottom="9dip"
android:layout_marginRight="4dip"
android:layout_weight="3.2"
android:layout_gravity="top"
android:background="@drawable/chat_button_fillet_shape"
android:text="發送"
android:textColor="@android:color/white" />
"
</LinearLayout>
<ListView
android:id="@+id/chat_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/chat_title"
android:layout_above="@id/chat_bottom_linear"
android:fadingEdge="none"
android:background="#f0f0f0"
android:divider="#aaaaaa"
android:dividerHeight="0px">
</ListView>
</RelativeLayout>
復制代碼
這是整個ChatActivity的布局文件,找到含有ViewFlipper的那一塊:
<RelativeLayout
android:id="@+id/faceLayout"
android:layout_width="match_parent" android:layout_height="1dip"
android:layout_alignParentBottom="true">
<ViewFlipper
android:id="@+id/faceFlipper"
android:layout_width="match_parent"
android:layout_height="130dip"
android:background="#d0d3d5"
>
</ViewFlipper>
<LinearLayout
android:id="@+id/fill_the_gap"
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="#272b34"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:id="@+id/pagePoint"
android:layout_width="match_parent"
android:layout_height="20dip"
android:layout_below="@id/faceFlipper"
android:background="#d0d3d5"
android:gravity="center"
android:orientation="horizontal">
</LinearLayout>
</RelativeLayout>
id為pagePoint 的的LinearLayout就是盛放圓點ImageView的一個布局空間(ViewGroup),ViewFlipper中有幾個子View就對應幾個圓點,也通過addView()添加ImageView,這一點在addGridView中有體現,為了實現好看的效果這裡為每一個圓點設置了布局文件,具體可參考我上傳的Demo中point_image_layout
復制代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/pointImageView"
android:layout_width="6dip"
android:layout_height="6dip"
android:layout_marginLeft="8dip"
android:layout_marginRight="8dip"/>
</LinearLayout>
復制代碼
為什麼設置id為 fill_the_gap的Linearlayout? 這個比較麻煩講,在本例中我關閉表情界面的方法是把id為faceLayout的RelativeLayout高度設為1dip。我想在關閉表情時,讓表情界面回到初始狀態(即顯示第一頁表情),這樣每次打開表情界面都是第一頁,和官方QQ一樣,但是如果viewFlipper如果在屏幕上沒有一點像素顯示就無法調用setDisplayedChild(0)方法,因此需要把faceLayout的高度設為1dip 而非0dip ,這樣又帶來另一個問題是屏幕最下方出現了一條白線,不好看,所以就在ViewFlipper的最上方重疊了一個高為1dip 的色條,用於把這個白縫“封住”,大家可以嘗試直接把高度設為0dip,會發現體驗較差。
這裡封裝了一個設置高度的方法:
復制代碼
private void setFaceLayoutExpandState(boolean isexpand){
if(isexpand==false){
viewFlipper.setDisplayedChild(0);
ViewGroup.LayoutParams params=faceLayout.getLayoutParams();
params.height=1;
faceLayout.setLayoutParams(params);
/**height不設為0是因為,希望可以使再次打開時viewFlipper已經初始化為第一頁 避免
*再次打開ViewFlipper時畫面在動的結果,
*為了避免因為1dip的高度產生一個白縫,所以這裡在ViewFlipper所在的RelativeLayout中ViewFlipper
*上層添加了一個1dip高的黑色色塊
*
*viewFlipper必須在屏幕中有像素才能執行setDisplayedChild()操作
*/
chatBottomLook.setBackgroundResource(R.drawable.chat_bottom_look);
}
else{
/**
* 讓軟鍵盤消失
*/
((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow
(ChatActivity.this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
ViewGroup.LayoutParams params=faceLayout.getLayoutParams();
params.height=150;
faceLayout.setLayoutParams(params);
chatBottomLook.setBackgroundResource(R.drawable.chat_bottom_keyboard);
}
}
復制代碼
另外這裡涉及到了對鍵盤的操作,主要目的是為了實現和官方版差不多的效果:
實現關閉鍵盤的方法:
((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow
(ChatActivity.this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
實現切換鍵盤狀態的方法:原來打開則關閉,原來關閉則打開
((InputMethodManager)ChatActivity.this.getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);