android-gif-drawable(https://github.com/koral--/android-gif-drawable/releases)开源项目---是一个蛮不错的android
gif显示实现.本文在android-gif-drawable基础上介绍怎样实现TextView、EditText上展示Gif动态图。

网上有蛮多介绍这个框架使用的文章,比方http://www.open-open.com/lib/view/open1404888098200.html。



核心类GifDrawable间隔一定时间读取下一帧数据,然后运行invalidateSelf()----》CallBack::invalidateDrawable()---》View::verifyDrawable()和View::invalidate(),该帧数据刷新流程就运行结束。

而android-gif-drawable框架眼下已支持GifImageView、GifImageButton、GifTextView三个android widget,且GifImageView、GifImageButton支持对src和backgroud设置Gif,而GifTextView对支持backgroud和CompoundDrawables设置Gif。

如今非常多app都支持Gif表情。但貌似还没有一个app对输入框(等)支持GIF。而基本全部的表情图片(包含Emoji)都是使用ImageSpan实现的。但默认的ImageSpan是无法支持GIF的。
參考android-gif-drawable框架中gif帧数据刷新流程,要支持GIF须要考虑并完毕以下三个操作:
1)对ImageSpan中的GifDrawable,何时设置其Callback,又何时清空该Callback,眼下TextView、ImageSpan和Spaned都没有设置Callback的地方。我们须要找一个合适的地方将TextView设置为GifDrawable的Callback;
2)在TextView::invalidateDrawable()中实现对GifDrawable的校验,即验证该GifDrawable是TextView的内容,须要刷新;
3)在TextView::invalidateDrawable()中实现怎样刷新TextView显示;

首先对于1)。我们參考下ImageView和TextView实现。ImageView的src drawable相应实现例如以下:
	/**
* Sets a drawable as the content of this ImageView.
*
* @param drawable The drawable to set
*/
public void setImageDrawable(Drawable drawable) {
if (mDrawable != drawable) {
...
updateDrawable(drawable);
...
}
} private void updateDrawable(Drawable d) {
if (mDrawable != null) {
mDrawable.setCallback(null);
unscheduleDrawable(mDrawable);
}
mDrawable = d;
if (d != null) {
d.setCallback(this);
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setLevel(mLevel);
d.setLayoutDirection(getLayoutDirection());
d.setVisible(getVisibility() == VISIBLE, true);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyColorMod();
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}

也就是说,ImageView在设置其src时。清空旧mDrawable的callback,然后将新设置的src drawable的callback设置为ImageView本身。

同理。TextView对于CompoundDrawables的callback处理也是在setCompoundDrawables()时。

而ImageSpan须要在什么时机设置GifDrawable的callback呢,
public class GifImageSpan extends ImageSpan{

	private Drawable mDrawable = null;

	public GifImageSpan(Drawable d) {
super(d);
mDrawable = d;
} public GifImageSpan(Drawable d, int verticalAlignment) {
super(d, verticalAlignment);
mDrawable = d;
} @Override
public Drawable getDrawable() {
return mDrawable;
}
}
public class GifEditText extends EditText {

	private GifSpanChangeWatcher mGifSpanChangeWatcher;
public GifEditText(Context context) {
super(context);
initGifSpanChangeWatcher();
} public GifEditText(Context context, AttributeSet attrs) {
super(context, attrs);
initGifSpanChangeWatcher();
} public GifEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initGifSpanChangeWatcher();
} private void initGifSpanChangeWatcher() {
mGifSpanChangeWatcher = new GifSpanChangeWatcher(this);
addTextChangedListener(mGifSpanChangeWatcher);
} @Override
public void setText(CharSequence text, BufferType type) { CharSequence oldText = null;
try {
//EditText的默认mText为""。是一个String。但getText()强转为Editable,尼玛。仅仅能try/catch了
oldText = getText();
//首先清空全部旧GifImageSpan的callback和oldText上的GifSpanChangeWatcher
if (!TextUtils.isEmpty(oldText) && oldText instanceof Spannable) {
Spannable sp = (Spannable) oldText;
final GifImageSpan[] spans = sp.getSpans(0, sp.length(), GifImageSpan.class);
final int count = spans.length;
for (int i = 0; i < count; i++) {
spans[i].getDrawable().setCallback(null);
} final GifSpanChangeWatcher[] watchers = sp.getSpans(0, sp.length(), GifSpanChangeWatcher.class);
final int count1 = watchers.length;
for (int i = 0; i < count1; i++) {
sp.removeSpan(watchers[i]);
}
}
} catch (Exception e) { } if (!TextUtils.isEmpty(text)) {
if (!(text instanceof Editable)) {
text = new SpannableStringBuilder(text);
}
} if (!TextUtils.isEmpty(text) && text instanceof Spannable) {
Spannable sp = (Spannable) text;
//设置新text中全部GifImageSpan的callback为当前EditText
final GifImageSpan[] spans = sp.getSpans(0, sp.length(), GifImageSpan.class);
final int count = spans.length;
for (int i = 0; i < count; i++) {
spans[i].getDrawable().setCallback(this);
} //清空新text上的GifSpanChangeWatcher
final GifSpanChangeWatcher[] watchers = sp.getSpans(0, sp.length(), GifSpanChangeWatcher.class);
final int count1 = watchers.length;
for (int i = 0; i < count1; i++) {
sp.removeSpan(watchers[i]);
} if (mGifSpanChangeWatcher == null) {
mGifSpanChangeWatcher = new GifSpanChangeWatcher(this);
} //设置新text上的GifSpanChangeWatcher
sp.setSpan(mGifSpanChangeWatcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE | (100 << Spanned.SPAN_PRIORITY_SHIFT));
} super.setText(text, type);
}
}

