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. Android:Drag and Drop的应用

    最近看了下Drag and Drop部分的原文,觉得很有意思就像自己试着做一下,说实在的原文真的是不好读啊,要感谢那些为我们发表译文的大神们, 真的是不容易,原文中给了例子,但是只有后面零星的代码,真 ...

  2. C++编程规范之11:隐藏信息

    摘要: 不要泄密,不要公开提供抽象的实体的内部信息. 为了尽量减少操作抽象的调用代码和抽象的实现之间的依赖性,必须隐藏实现内部的数据.否则,调用代码就能够访问该信息,或者更糟,操作该信息,而原来应属于 ...

  3. jfinal常见问题

    2014年的时候,学过一段时间的JFinal,当时主要是了解这个框架,研究了下源码,看懂了部分.今天,2015年2月7日,弄了一下午的JFinal,把未来要上线的一个官网项目,迁移到了JFinal.下 ...

  4. 内核编程实例,多文件的Makefile

    内核编程实例,多文件的Makefile 经典的hello word测试 ////# cat hello.c #include <linux/module.h> #include <l ...

  5. 参加2013中国软件开发者大会(SDCC)会,听软件开发趋势

    1.SDCC        盛大召开的会议,既然参加了,就写篇博客记一下. 2.蒋公子     首先向大会主席台走来的是csdn老大...... 额,好像不是走过来的.蒋涛采用了个特殊的上台方式呢~ ...

  6. java中synchronized的使用方法与具体解释

    Java语言的keyword.当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多仅仅有一个线程运行该段代码. 一.当两个并发线程訪问同一个对象object中的这个synchronized ...

  7. NET5

    ASP.NET5(RC1) - 翻译 前言 ASP.NET 5 是一次令人惊叹的对于ASP.NET的创新革命. 他将构建目标瞄准了 .NET Core CLR, 同时ASP.NET又是对于云服务进行优 ...

  8. 在VC++中启用内存泄露检测

    检测内存泄漏的主要工具是调试器和 CRT 调试堆函数.若要启用调试堆函数,请在程序中包括以下语句: #define CRTDBG_MAP_ALLOC#include <stdlib.h># ...

  9. sqlserver 自学笔记 函数实训 学分学期转换函数的设计

    设计目的: 1.运用sql基本知识,编写学期转换函数. 2.运用sql基本知识,编写学分转换函数,将考试成绩转换为学分 3.通过上述函数的编写与调试,熟练掌握 sql函数的编写.调试与使用方法. 设计 ...

  10. AIR

    There is a meaning for wings that cannot fly,it's a previous memory of when you once flew through th ...