package im.actor.sdk.view; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Build; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import im.actor.runtime.function.CancellableSimple; import im.actor.sdk.util.Screen; public class AsyncView<T, L> extends View { private ThreadLocal<InvalidationContext> contexts = new ThreadLocal<>(); private ViewAsyncDispatch asyncDispatch = new ViewAsyncDispatch(); private int currentWidth; private int currentHeight; private boolean isMeasured; private boolean isRequested; private T currentObject; private InvalidationContext currentLayoutingContext; private L currentLayout; public AsyncView(Context context) { super(context); } public AsyncView(Context context, AttributeSet attrs) { super(context, attrs); } public AsyncView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public AsyncView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (!isMeasured || currentWidth != getWidth() || currentHeight != getHeight()) { isMeasured = true; currentWidth = getWidth(); currentHeight = getHeight(); if (currentObject != null) { startLayout(); } } } public void requestLayout(T arg) { requestLayout(arg, true); } public void requestLayout(T arg, boolean clearOld) { if (clearOld) { this.currentLayout = null; } this.currentObject = arg; if (currentLayoutingContext != null) { currentLayoutingContext.cancel(); currentLayoutingContext = null; } invalidate(); if (isMeasured) { startLayout(); } } public void cancelLayout() { this.currentObject = null; if (currentLayoutingContext != null) { currentLayoutingContext.cancel(); currentLayoutingContext = null; } } private void startLayout() { T arg = this.currentObject; isRequested = true; int w = currentWidth; int h = currentHeight; if (currentLayoutingContext != null) { currentLayoutingContext.cancel(); currentLayoutingContext = null; } currentLayoutingContext = new InvalidationContext(arg); InvalidationContext context = currentLayoutingContext; contexts.set(context); asyncDispatch.dispatch(currentLayoutingContext, () -> { contexts.set(currentLayoutingContext); contexts.set(context); L res = buildLayout(arg, w, h); contexts.set(null); asyncDispatch.complete(() -> { currentLayout = res; layoutReady(res); invalidate(); }); }); } public InvalidationContext getCurrentLayoutContext() { return contexts.get(); } public L getLayout() { return currentLayout; } public L buildLayout(T arg, int width, int height) { return null; } public void layoutReady(L res) { } public TextPaint createTextPaint(Typeface typeface, int size, int color) { TextPaint res = new TextPaint(Paint.ANTI_ALIAS_FLAG); res.setSubpixelText(true); res.setTypeface(typeface); res.setTextSize(Screen.sp(size)); res.setColor(color); return res; } public Paint createFilledPaint(int color) { Paint res = new Paint(Paint.ANTI_ALIAS_FLAG); res.setColor(color); res.setStyle(Paint.Style.FILL); return res; } public StaticLayout singleLineText(CharSequence sequence, TextPaint paint, int maxWidth) { CharSequence ellipsizedTitle = TextUtils.ellipsize(sequence, paint, maxWidth - Screen.dp(2), TextUtils.TruncateAt.END); return new StaticLayout(ellipsizedTitle, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } public class InvalidationContext extends CancellableSimple { private T arg; private boolean isInvalidated = false; private InvalidationContext(T arg) { this.arg = arg; } public void invalidate() { if (isCancelled()) { return; } if (isInvalidated) { return; } isInvalidated = true; post(() -> { if (currentLayoutingContext == this) { requestLayout(arg, false); } }); } } }