公司的任务要求实现类似qq聊天动态表情,在网上找了不少资料,基本都是TextView加SpannableString来显示gif,通过自定义一个TextView,启动线程更新图片。由于聊天时可输入多个表情,gif图片各帧时间的不同,就导致了显示多个gif图片很别扭的问题,而且每个TextView就开一个线程也不实际。研究了一下qq聊天,发现所有表情相同的gif图片显示的都是相同的帧。由此我认为qq是通过一个线程来控制所有的TextView更新gif的。

在这里我使用的GifView的源码来解析gif图片,并修改了其中的GifView,使其继承TextView来达到要求,其他源码不变。源码忘了是在哪下载的,还带有demo,非常感谢好心人。

上代码 GifTextView

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.mcds.app.android.estar.component.MySpan;
import com.mcds.app.android.estar.util.FaceUtility;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.widget.TextView;

public class GifTextView extends TextView implements GifAction {

/** gif解码器 */
 private GifDecoder gifDecoder = null;
 private String input="";//需要显示的文字
 private SpannableString spannable;
 BitmapDrawable drawable;//需要添加的图片资源
 private  long time;//记录post到主线程的时间
 private class Info {//保存input中需要添加的SpannableString的信息
  String decodername;//gif图片名称,根据次名称去bitmapMap去图片
  int start;//开始位置
  int end;//结束位置
 }
 //保存需要更新的GifTextView,简单的检查者模式,退出activity时清空,新建GifTextView时添加到此list中
    public static ArrayList<GifTextView> textViewList = new ArrayList<GifTextView>();
    //保存gifDecoder的信息,使用gif图片的名称作key,只添加,不删除,
 public static HashMap<String, DecoderInfo> decodermap = new HashMap<String, DecoderInfo>();
 //保存更新后drawable的信息,使用gif图片的名称作key,GifTextView更新时,根据key取drawable
 public static HashMap<String, Bitmap> bitmapMap = new HashMap<String, Bitmap>();
 //保存需要添加的SpannableString的list
 private ArrayList<Info> infoList = new ArrayList<GifTextView.Info>();
    private Info info ;
    private ImageSpan span;
 public GifTextView(Context context) {
  super(context);
  textViewList.add(this);
 }

public GifTextView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
  textViewList.add(this);
 }

public GifTextView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  textViewList.add(this);

}

/**
  * 设置图片,记录开始结束信息,开始解码
  *
  * @param is
  *            要设置的图片
  */
 private void setGifDecoderImage(String resId, int start, int end) {
  Info info = new Info();
  info.decodername = resId;
  info.start = start;
  info.end = end;
  infoList.add(info);
  if (!decodermap.containsKey(resId)) {//主要是为了节省资源,如果已经包含,不在开启线程
   Resources r = getContext().getResources();
   //FaceUtility内主要是一个hash表,key是gif图片的名字,value是图片的资源id
   InputStream is = r.openRawResource(FaceUtility.getInstance().getBigItem(resId));
   gifDecoder = new GifDecoder(is, this);
   gifDecoder.start();
   DecoderInfo decoderInfo = new DecoderInfo();
   decoderInfo.decoder = gifDecoder;
   decodermap.put(resId, decoderInfo);
   //这里是为了防止gifdecoder解析失败所保存,如果解析失败,则直接显示静态图片
   Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), FaceUtility.getInstance().getBigItem(resId));
   bitmapMap.put(resId, bitmap);
  }
 }

/**
  * 设置文字
  */
 public void setSpanText(String input) {
  infoList.clear();
  this.input = input;
  if (input == null) {
   input = "";
  }
  String contentinfo = input;
  int staindex = contentinfo.indexOf("[");
  List<Integer> arraysta = new ArrayList<Integer>();
  while (staindex != -1) {
   arraysta.add(staindex);
   staindex = contentinfo.indexOf("[", staindex + 1);
  }

int endindex = contentinfo.indexOf("]");
  List<Integer> arrayend = new ArrayList<Integer>();
  while (endindex != -1) {
   arrayend.add(endindex + 1);
   endindex = contentinfo.indexOf("]", endindex + 1);
  }

String[] array_start = input.split("\\[");
  for (int i = 0; i < array_start.length - 1; i++) {
   String[] array_end = array_start[i + 1].split("\\]");

if (FaceUtility.getInstance().getBigItem("["+array_end[0]+"]") != null) {
    if (arraysta.size() - 1 >= i && arrayend.size() - 1 >= i) {
     setGifDecoderImage("["+array_end[0]+"]", arraysta.get(i), arrayend.get(i));
    }
   }
  }
  show1();//第一次显示
 }

public void parseOk(boolean parseStatus, int frameIndex) {

}

