Тормозит анимация gif смайлов в TextView
Приложение: аналог форума. В списке отображаются посты участников (html), текст с изображениями, в том числе и gif.
В обычном адаптере для recyclerview, через HtmlCompat.fromHtml загружаю html с тегами img, в которых посредством Glide загружаю gif смайлы из локальных ресурсов. Проблема в том, что при отображении текста со смайлами в TextView gif анимация сильно тормозит. Что-бы обойти эту проблему, указываю tv_text.setLayerType(View.LAYER_TYPE_SOFTWARE, null); Либо через xml разметку android:layerType="software". Анимация становится нормальной, однако появляется новая проблема: если в TextView очень много текста - он перестает отображаться.
Есть вариант: указать минимальный и максимальный размер шрифта в TextView, но длинные посты из-за этого превращаются в нечитаемый текст (размер шрифта автоматически уменьшается до минимума). Отключение аппаратного ускорения решает проблему анимации gif, но от этого страдает общая плавность работы приложения.
Нашел пост TextView with long text invisible with LAYER_TYPE_HARDWARE or LAYER_TYPE_SOFTWARE: вычисление ширины текста и сравнение с макс. шириной текстуры OpenGL. Этот пример так же не работает.
Пока нахожусь в поиске решений: обрезать текст, с добавление кнопки "раскрыть...", или найти другую реализацию GifDrawable.
public class GlideImageGetter implements Html.ImageGetter, Drawable.Callback {
private final Resources resources;
private final TextView textView;
HashMap<String, String> icons;
int width=-1;
public GlideImageGetter(Resources resources, TextView tv_text, int width) {
this.resources = resources;
this.textView = tv_text;
this.width = width;
new GlideImageGetter(resources, tv_text);
}
public GlideImageGetter(Resources resources, TextView target) {
super();
this.resources = resources;
this.textView = target;
this.icons = Utils.getIcons();
}
CustomTarget<Drawable> customTarget(final FutureDrawable result){
return new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
int maxWidth = 300;
if (resource instanceof GifDrawable) {
GifDrawable gifDrawable = (GifDrawable) resource;
if (gifDrawable.getIntrinsicWidth() > maxWidth) {
float aspectRatio = (float) gifDrawable.getIntrinsicHeight() / (float) gifDrawable.getIntrinsicWidth();
gifDrawable.setBounds(0, 0, maxWidth, (int) (aspectRatio * maxWidth));
} else {
int width=(int) (gifDrawable.getIntrinsicWidth() * 2.5) ;
int height=(int) (gifDrawable.getIntrinsicHeight() * 2.5);
gifDrawable.setBounds(0, 0, width, height);
}
result.isGif=true;
result.setDrawable(gifDrawable);
//смайлы-гифки запускаем автоматически
if (result.isLocalImage) {
gifDrawable.setCallback(GlideImageGetter.this);
gifDrawable.start();
}
} else {
if (resource.getIntrinsicWidth() > maxWidth) {
float aspectRatio = (float) resource.getIntrinsicHeight() / (float) resource.getIntrinsicWidth();
resource.setBounds(0, 0, maxWidth, (int) (aspectRatio * maxWidth));
} else {
resource.setBounds(0, 0, resource.getIntrinsicWidth(), resource.getIntrinsicHeight());
}
result.setDrawable(resource);
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
if (null != placeholder) {
if (placeholder instanceof GifDrawable)
((GifDrawable) placeholder).stop();
}
}
};
}
@Override
public Drawable getDrawable(String source) {
if (source==null) return null;
final FutureDrawable result = new FutureDrawable(resources);
//загружаем "пустую картинку"
Drawable empty = ContextCompat.getDrawable(textView.getContext(), R.drawable.ic_empty_image);
if (this.width>0){
empty = new ScaleDrawable(empty, 0, width, width).getDrawable();
empty.setBounds(0, 0, this.width, this.width);
result.setBounds(0, 0, this.width, this.width);
} else
result.setBounds(0, 0, empty.getIntrinsicWidth(), empty.getIntrinsicHeight());
result.setDrawable(empty);
final String imgsource;
//поменялся путь к локальным смайлам форума
if (source.contains("local=")) {
String src=icons.get(source.replace("local=",""));
imgsource = source.replace(source, "file:///android_asset/icons/"+src);
result.isLocalImage=true;
} else
imgsource=source;
if (!result.isLocalImage)
if (VideoUtils.isVideoFast(source))
result.isVideo=true;
if (result.isLocalImage){
GlideApp.with(textView)
.load(Uri.parse(imgsource))
.into(customTarget(result));
} else
GlideApp.with(textView)
.asDrawable()
.placeholder(empty)
.error(R.drawable.ic_error_image)
.load(Uri.parse(imgsource))
.centerInside() //облегчаем
.format(DecodeFormat.PREFER_RGB_565) //облегчаем
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(customTarget(result));
return result;
}
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {\
//textView.invalidate();
textView.invalidate(drawable.getBounds());
}
@Override
public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) {
}
@Override
public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) {
}
public class FutureDrawable extends Drawable {
private Drawable drawable;
public boolean isGif=false;
public boolean isVideo=false;
public boolean isLocalImage=false;
Bitmap play;
private Bitmap getBitmap(Drawable vectorDrawable) {
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
vectorDrawable.draw(canvas);
return bitmap;
}
@SuppressLint("UseCompatLoadingForDrawables")
FutureDrawable(Resources res) {
play = getBitmap(res.getDrawable(R.drawable.ic_play));
}
@Override
public void draw(@NonNull Canvas canvas) {
if(drawable != null) {
drawable.draw(canvas);
if (!isLocalImage && (isGif || isVideo)) {
//рисуем значок play поверх изображения
//если гифка запущена - не рисуем
if (this.drawable instanceof GifDrawable && ((GifDrawable) this.drawable).isRunning())
return;
//т.к. размер картинки может быть не пропорциональным
//берем размер например высоты
int width = getBounds().width() / 4;
int height = width;
if (width == 0 | height == 0) {
width = play.getWidth();
height = play.getHeight();
}
play = Bitmap.createScaledBitmap(play, width, height, false);
int x = (getBounds().width() - width) / 2;
int y = (getBounds().height() - height) / 2;
canvas.drawBitmap(play, x, y, new Paint(Paint.FILTER_BITMAP_FLAG));
}
}
}
public void play() {
if (drawable != null) {
if (isGif) {
GifDrawable gif=((GifDrawable) this.drawable);
if (gif.isRunning()) {
gif.stop();
gif.invalidateSelf();
gif.setCallback(null);
this.setDrawable(drawable); //draw() скрин с иконкой play
}else {
this.setDrawable(gif); //draw()
gif.setCallback(GlideImageGetter.this);
gif.start();
}
}
}
}
@NonNull
@Override
public Drawable getCurrent() {
return drawable;
}
@Override
public void setAlpha(int i) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.UNKNOWN;
}
public void setDrawable(Drawable drawable){
this.drawable=drawable;
int drawableWidth = drawable.getIntrinsicWidth();
int drawableHeight = drawable.getIntrinsicHeight();
if (isGif){
drawableWidth = (int) (drawable.getIntrinsicWidth() * 2.5);
drawableHeight = (int) (drawable.getIntrinsicHeight()* 2.5);
}
int maxWidth = textView.getMeasuredWidth();
if (drawableWidth > maxWidth) {
int calculatedHeight = maxWidth * drawableHeight / drawableWidth;
drawable.setBounds(0, 0, maxWidth, calculatedHeight);
setBounds(0, 0, maxWidth, calculatedHeight);
} else {
drawable.setBounds(0, 0, drawableWidth, drawableHeight);
setBounds(0, 0, drawableWidth, drawableHeight);
}
//для изменения размера картинок
//textView.setText(textView.getText());
textView.post(new Runnable() {
@Override
public void run() {
textView.setText(textView.getText());
}
});
}
}
}
В адаптере:
public class PostAdapter extends PagedListAdapter<PostView, RecyclerView.ViewHolder> {
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
switch(getItemViewType(position)){
case ITEM:
final PostView post = getItem(position);
final PostHolder postHolder=((PostHolder)holder);
SpannableStringBuilder text=(SpannableStringBuilder)HtmlCompat.fromHtml(post.text, HtmlCompat.FROM_HTML_MODE_LEGACY, new GlideImageGetter(resources,postHolder.tv_text), new Html.TagHandler() {
@Override
public void handleTag(boolean opening, String tag, Editable editable, XMLReader xmlReader) {
}
});
postHolder.tv_text.setText(text);
В разметке:
<TextView
android:id="@+id/tv_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4sp"
android:textSize="18sp"
tools:text="tv_text"
/>