public class GifSpanChangeWatcher implements SpanWatcher, TextWatcher{

	private Drawable.Callback mCallback;

	public GifSpanChangeWatcher(Drawable.Callback callback) {
mCallback = callback;
}
public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
//do nothing
} public void onSpanAdded(Spannable buf, Object what, int s, int e) {
//设置callback
if (what instanceof GifImageSpan) {
((GifImageSpan)what).getDrawable().setCallback(mCallback);
}
} public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
//清空callback
if (what instanceof GifImageSpan) {
((GifImageSpan)what).getDrawable().setCallback(null);
}
} @Override
public void afterTextChanged(Editable s) {
if (s != null) {
s.setSpan(this, 0, s.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE | (100 << Spanned.SPAN_PRIORITY_SHIFT));
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub } }

也就是,在setText()和onSpanAdded()、onSpanRemoved()中运行操作(1)

然后,对于2),相同參考ImageView和TextView
@Override
protected boolean verifyDrawable(Drawable dr) {
return mDrawable == dr || super.verifyDrawable(dr);
}
@Override
protected boolean verifyDrawable(Drawable who) {
final boolean verified = super.verifyDrawable(who);
if (!verified && mDrawables != null) {
return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
}
return verified;
}

直接上代码

public class GifEditText extends EditText {

	private GifImageSpan getImageSpan(Drawable drawable) {
GifImageSpan imageSpan = null;
CharSequence text = getText();
if (!TextUtils.isEmpty(text)) {
if (text instanceof Spanned) {
Spanned spanned = (Spanned) text;
GifImageSpan[] spans = spanned.getSpans(0, text.length(), GifImageSpan.class);
if (spans != null && spans.length > 0) {
for (GifImageSpan span : spans) {
if (drawable == span.getDrawable()) {
imageSpan = span;
}
}
}
}
} return imageSpan;
}
}

getImageSpan()方法通过getSpans()获取全部的GifImageSpan。然后对照drawable,返回对应的GifImageSpan。



最后。操作3)更新View显示。相同參考下TextView

@Override
public void invalidateDrawable(Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getBounds();
int scrollX = mScrollX;
int scrollY = mScrollY; // IMPORTANT: The coordinates below are based on the coordinates computed
// for each compound drawable in onDraw(). Make sure to update each section
// accordingly.
final TextView.Drawables drawables = mDrawables;
if (drawables != null) {
if (drawable == drawables.mDrawableLeft) {
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; scrollX += mPaddingLeft;
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
} else if (drawable == drawables.mDrawableRight) {
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
} else if (drawable == drawables.mDrawableTop) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
scrollY += mPaddingTop;
} else if (drawable == drawables.mDrawableBottom) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
}
} invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
}
}

计算compoundDrawable位置栏,然后运行invalidate。

对于GifEditText貌似也能够类似操作,依据GifImageSpan的start、end计算其位置栏,然后运行invalidate()。只是计算过程太过复杂了。只是android4.4的TextView提供这种方法void
invalidateRegion(int start, int end, boolean invalidateCursor) 方法用于刷新start和end之间的区域,但还是蛮复杂的看的人眼花缭乱。研究了下这种方法终于是由谁调用的。


invalidateRegion()<<---invalidateCursor()<<---spanChange()<<---ChangeWatcher::onSpanChanged()、ChangeWatcher::onSpanAdded()、ChangeWatcher::onSpanRemoved()

也就是说,仅仅要TextView内容中span发生变化都会触发invalidateRegion()来刷新相应区域和cursor。

