android平台TextView使用ImageSpan画廊GIF图像

简介:

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。








本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5045666.html,如需转载请自行联系原作者

相关文章
|
2月前
|
Android开发
安卓SO层开发 -- 编译指定平台的SO文件
安卓SO层开发 -- 编译指定平台的SO文件
30 0
|
3月前
|
开发工具 Android开发 开发者
Android UI设计: 解释Android的Nine-Patch图像是什么,它用于什么目的?
Android UI设计: 解释Android的Nine-Patch图像是什么,它用于什么目的?
31 4
|
4月前
|
XML 前端开发 Java
Android App开发图像加工中卡片视图CardView和给图像添加装饰的讲解以及实战(附源码 简单易懂)
Android App开发图像加工中卡片视图CardView和给图像添加装饰的讲解以及实战(附源码 简单易懂)
51 0
|
4月前
|
Android开发
Android Studio入门之图像显示解析及实战(附源码 超详细必看)(包括图像视图、图像按钮、同时展示文本与图像)
Android Studio入门之图像显示解析及实战(附源码 超详细必看)(包括图像视图、图像按钮、同时展示文本与图像)
67 1
|
21天前
|
Android开发
Android开发小技巧:怎样在 textview 前面加上一个小图标。
Android开发小技巧:怎样在 textview 前面加上一个小图标。
12 0
|
29天前
|
运维 监控 Java
应用研发平台EMAS产品常见问题之安卓构建版本失败如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
|
1月前
|
运维 监控 Android开发
应用研发平台EMAS常见问题之安卓push的离线转通知目前无法收到如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
25 1
|
3月前
|
编解码 测试技术 开发工具
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
|
3月前
|
开发工具 Android开发
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
|
3月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
100 0