public void show() {
  //设置两次post的时间,post主线程过快相当耗资源,这里自己设置
  //如果没有需要设置的spannablestring或者需要设置太多,直接返回
  if(System.currentTimeMillis()-time<500||infoList.size()==0||infoList.size()>7){
   return;
  }
  time = System.currentTimeMillis();
  spannable = new SpannableString(input);
  int length = infoList.size();
  for (int i = 0; i < length; i++) {
   info = infoList.get(i);
   drawable = new BitmapDrawable(getContext().getResources(), bitmapMap.get(info.decodername));
   drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
    span = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
   spannable.setSpan(span, info.start, info.end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
  }
//  spannable = MySpan.URLSpan(spannable, getContext(), true);
  //post到主线程,只有在主线程中才可对ui进行操作
  GifTextView.this.post(new Runnable() {

@Override
   public void run() {
    // TODO Auto-generated method stub
    GifTextView.this.setText(spannable);
   }
  });

}
 private void show1(){
  spannable = new SpannableString(input);
  for (int i = 0; i < infoList.size(); i++) {
    info = infoList.get(i);
   drawable = new BitmapDrawable(getContext().getResources(), bitmapMap.get(info.decodername));
   drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
    span = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
   spannable.setSpan(span, info.start, info.end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
  }
  spannable = MySpan.URLSpan(spannable, getContext(), true);
  GifTextView.this.post(new Runnable() {

@Override
   public void run() {
    // TODO Auto-generated method stub
    GifTextView.this.setText(spannable);
   }
  });
 }

}

线程MyThread 此线程我是设置为静态的,只要app启动,就会运行,虽然有点耗资源,现在没啥好办法。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.mcds.app.android.estar.MainApplication;

import android.graphics.Bitmap;
import android.os.SystemClock;
import android.util.Log;

public class MyThread extends Thread {
 public boolean stop = true;//线程暂停
 public boolean destroy = false;//线程结束
 private boolean animation = false;//是否有gif更新
 private HashMap<String, DecoderInfo> map = new HashMap<String, DecoderInfo>();//decodermap的拷贝,解决同步错误
 private ArrayList<GifTextView> textViewlist = new ArrayList<GifTextView>();//textViewList的拷贝,解决同步错误
 String key;
 DecoderInfo val;

@Override
 public void run() {
  // TODO Auto-generated method stub
  super.run();
  while (!destroy) {
   if (!stop) {
    if (!map.equals(GifTextView.decodermap)) {//如果decodermap变化,重新拷贝
     map.clear();
     map.putAll(GifTextView.decodermap);
    }
    Iterator iter = map.entrySet().iterator();
    while (iter.hasNext()) {
     Map.Entry entry = (Map.Entry) iter.next();
     key = (String) entry.getKey();
     val = (DecoderInfo) entry.getValue();
     if (val.decoder != null && val.decoder.getFrameCount() > 1) {
      if (System.currentTimeMillis() - val.time > val.delay) {
       GifFrame frame = val.decoder.next();
       val.time = System.currentTimeMillis();
       val.delay = frame.delay;
       putBitmap(key, frame.image);
      }
     }
    }
    if (animation) {
     if(!textViewlist.equals(GifTextView.textViewList)){//textViewList,重新拷贝
      textViewlist.clear();
      textViewlist.addAll( GifTextView.textViewList);
     }
     for (GifTextView view :textViewlist) {//监察者模式,出现变化,通知GifTextView更新
      view.show();
     }
     animation = false;
    }
    SystemClock.sleep(100);//设置循环时间,这里不要设置太长,会打乱gif图的效果
   } else {
    SystemClock.sleep(2000);//设置线程睡眠时间,
   }
  }

}

private void putBitmap(String name, Bitmap bitmap) {
  animation = true;
  //bitmapMap更新drawable,以便GifTextView调用
  if (GifTextView.bitmapMap.containsKey(name)) {
   GifTextView.bitmapMap.remove(name);
  }
  GifTextView.bitmapMap.put(name, bitmap);
 }
}

public class DecoderInfo {//保存decoder信息
 GifDecoder decoder;
 long time = 0;//记录上次图片更新时间
 long delay = 0;//本次帧间隔时间
}

在activity pause和resume时,分别把MyThread的stop值设置为true和false,退出时把destroy设置为true,节省资源。还有在列表进行滚动时,最好设置MyThread的stop值,不会太卡。如果感觉卡,调节GifTextView的post间隔值。

这是我的一点见解,如果有大神有更好的实现方式,请告知,不胜感激。

作者:qiangainannan(csdn)

如需转载 请注明转载出处 http://blog.csdn.net/qiangainannan/article/details/38349129

android qq聊天动态表情的实现相关推荐

  1. android qq聊天背景图片,手机qq聊天背景图片【突破指南】

    很多小伙伴都遇到过手机qq聊天背景图片的困惑吧,一些朋友看过网上零散的手机qq聊天背景图片的处理方法,并没有完完全全明白手机qq聊天背景图片是如何解决的,今天小编准备了简单的解决办法,只需要按照1:打 ...

  2. WPF仿QQ聊天框表情文字混排实现

    这个需求前后也历经了多版,上两张图给各位同学感受一下,第一张是初版,第二张是稳定不最终版~ 图中分别有文件.文本+表情.纯文本的展示,对于同一个list不同的展示形式,很明显,应该用多个DataTem ...

  3. php聊天动图,如何制作微信动态表情 真人qq聊天搞笑动态图片 多人摇头娃娃在线制作...

    呼呼,总算是完成了哦,最后就是保存摇头娃娃啦,单击"另存为"按钮-"保存为单独文件",在随即弹出的保存页面里面,设置图片的保存路径并为图片命名,最后选择&quo ...

  4. android仿qq聊天项目点评,android 实现qq聊天对话界面效果

    [实例简介] Android UI[android 仿微信.QQ聊天,带表情,可翻页,带翻页拖动缓冲] 博客介绍http://blog.csdn.net/lnb333666/article/detai ...

  5. 怎么制作gif动态图 QQ动态表情包怎么制作

    在平时的聊天中经常会使用到GIF动图,不仅仅可以缓解气氛,还很有趣,那这些动态图是如何制作的呢?没有想象的那么难,今天来看看怎么制作的吧! 1.先准备好素材,要制作什么样的动图,可以是图片也可以是视频 ...

  6. Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命

    Android特效专辑(六)--仿QQ聊天撒花特效,无形装逼,最为致命 我的关于特效的专辑已经在CSDN上申请了一个专栏--http://blog.csdn.net/column/details/li ...

  7. 类似qq聊天表情实现

    android qq上有这样的功能,点击表情,然后输入框EditText上显示表情,博客,论坛上也有这样的功能.有些是显示qq表情的代表符号洳:":()"这样的符号 先是从网上搜索 ...

  8. android气泡聊天消息背景,Android使用贝塞尔曲线仿QQ聊天消息气泡拖拽效果

    本文实例为大家分享了Android仿QQ聊天消息气泡拖拽效果展示的具体代码,供大家参考,具体内容如下 先画圆,都会吧.代码如下: public class Bezier extends View { ...

  9. 微信android版发布动态图片,太“炸”了!微信重大玩法更新!微信表情会动了!附安卓用户更新入口→...

    在我爱大乐昌微信对话框 发生"新版"二字 昨天(1月21日) 微信十周年之日 8.0.0 iOS版重磅上线 一口气更新了多个重要功能 包括动态表情.个人状态. 浮窗调整.创作音乐视 ...

  10. Android源码解析--SwipeMenuListView仿QQ聊天左滑

    版权声明:本文为博主原创文章,转载请标明出处. https://blog.csdn.net/lyhhj/article/details/50612714 绪论: 好久没写博客了,最近比较懒,不想写博客 ...

最新文章

  1. Linux的概念与体系 2. Linux文件管理(转载)
  2. 支持向量机:Numerical Optimization
  3. mysql not in优化_MySQL性能优化 — 实践篇2
  4. 数据分析、机器学习必读书,李航《统计学习方法》发布算法推导视频啦!(附作业讲解)...
  5. C++求三位数的水仙花数
  6. Splunk学习心得
  7. MongoDB+java+spirng+morphia
  8. c语言中的数组二分法排序程序,#C语言#二分法查找有序数组
  9. linux进程控制-exit()
  10. ajax请求后台php数据时查看报错parse error
  11. 从零开始配置 vim(4)——键盘映射的一些技巧
  12. RCC BUCK变压器设计
  13. python——获取矩形四个角点的坐标
  14. navicat 导入dmp文件
  15. php接入北斗定位,手机如何连接北斗卫星?
  16. 滴滴跨端框架 Chameleon 正式支持快应用
  17. Java调用python脚本,进程长时间卡住问题
  18. Vue中图片实现毛玻璃效果
  19. 支付宝存漏洞?这10招保护个人信息赶紧保存起来!
  20. python 操作微信_利用 Python 实现微信半自动化操作

热门文章

  1. java的duplicate用法_Java IntBuffer duplicate()用法及代码示例
  2. 拿不到offer退全款 | 廖雪峰的“Web 全栈架构师”开班了!
  3. 电场强度通量的高斯定理
  4. adb 详细使用文档
  5. 清华大学计算机系2016名单,清华大学2016年自主招生北京考生入选名单汇总
  6. word文件在线转换成pdf
  7. FTP和FXP的区别
  8. 透过西安未来人工智能计算中心,看到AI不一样的未来
  9. su 与 su - 的区别
  10. iOS音乐播放器(歌词自动滚动)