@Override
public void invalidateDrawable(Drawable drawable) {
GifImageSpan imageSpan = getImageSpan(drawable);
Log.e("", "invalidateDrawable imageSpan:" + imageSpan);
if (imageSpan != null) {
CharSequence text = getText();
if (!TextUtils.isEmpty(text)) {
if (text instanceof Editable) {
Log.e("", "invalidateDrawable Editable:");
Editable editable = (Editable)text;
int start = editable.getSpanStart(imageSpan);
int end = editable.getSpanEnd(imageSpan);
int flags = editable.getSpanFlags(imageSpan); editable.setSpan(imageSpan, start, end, flags);
}
} } else {
super.invalidateDrawable(drawable);
}
}

直接又一次设置该ImageSpan就可以触发ChangeWatcher::onSpanChanged()回调。也就会马上刷新其区域和cursor。


大功告成。执行ok。

上面是对EditText的实现,针对TextView实现略微有点差别
public class GifSpanTextView extends GifTextView {

	private GifSpanChangeWatcher mGifSpanChangeWatcher;
public GifSpanTextView(Context context) {
super(context);
initGifSpanChangeWatcher();
} public GifSpanTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initGifSpanChangeWatcher();
} public GifSpanTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initGifSpanChangeWatcher();
} private void initGifSpanChangeWatcher() {
mGifSpanChangeWatcher = new GifSpanChangeWatcher(this);
addTextChangedListener(mGifSpanChangeWatcher);
} @Override
public void setText(CharSequence text, BufferType type) {
type = BufferType.EDITABLE;
CharSequence oldText = getText();
if (!TextUtils.isEmpty(oldText) && oldText instanceof Spannable) {
Spannable sp = (Spannable) oldText;
final GifImageSpan[] spans = sp.getSpans(0, sp.length(), GifImageSpan.class);
final int count = spans.length;
for (int i = 0; i < count; i++) {
spans[i].getDrawable().setCallback(null);
} final GifSpanChangeWatcher[] watchers = sp.getSpans(0, sp.length(), GifSpanChangeWatcher.class);
final int count1 = watchers.length;
for (int i = 0; i < count1; i++) {
sp.removeSpan(watchers[i]);
}
} if (!TextUtils.isEmpty(text) && text instanceof Spannable) {
Spannable sp = (Spannable) text;
final GifImageSpan[] spans = sp.getSpans(0, sp.length(), GifImageSpan.class);
final int count = spans.length;
for (int i = 0; i < count; i++) {
spans[i].getDrawable().setCallback(this);
} final GifSpanChangeWatcher[] watchers = sp.getSpans(0, sp.length(), GifSpanChangeWatcher.class);
final int count1 = watchers.length;
for (int i = 0; i < count1; i++) {
sp.removeSpan(watchers[i]);
} if (mGifSpanChangeWatcher == null) {
mGifSpanChangeWatcher = new GifSpanChangeWatcher(this);;
} sp.setSpan(mGifSpanChangeWatcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE | (100 << Spanned.SPAN_PRIORITY_SHIFT));
} super.setText(text, type);
} private GifImageSpan getImageSpan(Drawable drawable) {
GifImageSpan imageSpan = null;
CharSequence text = getText();
if (!TextUtils.isEmpty(text)) {
if (text instanceof Spanned) {
Spanned spanned = (Spanned) text;
GifImageSpan[] spans = spanned.getSpans(0, text.length(), GifImageSpan.class);
if (spans != null && spans.length > 0) {
for (GifImageSpan span : spans) {
if (drawable == span.getDrawable()) {
imageSpan = span;
}
}
}
}
} return imageSpan;
} @Override
public void invalidateDrawable(Drawable drawable) {
GifImageSpan imageSpan = getImageSpan(drawable);
if (imageSpan != null) {
CharSequence text = getText();
if (!TextUtils.isEmpty(text)) {
if (text instanceof Editable) {
Editable editable = (Editable)text;
int start = editable.getSpanStart(imageSpan);
int end = editable.getSpanEnd(imageSpan);
int flags = editable.getSpanFlags(imageSpan); editable.removeSpan(imageSpan);
editable.setSpan(imageSpan, start, end, flags);
}
} } else {
super.invalidateDrawable(drawable);
}
} }

设置其android:editable="true"或正上方setText(CharSequence text, BufferType type)将type设置BufferType.EDITABLE。

