Android开发系列(十六) QQ聊天界面完整版——整合气泡、表情、历史表情等功能

 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游戏的开发...不多说了,希望新的大家都会取得新的成绩。

原文地址:https://www.cnblogs.com/carlos-vic/p/Carlos_V_Android_16.html