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("群发的我不回\n ^_^", 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; } } }
希望大家继续支持我,你们的支持是我前进的动力^^