QQ项目最新Demo:http://vdisk.weibo.com/s/uu2pYkVAKnYWh
这是寒假最后一弹,首先我应该对持续关注我博客的朋友们表示歉意,因为我只是想完成这个项目,由于其中涉及了很多的知识点,我实在每一办法一一讲出,只能提一些主要的东西,其余的一些细节还希望大家参考我的代码。这些代码实际上也为我以后再次着手学习Android开发打好基础。本节Demo下载链接会尽快上传~
正如之前所料,果然没有把QQ项目彻底完成。总体上看,总共做出了三个主要的界面,接下来的工作就是进行网络通信等相关的配置了,本节的主要内容是彻底完成QQ聊天界面,众所周知QQ聊天的表情界面有一个“历史表情”的功能,如下图所示:
【QQ官方效果】
要实现这一效果,就需要把ViewFlipper放进一个TabView中,PS:如果大家不想用TabView,可以考虑使用Fragment,但由于我对这一控件掌握的并不熟练,所以暂时仍用TabHost+TabView实现,我的效果图如下:
这意味着需要创建两个Activity,分别显示系统原有的表情和“历史表情”,实际上这两个Activity是类似的,只不过表情的列数不一样而已,下面就是其中一个Activity的代码:
package com.example.android_qqfix; import java.util.ArrayList; import java.util.HashMap; import com.dragon.face.FaceHistoryData; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.view.animation.AnimationUtils; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ViewFlipper; public class MyFaceActivity extends Activity{ int[] faceId={R.drawable.f_static_000,R.drawable.f_static_001,R.drawable.f_static_002,R.drawable.f_static_003 ,R.drawable.f_static_004,R.drawable.f_static_005,R.drawable.f_static_006,R.drawable.f_static_009,R.drawable.f_static_010,R.drawable.f_static_011 ,R.drawable.f_static_012,R.drawable.f_static_013,R.drawable.f_static_014,R.drawable.f_static_015,R.drawable.f_static_017,R.drawable.f_static_018}; String[] faceName={"\呲牙","\淘气","\流汗","\偷笑","\再见","\敲打","\擦汗","\流泪","\掉泪","\小声","\炫酷","\发狂" ,"\委屈","\便便","\菜刀","\微笑","\色色","\害羞"}; protected ViewFlipper viewFlipper=null; protected LinearLayout pagePoint=null; ArrayList<ArrayList<HashMap<String,Object>>> listGrid=null; ArrayList<ImageView> pointList=null; public static MyFaceHandler faceHandler=null; public static final int ActivityId=1; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.myface_layout); System.out.println("MyFace is onCreate"); faceHandler=new MyFaceHandler(Looper.myLooper()); viewFlipper=(ViewFlipper)findViewById(R.id.faceFlipper); pagePoint=(LinearLayout)findViewById(R.id.pagePoint); listGrid=new ArrayList<ArrayList<HashMap<String,Object>>>(); pointList=new ArrayList<ImageView>(); addFaceData(); addGridView(); setPointEffect(0); } public class MyFaceHandler extends Handler{ public MyFaceHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub switch(msg.what){ case ChatActivity.ActivityID: if(msg.obj.toString().equals("collapse")){ //关闭表情 if(ChatActivity.currentTabTag.equals("face")) viewFlipper.setDisplayedChild(0); setPointEffect(0); } } } } private void addFaceData(){ if(listGrid!=null) listGrid.clear(); //首先将先前的数据清空 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(){ if(viewFlipper!=null){ viewFlipper.removeAllViews(); pagePoint.removeAllViews(); pointList.clear(); //更新前首先清除原有的所有数据 } 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,便于修改点的颜色 */ } } /** * 设置游标(小点)的显示效果 * @param darkPointNum */ private void setPointEffect(int darkPointNum){ for(int i=0; i<pointList.size(); i++){ pointList.get(i).setBackgroundResource(R.drawable.qian_point); } if(pointList.size()>0) pointList.get(darkPointNum).setBackgroundResource(R.drawable.shen_point); } private boolean moveable=true; private float startX=0; /** * 用到的方法 viewFlipper.getDisplayedChild() 获得当前显示的ChildView的索引 * @author Administrator * */ 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>40){ moveable=false; int childIndex=viewFlipper.getDisplayedChild(); /** * 这里的这个if检测是防止表情列表循环滑动 */ if(childIndex>0){ viewFlipper.setInAnimation(AnimationUtils.loadAnimation(MyFaceActivity.this, R.anim.left_in)); viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(MyFaceActivity.this, R.anim.right_out)); viewFlipper.showPrevious(); setPointEffect(childIndex-1); } } else if(event.getX()-startX<-40){ moveable=false; int childIndex=viewFlipper.getDisplayedChild(); /** * 这里的这个if检测是防止表情列表循环滑动 */ if(childIndex<listGrid.size()-1){ viewFlipper.setInAnimation(AnimationUtils.loadAnimation(MyFaceActivity.this, R.anim.right_in)); viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(MyFaceActivity.this, R.anim.left_out)); viewFlipper.showNext(); setPointEffect(childIndex+1); } } } break; case MotionEvent.ACTION_UP:moveable=true;break; default:break; } return false; } } /** * GridViewAdapter * @param textView * @param text */ 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")); Message msg=new Message(); msg.what=MyFaceActivity.ActivityId; msg.arg1=0; msg.obj=(String)list.get(position).get("faceName"); ChatActivity.chatHandler.sendMessage(msg); //一个消息不能被多个Handler使用!!! if(FaceHistoryData.faceHistoryList==null){ FaceHistoryData.faceHistoryList=new ArrayList<HashMap<String,Object>>(); } int index=0; int curFaceId=(Integer)list.get(position).get("image"); for(index=0; index<FaceHistoryData.faceHistoryList.size(); index++){ if((Integer)FaceHistoryData.faceHistoryList.get(index).get("faceId")==curFaceId){ break; } } if(index>=FaceHistoryData.faceHistoryList.size()){ HashMap<String,Object> map=new HashMap<String,Object>(); map.put("faceId", (Integer)list.get(position).get("image")); map.put("faceName", (String)list.get(position).get("faceName")); FaceHistoryData.faceHistoryList.add(0, map); //前插入 int curSize=FaceHistoryData.faceHistoryList.size(); //控制长度 if(curSize>36){ FaceHistoryData.faceHistoryList.remove(curSize-1); } } } } //这里创建了一个方法内部类 holder.image.setOnClickListener(new MyGridImageClickListener(position)); return convertView; } } @Override protected void onResume() { // TODO Auto-generated method stub addFaceData(); addGridView(); setPointEffect(0); super.onResume(); } }
对应的布局文件
<?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="wrap_content" > <ViewFlipper android:id="@+id/faceFlipper" android:layout_width="match_parent" android:layout_height="130dip" android:background="#d0d3d5" > </ViewFlipper> <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>
另外一个是历史表情 FaceHistoryActivity,其实和MyFaceActivity很像
package com.example.android_qqfix; import java.util.ArrayList; import java.util.HashMap; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.view.animation.AnimationUtils; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ViewFlipper; import com.dragon.face.FaceHistoryData; import com.example.android_qqfix.MyFaceActivity.MyFaceHandler; import com.example.android_qqfix.MyFaceActivity.MyGridAdapter; import com.example.android_qqfix.MyFaceActivity.MyTouchListener; import com.example.android_qqfix.MyFaceActivity.MyGridAdapter.ViewHolder; public class FaceHistoryActivity extends Activity{ int[] faceId; String[] faceName; protected ViewFlipper viewFlipper=null; protected LinearLayout pagePoint=null; ArrayList<ArrayList<HashMap<String,Object>>> listGrid=null; ArrayList<ImageView> pointList=null; public static final int ActivityId=2; public static Handler faceHistoryHandler=null; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.myface_layout); faceHistoryHandler=new FaceHistoryHandler(Looper.myLooper()); viewFlipper=(ViewFlipper)findViewById(R.id.faceFlipper); pagePoint=(LinearLayout)findViewById(R.id.pagePoint); listGrid=new ArrayList<ArrayList<HashMap<String,Object>>>(); pointList=new ArrayList<ImageView>(); viewFlipper.setOnTouchListener(new MyTouchListener(viewFlipper)); parseFaceHistoryList(); //首先创建faceId faceName addFaceData(); addGridView(); setPointEffect(0); } public class FaceHistoryHandler extends Handler{ public FaceHistoryHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub switch(msg.what){ case ChatActivity.ActivityID: if(msg.obj.toString().equals("collapse")){ //关闭表情 if(ChatActivity.currentTabTag.equals("faceHistory")) viewFlipper.setDisplayedChild(0); setPointEffect(0); } } } } private void parseFaceHistoryList(){ if(FaceHistoryData.faceHistoryList==null){ faceId=new int[0]; faceName=new String[0]; System.out.println("没装进来!!!"); } else{ faceId=new int[FaceHistoryData.faceHistoryList.size()]; faceName=new String[FaceHistoryData.faceHistoryList.size()]; for(int i=0; i<FaceHistoryData.faceHistoryList.size(); i++){ faceId[i]=(Integer)FaceHistoryData.faceHistoryList.get(i).get("faceId"); faceName[i]=(String)FaceHistoryData.faceHistoryList.get(i).get("faceName"); } } } private void addFaceData(){ ArrayList<HashMap<String,Object>> list=null; if(listGrid!=null) listGrid.clear(); //首先将先前的数据清空 for(int i=0; i<faceId.length; i++){ if(i%11==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/11).add(map); } System.out.println("listGrid size is "+listGrid.size()); } private void addGridView(){ if(viewFlipper!=null){ viewFlipper.removeAllViews(); pagePoint.removeAllViews(); pointList.clear(); //更新前首先清除原有的所有数据 } 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(4); 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,便于修改点的颜色 */ } } /** * 设置游标(小点)的显示效果 * @param darkPointNum */ private void setPointEffect(int darkPointNum){ for(int i=0; i<pointList.size(); i++){ pointList.get(i).setBackgroundResource(R.drawable.qian_point); } if(pointList.size()>0) pointList.get(darkPointNum).setBackgroundResource(R.drawable.shen_point); } private boolean moveable=true; private float startX=0; /** * 用到的方法 viewFlipper.getDisplayedChild() 获得当前显示的ChildView的索引 * @author Administrator * */ 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>40){ moveable=false; int childIndex=viewFlipper.getDisplayedChild(); /** * 这里的这个if检测是防止表情列表循环滑动 */ if(childIndex>0){ viewFlipper.setInAnimation(AnimationUtils.loadAnimation(FaceHistoryActivity.this, R.anim.left_in)); viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(FaceHistoryActivity.this, R.anim.right_out)); viewFlipper.showPrevious(); setPointEffect(childIndex-1); } } else if(event.getX()-startX<-40){ moveable=false; int childIndex=viewFlipper.getDisplayedChild(); /** * 这里的这个if检测是防止表情列表循环滑动 */ if(childIndex<listGrid.size()-1){ viewFlipper.setInAnimation(AnimationUtils.loadAnimation(FaceHistoryActivity.this, R.anim.right_in)); viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(FaceHistoryActivity.this, R.anim.left_out)); viewFlipper.showNext(); setPointEffect(childIndex+1); } } } break; case MotionEvent.ACTION_UP:moveable=true;break; default:break; } return false; } } /** * GridViewAdapter * @param textView * @param text */ 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")); Message msg=new Message(); msg.what=MyFaceActivity.ActivityId; msg.arg1=0; msg.obj=(String)list.get(position).get("faceName"); ChatActivity.chatHandler.sendMessage(msg); } } //这里创建了一个方法内部类 holder.image.setOnClickListener(new MyGridImageClickListener(position)); return convertView; } } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); } @Override protected void onResume() { // TODO Auto-generated method stub parseFaceHistoryList(); //首先得更新数组!! addFaceData(); addGridView(); setPointEffect(0); super.onResume(); } @Override protected void onStart() { // TODO Auto-generated method stub super.onStart(); } }
因为在点击表情界面的某个表情时会修改ChatActivity中EditText中的内容,所以需要为ChatActivity设置Handler,该Handler对象绑定ChatActivity的Looper,这里把Handler设置为静态公共 public static对象,以使在其他Activity中能够访问到这一个handler,具体代码实现方式可以参考 ChatActivity ,MyFaceActivity中的 Handler的相关操作。
ChatActivity:
package com.example.android_qqfix; import android.app.Activity; import android.app.TabActivity; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.ImageSpan; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.view.ViewGroup.LayoutParams; import android.view.animation.AnimationUtils; import android.view.inputmethod.InputMethodManager; import android.view.ViewGroup; import android.view.Window; import android.widget.*; import android.widget.AdapterView.OnItemClickListener; import android.widget.TabHost.OnTabChangeListener; import android.widget.TabHost.TabSpec; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ChatActivity extends TabActivity{ int[] faceId={R.drawable.f_static_000,R.drawable.f_static_001,R.drawable.f_static_002,R.drawable.f_static_003 ,R.drawable.f_static_004,R.drawable.f_static_005,R.drawable.f_static_006,R.drawable.f_static_009,R.drawable.f_static_010,R.drawable.f_static_011 ,R.drawable.f_static_012,R.drawable.f_static_013,R.drawable.f_static_014,R.drawable.f_static_015,R.drawable.f_static_017,R.drawable.f_static_018}; String[] faceName={"\呲牙","\淘气","\流汗","\偷笑","\再见","\敲打","\擦汗","\流泪","\掉泪","\小声","\炫酷","\发狂" ,"\委屈","\便便","\菜刀","\微笑","\色色","\害羞"}; HashMap<String,Integer> faceMap=null; ArrayList<HashMap<String,Object>> chatList=null; String[] from={"image","text"}; int[] to={R.id.chatlist_image_me,R.id.chatlist_text_me,R.id.chatlist_image_other,R.id.chatlist_text_other}; int[] layout={R.layout.chat_listitem_me,R.layout.chat_listitem_other}; String userQQ=null; /** * 这里两个布局文件使用了同一个id,测试一下是否管用 * TT事实证明这回产生id的匹配异常!所以还是要分开。。 * * userQQ用于接收Intent传递的qq号,进而用来调用数据库中的相关的联系人信息,这里先不讲 * 先暂时使用一个头像 */ public final static int OTHER=1; public final static int ME=0; public final static int ActivityID=0; protected ListView chatListView=null; protected Button chatSendButton=null; protected EditText editText=null; protected ImageButton chatBottomLook=null; protected RelativeLayout faceLayout=null; protected TabHost tabHost=null; protected TabWidget tabWidget=null; private boolean expanded=false; protected MyChatAdapter adapter=null; protected View tabFaceHistory=null,tabFace=null; protected ImageView tabFaceHistoryImage=null,tabFaceImage=null; public static Handler chatHandler=null; public static String currentTabTag="face"; public TabSpec tabSpecFaceHistory,tabSpecFace; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_chat); chatHandler=new MyChatHandler(Looper.myLooper()); faceMap=new HashMap<String,Integer>(); chatList=new ArrayList<HashMap<String,Object>>(); addTextToList("不管你是谁", ME); addTextToList("群发的我不回 ^_^", OTHER); addTextToList("哈哈哈哈", ME); addTextToList("新年快乐!", OTHER); chatSendButton=(Button)findViewById(R.id.chat_bottom_sendbutton); editText=(EditText)findViewById(R.id.chat_bottom_edittext); chatListView=(ListView)findViewById(R.id.chat_list); tabWidget=(TabWidget)findViewById(android.R.id.tabs); tabHost=(TabHost)findViewById(android.R.id.tabhost); chatBottomLook=(ImageButton)findViewById(R.id.chat_bottom_look); faceLayout=(RelativeLayout)findViewById(R.id.faceLayout); /** * 添加选项卡 */ tabSpecFaceHistory=tabHost.newTabSpec("faceHistory"); tabFaceHistory=LayoutInflater.from(this).inflate(R.layout.tabwidget_image_disselected,null); tabFaceHistoryImage=(ImageView)tabFaceHistory.findViewById(R.id.tabImage_disselected); tabFaceHistoryImage.setImageResource(R.drawable.face_history_disselected); tabSpecFaceHistory.setIndicator(tabFaceHistory); Intent intent1=new Intent(); intent1.setClass(ChatActivity.this, FaceHistoryActivity.class); tabSpecFaceHistory.setContent(intent1); tabHost.addTab(tabSpecFaceHistory); tabSpecFace=tabHost.newTabSpec("face"); tabFace=LayoutInflater.from(this).inflate(R.layout.tabwidget_image_selected, null); tabFaceImage=(ImageView)tabFace.findViewById(R.id.tabImage_selected); tabFaceImage.setImageResource(R.drawable.face_look_selected); tabSpecFace.setIndicator(tabFace); Intent intent2=new Intent(); intent2.setClass(ChatActivity.this, MyFaceActivity.class); tabSpecFace.setContent(intent2); tabHost.addTab(tabSpecFace); tabHost.setCurrentTabByTag("face"); tabHost.setOnTabChangedListener(new OnTabChangeListener() { @Override public void onTabChanged(String tabId) { // TODO Auto-generated method stub // System.out.println("current Selected Tab "+tabId); currentTabTag=tabId; if(tabId.equals("face")){ tabFace.setBackgroundResource(R.drawable.tabwidget_selected); tabFaceImage.setImageResource(R.drawable.face_look_selected); tabSpecFace.setIndicator(tabFace); tabFaceHistory.setBackgroundResource(R.drawable.tab_widget_disselected); tabFaceHistoryImage.setImageResource(R.drawable.face_history_disselected); tabSpecFaceHistory.setIndicator(tabFaceHistory); } else if(tabId.equals("faceHistory")){ tabFace.setBackgroundResource(R.drawable.tabwidget_disselected); tabFaceImage.setImageResource(R.drawable.face_look_disselected); tabSpecFace.setIndicator(tabFace); tabFaceHistory.setBackgroundResource(R.drawable.tabwidget_selected); tabFaceHistoryImage.setImageResource(R.drawable.face_history_selected); tabSpecFaceHistory.setIndicator(tabFaceHistory); } } }); chatBottomLook.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub if(expanded){ setFaceLayoutExpandState(false); expanded=false; InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); /**height不设为0是因为,希望可以使再次打开时viewFlipper已经初始化为第一页 避免 *再次打开ViewFlipper时画面在动的结果, *为了避免因为1dip的高度产生一个白缝,所以这里在ViewFlipper所在的RelativeLayout *最上面添加了一个1dip高的黑色色块 */ } else{ setFaceLayoutExpandState(true); expanded=true; } } }); /**EditText从未获得焦点到首次获得焦点时不会调用OnClickListener方法,所以应该改成OnTouchListener * 从而保证点EditText第一下就能够把表情界面关闭 editText.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub ViewGroup.LayoutParams params=viewFlipper.getLayoutParams(); params.height=0; viewFlipper.setLayoutParams(params); expanded=false; System.out.println("WHYWHWYWHYW is Clicked"); } }); **/ editText.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(expanded){ setFaceLayoutExpandState(false); expanded=false; } return false; } }); adapter=new MyChatAdapter(this,chatList,layout,from,to); chatSendButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String myWord=null; /** * 这是一个发送消息的监听器,注意如果文本框中没有内容,那么getText()的返回值可能为 * null,这时调用toString()会有异常!所以这里必须在后面加上一个""隐式转换成String实例 * ,并且不能发送空消息。 */ myWord=(editText.getText()+"").toString(); if(myWord.length()==0) return; editText.setText(""); addTextToList(myWord, ME); /** * 更新数据列表,并且通过setSelection方法使ListView始终滚动在最底端 */ adapter.notifyDataSetChanged(); chatListView.setSelection(chatList.size()-1); } }); chatListView.setAdapter(adapter); chatListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { // TODO Auto-generated method stub setFaceLayoutExpandState(false); ((InputMethodManager)ChatActivity.this.getSystemService(INPUT_METHOD_SERVICE)). hideSoftInputFromWindow(ChatActivity.this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); expanded=false; } }); /** * 为表情Map添加数据 */ for(int i=0; i<faceId.length; i++){ faceMap.put(faceName[i], faceId[i]); } } public class MyChatHandler extends Handler{ public MyChatHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub switch(msg.what){ case MyFaceActivity.ActivityId: if(msg.arg1==0){ //添加表情字符串 editText.append(msg.obj.toString()); } } } } /** * 打开或者关闭软键盘,之前若打开,调用该方法后关闭;之前若关闭,调用该方法后打开 */ private void setSoftInputState(){ ((InputMethodManager)ChatActivity.this.getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); } private void setFaceLayoutExpandState(boolean isexpand){ if(isexpand==false){ ViewGroup.LayoutParams params=faceLayout.getLayoutParams(); params.height=1; faceLayout.setLayoutParams(params); chatBottomLook.setBackgroundResource(R.drawable.chat_bottom_look); Message msg=new Message(); msg.what=this.ActivityID; msg.obj="collapse"; if(MyFaceActivity.faceHandler!=null) MyFaceActivity.faceHandler.sendMessage(msg); Message msg2=new Message(); msg2.what=this.ActivityID; msg2.obj="collapse"; if(FaceHistoryActivity.faceHistoryHandler!=null) FaceHistoryActivity.faceHistoryHandler.sendMessage(msg2); chatListView.setSelection(chatList.size()-1);//使会话列表自动滑动到最低端 } else{ ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow (ChatActivity.this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); ViewGroup.LayoutParams params=faceLayout.getLayoutParams(); params.height=185; // faceLayout.setLayoutParams(new RelativeLayout.LayoutParams( )); RelativeLayout.LayoutParams relativeParams=new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT); relativeParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); faceLayout.setLayoutParams(relativeParams); chatBottomLook.setBackgroundResource(R.drawable.chat_bottom_keyboard); } } private void setFaceText(TextView textView,String text){ SpannableString spanStr=parseString(text); textView.setText(spanStr); } private void setFace(SpannableStringBuilder spb, String faceName){ Integer faceId=faceMap.get(faceName); if(faceId!=null){ Bitmap bitmap=BitmapFactory.decodeResource(getResources(), faceId); bitmap=Bitmap.createScaledBitmap(bitmap, 30, 30, true); ImageSpan imageSpan=new ImageSpan(this,bitmap); SpannableString spanStr=new SpannableString(faceName); spanStr.setSpan(imageSpan, 0, faceName.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spb.append(spanStr); } else{ spb.append(faceName); } } private SpannableString parseString(String inputStr){ SpannableStringBuilder spb=new SpannableStringBuilder(); Pattern mPattern= Pattern.compile("\\.."); Matcher mMatcher=mPattern.matcher(inputStr); String tempStr=inputStr; while(mMatcher.find()){ int start=mMatcher.start(); int end=mMatcher.end(); spb.append(tempStr.substring(0,start)); String faceName=mMatcher.group(); setFace(spb, faceName); tempStr=tempStr.substring(end, tempStr.length()); /** * 更新查找的字符串 */ mMatcher.reset(tempStr); } spb.append(tempStr); return new SpannableString(spb); } protected void addTextToList(String text, int who){ HashMap<String,Object> map=new HashMap<String,Object>(); map.put("person",who ); map.put("image", who==ME?R.drawable.contact_0:R.drawable.contact_1); map.put("text", text); chatList.add(map); } private class MyChatAdapter extends BaseAdapter{ Context context=null; ArrayList<HashMap<String,Object>> chatList=null; int[] layout; String[] from; int[] to; public MyChatAdapter(Context context, ArrayList<HashMap<String, Object>> chatList, int[] layout, String[] from, int[] to) { super(); this.context = context; this.chatList = chatList; this.layout = layout; this.from = from; this.to = to; } @Override public int getCount() { // TODO Auto-generated method stub return chatList.size(); } @Override public Object getItem(int arg0) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } class ViewHolder{ public ImageView imageView=null; public TextView textView=null; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub ViewHolder holder=null; int who=(Integer)chatList.get(position).get("person"); convertView= LayoutInflater.from(context).inflate( layout[who==ME?0:1], null); holder=new ViewHolder(); holder.imageView=(ImageView)convertView.findViewById(to[who*2+0]); holder.textView=(TextView)convertView.findViewById(to[who*2+1]); holder.imageView.setBackgroundResource((Integer)chatList.get(position).get(from[0])); setFaceText(holder.textView, chatList.get(position).get(from[1]).toString()); return convertView; } } }
此外点击Tab选项卡时所显示出的一些动画效果是我 调用了tabHost的onTabChangedListener()方法,根据选中的不同Tab,调用 TabSpec的setIndicator设置不同的布局文件,关于背景效果的设置采用的是layer-list 这一知识点我在前面讲联系人列表的界面时已经提到,这个东西确实非常非常强大!,从最终效果可以看出,用TabView也可以做出比较漂亮的界面,也没有必要因为它被谷歌弃用了就完全抛弃它,实际上在实现一些简单的tab导航页时使用tabView更加简洁。
另外要提的一点是:上面说的历史表情是怎样实现的呢?原理很简单:创建一个ArrayList类型或者队列类型的静态对象,用户使用某个表情后,如果这个表情之前没有使用过,就把它添加进静态对象中,然后在FaceHistoryActivity中调用parseFaceHistoryList进行解析成相应的表情列表,可以参考FaceHistoryActivity中的相关代码。
为了从这个静态对象我单独创建了一个类,实际上只有一行代码:
public class FaceHistoryData { public static ArrayList<HashMap<String,Object>> faceHistoryList=null; }
最后一点是很遗憾,我并没有实现动态表情的效果,我发现这个效果所用到的技术并不是我短时间内能掌握的,还望海涵。如果我以后找到了实现动态表情的好方法,还会更新本节的博客~~
好啦,以上就是本节的全部内容,《Android开发系列》也会暂时告一段落了。新学期开始,我要去迎接新的挑战,重启这一系列应该是这个暑假的事情了,这一段时间在Android应用的控件和UI美化上花了一些功夫,也确实感到比半年前初学安卓时有了更进一步的理解。下次我应该会把QQ项目彻底完成了,如果时间允许可能会涉及Android游戏的开发...不多说了,希望新的大家都会取得新的成绩。