El texto enriquecido de Android SpannableString es compatible con el gráfico de red ImageSpan

Texto enriquecido SpannableString admite diagramas de red

Idea de implementación: agregue un espacio antes del texto, rellénelo con la imagen predeterminada, descargue la imagen de red y reemplace la imagen predeterminada cuando se complete la descarga.
CenterVerticalImageSpan realiza el efecto de centrar la imagen en el texto.
Recuerde llamar a textView.setText(spannableString, TextView.BufferType.SPANNABLE) después de que regrese el mapa de red;

Representaciones:
inserte la descripción de la imagen aquí

TextView textView = (TextView)weakView;
            SpannableString spannableString = new SpannableString("  " + textView.getText());
            ImageSpan defaultSpan = new CenterVerticalImageSpan(context, R.drawable.ic_default);
            spannableString.setSpan(defaultSpan,0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            textView.setText(spannableString, TextView.BufferType.SPANNABLE);

            String imageUrl = imageSpan.getString("image"); // 图片链接

            if (!TextUtils.isEmpty(imageUrl)) {
    
    
                RemoteImageView remoteImageView = new RemoteImageView(context);
                remoteImageView.setLoadOriginal(true);
                remoteImageView.setImageLoadListener(new PainterImageLoadListener() {
    
    
                    @Override
                    public boolean onHandleResourceReady(ImageView imageView, Object drawable) {
    
    
                        int start = spannableString.getSpanStart(defaultSpan);
                        int end = spannableString.getSpanEnd(defaultSpan);
                        if (start != -1 && end != -1) {
    
    
                            if (drawable instanceof Drawable) {
    
    
                                ((Drawable) drawable).setBounds(0, 0, ((Drawable) drawable).getIntrinsicWidth(), ((Drawable) drawable).getIntrinsicHeight());
                                spannableString.removeSpan(defaultSpan);
                                spannableString.setSpan(new CenterVerticalImageSpan((Drawable) drawable), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                                textView.setText(spannableString, TextView.BufferType.SPANNABLE);
                                textView.requestLayout();
                            }
                        }
                        return false;
                    }

                    @Override
                    public boolean onHandleLoadFailed(ImageView imageView) {
    
    
                        return false;
                    }
                });
                remoteImageView.load(imageUrl);
            }

Centrado de imágenes: CenterVerticalImageSpan.java

public class CenterVerticalImageSpan extends ImageSpan {
    
    

    public CenterVerticalImageSpan(Context context, int resourceId) {
    
    
        super(context, resourceId);
    }

    public CenterVerticalImageSpan(Drawable drawable) {
    
    
        super(drawable);
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end,
                       Paint.FontMetricsInt fontMetricsInt) {
    
    
        try {
    
    
            Drawable drawable = getDrawable();
            Rect rect = drawable.getBounds();
            if (fontMetricsInt != null) {
    
    
                Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
                int fontHeight = fmPaint.bottom - fmPaint.top;
                int drHeight = rect.bottom - rect.top;

                //对于这里我表示,我不知道为啥是这样。不应该是fontHeight/2?但是只有fontHeight/4才能对齐
                //难道是因为TextView的draw的时候top和bottom是大于实际的?具体请看下图
                //所以fontHeight/4是去除偏差?
                int top = drHeight / 2 - fontHeight / 4;
                int bottom = drHeight / 2 + fontHeight / 4;

                fontMetricsInt.ascent = -bottom;
                fontMetricsInt.top = -bottom;
                fontMetricsInt.bottom = top;
                fontMetricsInt.descent = top;
            }
            return rect.right;
        } catch (Exception e) {
    
    
            return 20;
        }
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end,
                     float x, int top, int y, int bottom, Paint paint) {
    
    
        try {
    
    
            Drawable drawable = getDrawable();
            canvas.save();
            int transY;
            //获得将要显示的文本高度-图片高度除2等居中位置+top(换行情况)
            transY = ((bottom - top) - drawable.getBounds().bottom) / 2 + top;
            canvas.translate(x, transY);
            drawable.draw(canvas);
            canvas.restore();
        } catch (Exception e) {
    
    
        }
    }

}

Hay un problema con el código anterior, es decir, se mostrará el tamaño de la imagen y no se adapta según la relación de aspecto de la imagen.
Cuando se requiere autoadaptación, simplemente pase el ancho y la altura (anchura real, altura real) externamente y configúrelo directamente en el dibujable.
El pseudocódigo es el siguiente:
((Drawable) dibujable).setBounds(0, 0, realWidth, altura real);

El siguiente es otro conjunto de código de ideas de implementación:

DraweeTextView.java

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Spanned;
import android.util.AttributeSet;
import android.widget.TextView;

public class DraweeTextView extends TextView {
    
    
    public DraweeTextView(Context context) {
    
    
        super(context);
    }

    public DraweeTextView(Context context, AttributeSet attrs) {
    
    
        super(context, attrs);
    }

    public DraweeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public DraweeTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    
    
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    private boolean mHasDraweeInText;
    // detect drawee-spans has been attached or not
    private boolean mIsSpanAttached;

    @Override
    public void setText(CharSequence text, BufferType type) {
    
    
        boolean wasSpanAttached = mIsSpanAttached;
        if (mHasDraweeInText && wasSpanAttached) {
    
    
            onDetach(); // detach all old images
            mHasDraweeInText = false;
        }
        if (text instanceof Spanned) {
    
    
            // find DraweeSpan in text
            DraweeSpan[] spans = ((Spanned) text).getSpans(0, text.length(), DraweeSpan.class);
            mHasDraweeInText = spans.length > 0;
        }
        super.setText(text, type);
        if (mHasDraweeInText && wasSpanAttached) {
    
    
            onAttach(); // reattach drawee spans
        }
    }

    @Override
    protected void onAttachedToWindow() {
    
    
        super.onAttachedToWindow();
        onAttach();
    }

    @Override
    protected void onDetachedFromWindow() {
    
    
        super.onDetachedFromWindow();
        onDetach();
    }


    @Override
    public void onStartTemporaryDetach() {
    
    
        super.onStartTemporaryDetach();
        onDetach();
    }

    @Override
    public void onFinishTemporaryDetach() {
    
    
        super.onFinishTemporaryDetach();
        onAttach();
    }

    @Override
    public void invalidateDrawable(Drawable dr) {
    
    
        if (mHasDraweeInText) {
    
    
            /* invalidate the whole view in this case because it's very
             * hard to know what the bounds of drawables actually is.
             */
            invalidate();
        } else {
    
    
            super.invalidateDrawable(dr);
        }
    }


    /**
     * Attach DraweeSpans in text
     */
    final void onAttach() {
    
    
        DraweeSpan[] images = getImages();
        for (DraweeSpan image : images) {
    
    
            image.onAttach(this);
        }
        mIsSpanAttached = true;
    }

    private DraweeSpan[] getImages() {
    
    
        try {
    
    
            if (mHasDraweeInText && length() > 0) {
    
    
                return ((Spanned) getText()).getSpans(0, length(), DraweeSpan.class);
            }
        } catch (Exception e) {
    
    

        }
        return new DraweeSpan[0]; //TODO: pool empty typed array
    }

    /**
     * Detach all of the DraweeSpans in text
     */
    final void onDetach() {
    
    
        DraweeSpan[] images = getImages();
        for (DraweeSpan image : images) {
    
    
            Drawable drawable = image.getDrawable();
            // reset callback first
            if (drawable != null) {
    
    
                unscheduleDrawable(drawable);
            }
            image.onDetach();
        }
        mIsSpanAttached = false;
    }
}

DraweeSpan.java

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.text.style.DynamicDrawableSpan;
import android.view.View;

import com.xx.xx.painter.image.ImageLoadRequestListener;
import com.xx.xx.painter.image.Painter;
import com.xx.xx.painter.image.request.RequestParams;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;


public class DraweeSpan extends DynamicDrawableSpan implements DeferredReleaser.Releasable {
    
    

    private final DeferredReleaser mDeferredReleaser;
    private final ForwardingDrawable mActualDrawable;
    private Drawable mDrawable;
    private Drawable mPlaceHolder;
    private View mAttachedView;
    private String mImageUri;
    private boolean mIsAttached;
    private Rect mMargin = new Rect();
    private Point mLayout = new Point();


    /**
     * 顶部对齐
     */
    public static final int ALIGN_TOP = 3;
    /**
     * 垂直居中
     */
    public static final int ALIGN_CENTER = 4;

    @IntDef({
    
    ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_TOP, ALIGN_CENTER})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Alignment {
    
    
    }

    public DraweeSpan(String uri) {
    
    
        this(uri, createEmptyDrawable());
    }

    public DraweeSpan(String uri, int width, int height) {
    
    
        this(uri, createEmptyDrawable(width, height));
    }

    public DraweeSpan(String uri, Drawable placeHolder) {
    
    
        this(uri, ALIGN_CENTER, placeHolder);
    }

    public DraweeSpan(String uri, @Alignment int verticalAlignment, Drawable placeHolder) {
    
    
        super(verticalAlignment);
        mImageUri = uri;
        mDeferredReleaser = DeferredReleaser.getInstance();
        mPlaceHolder = placeHolder;
        // create forwarding drawable with placeholder
        mActualDrawable = new ForwardingDrawable(mPlaceHolder);
        Rect bounds = mPlaceHolder.getBounds();
        if (bounds.right == 0 || bounds.bottom == 0) {
    
    
            mActualDrawable.setBounds(0, 0, mPlaceHolder.getIntrinsicWidth(), mPlaceHolder.getIntrinsicHeight());
        } else {
    
    
            mActualDrawable.setBounds(bounds);
        }
    }

    private static Drawable createEmptyDrawable(int width, int height) {
    
    
        ColorDrawable d = new ColorDrawable(Color.TRANSPARENT);
        d.setBounds(0, 0, width, height);
        return d;
    }

    private static Drawable createEmptyDrawable() {
    
    
        ColorDrawable d = new ColorDrawable(Color.TRANSPARENT);
        d.setBounds(0, 0, 100, 100);
        return d;
    }

    protected void layout() {
    
    
        mActualDrawable.setBounds(0, 0, mLayout.x, mLayout.y);
    }

    @Override
    public Drawable getDrawable() {
    
    
        return mActualDrawable;
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
    
    
        Drawable d = getDrawable();
        Rect rect = d.getBounds();
        try {
    
    
            if (fm != null) {
    
    
                Paint.FontMetrics fmPaint = paint.getFontMetrics();
                // 顶部 leading
                float topLeading = fmPaint.top - fmPaint.ascent;
                // 底部 leading
                float bottomLeading = fmPaint.bottom - fmPaint.descent;
                // drawable 的高度
                int drHeight = rect.height();

                switch (mVerticalAlignment) {
    
    
                    case ALIGN_CENTER: {
    
     // drawable 的中间与 行中间对齐
                        // 当前行 的高度
                        float fontHeight = fmPaint.descent - fmPaint.ascent;
                        // 整行的 y方向上的中间 y 坐标
                        float center = fmPaint.descent - fontHeight / 2;

                        // 算出 ascent 和 descent
                        float ascent = center - drHeight / 2;
                        float descent = center + drHeight / 2;

                        fm.ascent = (int) ascent;
                        fm.top = (int) (ascent + topLeading);
                        fm.descent = (int) descent;
                        fm.bottom = (int) (descent + bottomLeading);
                        break;
                    }
                    case ALIGN_BASELINE: {
    
     // drawable 的底部与 baseline 对齐
                        // 所以 ascent 的值就是 负的 drawable 的高度
                        float ascent = -drHeight;
                        fm.ascent = -drHeight;
                        fm.top = (int) (ascent + topLeading);
                        break;
                    }
                    case ALIGN_TOP: {
    
     // drawable 的顶部与 行的顶部 对齐
                        // 算出 descent
                        float descent = drHeight + fmPaint.ascent;
                        fm.descent = (int) descent;
                        fm.bottom = (int) (descent + bottomLeading);
                        break;
                    }
                    case ALIGN_BOTTOM: // drawable 的底部与 行的底部 对齐
                    default: {
    
    
                        // 算出 ascent
                        float ascent = fmPaint.descent - drHeight;
                        fm.ascent = (int) ascent;
                        fm.top = (int) (ascent + topLeading);
                    }
                }
            }
        } catch (Exception e) {
    
    

        }
        return rect.right + mMargin.left + mMargin.right;
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
    
    
        try{
    
    
            Drawable drawable = getDrawable();
            Rect rect = drawable.getBounds();
            float transY;
            switch (mVerticalAlignment) {
    
    
                case ALIGN_BASELINE:
                    transY = y - rect.height();
                    break;
                case ALIGN_CENTER:
                    transY = ((bottom - top) - drawable.getBounds().bottom) / 2 + top;
                    break;
                case ALIGN_TOP:
                    transY = top;
                    break;
                case ALIGN_BOTTOM:
                default:
                    transY = bottom - rect.height();
            }
            canvas.save();
            canvas.translate(x + mMargin.left, transY);
            drawable.draw(canvas);
            canvas.restore();
        }catch (Exception e){
    
    

        }
    }

    public void setImageWithIntrinsicBounds(Drawable drawable) {
    
    
        if (mDrawable != drawable) {
    
    
            mActualDrawable.setDrawable(drawable);
            mDrawable = drawable;
        }
    }

    public void reset() {
    
    
        mActualDrawable.setDrawable(mPlaceHolder);
    }

    /**
     * set bounds
     */
    public void setSize(int width, int height) {
    
    
        mActualDrawable.setBounds(0, 0, width, height);
    }

    public void onAttach(@NonNull View view) {
    
    
        mIsAttached = true;
        if (mAttachedView != view) {
    
    
            mActualDrawable.setCallback(null);
            if (mAttachedView != null) {
    
    
                throw new IllegalStateException("has been attached to view:" + mAttachedView);
            }
            mAttachedView = view;
            mActualDrawable.setCallback(mAttachedView);
        }
        mDeferredReleaser.cancelDeferredRelease(this);
        submitRequest();
    }

    private void submitRequest() {
    
    
        ImageLoadRequestListener imageLoadRequestListener = new DraweeSpanImageLoadRequestListener(mAttachedView.getContext());
        Painter.getInstance().loadPhoto(imageLoadRequestListener, RequestParams.get().url(getImageUri()).asBitmap(true));
    }

    public class DraweeSpanImageLoadRequestListener extends ImageLoadRequestListener<Bitmap> {
    
    
        private final Context context;

        public DraweeSpanImageLoadRequestListener(Context context) {
    
    
            this.context = context;
        }

        @Override
        public void setResource(Bitmap resource) {
    
    
            Drawable drawable;
            try {
    
    
                drawable = new BitmapDrawable(context.getResources(), resource);
                setImageWithIntrinsicBounds(drawable);
            } catch (Exception exception) {
    
    

            }
        }

        @Override
        public void onSucess() {
    
    

        }

        @Override
        public Context getContext() {
    
    
            return context;
        }
    }

    @NonNull
    protected String getImageUri() {
    
    
        return mImageUri;
    }

    protected String getId() {
    
    
        return String.valueOf(getImageUri().hashCode());
    }


    public void onDetach() {
    
    
        if (!mIsAttached)
            return;
        mActualDrawable.setCallback(null);
        mAttachedView = null;
        reset();
        mDeferredReleaser.scheduleDeferredRelease(this);
    }

    @Override
    public void release() {
    
    
        mIsAttached = false;
        mAttachedView = null;
        mDrawable = null;
    }

    public static class Builder {
    
    
        String uri;
        int width = 100;
        int height = 100;
        int verticalAlignment = ALIGN_CENTER;
        Drawable placeholder;
        Rect margin = new Rect();

        public Builder(String uri) {
    
    
            this(uri, false);
        }

        /**
         * Construct drawee span builder.
         *
         * @param uri image uri.
         * @param alignBaseline true to set {@link #ALIGN_BASELINE}, otherwise {@link #ALIGN_BOTTOM} .
         */
        public Builder(String uri, boolean alignBaseline) {
    
    
            this.uri = uri;
            if (uri == null) {
    
    
                throw new NullPointerException("Attempt to create a DraweeSpan with null uri string!");
            }
            if (alignBaseline) {
    
    
                this.verticalAlignment = ALIGN_CENTER;
            }
        }

        /**
         * @param width width of this span, px
         * @param height height of this span, px
         */
        public Builder setLayout(int width, int height) {
    
    
            this.width = width;
            this.height = height;
            return this;
        }

        /**
         * You can set margin in left, right and top in px. Bottom is in baseline.
         */
        public Builder setMargin(int margin) {
    
    
            this.margin.set(margin, margin, margin, 0);
            return this;
        }

        /**
         * You can set margin in left, right and top in px. Bottom is in baseline.
         */
        public Builder setMargin(int left, int top, int right) {
    
    
            this.margin.set(left, top, right, 0);
            return this;
        }

        /**
         * @param placeholder The drawable shows on loading image {@code uri}
         */
        public Builder setPlaceHolderImage(Drawable placeholder) {
    
    
            this.placeholder = placeholder;
            return this;
        }

        public DraweeSpan build() {
    
    
            if (placeholder == null) {
    
    
                placeholder = new ColorDrawable(Color.TRANSPARENT);
                placeholder.setBounds(0, 0, width, height);
            }
            DraweeSpan span = new DraweeSpan(uri, verticalAlignment, placeholder);
            span.mLayout.set(width, height);
            span.mMargin.set(margin.left, margin.top, margin.right, 0);
            span.layout();
            return span;
        }
    }
}

DeferredReleaser.java

import android.os.Handler;
import android.os.Looper;

import java.util.HashSet;
import java.util.Set;


/**
 * Component that defers {@code release} until after the main Looper has completed its current
 * message. Although we would like for defer {@code release} to happen immediately after the current
 * message is done, this is not guaranteed as there might be other messages after the current one,
 * but before the deferred one, pending in the Looper's queue.
 * <p>
 * onDetach / onAttach events are used for releasing / acquiring resources. However, sometimes we
 * get an onDetach event followed by an onAttach event within the same loop. In order to avoid
 * overaggressive resource releasing / acquiring, we defer releasing. If onAttach happens within
 * the same loop, we will simply cancel corresponding deferred release, avoiding an unnecessary
 * resource release / acquire cycle. If onAttach doesn't happen before the deferred message gets
 * executed, the resources will be released.
 * <p>
 * This class is not thread-safe and should only be used from the main thread (UI thread).
 */
public class DeferredReleaser {
    
    

  private static DeferredReleaser sInstance = null;

  public static synchronized DeferredReleaser getInstance() {
    
    
    if (sInstance == null) {
    
    
      sInstance = new DeferredReleaser();
    }
    return sInstance;
  }

  public interface Releasable {
    
    
    public void release();
  }

  private final Set<Releasable> mPendingReleasables;
  private final Handler mUiHandler;

  public DeferredReleaser() {
    
    
    mPendingReleasables =  new HashSet<Releasable>();
    mUiHandler = new Handler(Looper.getMainLooper());
  }

  /*
   * Walks through the set of pending releasables, and calls release on them.
   * Resets the pending list to an empty list when done.
   */
  private final Runnable releaseRunnable = new Runnable() {
    
    
    @Override
    public void run() {
    
    
      ensureOnUiThread();
      for (Releasable releasable : mPendingReleasables) {
    
    
        releasable.release();
      }
      mPendingReleasables.clear();
    }
  };

  /**
   * Schedules deferred release.
   * <p>
   * The object will be released after the current Looper's loop,
   * unless {@code cancelDeferredRelease} is called before then.
   * @param releasable Object to release.
   */
  public void scheduleDeferredRelease(Releasable releasable) {
    
    
    ensureOnUiThread();

    if (!mPendingReleasables.add(releasable)) {
    
    
      return;
    }
    // Posting to the UI queue is an O(n) operation, so we only do it once.
    // The one runnable does all the releases.
    if (mPendingReleasables.size() == 1) {
    
    
      mUiHandler.post(releaseRunnable);
    }
  }

  /**
   * Cancels a pending release for this object.
   * @param releasable Object to cancel release of.
   */
  public void cancelDeferredRelease(Releasable releasable) {
    
    
    ensureOnUiThread();
    mPendingReleasables.remove(releasable);
  }

  private static void ensureOnUiThread() {
    
    
    Preconditions.checkState(Looper.getMainLooper().getThread() == Thread.currentThread());
  }
}

ReenvíoDrawable.java

import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;

/**
 * A forwarding drawable class - the goal is to forward (delegate) drawable functionality to an
 * inner drawable instance. ForwardingDrawable intercepts the public (and protected) methods of
 * {@link Drawable}, maintains local state if needed.
 * <p>
 * Design note: It would have been very helpful to re-use Android library classes
 * like DrawableContainer, LevelListDrawable etc. DrawableContainer is not directly subclassable,
 * and the others don't allow changing the member drawables.
 */
public class ForwardingDrawable extends Drawable
    implements Drawable.Callback, TransformCallback, TransformAwareDrawable, DrawableParent {
    
    

  /** The current drawable to be drawn by this drawable when drawing is needed */
  private Drawable mCurrentDelegate;

  private final DrawableProperties mDrawableProperties = new DrawableProperties();

  protected TransformCallback mTransformCallback;

  /**
   * Matrix used to store temporary transform. Drawables should be accessed on UI thread only, and
   * this matrix is used only as a temporary variable so it's safe to be static.
   */
  private static final Matrix sTempTransform = new Matrix();

  /**
   * Constructs a new forwarding drawable.
   * @param drawable drawable that this forwarding drawable will forward to
   */
  public ForwardingDrawable(Drawable drawable) {
    
    
    mCurrentDelegate = drawable;
    DrawableUtils.setCallbacks(mCurrentDelegate, this, this);
  }

  /**
   * Sets a new drawable to be the delegate, and returns the old one (or null).
   *
   * <p>This method will cause the drawable to be invalidated.
   * @param newDelegate
   * @return the previous delegate
   */
  public Drawable setCurrent(Drawable newDelegate) {
    
    
    Drawable previousDelegate = setCurrentWithoutInvalidate(newDelegate);
    invalidateSelf();
    return previousDelegate;
  }

  /**
   * As {@code setCurrent}, but without invalidating a drawable. Subclasses are responsible to call
   * {@code invalidateSelf} on their own.
   * @param newDelegate
   * @return the previous delegate
   */
  protected Drawable setCurrentWithoutInvalidate(Drawable newDelegate) {
    
    
    Drawable previousDelegate = mCurrentDelegate;
    DrawableUtils.setCallbacks(previousDelegate, null, null);
    DrawableUtils.setCallbacks(newDelegate, null, null);
    DrawableUtils.setDrawableProperties(newDelegate, mDrawableProperties);
    DrawableUtils.copyProperties(newDelegate, this);
    DrawableUtils.setCallbacks(newDelegate, this, this);
    mCurrentDelegate = newDelegate;
    return previousDelegate;
  }

  @Override
  public int getOpacity() {
    
    
    return mCurrentDelegate.getOpacity();
  }

  @Override
  public void setAlpha(int alpha) {
    
    
    mDrawableProperties.setAlpha(alpha);
    mCurrentDelegate.setAlpha(alpha);
  }

  @Override
  public void setColorFilter(ColorFilter colorFilter) {
    
    
    mDrawableProperties.setColorFilter(colorFilter);
    mCurrentDelegate.setColorFilter(colorFilter);
  }

  @Override
  public void setDither(boolean dither) {
    
    
    mDrawableProperties.setDither(dither);
    mCurrentDelegate.setDither(dither);
  }

  @Override
  public void setFilterBitmap(boolean filterBitmap) {
    
    
    mDrawableProperties.setFilterBitmap(filterBitmap);
    mCurrentDelegate.setFilterBitmap(filterBitmap);
  }

  @Override
  public boolean setVisible(boolean visible, boolean restart) {
    
    
    super.setVisible(visible, restart);
    return mCurrentDelegate.setVisible(visible, restart);
  }

  @Override
  protected void onBoundsChange(Rect bounds) {
    
    
    mCurrentDelegate.setBounds(bounds);
  }

  @Override
  public boolean isStateful() {
    
    
    return mCurrentDelegate.isStateful();
  }

  @Override
  protected boolean onStateChange(int[] state) {
    
    
    return mCurrentDelegate.setState(state);
  }

  @Override
  protected boolean onLevelChange(int level) {
    
    
    return mCurrentDelegate.setLevel(level);
  }

  @Override
  public void draw(Canvas canvas) {
    
    
    mCurrentDelegate.draw(canvas);
  }

  @Override
  public int getIntrinsicWidth() {
    
    
    return mCurrentDelegate.getIntrinsicWidth();
  }

  @Override
  public int getIntrinsicHeight() {
    
    
    return mCurrentDelegate.getIntrinsicHeight();
  }

  @Override
  public boolean getPadding(Rect padding) {
    
    
    return mCurrentDelegate.getPadding(padding);
  }

  @Override
  public Drawable mutate() {
    
    
    mCurrentDelegate.mutate();
    return this;
  }

  @Override
  public Drawable getCurrent() {
    
    
    return mCurrentDelegate;
  }

  // DrawableParent methods

  @Override
  public Drawable setDrawable(Drawable newDrawable) {
    
    
    return setCurrent(newDrawable);
  }

  @Override
  public Drawable getDrawable() {
    
    
    return getCurrent();
  }

  // Drawable.Callback methods

  @Override
  public void invalidateDrawable(Drawable who) {
    
    
    invalidateSelf();
  }

  @Override
  public void scheduleDrawable(Drawable who, Runnable what, long when) {
    
    
    scheduleSelf(what, when);
  }

  @Override
  public void unscheduleDrawable(Drawable who, Runnable what) {
    
    
    unscheduleSelf(what);
  }

  //  TransformAwareDrawable methods

  @Override
  public void setTransformCallback(TransformCallback transformCallback) {
    
    
    mTransformCallback = transformCallback;
  }

  // TransformationCallback methods

  protected void getParentTransform(Matrix transform) {
    
    
    if (mTransformCallback != null) {
    
    
      mTransformCallback.getTransform(transform);
    } else {
    
    
      transform.reset();
    }
  }

  @Override
  public void getTransform(Matrix transform) {
    
    
    getParentTransform(transform);
  }

  @Override
  public void getRootBounds(RectF bounds) {
    
    
    if (mTransformCallback != null) {
    
    
      mTransformCallback.getRootBounds(bounds);
    } else {
    
    
      bounds.set(getBounds());
    }
  }

  /**
   * Gets the transformed bounds of this drawable.
   * Note: bounds are not cropped (otherwise they would likely be the same as drawable's bounds).
   * @param outBounds rect to fill with bounds
   */
  public void getTransformedBounds(RectF outBounds) {
    
    
    getParentTransform(sTempTransform);
    // IMPORTANT: {@code getBounds} should be called after {@code getParentTransform},
    // because the parent may have to change our bounds.
    outBounds.set(getBounds());
    sTempTransform.mapRect(outBounds);
  }

  @Override
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public void setHotspot(float x, float y) {
    
    
    mCurrentDelegate.setHotspot(x, y);
  }
}

TransformCallback.java

import android.graphics.Matrix;
import android.graphics.RectF;

/**
 * Callback that is used to pass any transformation matrix and the root bounds from a parent
 * drawable to its child.
 */
public interface TransformCallback {
    
    

  /**
   * Called when the drawable needs to get all matrices applied to it.
   *
   * @param transform Matrix that is applied to the drawable by the parent drawables.
   */
  void getTransform(Matrix transform);

  /**
   * Called when the drawable needs to get its root bounds.
   *
   * @param bounds The root bounds of the drawable.
   */
  void getRootBounds(RectF bounds);
}

TransformAwareDrawable.java

/**
 * Interface that enables setting a transform callback.
 */
public interface TransformAwareDrawable {
    
    

  /**
   * Sets a transform callback.
   *
   * @param transformCallback the transform callback to be set
   */
  void setTransformCallback(TransformCallback transformCallback);
}

condiciones previas.java

public final class Preconditions {
    
    
  private Preconditions() {
    
    }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   * @param expression a boolean expression
   * @throws IllegalArgumentException if {@code expression} is false
   */
  public static void checkArgument(boolean expression) {
    
    
    if (!expression) {
    
    
      throw new IllegalArgumentException();
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   * @param expression a boolean expression
   * @param errorMessage the exception message to use if the check fails; will be converted to a
   *     string using {@link String#valueOf(Object)}
   * @throws IllegalArgumentException if {@code expression} is false
   */
  public static void checkArgument(boolean expression, @Nullable Object errorMessage) {
    
    
    if (!expression) {
    
    
      throw new IllegalArgumentException(String.valueOf(errorMessage));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   * @param expression a boolean expression
   * @param errorMessageTemplate a template for the exception message should the check fail. The
   *     message is formed by replacing each {@code %s} placeholder in the template with an
   *     argument. These are matched by position - the first {@code %s} gets {@code
   *     errorMessageArgs[0]}, etc.  Unmatched arguments will be appended to the formatted message
   *     in square braces. Unmatched placeholders will be left as-is.
   * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
   *     are converted to strings using {@link String#valueOf(Object)}.
   * @throws IllegalArgumentException if {@code expression} is false
   * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or
   *     {@code errorMessageArgs} is null (don't let this happen)
   */
  public static void checkArgument(boolean expression,
      @Nullable String errorMessageTemplate,
      @Nullable Object... errorMessageArgs) {
    
    
    if (!expression) {
    
    
      throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   * @param expression a boolean expression
   * @throws IllegalStateException if {@code expression} is false
   */
  public static void checkState(boolean expression) {
    
    
    if (!expression) {
    
    
      throw new IllegalStateException();
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   * @param expression a boolean expression
   * @param errorMessage the exception message to use if the check fails; will be converted to a
   *     string using {@link String#valueOf(Object)}
   * @throws IllegalStateException if {@code expression} is false
   */
  public static void checkState(boolean expression, @Nullable Object errorMessage) {
    
    
    if (!expression) {
    
    
      throw new IllegalStateException(String.valueOf(errorMessage));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   * @param expression a boolean expression
   * @param errorMessageTemplate a template for the exception message should the check fail. The
   *     message is formed by replacing each {@code %s} placeholder in the template with an
   *     argument. These are matched by position - the first {@code %s} gets {@code
   *     errorMessageArgs[0]}, etc.  Unmatched arguments will be appended to the formatted message
   *     in square braces. Unmatched placeholders will be left as-is.
   * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
   *     are converted to strings using {@link String#valueOf(Object)}.
   * @throws IllegalStateException if {@code expression} is false
   * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or
   *     {@code errorMessageArgs} is null (don't let this happen)
   */
  public static void checkState(boolean expression,
      @Nullable String errorMessageTemplate,
      @Nullable Object... errorMessageArgs) {
    
    
    if (!expression) {
    
    
      throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs));
    }
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   * @param reference an object reference
   * @return the non-null reference that was validated
   * @throws NullPointerException if {@code reference} is null
   */
  public static <T> T checkNotNull(T reference) {
    
    
    if (reference == null) {
    
    
      throw new NullPointerException();
    }
    return reference;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   * @param reference an object reference
   * @param errorMessage the exception message to use if the check fails; will be converted to a
   *     string using {@link String#valueOf(Object)}
   * @return the non-null reference that was validated
   * @throws NullPointerException if {@code reference} is null
   */
  public static <T> T checkNotNull(T reference, @Nullable Object errorMessage) {
    
    
    if (reference == null) {
    
    
      throw new NullPointerException(String.valueOf(errorMessage));
    }
    return reference;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   * @param reference an object reference
   * @param errorMessageTemplate a template for the exception message should the check fail. The
   *     message is formed by replacing each {@code %s} placeholder in the template with an
   *     argument. These are matched by position - the first {@code %s} gets {@code
   *     errorMessageArgs[0]}, etc.  Unmatched arguments will be appended to the formatted message
   *     in square braces. Unmatched placeholders will be left as-is.
   * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
   *     are converted to strings using {@link String#valueOf(Object)}.
   * @return the non-null reference that was validated
   * @throws NullPointerException if {@code reference} is null
   */
  public static <T> T checkNotNull(T reference,
      @Nullable String errorMessageTemplate,
      @Nullable Object... errorMessageArgs) {
    
    
    if (reference == null) {
    
    
      // If either of these parameters is null, the right thing happens anyway
      throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
    }
    return reference;
  }

  /*
   * All recent hotspots (as of 2009) *really* like to have the natural code
   *
   * if (guardExpression) {
   *    throw new BadException(messageExpression);
   * }
   *
   * refactored so that messageExpression is moved to a separate String-returning method.
   *
   * if (guardExpression) {
   *    throw new BadException(badMsg(...));
   * }
   *
   * The alternative natural refactorings into void or Exception-returning methods are much slower.
   * This is a big deal - we're talking factors of 2-8 in microbenchmarks, not just 10-20%.  (This
   * is a hotspot optimizer bug, which should be fixed, but that's a separate, big project).
   *
   * The coding pattern above is heavily used in java.util, e.g. in ArrayList.  There is a
   * RangeCheckMicroBenchmark in the JDK that was used to test this.
   *
   * But the methods in this class want to throw different exceptions, depending on the args, so it
   * appears that this pattern is not directly applicable.  But we can use the ridiculous, devious
   * trick of throwing an exception in the middle of the construction of another exception.  Hotspot
   * is fine with that.
   */

  /**
   * Ensures that {@code index} specifies a valid <i>element</i> in an array, list or string of size
   * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive.
   *
   * @param index a user-supplied index identifying an element of an array, list or string
   * @param size the size of that array, list or string
   * @return the value of {@code index}
   * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  public static int checkElementIndex(int index, int size) {
    
    
    return checkElementIndex(index, size, "index");
  }

  /**
   * Ensures that {@code index} specifies a valid <i>element</i> in an array, list or string of size
   * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive.
   *
   * @param index a user-supplied index identifying an element of an array, list or string
   * @param size the size of that array, list or string
   * @param desc the text to use to describe this index in an error message
   * @return the value of {@code index}
   * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  public static int checkElementIndex(
      int index, int size, @Nullable String desc) {
    
    
    // Carefully optimized for execution by hotspot (explanatory comment above)
    if (index < 0 || index >= size) {
    
    
      throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
    }
    return index;
  }

  private static String badElementIndex(int index, int size, @Nullable String desc) {
    
    
    if (index < 0) {
    
    
      return format("%s (%s) must not be negative", desc, index);
    } else if (size < 0) {
    
    
      throw new IllegalArgumentException("negative size: " + size);
    } else {
    
     // index >= size
      return format("%s (%s) must be less than size (%s)", desc, index, size);
    }
  }

  /**
   * Ensures that {@code index} specifies a valid <i>position</i> in an array, list or string of
   * size {@code size}. A position index may range from zero to {@code size}, inclusive.
   *
   * @param index a user-supplied index identifying a position in an array, list or string
   * @param size the size of that array, list or string
   * @return the value of {@code index}
   * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  public static int checkPositionIndex(int index, int size) {
    
    
    return checkPositionIndex(index, size, "index");
  }

  /**
   * Ensures that {@code index} specifies a valid <i>position</i> in an array, list or string of
   * size {@code size}. A position index may range from zero to {@code size}, inclusive.
   *
   * @param index a user-supplied index identifying a position in an array, list or string
   * @param size the size of that array, list or string
   * @param desc the text to use to describe this index in an error message
   * @return the value of {@code index}
   * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  public static int checkPositionIndex(int index, int size, @Nullable String desc) {
    
    
    // Carefully optimized for execution by hotspot (explanatory comment above)
    if (index < 0 || index > size) {
    
    
      throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
    }
    return index;
  }

  private static String badPositionIndex(int index, int size, @Nullable String desc) {
    
    
    if (index < 0) {
    
    
      return format("%s (%s) must not be negative", desc, index);
    } else if (size < 0) {
    
    
      throw new IllegalArgumentException("negative size: " + size);
    } else {
    
     // index > size
      return format("%s (%s) must not be greater than size (%s)", desc, index, size);
    }
  }

  /**
   * Ensures that {@code start} and {@code end} specify a valid <i>positions</i> in an array, list
   * or string of size {@code size}, and are in order. A position index may range from zero to
   * {@code size}, inclusive.
   *
   * @param start a user-supplied index identifying a starting position in an array, list or string
   * @param end a user-supplied index identifying a ending position in an array, list or string
   * @param size the size of that array, list or string
   * @throws IndexOutOfBoundsException if either index is negative or is greater than {@code size},
   *     or if {@code end} is less than {@code start}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  public static void checkPositionIndexes(int start, int end, int size) {
    
    
    // Carefully optimized for execution by hotspot (explanatory comment above)
    if (start < 0 || end < start || end > size) {
    
    
      throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
    }
  }

  private static String badPositionIndexes(int start, int end, int size) {
    
    
    if (start < 0 || start > size) {
    
    
      return badPositionIndex(start, size, "start index");
    }
    if (end < 0 || end > size) {
    
    
      return badPositionIndex(end, size, "end index");
    }
    // end < start
    return format("end index (%s) must not be less than start index (%s)", end, start);
  }

  /**
   * Substitutes each {@code %s} in {@code template} with an argument. These are matched by
   * position: the first {@code %s} gets {@code args[0]}, etc.  If there are more arguments than
   * placeholders, the unmatched arguments will be appended to the end of the formatted message in
   * square braces.
   *
   * @param template a non-null string containing 0 or more {@code %s} placeholders.
   * @param args the arguments to be substituted into the message template. Arguments are converted
   *     to strings using {@link String#valueOf(Object)}. Arguments can be null.
   */
  // Note that this is somewhat-improperly used from Verify.java as well.
  static String format(@Nullable String template, @Nullable Object... args) {
    
    
    template = String.valueOf(template); // null -> "null"

    // start substituting the arguments into the '%s' placeholders
    StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
    int templateStart = 0;
    int i = 0;
    while (i < args.length) {
    
    
      int placeholderStart = template.indexOf("%s", templateStart);
      if (placeholderStart == -1) {
    
    
        break;
      }
      builder.append(template.substring(templateStart, placeholderStart));
      builder.append(args[i++]);
      templateStart = placeholderStart + 2;
    }
    builder.append(template.substring(templateStart));

    // if we run out of placeholders, append the extra args in square braces
    if (i < args.length) {
    
    
      builder.append(" [");
      builder.append(args[i++]);
      while (i < args.length) {
    
    
        builder.append(", ");
        builder.append(args[i++]);
      }
      builder.append(']');
    }

    return builder.toString();
  }
}

DrawableUtils.java

import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;

/**
 * Helper class containing functionality commonly used by drawables.
 */
public class DrawableUtils {
    
    

  /**
   * Copies various properties from one drawable to the other.
   * @param to drawable to copy properties to
   * @param from drawable to copy properties from
   */
  public static void copyProperties(Drawable to, Drawable from) {
    
    
    if (from == null || to == null || to == from) {
    
    
      return;
    }

    to.setBounds(from.getBounds());
    to.setChangingConfigurations(from.getChangingConfigurations());
    to.setLevel(from.getLevel());
    to.setVisible(from.isVisible(), /* restart */ false);
    to.setState(from.getState());
  }

  /**
   * Sets various paint properties on the drawable
   * @param drawable Drawable on which to set the properties
   * @param properties wrapper around mValue values to set on the drawable
   */
  public static void setDrawableProperties(Drawable drawable, DrawableProperties properties) {
    
    
    if (drawable == null || properties == null) {
    
    
      return;
    }
    properties.applyTo(drawable);
  }

  /**
   * Sets callback to the drawable.
   * @param drawable drawable to set callbacks to
   * @param callback standard Android Drawable.Callback
   * @param transformCallback TransformCallback used by TransformAwareDrawables
   */
  public static void setCallbacks(
      Drawable drawable,
      @Nullable Drawable.Callback callback,
      @Nullable TransformCallback transformCallback) {
    
    
    if (drawable != null) {
    
    
      drawable.setCallback(callback);
      if (drawable instanceof TransformAwareDrawable) {
    
    
        ((TransformAwareDrawable) drawable).setTransformCallback(transformCallback);
      }
    }
  }

  /**
   * Multiplies the color with the given alpha.
   * @param color color to be multiplied
   * @param alpha value between 0 and 255
   * @return multiplied color
   */
  public static int multiplyColorAlpha(int color, int alpha) {
    
    
    if (alpha == 255) {
    
    
      return color;
    }
    if (alpha == 0) {
    
    
      return color & 0x00FFFFFF;
    }
    alpha = alpha + (alpha >> 7); // make it 0..256
    int colorAlpha = color >>> 24;
    int multipliedAlpha = colorAlpha * alpha >> 8;
    return (multipliedAlpha << 24) | (color & 0x00FFFFFF);
  }

  /**
   * Gets the opacity from a color. Inspired by Android ColorDrawable.
   * @param color
   * @return opacity expressed by one of PixelFormat constants
   */
  public static int getOpacityFromColor(int color) {
    
    
    int colorAlpha = color >>> 24;
    if (colorAlpha == 255) {
    
    
      return PixelFormat.OPAQUE;
    } else if (colorAlpha == 0) {
    
    
      return PixelFormat.TRANSPARENT;
    } else {
    
    
      return PixelFormat.TRANSLUCENT;
    }
  }
}

DrawableProperties.java

public class DrawableProperties {
    
    

  private static final int UNSET = -1;

  private int mAlpha = UNSET;
  private boolean mIsSetColorFilter = false;
  private ColorFilter mColorFilter = null;
  private int mDither = UNSET;
  private int mFilterBitmap = UNSET;

  public void setAlpha(int alpha) {
    
    
    mAlpha = alpha;
  }

  public void setColorFilter(ColorFilter colorFilter) {
    
    
    mColorFilter = colorFilter;
    mIsSetColorFilter = true;
  }

  public void setDither(boolean dither) {
    
    
    mDither = dither ? 1 : 0;
  }

  public void setFilterBitmap(boolean filterBitmap) {
    
    
    mFilterBitmap = filterBitmap ? 1 : 0;
  }

  public void applyTo(Drawable drawable) {
    
    
    if (drawable == null) {
    
    
      return;
    }
    if (mAlpha != UNSET) {
    
    
      drawable.setAlpha(mAlpha);
    }
    if (mIsSetColorFilter) {
    
    
      drawable.setColorFilter(mColorFilter);
    }
    if (mDither != UNSET) {
    
    
      drawable.setDither(mDither != 0);
    }
    if (mFilterBitmap != UNSET) {
    
    
      drawable.setFilterBitmap(mFilterBitmap != 0);
    }
  }
}

DrawableParent.java

public interface DrawableParent {
    
    

  /**
   * Sets the new child drawable.
   * @param newDrawable a new child drawable to set
   * @return the old child drawable
   */
  Drawable setDrawable(Drawable newDrawable);

  /**
   * Gets the child drawable.
   * @return the current child drawable
   */
  Drawable getDrawable();

}

Supongo que te gusta

Origin blog.csdn.net/adayabetter/article/details/129056491
Recomendado
Clasificación