Android开发系列(十五) QQ聊天界面升级版——QQ聊天表情与SpannableString

  好几天没有更新博客了,马上就要开学,心情悲怆。

     首先说一下 QQ开源项目的Demo已经更新至本节,欢迎下载~ 地址: http://ishare.iask.sina.com.cn/f/67274366.html

  本节继续介绍QQ界面的开发,上一节做了QQ聊天界面的1.0版,前几日上传的Demo发现有很多下载(导致我的新浪爱问积分瞬间爆满^^,过几日我会取消下载积分,供大家自由下载),使用过的朋友会发现上一节并没有实现QQ聊天表情的效果,本节的主要内容就是实现这一功能,首先呈上效果图:

 

  虽然只是增加一个表情功能,但使用到的知识点相当多:

一、表情列表的实现

  需要用到的控件: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);

  由于本节的内容相对较多,所以临时决定拆成两节,下一节会介绍如何在textView 中添加表情SpannableString的使用方法,并可能会添加进TabHost+FrameLayout控件,敬请期待~

     最后附上ChatActivity的代码:点击展开

package com.example.android_qqfix;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
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 java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;







public class ChatActivity 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={"\呲牙","\淘气","\流汗","\偷笑","\再见","\敲打","\擦汗","\流泪","\掉泪","\小声","\炫酷","\发狂"
             ,"\委屈","\便便","\菜刀","\微笑","\色色","\害羞"};
    
    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;
    
    ArrayList<ImageView> pointList=null;
    ArrayList<ArrayList<HashMap<String,Object>>> listGrid=null;
    protected ListView chatListView=null;
    protected Button chatSendButton=null;
    protected EditText editText=null;
    protected ViewFlipper viewFlipper=null;
    protected ImageButton chatBottomLook=null;
    protected RelativeLayout faceLayout=null;
    protected LinearLayout pagePoint=null,fillGapLinear=null;
   
    private boolean expanded=false;
    
    
    
    protected MyChatAdapter adapter=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_chat);
    
        faceMap=new HashMap<String,Integer>();    
        chatList=new ArrayList<HashMap<String,Object>>();
        listGrid=new ArrayList<ArrayList<HashMap<String,Object>>>();
        pointList=new ArrayList<ImageView>();
        
        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);
        viewFlipper=(ViewFlipper)findViewById(R.id.faceFlipper);
        chatBottomLook=(ImageButton)findViewById(R.id.chat_bottom_look);
        faceLayout=(RelativeLayout)findViewById(R.id.faceLayout);
        pagePoint=(LinearLayout)findViewById(R.id.pagePoint);
        fillGapLinear=(LinearLayout)findViewById(R.id.fill_the_gap);
        
        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;
                    setPointEffect(0);

                }
            }
            
        });
        
        /**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]);
        }
        
        
        addFaceData();
        addGridView();
        
        
    }
    
    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,便于修改点的颜色
             */
        }
    
    }
    
    /**
     * 打开或者关闭软键盘,之前若打开,调用该方法后关闭;之前若关闭,调用该方法后打开
     */
    
    private void setSoftInputState(){
        ((InputMethodManager)ChatActivity.this.getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
    }
    
    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);

        }
    }
    
    /**
     * 设置游标(小点)的显示效果
     * @param darkPointNum
     */
    private void setPointEffect(int darkPointNum){
        for(int i=0; i<pointList.size(); i++){
            pointList.get(i).setBackgroundResource(R.drawable.qian_point);
        }
        pointList.get(darkPointNum).setBackgroundResource(R.drawable.shen_point);
    }
    
    /**
     * 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"));
                }
                
            }
            //这里创建了一个方法内部类
            holder.image.setOnClickListener(new MyGridImageClickListener(position));
            
            
            
            return convertView;
        }
        
    }
    
    
    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>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;
        }
        
    }
    
    
    
    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]);
            
            
            System.out.println(holder);
            System.out.println(holder.imageView);
            holder.imageView.setBackgroundResource((Integer)chatList.get(position).get(from[0]));
            setFaceText(holder.textView, chatList.get(position).get(from[1]).toString());
            return convertView;
        }
        
    }
    
    

}
View Code


 希望大家继续支持我,你们的支持是我前进的动力^^


 

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