Android TextView uses ImageSpan to display GIF images,
Android-gif-drawable (https://github.com/koral--/android-gif-drawable/releases) open source project --- is a pretty good android gif display implementation. this article describes how to display gif dynamic images in TextView and EditText Based on android-Gif-drawable. There are quite a few articles on this framework, such as http://www.open-open.com/lib/view/open1404888098200.html.
Core class GifDrawable reads data from the next frame at a certain interval, and then runs invalidateSelf () ---- "CallBack: invalidateDrawable () ---" View: verifyDrawable () and View: invalidate (), the data refresh process of this frame is completed. The android-gif-drawable framework currently supports three android widgets: GifImageView, GifImageButton, and GifTextView. Besides, GifImageView and GifImageButton support setting Gif for src and backgroud, gifTextView sets Gif for backgroud and CompoundDrawables.
Currently, many apps support Gif expressions, but it seems that no app supports GIF for input boxes (and so on. Basically all emojis (including Emoji) are implemented using ImageSpan. However, the default ImageSpan does not support GIF. Refer to the gif frame data refresh process in the android-gif-drawable framework. To support GIF, consider and complete the following three operations: 1) set the Callback for GifDrawable in ImageSpan, when is the Callback cleared? Currently, no Callback is set for TextView, ImageSpan, and Spaned. We need to find a proper place to set TextView to the Callback of GifDrawable; 2) in TextView :: in invalidateDrawable (), GifDrawable is verified to verify that the GifDrawable is the content of TextView and needs to be refreshed. 3) How to refresh TextView display in TextView: invalidateDrawable;
First for 1), refer to ImageView and TextView implementation. The implementation of src drawable in ImageView is as follows:
/** * 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; } }
That is to say, when setting src for ImageView, clear the callback of the old mDrawable and set the callback of the newly set src drawable to ImageView. Similarly, TextView's callback processing for CompoundDrawables is also performed at setCompoundDrawables. When does ImageSpan need to set the 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;}@Overridepublic Drawable getDrawable() {return mDrawable;}}
Public class GifEditText extends EditText {private GifSpanChangeWatcher mGifSpanChangeWatcher; public GifEditText (Context context) {super (context); convert ();} public GifEditText (Context context, AttributeSet attrs) {super (context, attrs); initGifSpanChangeWatcher ();} public GifEditText (Context context, AttributeSet attrs, int defStyle) {super (context, attrs, defStyle); initGifSpanChange Watcher ();} private void listener () {mGifSpanChangeWatcher = new listener (this); addTextChangedListener (mGifSpanChangeWatcher);} @ Overridepublic void setText (CharSequence text, BufferType type) {CharSequence oldText = null; try {// The default mText of EditText is "", which is a String, but getText () is strongly converted to Editable, Nima, only try/catch oldText = getText (); // first clear all the callback of the old GifImageSpan and GifSpanChangeWatch on the oldText Erif (! 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 = w Atchers. 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; // set the callback of all GifImageSpan in the new text to the current EditTextfinal 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);} // clear the 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);} // set GifSpanChangeWatchersp on the new text. 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) {// set callback if (what instanceof GifImageSpan) {(GifImageSpan) what ). getDrawable (). setCallback (mCallback) ;}} public void onSpanRemoved (Spannable buf, Object what, int s, int e) {// clear callback if (what instanceof GifImageSpan) {(GifImageSpan) what ). getDrawable (). setCallback (null) ;}@overridepublic void afterTextChanged (Editable s) {if (s! = Null) {s. setSpan (this, 0, s. length (), Spanned. SPAN_INCLUSIVE_INCLUSIVE | (100 <Spanned. SPAN_PRIORITY_SHIFT); }}@ Overridepublic void beforeTextChanged (CharSequence s, int start, int count, int after) {// TODO Auto-generated method stub} @ Overridepublic void onTextChanged (CharSequence s, int start, int before, int count) {// TODO Auto-generated method stub }}
That is, the operation is performed in setText (), onSpanAdded (), onSpanRemoved () (1)
Then, for 2), also refer to ImageView and 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; }
Directly Add code
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;}}
The getImageSpan () method obtains all GifImageSpan through getSpans (), compares drawable, and returns the corresponding GifImageSpan.
Finally, Operation 3) Update View display. For more information, see 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); } }
Calculate the compoundDrawable location and then execute invalidate. Similar operations can be performed on GifEditText. Calculate the position and region based on the start and end of GifImageSpan, and then execute invalidate (). However, the computing process is too complicated. However, TextView of android4.4 provides the void invalidateRegion (int start, int end, boolean invalidateCursor) method to refresh the area between start and end, but it is still quite complicated to see. I studied who calls this method.
InvalidateRegion () <--- invalidateCursor () <--- spanChange () <--- ChangeWatcher: onSpanChanged (), ChangeWatcher: onSpanAdded (), ChangeWatcher: Login ()
That is, as long as the span in the TextView content changes, invalidateRegion () is triggered to refresh the corresponding region and cursor.
@Overridepublic 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);}}
Directly reset the ImageSpan to trigger the ChangeWatcher: onSpanChanged () callback, and the region and cursor will be refreshed immediately.
Run OK. The above is the implementation of EditText, which is slightly different from 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);}@Overridepublic 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;}@Overridepublic 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);}}}
Set android: editable = "true" or set the type to BufferType. EDITABLE directly in setText (CharSequence text, BufferType type.