android平台TextView使用ImageSpan画廊GIF图像的更多相关文章

  1. Android的TextView使用Html来处理图片显示、字体样式、超链接等

    一.[Android实例]实现TextView里的文字有不同颜色 转eoe:http://www.eoeandroid.com/thread-4496-1-1.html import android. ...

  2. 我的Android进阶之旅------&gt; Android在TextView中显示图片方法

    面试题:请说出Android SDK支持哪些方式显示富文本信息(不同颜色.大小.并包括图像的文本信息).并简要说明实现方法. 答案:Android SDK支持例如以下显示富文本信息的方式. 1.使用T ...

  3. 我的Android进阶之旅------> Android在TextView中显示图片方法

    面试题:请说出Android SDK支持哪些方式显示富文本信息(不同颜色.大小.并包含图像的文本信息),并简要说明实现方法. 答案:Android SDK支持如下显示富文本信息的方式. 1.使用Tex ...

  4. [译]:Xamarin.Android平台功能——位置服务

    返回索引目录 原文链接:Location Services. 译文链接:Xamarin.Android平台功能--位置服务 本部分介绍位置服务以及与如何使用位置提供商服务 Location Servi ...

  5. android平台手电筒开发源代码

    android平台手电筒开发源代码,AndroidManifest.xml文件的入口是startapp,这个文件没上传上来,大家可以自己写. 1. [代码]android 1 2 3 4 5 6 7 ...

  6. [Android教程]TextView使用SpannableString设置复合文本

    TextView通常用来显示普通文本,但是有时候需要对其中某些文本进行样式.事件方面的设置.Android系统通过SpannableString类来对指定文本进行相关处理,具体有以下功能: 1.Bac ...

  7. OpenCV在Android平台上的应用

    今年8月份, OpenCV 2.3.1发布了. 虽然从2.2开始, OpenCV就号称支持Android平台, 但真正能让OpenCV在Android上运行起来还是在2.3.1版本上. 在这个版本上, ...

  8. dp和px,那些不得不吐槽的故事——Android平台图

    http://blog.sina.com.cn/s/blog_6499f8f101014ipq.html 一个优秀的手机软件,不仅要有精巧的功能,流畅的速度,让人赏心悦目的UI也往往是用户选择的重要理 ...

  9. 【转】Android平台下利用zxing实现二维码开发

    http://www.cnblogs.com/dolphin0520/p/3355728.html 现在走在大街小巷都能看到二维码,而且最近由于项目需要,所以研究了下二维码开发的东西,开源的二维码扫描 ...

随机推荐

  1. java如何运行OS命令(转)

    •javac TestRunTime.java•java TestRunTime hostname // 执行“hostname”Linux命令•即可看到输出 import java.io.IOExc ...

  2. javascript实现图片无缝滚动(scrollLeft的使用方法介绍)

    <!DOCTYPE html > <html> <head> <meta http-equiv="Content-Type" conten ...

  3. json转String 和 String转json 和判断对象类型

    function ajaxGetMenuList(){ $.getJSON("login.do", function(json){ var r = ""; zN ...

  4. 从O2O体验活动看华硕平板也“来电”新融合理念

          随着平板电脑的普及,用户对平板的要求也变得越来越高,且这种要求已经逐渐从单纯的性能方面拓展到全方位的功能方面,尤其在通讯.社交.娱乐等层面,平板迫切需要满足用户日益增长的需求.       ...

  5. KMP(Knuth-Morris-Pratt)算法

    一.朴素匹配算法 也就是暴力匹配算法.设匹配字符串的长度为n,模式串的长度为m,在最坏情况下,朴字符串匹配算法执行时间为O((n - m + 1)m). 假设m = n / 2, 那么该算法的复杂度就 ...

  6. listview改变选中行字体颜色

    [android]listview改变选中行字体颜色 目标:选中item,其字体设置为#3197FF,未选中的,其字体为#FFFFFF 与listvew设置选中行item背景图片一样,使用select ...

  7. 学习日记之命令模式和Effective C++

    命令模式(Command): 讲一个请求封装为一个对象.从而使你可用不同的请求对客户进行參数化.对请求队列或记录请求日志.以及支持可撤销的操作. 命令模式长处: (1),它能较easy地设计一个命令队 ...

  8. 黑马程序员:Java基础总结----类加载器

    黑马程序员:Java基础总结 类加载器   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个 ...

  9. WebService-03-使用CXF开发服务端和客户端

    写在前面的话 前面两节说了使用Java提供的包开发服务端和客户端,现在使用CXF来开发,CXF提供了两个类发而服务,一个是ServerFactoryBean,另一个是JaxWsServerFactor ...

  10. WCF(1)----服务创建

    本例中,我们通过一个关于Camera的服务为例子来说明WCF的开发流程,该服务比较简单,只是用来实现对Camera的添加,枚举,删除等操作. 详细步骤如下: 1:创建一个WCF Service Lib ...