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月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
123 1
|
3月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
22天前
|
IDE 开发工具 Android开发
移动应用开发之旅:探索Android和iOS平台
在这篇文章中,我们将深入探讨移动应用开发的两个主要平台——Android和iOS。我们将了解它们的操作系统、开发环境和工具,并通过代码示例展示如何在这两个平台上创建一个简单的“Hello World”应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧,帮助你更好地理解和掌握移动应用开发。
51 17
|
2月前
|
Android开发 UED
Android 中加载 Gif 动画
【10月更文挑战第20天】加载 Gif 动画是 Android 开发中的一项重要技能。通过使用第三方库或自定义实现,可以方便地在应用中展示生动的 Gif 动画。在实际应用中,需要根据具体情况进行合理选择和优化,以确保用户体验和性能的平衡。可以通过不断的实践和探索,进一步掌握在 Android 中加载 Gif 动画的技巧和方法,为开发高质量的 Android 应用提供支持。
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
116 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
3月前
|
监控 Android开发 iOS开发
深入探索安卓与iOS的系统架构差异:理解两大移动平台的技术根基在移动技术日新月异的今天,安卓和iOS作为市场上最为流行的两个操作系统,各自拥有独特的技术特性和庞大的用户基础。本文将深入探讨这两个平台的系统架构差异,揭示它们如何支撑起各自的生态系统,并影响着全球数亿用户的使用体验。
本文通过对比分析安卓和iOS的系统架构,揭示了这两个平台在设计理念、安全性、用户体验和技术生态上的根本区别。不同于常规的技术综述,本文以深入浅出的方式,带领读者理解这些差异是如何影响应用开发、用户选择和市场趋势的。通过梳理历史脉络和未来展望,本文旨在为开发者、用户以及行业分析师提供有价值的见解,帮助大家更好地把握移动技术发展的脉络。
120 6
|
3月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
3月前
|
IDE 开发工具 Android开发
安卓与iOS开发对比:平台选择对项目成功的影响
【9月更文挑战第10天】在移动应用开发的世界中,选择正确的平台是至关重要的。本文将深入探讨安卓和iOS这两大主要移动操作系统的开发环境,通过比较它们的市场份额、开发工具、编程语言和用户群体等方面,为开发者提供一个清晰的指南。我们将分析这两个平台的优势和劣势,并讨论如何根据项目需求和目标受众来做出最佳选择。无论你是初学者还是有经验的开发者,这篇文章都将帮助你更好地理解每个平台的特性,并指导你做出明智的决策。
|
2月前
|
XML 存储 Java
浅谈Android的TextView控件
浅谈Android的TextView控件
47 0
|
3月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术与科学
在移动应用开发的广阔天地中,安卓与iOS两大平台如同东西方哲学的碰撞,既有共通之处又各具特色。本文将深入探讨这两个平台的设计理念、开发工具和市场定位,旨在为开发者提供一份简明扼要的指南,帮助他们在这场技术与商业的博弈中找到自己的道路。通过比较分析,我们将揭示每个平台的优势与局限,以及它们如何影响应用的性能、用户体验和市场接受度。无论你是初涉江湖的新手,还是经验丰富的老手,这篇文章都将为你的选择提供新的视角和思考。
47 5