mirror of https://github.com/kurisufriend/Clover
The icons in PostCell is now its own view instead of a spanned string. Optimized ThreadStatusCell updating Add String.format style loggingmultisite
parent
a5e96882cd
commit
0412b181f6
@ -0,0 +1,261 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.text; |
||||
|
||||
import android.content.Context; |
||||
import android.content.res.TypedArray; |
||||
import android.graphics.Canvas; |
||||
import android.graphics.Paint; |
||||
import android.text.Layout; |
||||
import android.text.Spannable; |
||||
import android.text.StaticLayout; |
||||
import android.text.TextPaint; |
||||
import android.text.TextUtils; |
||||
import android.util.AttributeSet; |
||||
import android.util.LruCache; |
||||
import android.view.MotionEvent; |
||||
import android.view.View; |
||||
|
||||
import org.floens.chan.R; |
||||
import org.floens.chan.ui.cell.PostCell; |
||||
import org.floens.chan.utils.Logger; |
||||
|
||||
import static org.floens.chan.utils.AndroidUtils.sp; |
||||
|
||||
/** |
||||
* A simple implementation of a TextView that caches the used StaticLayouts for performance.<br> |
||||
* This view was made for {@link PostCell} and may have untested behaviour with other layouts. |
||||
*/ |
||||
public class FastTextView extends View { |
||||
private static final String TAG = "FastTextView"; |
||||
private static LruCache<Long, StaticLayout> textCache = new LruCache<>(1000); |
||||
|
||||
private TextPaint paint; |
||||
private boolean singleLine; |
||||
|
||||
private CharSequence text; |
||||
|
||||
private boolean update = false; |
||||
private StaticLayout layout; |
||||
private int width; |
||||
private FastTextViewMovementMethod movementMethod; |
||||
|
||||
public FastTextView(Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
public FastTextView(Context context, AttributeSet attrs) { |
||||
this(context, attrs, 0); |
||||
} |
||||
|
||||
public FastTextView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, defStyleAttr); |
||||
|
||||
paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); |
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FastTextView); |
||||
setTextColor(a.getColor(R.styleable.FastTextView_textColor, 0xff000000)); |
||||
setTextSize(a.getDimensionPixelSize(R.styleable.FastTextView_textSize, 15)); |
||||
singleLine = a.getBoolean(R.styleable.FastTextView_singleLine, false); |
||||
a.recycle(); |
||||
} |
||||
|
||||
public void setText(CharSequence text) { |
||||
if (!TextUtils.equals(this.text, text)) { |
||||
this.text = text; |
||||
|
||||
update = true; |
||||
invalidate(); |
||||
requestLayout(); |
||||
} |
||||
} |
||||
|
||||
public void setTextSize(float size) { |
||||
int sizeSp = sp(size); |
||||
if (paint.getTextSize() != sizeSp) { |
||||
paint.setTextSize(sizeSp); |
||||
update = true; |
||||
invalidate(); |
||||
} |
||||
} |
||||
|
||||
public void setTextColor(int color) { |
||||
if (paint.getColor() != color) { |
||||
paint.setColor(color); |
||||
update = true; |
||||
invalidate(); |
||||
} |
||||
} |
||||
|
||||
public void setMovementMethod(FastTextViewMovementMethod movementMethod) { |
||||
if (this.movementMethod != movementMethod) { |
||||
this.movementMethod = movementMethod; |
||||
|
||||
if (movementMethod != null) { |
||||
setFocusable(true); |
||||
setClickable(true); |
||||
setLongClickable(true); |
||||
} else { |
||||
setFocusable(false); |
||||
setClickable(false); |
||||
setLongClickable(false); |
||||
} |
||||
|
||||
update = true; |
||||
invalidate(); |
||||
} |
||||
} |
||||
|
||||
public StaticLayout getLayout() { |
||||
return layout; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onTouchEvent(MotionEvent event) { |
||||
boolean handled = false; |
||||
|
||||
if (movementMethod != null && text instanceof Spannable && layout != null && isEnabled()) { |
||||
handled |= movementMethod.onTouchEvent(this, (Spannable) layout.getText(), event); |
||||
} |
||||
|
||||
return handled || super.onTouchEvent(event); |
||||
} |
||||
|
||||
@Override |
||||
protected void onDraw(Canvas canvas) { |
||||
updateLayout(); |
||||
|
||||
if (layout != null) { |
||||
canvas.save(); |
||||
canvas.translate(getPaddingLeft(), getPaddingTop()); |
||||
layout.draw(canvas); |
||||
canvas.restore(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec); |
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec); |
||||
int widthSize = MeasureSpec.getSize(widthMeasureSpec); |
||||
int heightSize = MeasureSpec.getSize(heightMeasureSpec); |
||||
|
||||
// Logger.test("%X %s %s", System.identityHashCode(this), MeasureSpec.toString(widthMeasureSpec), MeasureSpec.toString(heightMeasureSpec));
|
||||
|
||||
if ((widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) && !singleLine) { |
||||
throw new IllegalArgumentException("FasTextView only supports wrapping widths on a single line"); |
||||
} |
||||
|
||||
int width = 0; |
||||
if (widthMode == MeasureSpec.EXACTLY) { |
||||
width = widthSize; |
||||
} else if ((widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) && !TextUtils.isEmpty(text)) { |
||||
width = (int) Layout.getDesiredWidth(text, paint) + getPaddingLeft() + getPaddingRight(); |
||||
if (widthMode == MeasureSpec.AT_MOST) { |
||||
width = Math.min(width, widthSize); |
||||
} |
||||
} |
||||
|
||||
if (width > 0) { |
||||
if (this.width != width) { |
||||
this.width = width; |
||||
update = true; |
||||
} |
||||
|
||||
updateLayout(); |
||||
|
||||
if (layout != null) { |
||||
int height; |
||||
if (heightMode == MeasureSpec.EXACTLY) { |
||||
height = heightSize; |
||||
} else { |
||||
height = layout.getHeight() + getPaddingTop() + getPaddingBottom(); |
||||
if (heightMode == MeasureSpec.AT_MOST) { |
||||
height = Math.min(height, heightSize); |
||||
} |
||||
} |
||||
|
||||
setMeasuredDimension(width, height); |
||||
} else { |
||||
int height; |
||||
if (heightMode == MeasureSpec.EXACTLY) { |
||||
height = heightSize; |
||||
} else { |
||||
height = 0; |
||||
} |
||||
|
||||
setMeasuredDimension(width, height); |
||||
} |
||||
} else { |
||||
// Width is 0, ignore
|
||||
Logger.w(TAG, "Width = 0"); |
||||
setMeasuredDimension(0, 0); |
||||
} |
||||
} |
||||
|
||||
private void updateLayout() { |
||||
if (!TextUtils.isEmpty(text)) { |
||||
if (update) { |
||||
int layoutWidth = width - getPaddingLeft() - getPaddingRight(); |
||||
if (layoutWidth > 0) { |
||||
// long start = Time.startTiming();
|
||||
|
||||
// The StaticLayouts are cached with the static textCache LRU map
|
||||
|
||||
// Use .toString() to make sure we take the hashcode from the string representation
|
||||
// and not from any spannables that are in it
|
||||
long cacheKey = text.toString().hashCode(); |
||||
cacheKey = 31 * cacheKey + paint.getColor(); |
||||
cacheKey = 31 * cacheKey + Float.floatToIntBits(paint.getTextSize()); |
||||
cacheKey = 31 * cacheKey + layoutWidth; |
||||
|
||||
StaticLayout cached = textCache.get(cacheKey); |
||||
if (cached == null) { |
||||
// Logger.test("staticlayout cache miss: text = %s", text);
|
||||
cached = getStaticLayout(layoutWidth); |
||||
textCache.put(cacheKey, cached); |
||||
} else { |
||||
// Logger.test("staticlayout cache hit");
|
||||
// Make sure the layout has the actual text, color, size and width, hashcodes almost never collide
|
||||
Paint cachedPaint = cached.getPaint(); |
||||
if (!text.toString().equals(cached.getText().toString()) || |
||||
cachedPaint.getColor() != paint.getColor() || |
||||
cachedPaint.getTextSize() != paint.getTextSize() || |
||||
cached.getWidth() != layoutWidth) { |
||||
Logger.w(TAG, "Cache miss with the same hashcode %x: \"%s\" \"%s\"!", cacheKey, text.toString(), cached.getText().toString()); |
||||
cached = getStaticLayout(layoutWidth); |
||||
} |
||||
} |
||||
|
||||
layout = cached; |
||||
// Time.endTiming(Integer.toHexString(System.identityHashCode(this)) + " staticlayout for width = " + layoutWidth + "\t", start);
|
||||
} else { |
||||
layout = null; |
||||
} |
||||
} |
||||
} else { |
||||
layout = null; |
||||
} |
||||
update = false; |
||||
} |
||||
|
||||
private StaticLayout getStaticLayout(int layoutWidth) { |
||||
// Logger.test("new staticlayout width=%d", layoutWidth);
|
||||
return new StaticLayout(text, paint, layoutWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false); |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.text; |
||||
|
||||
import android.text.Spannable; |
||||
import android.view.MotionEvent; |
||||
|
||||
public interface FastTextViewMovementMethod { |
||||
boolean onTouchEvent(FastTextView widget, Spannable text, MotionEvent event); |
||||
} |
@ -1,106 +0,0 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.view; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.Canvas; |
||||
import android.graphics.Paint; |
||||
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 static org.floens.chan.utils.AndroidUtils.sp; |
||||
|
||||
public class FastTextView extends View { |
||||
private TextPaint paint; |
||||
|
||||
private CharSequence text; |
||||
|
||||
private boolean update = false; |
||||
private StaticLayout layout; |
||||
|
||||
public FastTextView(Context context) { |
||||
super(context); |
||||
init(); |
||||
} |
||||
|
||||
public FastTextView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
init(); |
||||
} |
||||
|
||||
public FastTextView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, defStyleAttr); |
||||
init(); |
||||
} |
||||
|
||||
private void init() { |
||||
paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); |
||||
} |
||||
|
||||
public void setText(CharSequence text) { |
||||
if (!TextUtils.equals(this.text, text)) { |
||||
this.text = text; |
||||
|
||||
if (text == null) { |
||||
layout = null; |
||||
} else { |
||||
update = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void setTextSize(float size) { |
||||
int sizeSp = sp(size); |
||||
if (paint.getTextSize() != sizeSp) { |
||||
paint.setTextSize(sizeSp); |
||||
update = true; |
||||
} |
||||
} |
||||
|
||||
public void setTextColor(int color) { |
||||
if (paint.getColor() != color) { |
||||
paint.setColor(color); |
||||
update = true; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
||||
update = true; |
||||
} |
||||
|
||||
@Override |
||||
protected void onDraw(Canvas canvas) { |
||||
if (update) { |
||||
int width = getWidth() - getPaddingLeft() - getPaddingRight(); |
||||
layout = new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false); |
||||
update = false; |
||||
} |
||||
|
||||
if (layout != null) { |
||||
canvas.save(); |
||||
canvas.translate(getPaddingLeft(), getPaddingTop()); |
||||
layout.draw(canvas); |
||||
canvas.restore(); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue